docs(mcp): add bridge implementation plans
This commit is contained in:
parent
b7187e0155
commit
217b0e4e83
259
.plans/mcp-bridge-plan.md
Normal file
259
.plans/mcp-bridge-plan.md
Normal file
@ -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 <computerId> (Label: <computerLabel>)
|
||||
```
|
||||
|
||||
## 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.
|
||||
242
.plans/mcp-computer-lua-plan.md
Normal file
242
.plans/mcp-computer-lua-plan.md
Normal file
@ -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 <computerId> (Label: <computerLabel>)
|
||||
```
|
||||
|
||||
## 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://<host>: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 <ws-url>
|
||||
mcp-computer -url <ws-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 <ws-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://<bridge-host>: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.
|
||||
Loading…
Reference in New Issue
Block a user