pi-mcp-server
This server does not currently have a deployable release on Glama. Follow the repository's setup instructions to install and configure it for your MCP client, including any required credentials and local dependencies.
pi-mcp-server
MCP server that bridges Claude Code and the pi coding agent.
Why this exists
Claude Code's built-in shell tools run inside a sandbox without outbound DNS,
so anything that needs to reach OpenRouter, Anthropic, Google or another LLM
provider has to live outside that sandbox. The pi coding agent
(@earendil-works/pi-coding-agent)
is one such tool: it does real work, but only if it can call an API.
pi-mcp-server runs outside the sandbox as a normal user-process and
exposes pi sessions as MCP tools. Claude Code attaches as an MCP client over
local HTTP and can spawn pi agents, send them prompts, inspect their cost,
and dispose of them without ever leaving the sandbox itself.
Related MCP server: pi-mcp
Quickstart
npm install -g @a-fig/pi-mcp-server
export OPENROUTER_API_KEY=sk-or-... # or set in your shell config
pi-mcp-server & # listens on http://127.0.0.1:3700/mcp
claude mcp add pi-mcp --transport http --url http://127.0.0.1:3700/mcpYou also need a working pi setup at ~/.pi/agent/. The server defers to pi
for authentication: if ~/.pi/agent/auth.json contains a valid OAuth token,
pi will use that; otherwise it falls back to provider env vars like
OPENROUTER_API_KEY. See pi's own docs for first-time setup.
How Claude uses this
Once attached, Claude has four tools.
Tool | Inputs | Output |
|
|
|
|
|
|
| (none) |
|
|
|
|
A typical Claude flow: create a session in your repo with a cheap model and
a max_cost_usd of, say, 0.50; send pi a prompt like "rewrite this module
to use async iterators, then run the tests"; pi works, edits files, and
returns when done; check the cumulative cost; close the session.
Tool reference
pi_new_session
Spawns a fresh pi agent backed by its own isolated agentDir under
SESSION_DIR. Returns a sessionId to use with the other three tools.
Parameter | Type | Required | Description |
| string | yes | Absolute path. Pi runs as if it were launched here. |
| string | yes |
|
| number > 0 | no | Per-session USD cap. See Cost caps. |
| enum | no |
|
| string | no | Custom system prompt for this session. |
Output (structuredContent):
{ "sessionId": "0a2c..." }isError: true when cwd is not absolute, when max_cost_usd <= 0 or NaN,
when pi rejects the model string, or when the underlying agent fails to start.
pi_prompt
Sends text to the named session and waits for pi to finish the turn.
Emits notifications/progress every 10 seconds while pi is running so MCP
clients don't time the call out.
Parameter | Type | Required | Description |
| string | yes | Returned by |
| string (non-empty) | yes | User message to send. |
Output (structuredContent):
{
"text": "...",
"costUsd": 0.000123,
"cumulativeCostUsd": 0.000456,
"inputTokens": 512,
"outputTokens": 88,
"finishReason": "completed",
"errorMessage": "..."
}finishReason is one of completed, error, or cost_cap. The tool
returns isError: true for anything other than completed. errorMessage
is only present when finishReason !== 'completed'. For cost_cap, text
may still contain the partial response that streamed before pi was aborted.
pi_list_sessions
No inputs. Returns every session known to this server process, sorted by creation time.
Output (structuredContent):
{
"sessions": [
{
"sessionId": "0a2c...",
"cwd": "/abs/path",
"model": "openrouter:amazon/nova-micro-v1",
"cumulativeCostUsd": 0.000456,
"maxCostUsd": 0.5,
"createdAt": 1747958400000,
"lastActiveAt": 1747958412000
}
]
}maxCostUsd is null when no cap was set.
pi_close_session
Disposes a session and frees its resources. Idempotent: closing an unknown
sessionId succeeds.
Parameter | Type | Required | Description |
| string | yes | Session to close. |
Output (structuredContent):
{ "closed": true }isError: true only on unexpected disposal failure from pi itself.
Inspecting sessions (pi-sessions)
The package also ships a read-only CLI that reads the JSONL transcripts pi
writes under SESSION_DIR. It works whether or not pi-mcp-server is
running, and never mutates a transcript.
pi-sessions # same as `pi-sessions list`
pi-sessions show bd6d6dc8 # unique id prefix, full id, or a .jsonl path
pi-sessions watch bd6d6dc8 # follow new messages as pi writes themlist prints one row per session with its inferred status, model, working
directory, last activity, message count and cumulative cost:
STATUS SESSION MODEL CWD LAST MSGS COST
running bd6d6dc8… opencode-go:deepseek-v4.1-f… ~/code/app 12s ago 317 $0.1873
interrupted df492fb9… openrouter:qwen/qwen3.6-plus ~/code/app 2h ago 9 $0.0041Status values are inferred from the transcript, not reported by a live process:
Status | Meaning |
| Last entry is a user message, tool call or tool result, and it is newer than |
| Last entry is an assistant message whose |
| Same shape as |
| No messages yet. |
| The file is missing, unreadable, or larger than 64 MiB. |
Useful flags:
Flag | Command | Description |
| list | Only sessions started in this directory or below it. |
| list | Filter by inferred status. |
| list | Only sessions active within the window, e.g. |
| list | Sort order (default |
| show, watch | Messages to print ( |
| show, watch | Do not truncate message text or tool output. |
| show, watch | Include |
| watch | Replay the whole transcript before following. |
| all | Stable machine-readable output (NDJSON for |
| all | Override |
Caveats:
Status is inferred at message granularity. While the model is generating a long reply pi has not written anything yet, so the CLI can only report that the previous entry is recent; expect a delay of up to one assistant message.
Only sessions under
SESSION_DIRare visible. Sessions created by the pi CLI itself (under~/.pi/agent/sessions) are out of scope.Directories left behind by a stopped server still show up. They look
idleorinterrupted; deleteSESSION_DIR/sessions/<id>when you no longer need them.
Configuration
Variable | Default | Description |
|
| HTTP port. Must be 1-65535. |
|
| Root directory for per-session pi agent dirs. |
|
| One of |
| none | Passed through to pi sessions. Required if |
| none | Same fallback chain. Other provider env vars pi knows about also work. |
The HTTP server binds 127.0.0.1 only and has no authentication. It
checks the Host header on every request and rejects mismatches with
421 Misdirected Request to defend against DNS rebinding from
attacker-controlled pages. If you need multi-machine access or auth, put a
reverse proxy in front; first-class auth and remote-binding support are
out of scope for this version. PRs welcome.
Cost caps
max_cost_usd is enforced per session by two checks:
Pre-flight. Before every prompt, if cumulative spend is already at-or-past the cap, the prompt short-circuits without calling pi. It returns
finishReason: 'cost_cap'andcostUsd: 0.Mid-turn. On every assistant
message_end, the cumulative usage reported by pi is compared against the cap. If at-or-past, pi is aborted (best-effort) and the turn completes withfinishReason: 'cost_cap'. Any text that already streamed is preserved in the result.
The cap is not a hard guarantee. Pi may have an in-flight LLM call at the moment the check fires; that call is allowed to complete its current assistant message, which can push spend a small amount past the cap. Subsequent prompts on the same session will then short-circuit pre-flight. Set caps with some headroom.
Models
Models are addressed by provider:modelId. Examples:
openrouter:anthropic/claude-3.5-haikuopenrouter:amazon/nova-micro-v1openrouter:google/gemini-2.0-flash-exp:free
Pi resolves the identifier through its own ModelRegistry. Anything pi
recognises, this server can use. For development and tests we lean on
openrouter:amazon/nova-micro-v1 because a smoke run costs well under one
tenth of a cent.
Limitations and known issues
No persistence across server restarts. Sessions live in the server process. Pi writes its own JSONL session files under
~/.pi-mcp/sessions/<uuid>/, but this server does not currently re-attach to them on startup. Restart the server, lose the session map.Disk usage grows. Each session leaves its
agentDirbehind. Clean up periodically withrm -rf ~/.pi-mcp/sessions/(or the Windows equivalent) when no live server is using it.Windows signal handling is best-effort. SIGINT works under most shells; if shutdown gets stuck, use
taskkill /PID <pid>.No authentication. Bind is
127.0.0.1. Don't expose the port on a public interface without putting auth in front.One pi session per
sessionId. Concurrent sessions are isolated via per-sessionagentDir, so a busy server can run many sessions in parallel; a single session can only handle one prompt at a time.
Development
npm install
npm run build # tsc → dist/
npm run dev # tsx watch src/index.ts
npm test # vitest, 80 unit tests, no network
npm run typecheck # tsc --noEmit
npm run sessions # run the pi-sessions CLI from dist/Live smokes hit real OpenRouter and need OPENROUTER_API_KEY set. Each
run costs less than $0.001 against amazon/nova-micro-v1.
npx tsx scripts/smoke-pi.ts # PiSession against real OpenRouter
npx tsx scripts/smoke-l4.ts # Full 4-tool MCP client end-to-end
npx tsx scripts/smoke-l5.ts # Cost-cap behaviour, pre-flight + mid-turnnpm run test:live is a placeholder; the live coverage today is the smoke
scripts above.
Architecture
Claude Code (MCP client, Streamable HTTP)
|
v
pi-mcp-server (Node, 127.0.0.1:3700/mcp)
| - one McpServer per HTTP MCP-Session-Id
| - one process-wide SessionManager
| - 10s notifications/progress heartbeat per prompt
| - DNS-rebinding Host-header check
|
+--> PiSession A (createAgentSession, isolated agentDir)
+--> PiSession B (createAgentSession, isolated agentDir)
+--> ...Pi is consumed in-process via the createAgentSession SDK rather than as
a child process. This avoids JSONL framing fragility, gives us typed
events, and lets the server intercept usage updates on every
message_end for cost-cap enforcement.
Contributing
Issues and PRs welcome at https://github.com/a-fig/pi-mcp-server. The
test suite is npm test; please keep it green and add a unit test for
any new behaviour. For changes that touch live pi, run the relevant smoke
script before opening the PR.
License
MIT. See LICENSE.
Acknowledgments
@earendil-works/pi-coding-agentfor the underlying agent SDK.@modelcontextprotocol/sdkfor the MCP server and Streamable HTTP transport.
This server cannot be deployed
Maintenance
Related MCP Connectors
Shared memory MCP server for Claude Code, Codex, Gemini CLI and Cursor. Cited, editable.
MCP server for your apps' tools and custom tools, plus hosted AI agents and approval-gated workflows
Nifty's MCP server — exposes tasks, projects, messages, and files as tools for AI agents.
Real-time chat hub for AI agents — Claude Code, Cursor, Cline, Codex over MCP or REST.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceEnables MCP hosts like Claude Code and Codex to spawn, manage, and interact with persistent, reusable Pi coding-agent sessions, supporting task dispatch, status checks, and session lifecycle control.4 npm-
- AlicenseNot gradedqualityCmaintenanceLets MCP clients create, resume, message, and stop Pi coding-agent sessions running on another machine, with conversations that survive client disconnects and server restarts. Sessions can be kept loopback-only or published privately to trusted callers over Tailscale HTTPS.1Apache 2.0
- AlicenseNot gradedqualityBmaintenanceEnables voice assistants to start, monitor, message, and approve local Claude Code sessions on your computer, including sessions already open in a terminal. It exposes session management, recaps, approvals, and history search as MCP tools.7GPL 3.0
- AlicenseNot gradedqualityBmaintenanceEnables an AI assistant to delegate coding tasks to local Codex or Claude Code CLIs, monitor their progress, read results, continue sessions, and browse the files in user-approved project workspaces. It exposes these capabilities over Streamable HTTP through a Cloudflare tunnel or locally over stdio, so a remote or cloud client can drive agents running on the user's own machine.70 npm1MIT