pi-subagent-mcp
# pi-subagent-mcp
MCP server that exposes [pi](https://github.com/earendil-works/pi) (`pi --mode rpc`)
as a sub-agent for MCP-capable coding assistants (e.g. Kimi CLI).
## Build
npm install
npm run build
## Tools
| Tool | Purpose |
|---|---|
| `pi_dispatch` | Spawn a pi session on a task (`task`, `cwd`, `model?`, `name?`, `interactive?`, `approve?`) → `{taskId}` |
| `pi_send` | Message a task (`mode`: `auto`/`prompt`/`steer`/`followUp`) |
| `pi_status` | State, streaming flag, last assistant text, current tool, `pendingRequest` |
| `pi_read` | Incremental event buffer read (`since` seq) |
| `pi_wait` | Block until the run finishes or a dialog needs answering; returns last assistant text + progress snapshot (`isStreaming`/`eventCount`/`currentTool`) + `pendingRequest`. Timeout is a checkpoint, not a failure — no server-side max, use a large `timeoutMs` for long tasks (see [async-wait model](docs/design/async-wait-model.md)) |
| `pi_respond` | Answer a `pendingRequest` dialog (`value` / `confirmed` / `cancelled`) |
| `pi_abort` | Abort current operation; session stays alive |
| `pi_close` | Kill the process, drop the task |
| `pi_list` | All tasks |
| `pi_bridges` | List pi sessions running elsewhere (e.g. in a terminal) that have the bridge extension and can be attached |
| `pi_attach` | Attach to a bridge session (`sessionId` or `socketPath`) → `taskId`; all task tools then work on it |
| `pi_detach` | Disconnect an attached task; the pi session stays alive |
| `pi_notify` | Push text onto an attached pi's TUI console (`level`: `info`/`warning`/`error`) |
## Extension UI dialogs (bidirectional interaction)
pi extensions can block on `select`/`confirm`/`input`/`editor` dialogs
(`extension_ui_request`). By default (`interactive: true`) the server surfaces these as
`pendingRequest` in `pi_status`/`pi_wait` — which returns early — and the caller answers
with `pi_respond`. With `interactive: false` (unattended runs) dialogs are auto-cancelled,
preserving the original behavior.
Note: project-local pi extensions (`.pi/extensions/`) only load when the project is
trusted — pass `approve: true` to `pi_dispatch` to grant that for the run. Also, pi
delays the command response for extension commands that block on a dialog; the server
therefore treats dialog activity as command acceptance so `pi_dispatch`/`pi_send`
return instead of deadlocking.
## Attaching to terminal pi sessions (bridge)
pi's RPC mode is stdio-only, so a pi already running in your terminal cannot be
attached retroactively. The bridge extension solves this: installed once, every pi
session started **afterwards** (interactive TUI included) exposes a Unix socket at
`~/.pi/bridge/<sessionId>.sock` that this server can attach to.
Install (user scope, loads in every pi session):
pi install /absolute/path/to/extensions/bridge.ts
Then from any MCP client:
pi_bridges → discover running sessions
pi_attach { sessionId } → get a taskId; pi_send / pi_wait / pi_read /
pi_status / pi_abort work as usual
pi_notify { taskId, message } → text appears on that pi's TUI console
pi_detach { taskId } → disconnect; the terminal session stays alive
Limitations: sessions started before the install can never be attached; attached
sessions answer their own extension dialogs in their terminal (no `pendingRequest`);
session replacement (`/new`, `/resume`) ends the attachment (re-attach via
`pi_bridges`); Unix sockets only (no Windows named pipes). See
[docs/design/terminal-bridge.md](docs/design/terminal-bridge.md).
## Configuration
Environment variables:
- `PI_SUBAGENT_SESSION_DIR` — where pi session files are stored
(default: `.pi-subagent/sessions/` next to the package).
- `PI_SUBAGENT_PI_PATH` — override the pi binary path (default: `pi`).
MCP registration (example): point your client at `node /path/to/dist/src/server.js`.
## Test
npm test # unit + smoke + (if pi is installed and logged in) integration
Additional end-to-end scripts in `scripts/` (`acceptance-test.mjs`,
`mcp-e2e-test.mjs`, `dialog-e2e-test.mjs`, `bridge-e2e-test.mjs`). Issues found during development are
logged in [docs/issues/](docs/issues/README.md); design notes in
[docs/design/](docs/design/async-wait-model.md).
TDQS
Scored across 9 tools
pi_status, pi_read, and pi_wait all concern state but are clearly differentiated as snapshot, event stream, and blocking wait respectively. pi_dispatch and pi_send are also distinct (create new vs. message existing). No two tools appear to do the same thing.
All nine tools are prefixed with pi_ and use lowercase imperative-style names (dispatch, send, read, wait, respond, abort, close, list). The only mild outlier is pi_status, which uses a noun rather than a verb, but it still fits the established pattern and creates no confusion.
With nine tools, the server is in the ideal range for its purpose. Each tool addresses a specific aspect of the subagent lifecycle without redundancy or bloat.
The set covers the full workflow: dispatch to create, send to communicate, status/read/wait to observe, respond to handle dialogs, abort to interrupt, close to clean up, and list to enumerate. There are no missing operations that would trap an agent mid-workflow.