# 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 HTTP API, 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 turn (or starts a turn 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: turn, session, last messages, 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 turn | | `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`, and the event lands in the master conversation as an urgent inject. 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://: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 --token-only --ttl 365d --label beaver`. `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 # 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.