cc-libs/docs/adrs/adr-0019-mcp-over-cloud-gateway.md

4.1 KiB

ADR 0019: MCP Over The Cloud 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/trapos-server introduced a second, richer host↔computer connection: a single persistent gateway WebSocket per computer (servers/cloud + apis/libcloud), a hello handshake with a shared secret, a coded error model, and a bidirectional request/reply (GatewayRegistry.requestserver_requestcomputer_response).

Running both stacks meant two sockets, two protocols, and a second autostart daemon per computer. The cloud gateway already had everything MCP needs.

Decision

Move the MCP features onto the cloud gateway and retire the dedicated MCP transport.

  • Host (tools/trapos-server): 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/libcloud gains a reactive serving path symmetric to its request path. In-OS servers call cloud.serve(type, handler, { eventloop }), which advertises the type to the daemon (trapos_cloud_serve) and reacts to inbound trapos_cloud_request events, replying via trapos_cloud_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 cloud daemon.
  • servers/mcp-computer-server is rewritten to be reactive: it owns no socket, serves exec-lua / write-file over the gateway via cloud.serve, and is gated on cloud.url being set. apis/libmcpcomputer is slimmed to the protocol-agnostic execution primitives (executeLua, writeFile); both move into the trapos-cloud package (autostart servers/mcp-computer-server after servers/cloud). 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 has since been removed entirely. Its MCP server and CC-link parts were superseded here; its opencode HTTP-over-WS proxy (and the in-game ai WebSocket bridge mode it backed, apis/libhttpws) was intentionally dropped rather than migrated. The ai client now talks direct HTTP/HTTPS to opencode serve (opencc.server_url); if a proxy is needed again it will be re-implemented from scratch. The old code remains in git history.

Consequences

  • One connection and one protocol per computer; no mcp-computer.ws-url setting.
  • The exec trust boundary is now the authenticated gateway connection (cloud.url + TRAPOS_SERVER_PASSWORD) plus the local-only MCP bind, instead of "trust the bridge you run mcp-computer against."
  • apis/libcloud.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

  • Re-implement an opencode proxy / WebSocket transport for the ai client if the ComputerCraft ~60s HTTP cap becomes a problem again (the old mcp-bridge proxy was dropped).
  • Per-computer exec policy if the gateway is ever shared beyond trusted development.