73 lines
3.8 KiB
Markdown
73 lines
3.8 KiB
Markdown
# ADR 0019: MCP Over The Sandbox 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/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.request` → `gateway_request` → `computer_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](adr-0016-js-tool-verification.md).
|
|
|
|
## 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.
|