3.8 KiB
ADR 0019: MCP Over The Sandbox Gateway
Status
Accepted (supersedes the transport of ADR-0017)
Date
2026-06-15
Context
The MCP tools (probe-computers, exec-lua, write-file) shipped in tools/mcp-bridge
(ADR-0017) over a dedicated CC-link WebSocket and
the mcp-computer / mcp-computer-server Lua programs.
Meanwhile tools/trap-sandbox introduced a second, richer host↔computer connection: a
single persistent gateway WebSocket per computer (servers/sandbox + apis/libsandbox),
a hello handshake with a shared secret, a coded error model, and a bidirectional
request/reply (GatewayRegistry.request → gateway_request → computer_response).
Running both stacks meant two sockets, two protocols, and a second autostart daemon per computer. The sandbox gateway already had everything MCP needs.
Decision
Move the MCP features onto the sandbox gateway and retire the dedicated MCP transport.
- Host (
tools/trap-sandbox): a second Fastify listener (src/modules/mcp) bound to127.0.0.1(defaultMCP_PORT=4445) speaks the same custom JSON-RPC 2.0 as the old bridge. Each tool call is aGatewayRegistry.request(accountId, traposId, type, payload)reusing the existing gateway socket. Computers are addressed bytraposId("<id>:<label>", the registry's key) rather than a numericcomputerId;probe-computerslists the connected traposIds. - Computer side:
apis/libsandboxgains a reactive serving path symmetric to its request path. In-OS servers callsandbox.serve(type, handler, { eventloop }), which advertises the type to the daemon (trapos_sandbox_serve) and reacts to inboundtrapos_sandbox_requestevents, replying viatrapos_sandbox_respond(computer_response). Unadvertised non-pingtypes still get an immediateunknown_typereply (the daemon tracks advertised types, so there is no timer race).pingstays built-in, soprobe-computersworks against any computer running just the sandbox daemon. servers/mcp-computer-serveris rewritten to be reactive: it owns no socket, servesexec-lua/write-fileover the gateway viasandbox.serve, and is gated onsandbox.urlbeing set.apis/libmcpcomputeris slimmed to the protocol-agnostic execution primitives (executeLua,writeFile); both move into thetrapos-sandboxpackage (autostartservers/mcp-computer-serverafterservers/sandbox). The standaloneprograms/mcp-computerclient is deleted and the MCP entries are removed fromtrapos-sandbox-legacy.- A code error or a failed write is reported as a successful round-trip carrying an
ok:falseresult payload (preserving capturedoutput), matching the old bridge's semantics; only a dropped connection or timeout is a transport-level failure.
tools/mcp-bridge is not deleted: its opencode HTTP-over-WS proxy is still used by the
in-game ai bridge mode (apis/libhttpws, apis/libai). Only the MCP server and CC-link
parts are superseded here; the proxy migration is deferred.
Consequences
- One connection and one protocol per computer; no
mcp-computer.ws-urlsetting. - The exec trust boundary is now the authenticated gateway connection (
sandbox.url+SANDBOX_PASSWORD) plus the local-only MCP bind, instead of "trust the bridge you runmcp-computeragainst." apis/libsandbox.serve/respondare reusable for any future computer-side service invoked from the host, not just MCP.- Tests cover host-side MCP routing (unit) and the real CraftOS gateway round-trip (integration), per ADR-0016.
Future Work
- Migrate the opencode HTTP proxy off
mcp-bridgeso the tool can be fully retired. - Per-computer exec policy if the gateway is ever shared beyond trusted development.