diff --git a/.plans/mcp-bridge-plan.md b/.plans/mcp-bridge-plan.md new file mode 100644 index 0000000..d80c54d --- /dev/null +++ b/.plans/mcp-bridge-plan.md @@ -0,0 +1,259 @@ +# MCP Bridge Plan + +## Goal + +Build a small TypeScript bridge that lets MCP clients call tools backed by live ComputerCraft computers. + +The first MCP tool is `probe-computers`. It broadcasts a probe request to every linked computer and returns one line per response: + +```text +pong from (Label: ) +``` + +## Decisions + +- Host-side implementation lives in `tools/mcp-bridge/`. +- MCP clients connect on `MCP_PORT`, default `3000`. +- ComputerCraft computers connect on `CC_LINK_PORT`, default `3001`. +- The MCP port and ComputerCraft link port are separate listeners. +- No auth is required for the first bridge implementation. +- `probe-computers` takes no arguments and probes every currently linked computer. +- The bridge owns real HTTP/MCP transport. ComputerCraft computers connect outbound over WebSocket. + +## Background + +CC:Tweaked computers cannot bind a real TCP/HTTP port with the standard APIs. They can make outbound HTTP/WebSocket connections. Therefore the host bridge exposes real MCP over HTTP and maintains outbound WebSocket links from computers. + +The current MCP TypeScript SDK situation is split: + +- `@modelcontextprotocol/sdk` v1 is stable and commonly used by existing examples. +- Newer docs describe split packages such as `@modelcontextprotocol/server` and `@modelcontextprotocol/node`, but the upstream README says that branch is v2/pre-alpha as of this research. + +Prefer stable v1 for the first implementation unless opencode compatibility requires the newer packages. Re-check package docs immediately before implementation. + +## Architecture + +```text +[ opencode / MCP client ] --HTTP MCP--> [ tools/mcp-bridge ] --WebSocket--> [ CC computer(s) ] + :3000 :3001 +``` + +The bridge has two independent surfaces: + +- MCP listener: streamable HTTP MCP endpoint for agents. +- Link listener: WebSocket endpoint for ComputerCraft computers. + +The bridge keeps an in-memory registry of connected computers: + +```ts +type ComputerConnection = { + computerId: number; + label: string | null; + ws: WebSocket; + connectedAt: number; + lastSeenAt: number; +}; +``` + +## Link Protocol + +All ComputerCraft link messages are JSON text frames. + +### Computer Hello + +Sent by the Lua program immediately after opening the WebSocket: + +```json +{ + "type": "hello", + "computerId": 12, + "computerLabel": "base-turtle" +} +``` + +Bridge behavior: + +- Validate `computerId` is a number. +- Treat missing/empty `computerLabel` as `null`. +- Register or replace the existing connection for that `computerId`. +- Reply with `hello-ok`. + +### Hello OK + +```json +{ + "type": "hello-ok" +} +``` + +### Probe Request + +Sent by the bridge when MCP tool `probe-computers` is called: + +```json +{ + "type": "request", + "id": "req-123", + "method": "ping" +} +``` + +### Probe Response + +Sent by each Lua computer: + +```json +{ + "type": "response", + "id": "req-123", + "ok": true, + "result": "pong from 12 (Label: base-turtle)" +} +``` + +Error shape, reserved for later commands: + +```json +{ + "type": "response", + "id": "req-123", + "ok": false, + "error": "unknown method" +} +``` + +## MCP Tool Behavior + +Tool name: `probe-computers`. + +Input schema: no arguments. + +Behavior: + +1. Snapshot the current connected computer registry. +2. If none are connected, return `No computers connected.`. +3. Generate one request id per computer, or one shared batch id with per-computer tracking. +4. Send a `ping` request to every linked computer. +5. Wait for responses until either all answer or the probe timeout expires. +6. Return text with one line per successful response. +7. Include timeout lines for computers that did not answer. + +Suggested timeout: `CC_PROBE_TIMEOUT_MS`, default `2000`. + +Example output: + +```text +pong from 12 (Label: base-turtle) +pong from 13 (Label: miner-1) +timeout from 14 (Label: farm-turtle) +``` + +## Project Layout + +```text +tools/mcp-bridge/ + package.json + package-lock.json + tsconfig.json + src/ + index.ts + link-server.ts + mcp-server.ts + protocol.ts + test/ + protocol.test.ts + probe-computers.test.ts +``` + +Keep the first implementation minimal. Collapse files if the code is clearer in fewer modules. + +## Implementation Sections + +### 1. Tooling Setup + +- Create `tools/mcp-bridge/package.json`. +- Use TypeScript with strict checks. +- Use Node's built-in test runner if practical, or Vitest if SDK/test ergonomics require it. +- Add scripts: + - `npm run build` + - `npm test` + - `npm run dev` + - `npm start` + +### 2. Link Server + +- Start WebSocket server on `CC_LINK_PORT`. +- Accept JSON text frames only. +- Register computers after valid `hello`. +- Remove computers on close/error. +- Track pending request resolvers by request id. +- Ensure malformed frames do not crash the process. + +### 3. MCP Server + +- Start MCP HTTP transport on `MCP_PORT`. +- Register tool `probe-computers`. +- Implement tool response as MCP text content. +- Keep ordinary `/health` endpoint available on the MCP listener if the selected transport/framework allows it without fighting the SDK. + +`/health` response should be normal HTTP, not MCP: + +```json +{ + "ok": true, + "computers": 2 +} +``` + +### 4. Configuration + +Environment variables: + +- `MCP_HOST`, default `127.0.0.1`. +- `MCP_PORT`, default `3000`. +- `CC_LINK_HOST`, default `0.0.0.0`. +- `CC_LINK_PORT`, default `3001`. +- `CC_PROBE_TIMEOUT_MS`, default `2000`. + +### 5. Verification + +Unit tests: + +- Protocol validation accepts valid `hello` and rejects invalid frames. +- `probe-computers` returns `No computers connected.` when registry is empty. +- `probe-computers` aggregates multiple successful responses. +- `probe-computers` reports timeout for a connected computer that does not answer. + +Integration tests: + +- Start bridge on ephemeral ports. +- Connect fake WebSocket computer clients. +- Call the probe path through the MCP handler, or through a thin internal function if the SDK transport is hard to drive in tests. + +Manual/agent checks: + +- `npm run build` from `tools/mcp-bridge`. +- `npm test` from `tools/mcp-bridge`. +- Use opencode headless mode only for critical MCP compatibility checks after the bridge can run locally. + +## Open Questions To Re-check During Implementation + +- Exact HTTP MCP transport API for the chosen stable SDK version. +- Whether opencode expects streamable HTTP only, SSE compatibility, or can use stdio. This bridge should prioritize HTTP because the requirement is an MCP port. +- Whether tool names with hyphens are accepted cleanly by the MCP clients we care about. If not, challenge `probe-computers` and use `probe_computers`. + +## Out Of Scope For First Pass + +- Authentication and authorization. +- Persistent computer registry. +- Commands other than `ping`/`probe-computers`. +- Multi-bridge clustering. +- TLS termination. Put the bridge behind a reverse proxy if needed later. + +## Deliverables + +- `tools/mcp-bridge/` TypeScript project. +- MCP HTTP listener on port `3000` by default. +- Computer WebSocket link listener on port `3001` by default. +- MCP tool `probe-computers` returning live ComputerCraft pong lines. +- Tests for protocol parsing and probe aggregation. diff --git a/.plans/mcp-computer-lua-plan.md b/.plans/mcp-computer-lua-plan.md new file mode 100644 index 0000000..eae3a30 --- /dev/null +++ b/.plans/mcp-computer-lua-plan.md @@ -0,0 +1,242 @@ +# MCP Computer Lua Plan + +## Goal + +Add a TrapOS sandbox program that links a ComputerCraft computer to the host-side MCP bridge over WebSocket. + +The first supported command is `ping`, used by the bridge's MCP tool `probe-computers`. The Lua program answers: + +```text +pong from (Label: ) +``` + +## Decisions + +- Lua program path: `programs/mcp-computer.lua`. +- Package: `trapos-sandbox`. +- The program connects outbound to the bridge's ComputerCraft link port. +- The bridge link default is `ws://:3001`. +- The host-side MCP tool is named `probe-computers` and broadcasts to every connected computer. +- No link auth for the first version. +- Keep the first Lua implementation as a program, not an autostart server, until the bridge loop is proven. + +## Background + +CC:Tweaked cannot listen on real HTTP/TCP ports with standard APIs. It can connect outward using `http.websocket`. This program is therefore the in-game half of the bridge: + +```text +[ CC computer ] --outbound WebSocket--> [ tools/mcp-bridge link port :3001 ] +``` + +The Lua program is effectively an agent. It identifies itself to the bridge, waits for JSON request frames, executes supported methods, and sends JSON response frames. + +## User Interface + +Program usage: + +```text +mcp-computer +mcp-computer -url +mcp-computer --version +mcp-computer --help +``` + +Examples: + +```text +mcp-computer ws://192.168.1.20:3001 +mcp-computer -url ws://mcp-bridge.local:3001 +``` + +The program should print clear status lines: + +```text +mcp-computer v0.1.1 connecting to ws://192.168.1.20:3001 +linked as 12 (Label: base-turtle) +waiting for requests... Press Ctrl+T to stop. +``` + +If `http` or `http.websocket` is unavailable, print a clear error explaining that CC:Tweaked HTTP/WebSocket must be enabled. + +## Link Protocol + +All frames are JSON strings compatible with `textutils.serializeJSON` and `textutils.unserializeJSON`. + +### Computer Hello + +Sent immediately after opening the websocket: + +```json +{ + "type": "hello", + "computerId": 12, + "computerLabel": "base-turtle" +} +``` + +Lua fields: + +- `computerId`: `os.getComputerID()`. +- `computerLabel`: `os.getComputerLabel()`, or `nil` encoded as JSON null/omitted depending on `textutils.serializeJSON` behavior. + +### Hello OK + +Expected from bridge: + +```json +{ + "type": "hello-ok" +} +``` + +The first version may continue even if `hello-ok` is not received immediately, but implementation should prefer waiting briefly so connection problems are obvious. + +### Probe Request + +Received from bridge: + +```json +{ + "type": "request", + "id": "req-123", + "method": "ping" +} +``` + +### Probe Response + +Sent back by Lua: + +```json +{ + "type": "response", + "id": "req-123", + "ok": true, + "result": "pong from 12 (Label: base-turtle)" +} +``` + +Unknown method response: + +```json +{ + "type": "response", + "id": "req-123", + "ok": false, + "error": "unknown method" +} +``` + +## Implementation Sections + +### 1. Program Skeleton + +- Add `programs/mcp-computer.lua`. +- Support `--help`, `-help`, `help`, `--version`, `-version`, and `version`. +- Version output uses `require('/apis/libversion')().forSelf()`. +- Parse URL from first positional argument or `-url `. +- Reject missing URL with usage. + +### 2. Connection Handling + +- Check `http` exists. +- Check `http.websocket` exists. +- Call `http.websocket(url)`. +- On failure, print the returned error and exit non-zero if possible. +- Send `hello` frame. +- Optionally wait up to a short timeout for `hello-ok`. + +### 3. Request Loop + +- Receive websocket messages in a loop. +- Decode JSON with `textutils.unserializeJSON` inside `pcall`. +- Ignore or respond with error for malformed messages. +- Handle `terminate` cleanly if using `parallel.waitForAny` around websocket receive and terminate watcher. +- For `request` with `method = 'ping'`, send success response. +- For unknown request methods, send error response. +- Close websocket on exit. + +### 4. Pong Formatting + +Implement a local function: + +```lua +local function formatPong() + local label = os.getComputerLabel(); + if not label or label == '' then + label = 'nil'; + end + return 'pong from ' .. tostring(os.getComputerID()) .. ' (Label: ' .. tostring(label) .. ')'; +end +``` + +Keep the output exactly aligned with the bridge plan. + +### 5. Packaging + +- Add `programs/mcp-computer.lua` to `packages/trapos-sandbox/ccpm.json`. +- Bump `trapos-sandbox` version. +- Mirror the version bump in `packages/index.json`. +- Do not add it to `autostart` in the first pass. +- Do not add `trapos-sandbox` as a `trapos` dependency unless explicitly desired later. + +## Testing Strategy + +### Unit-Style Lua Tests + +Prefer moving deterministic protocol helpers into `apis/libmcpcomputer.lua` only if testing the program directly becomes awkward. The minimal testable API could expose: + +- `formatPong(osLike)`. +- `handleRequest(request, osLike)`. +- `encode/decode` helpers if needed. + +If a helper API is added, include it in `trapos-sandbox` and add tests under `tests/` using `/apis/libtest.lua`. + +If keeping everything in one program, at least verify syntax and help/version behavior with CraftOS-PC probes. + +### CraftOS-PC Probes + +Use automated probes only; do not run `just repl`. + +Examples after implementation: + +```bash +just trapos-exec 'shell.run("/programs/mcp-computer.lua", "--help")' +just trapos-exec 'shell.run("/programs/mcp-computer.lua", "--version")' +``` + +For an end-to-end probe, run the TS bridge on localhost and use CraftOS-PC with HTTP local access enabled if the harness permits it. If localhost access is blocked by CC:Tweaked config, document the limitation and rely on an in-game/manual validation for the WebSocket connection. + +### Required Repo Checks + +After editing Lua or package descriptors: + +- `just check` +- `just test` if tests are added or changed. + +## Manual Validation + +1. Start the TypeScript bridge. +2. On one ComputerCraft computer, run `mcp-computer ws://:3001`. +3. Confirm the bridge logs the computer registration. +4. Connect an MCP client to the bridge MCP port. +5. Call `probe-computers`. +6. Confirm response includes the computer id and label. +7. Connect a second computer and confirm the tool returns two lines. + +## Out Of Scope For First Pass + +- Autostart server behavior. +- Reconnect/backoff loop. +- Authentication. +- Commands that mutate the world. +- Turtle/peripheral control. +- MCP protocol implementation inside Lua. Lua only speaks the simple bridge link protocol. + +## Deliverables + +- `programs/mcp-computer.lua` in `trapos-sandbox`. +- Package version bump for `trapos-sandbox`. +- Optional helper API and tests if implementation benefits from unit coverage. +- Working WebSocket link to `tools/mcp-bridge`. +- Correct response to bridge `ping` requests.