cc-libs/docs/adrs/adr-0019-mcp-over-sandbox-gateway.md
2026-06-15 19:57:45 +02:00

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.requestgateway_requestcomputer_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 to 127.0.0.1 (default MCP_PORT=4445) speaks the same custom JSON-RPC 2.0 as the old bridge. Each tool call is a GatewayRegistry.request(accountId, traposId, type, payload) reusing the existing gateway socket. Computers are addressed by traposId ("<id>:<label>", the registry's key) rather than a numeric computerId; probe-computers lists the connected traposIds.
  • Computer side: apis/libsandbox gains a reactive serving path symmetric to its request path. In-OS servers call sandbox.serve(type, handler, { eventloop }), which advertises the type to the daemon (trapos_sandbox_serve) and reacts to inbound trapos_sandbox_request events, replying via trapos_sandbox_respond (computer_response). Unadvertised non-ping types still get an immediate unknown_type reply (the daemon tracks advertised types, so there is no timer race). ping stays built-in, so probe-computers works against any computer running just the sandbox daemon.
  • servers/mcp-computer-server is rewritten to be reactive: it owns no socket, serves exec-lua / write-file over the gateway via sandbox.serve, and is gated on sandbox.url being set. apis/libmcpcomputer is slimmed to the protocol-agnostic execution primitives (executeLua, writeFile); both move into the trapos-sandbox package (autostart servers/mcp-computer-server after servers/sandbox). The standalone programs/mcp-computer client is deleted and the MCP entries are removed from trapos-sandbox-legacy.
  • A code error or a failed write is reported as a successful round-trip carrying an ok:false result payload (preserving captured output), 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-url setting.
  • 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 run mcp-computer against."
  • apis/libsandbox.serve / respond are 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-bridge so the tool can be fully retired.
  • Per-computer exec policy if the gateway is ever shared beyond trusted development.