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

76 lines
4.1 KiB
Markdown

# ADR 0019: MCP Over The Cloud Gateway
## Status
Accepted (supersedes the transport of [ADR-0017](adr-0017-mcp-remote-lua-execution.md))
## Date
2026-06-15
## Context
The MCP tools (`probe-computers`, `exec-lua`, `write-file`) shipped in `tools/mcp-bridge`
([ADR-0017](adr-0017-mcp-remote-lua-execution.md)) 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 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(networkId, 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](adr-0016-js-tool-verification.md).
## 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.