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