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.request → server_request → computer_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 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(networkId, 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/libcloudgains a reactive serving path symmetric to its request path. In-OS servers callcloud.serve(type, handler, { eventloop }), which advertises the type to the daemon (trapos_cloud_serve) and reacts to inboundtrapos_cloud_requestevents, replying viatrapos_cloud_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 cloud daemon. servers/mcp-computer-serveris rewritten to be reactive: it owns no socket, servesexec-lua/write-fileover the gateway viacloud.serve, and is gated oncloud.urlbeing set.apis/libmcpcomputeris slimmed to the protocol-agnostic execution primitives (executeLua,writeFile); both move into thetrapos-cloudpackage (autostartservers/mcp-computer-serverafterservers/cloud). 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 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-urlsetting. - 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 runmcp-computeragainst." apis/libcloud.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
- Re-implement an opencode proxy / WebSocket transport for the
aiclient if the ComputerCraft ~60s HTTP cap becomes a problem again (the oldmcp-bridgeproxy was dropped). - Per-computer exec policy if the gateway is ever shared beyond trusted development.