76 lines
4.1 KiB
Markdown
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(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](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.
|