| Name | Latest commit | Last update |
|---|---|---|
| 📂 cmake | ||
| 📂 deploy | ||
| 📂 designs | ||
| 📂 include | ||
| 📂 packages | ||
| 📂 src | ||
| 📂 web | ||
| 📄 .gitignore | Implement NetHack MCP server and engine integration | 29 hours ago |
| 📄 CMakeLists.txt | Gate engine probe behind BUILD_TESTING | 2 hours ago |
| 📄 README.md | Allow shared database directory permissions | 43 minutes ago |
This project builds the pinned NetHack 5.0 engine as libnethack.a, runs
each game in an isolated worker process, and serves multiple games through
one MCP endpoint. Spectators can watch active games and browse completed
records without receiving gameplay controls.
The default build fetches and statically builds Lua 5.4.9 for the NetHack engine, so a system Lua installation is not required.
cmake -S . -B build
cmake --build build -j24
The produced engine library is build/nethack-work/src/libnethack.a.
When BUILD_TESTING is enabled, CMake also builds nethack_engine_probe and
registers its startup check with CTest. The Arch package disables it.
HTML, JavaScript, CSS, and font assets are maintained as ordinary files in
web/. A build step embeds them in a generated C++ translation unit, which is
compiled into the executable. Asset changes regenerate that translation unit.
./build/nethack_mcp --data-root /tmp/nethack-mcp --port 8765
The MCP endpoint is http://127.0.0.1:8765/mcp; the read-only landing page is
at http://127.0.0.1:8765/. Game records are stored by default at
~/.local/share/nethack-mcp/games.sqlite3, outside the temporary runtime
directory. Stop the server with Ctrl-C. A server restart ends all active
games and records them as interrupted.
Startup settings can be loaded from a TOML file with --config PATH. The
configuration is read first; command-line options override it. The Arch
package ships a sample configuration at
packages/arch/nethack-mcp.toml. Set [server].listen_address to choose an
IP address or a Unix domain socket path; it defaults to 127.0.0.1. For
example, /run/nethack-mcp/http.sock selects a socket file and makes the
configured port irrelevant. Set listen_socket_permission = 0o660 in
[server] to set the socket file's permission bits. The --listen-address
option overrides the config file.
Create games with the MCP new_game tool and a caller-supplied model_slug.
Keep the returned game_id and control_token; every later game tool call
must include both. The token is returned once and acts as a temporary game
password. Share only the /g/<game-id> viewer URL with spectators.
The endpoint supports the sessionless MCP 2026-07-28 protocol and the
2025 handshake revisions.
The home page lists the ten most recently completed games. Active games use
/g/<game-id> and poll /api/games/<game-id>/state. Once a game ends, its
viewer returns to the home page. Completed games have no individual page.
Run the application on loopback behind a TLS reverse proxy. Set the public base URL in the application configuration and configure the proxy to accept the intended hostname. The server uses the peer address of each connection; behind a reverse proxy, per-client limits are therefore shared by clients connecting through that proxy. The database directory must be persistent, local (not a network filesystem), and writable by the service account. The game data root can be temporary storage.
Example application command (replace the paths and hostname and choose limits that fit the host):
./build/nethack_mcp \
--port 8765 \
--data-root /run/nethack-mcp/games \
--database /var/lib/nethack-mcp/games.sqlite3 \
--public-base-url https://nethack.example/ \
--max-active-games 8 \
--new-games-per-client 8 \
--new-game-rate-window-seconds 3600 \
--max-rate-limit-clients 4096 \
--max-concurrent-requests 64 \
--max-open-connections 128 \
--max-worker-output-bytes 1048576
Public mode requires explicit host-dependent capacity settings. The default
idle timeout is 600 seconds and the absolute game lifetime is 86,400 seconds.
Use --help for the full list of startup options. The sample reverse proxy
configuration in deploy/nginx.conf.example includes TLS termination,
connection limits, and request limits.
Set the application limits together with the host's task, memory, and file
descriptor limits. Each active game uses a worker process and parent I/O
threads; set the active-game cap from measurements on the deployment host.
/metrics reports active games and workers, task and memory estimates,
runtime bytes, database write latency, HTTP latency, and expiry cleanup
failures. The spectator page checks state every two seconds after its previous
request finishes; unchanged state receives an immediate 304 response. State
responses contain at most the ten most recent messages; MCP observations keep
the same ten-message history. Older messages are dropped, and an observation
reports when its message cursor has missed them.
Cgroup task and memory values are available when the service
runs under a visible Linux cgroup v2 hierarchy.
Back up the SQLite database with SQLite's backup support while the service is
running, for example with the sqlite3 shell's .backup command. Do not copy
only the main database file while WAL mode is active.