# 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.
