53 lines
4.3 KiB
Markdown
53 lines
4.3 KiB
Markdown
# t3code-mcp
|
|
|
|
An MCP connector to [T3 Code](https://github.com/pingdotgg/t3code) servers. It fills the one gap T3 Code leaves for agents: they cannot create their own threads. With this connector an agent dispatches a coding thread on any machine that runs T3 Code, waits for it, answers its questions and reads the result - all through T3's own API (orchestration protocol 2: reads over HTTP, commands over the WebSocket RPC), with no shell or terminal involved.
|
|
|
|
Built for the dispatcher in [beaver-agent](https://git.kotikot.com/beaver/beaver-agent); works with any MCP client.
|
|
|
|
## Tools
|
|
|
|
| Tool | What it does |
|
|
|---|---|
|
|
| `t3_machines()` | machines from the config, which are online now, their project allowlists |
|
|
| `t3_projects(machine)` | allowlisted projects on a machine: path, default model, thread count |
|
|
| `t3_dispatch(machine, project, prompt, title?, model?, thread_id?, watch?)` | creates a thread and starts a run (or starts a run in an existing thread); returns `thread_id` at once; `watch=false` starts it without reporting events |
|
|
| `t3_thread(thread_id, turns?)` | the state without waiting: latest run, session, messages of the last runs, tools, files, a pending question |
|
|
| `t3_wait(thread_id, timeout?)` | blocks until completed, interrupted, failed, a question, or the timeout |
|
|
| `t3_answer(thread_id, answers)` | answers a question the thread is waiting on (question id → label; a list for multi-select) |
|
|
| `t3_interrupt(thread_id)` | interrupts the running run |
|
|
| `t3_watch(thread_id?, enabled?)` | without arguments lists the watched and unwatched threads; with both turns a thread's events on or off |
|
|
|
|
`t3_wait` is an ordinary long call, not an MCP Task: Claude Code moves calls over two minutes into the background by itself (`CLAUDE_CODE_MCP_AUTO_BACKGROUND_MS`). Threads run in `full-access` without approvals. A thread is looked up across all machines, so the connector may restart in between.
|
|
|
|
## Events
|
|
|
|
The connector keeps a websocket subscription to every machine (`orchestration.subscribeShell`, Effect RPC over JSON, reconnect with replay). Threads started through `t3_dispatch` are tracked in a state file; the transitions running → completed / interrupted / failed and a new pending question become events, `POST`ed to `T3CODE_MCP_HOOK_URL` with a bearer through a persisted outbox with retries. In the beaver setup that URL is the gateway's `/hooks/t3code`. `t3_dispatch` remembers the `X-Conversation-Origin` request header as the thread's `origin` and every event carries it back; the beaver gateway puts the calling conversation's id there, so the event lands as an urgent inject in the conversation that started the thread (the master when it is gone or the header was absent). Every `t3_dispatch` subscribes its thread, a continuation included; `t3_watch(thread_id, enabled=false)` or `t3_dispatch(..., watch=false)` lets a thread run silently.
|
|
|
|
## Configuration
|
|
|
|
`t3code.toml` (path in `T3CODE_MCP_CONFIG`; in the image `/config/t3code.toml`), see `t3code.example.toml`:
|
|
|
|
```toml
|
|
[machines.mac]
|
|
url = "http://<tailnet-ip>:3773"
|
|
token_env = "T3_MAC_TOKEN"
|
|
projects = ["*"]
|
|
model = "claudeAgent/claude-opus-5"
|
|
options = { effort = "high", contextWindow = "1m" }
|
|
```
|
|
|
|
Tokens never live in the file: `token_env` names the variable, and the token is issued on each machine with `t3 auth session issue --scope orchestration:read --scope orchestration:operate --token-only --ttl 365d --label beaver` - nothing beyond those two scopes is used. T3 Code from 0.1.0 (orchestration protocol 2) is required; older servers do not answer. `projects` are fnmatch patterns against a project's title and workspace root; a project that matches nothing does not exist for the client. The server listens on `T3CODE_MCP_HOST` / `T3CODE_MCP_PORT` (8000), MCP at `/mcp`, health at `/healthz`.
|
|
|
|
## Develop
|
|
|
|
```sh
|
|
make sync # uv sync
|
|
make check # ruff format --check, ruff check, ty, pytest
|
|
make run # T3CODE_MCP_CONFIG=... uv run python -m t3code_mcp
|
|
uv run python scripts/smoke.py http://127.0.0.1:8000/mcp mac <project> # a live dispatch and wait
|
|
```
|
|
|
|
## Deploy
|
|
|
|
A service in `beaver-agent/docker-compose.yml` (profile `t3`), image built from this repository at `T3CODE_MCP_REF`. The gateway attaches it as `McpServer.http(name="t3code", url="http://t3code-mcp:8000/mcp")` and hands it to the dispatcher only.
|