pi-mcp
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
MCP server for running and supervising pi
coding agents over RPC. The design keeps a pi process alive between turns (hot
resume); an explicit resurrect cold-resumes a ripped or exited agent from its
session file.
Status: SQLite-backed agents, bwrap-caged
pi --mode rpcprocesses, normalized mounts, idle keep-alive, logs, chained agents, callback settle notifications, per-child token/MCP config patching, per-agent context budgets that trigger pi autocompaction, rip/resurrect with lifecycle counters, child mounts scoped to their parent agent, background agent refresh, and focused runner, store, artifact, and server modules.
Quick start
uv sync --extra dev
PI_MCP_DB=/tmp/pi-mcp.sqlite3 \
PI_MCP_AGENTS_DIR=/tmp/pi-mcp/agents \
PI_MCP_ACCESS_TOKEN=alpha \
uv run pi-mcprun needs pi and bubblewrap (bwrap) on the host. pi-mcp-fake can still
seed fake sessions. stdio is the default transport. For HTTP:
uv run pi-mcp --transport streamable-http --host 0.0.0.0 --port 8000Current tools:
Tool | Arguments | Returns |
| none | Agents visible to the caller's token |
|
| Metadata, public state, last assistant text |
|
| Starts a caged pi RPC agent and waits for handshake |
|
| Stops the process but keeps session and properties |
|
| Cold-resumes with stored properties and optional turn |
|
| Prompts an idle agent or queues for a running one |
|
| Stops the process and deletes its artifact directory |
|
| Proxy the real host |
|
| List forwarded |
|
| Proxy the real host |
|
| Read a file by line or byte range |
|
| Read the stdout or errors log |
See Tool reference for full arguments and response examples.
The main transport process is bound to one PI_MCP_ACCESS_TOKEN; per-request
HTTP auth for that transport is not implemented yet. The generated child MCP
endpoint does use per-request bearer authentication.
Related MCP server: pi-agent-mcp
Chaining agents
Every caged agent receives AGENT_ID in its environment. To have a child report
back, call run(..., callback_agent_id=AGENT_ID) from inside the parent. When
the child settles, pi-mcp prompts the parent with the child's agent_id, last
text, error/exit code, session, and artifact paths. The parent can then use
list_tmp, read, grep, get_info, and read_tmp on that id.
Agents get a generated /tmp/pi-agent/mcp.json that points at a loopback
pi-mcp endpoint, so a caged agent can call the same server. The config carries a
bearer token bound to that agent. When an agent starts a child, the child's
write/read/denied rules are narrowed to the parent's mounts: it cannot
request paths outside the parent's grants, cannot enter a parent-denied path,
and inherits parent read-only overrides and denials. Each child's stored rules
are already scoped, so validation is transitive without walking the parent
chain. A parent with no mount grants cannot give a child new mounts. Callback validation happens before the
child starts; unknown ids abort without creating artifacts. Callback delivery
may cross groups by id, but artifact tools remain group-scoped. Set
PI_MCP_CHILD_MCP=0 to disable the child endpoint and generated config.
Token patching
Pass run(access_token="...") to put a child in another token group. A known
token joins its group; an unknown token creates one. Only that child's private
mcp.json is patched, with a new server-minted token bound to the child agent;
the explicit group token is never copied into the config. Global and project pi
MCP config are never rewritten, and the minted token is mode 0600 in the child
config, is not returned by tools, and is not stored in SQLite.
The loopback endpoint accepts only tokens minted for an agent; arbitrary bearer values and raw group tokens are rejected. Tokens are revoked when the agent exits, is ripped, or is removed.
Without access_token, the child stays in the caller's group and receives an
ephemeral capability token. This keeps chained parents able to inspect their
children. With an explicit token, the parent cannot see the child through normal
group-scoped tools; use callback_agent_id for the result.
Context limits
Pass run(context_size=50000) to give an agent a lower pi context trigger.
context_size has a 20,000-token minimum. pi-mcp writes a private
pi-agent/models.json with contextWindow = context_size + headroom, where
headroom = max(context_size // 10, 10000). It writes reserveTokens equal to
the same headroom, so autocompaction starts when the projected context exceeds
context_size. The headroom is room for the response and summary call; without
it pi can clamp the summary to one token. The summary keeps 10% of
context_size as recent tokens; this is hardcoded and not configurable. The
setting is shown as context_size by list and get_info.
The budget is an accounting override, not the provider's real window. The server
only requires an integer of at least 20,000; it does not resolve provider
catalogs. Keep context_size + headroom + model.maxTokens below the model's
actual window. If the resulting window is at or above the real one, compaction
turns into overflow recovery. Without context_size, pi's own model window and
defaults apply.
Agent states
list, get_info, rip, and resurrect expose one public state:
active— the process is live and a turn is running.idle— the process is live and settled.ripped— the process was explicitly stopped; the session is resumable.exited— the process ended on its own; the session is resumable.
A background thread refreshes each live agent with a configurable pause. The
default is one second, so a full lap takes pause × live agents. Configure it
at server start with --refresh-pause or PI_MCP_REFRESH_PAUSE; 0 disables
the background thread. Natural exits are marked exited and their child tokens
are revoked during this lap. rip is immediate and marks the agent ripped.
Rip and resurrect
rip(agent_id) closes the pi process but keeps the agent record, session
file, logs, forwarded /tmp, and original spawn settings. Its state becomes
ripped. It is idempotent; a second rip does not increment the counter.
resurrect(agent_id, prompt=...) starts a new process for the same agent id and
session using the stored provider, model, context_size, mount rules, token
group, and callback target. Those properties cannot be overridden. If prompt
is supplied, the resurrected agent starts that turn immediately; otherwise it
returns idle.
get_info and list expose lifecycle counters:
{
"lifecycle": {
"retries": 1,
"rips": 1,
"self_exits": 1,
"ripped_at": "2026-11-01T12:00:00Z",
"resurrected_at": "2026-11-01T12:05:00Z"
}
}ripscounts successful explicit rip transitions.retriescounts successful resurrect calls.self_exitscounts processes that exited without being ripped, removed, or shut down with the server. This is the counter for “the agent exited on its own.”
send_message still requires a running process; automatic cold resume is not
implemented. Call resurrect first; the state returns to idle or active.
A second prompt after resurrection reuses the new process, just like a hot
prompt.
Agent artifacts
PI_MCP_AGENTS_DIR (or --agents-dir) selects the artifact root. It defaults
to <db-parent>/agents. Each agent gets one directory:
$PI_MCP_AGENTS_DIR/<agent_id>/
stdout.log
errors.log
agent_session.jsonl
tmp/ # bind-mounted at /tmp inside the cageThe session file is mounted read-write at /agent_session.jsonl. tmp/ is
mounted read-write at /tmp, so files the agent writes there are forwarded to
the host. Use list_tmp to inspect the tree, grep_tmp to proxy the host
grep, and read_tmp to read or download files. read selects the stdout or
errors stream. Reads default to lines_from=0 with max_bytes=1024; line
ranges are 0-based and end-exclusive.
Use bytes_from/bytes_to instead for exact byte ranges. Long lines over 10k
chars are truncated with ...<truncated N chars>, warnings, and byte offsets in
truncated_regions. Binary data is returned as base64. grep proxies the
host binary against the selected log path. Read/list paths are
scoped to that agent's tmp/, and traversal or symlinks escaping it are
rejected. The directory survives process exit; remove deletes it. get_info
returns all paths under artifacts.
Tool reference
All successful tool results are returned in MCP structuredContent. IDs, paths,
byte counts, and timestamps in the examples are illustrative. Tool failures are
returned as MCP tool errors with isError: true.
list
List all sessions visible to the caller's token group.
Argument | Type | Required | Default | Notes |
— | — | — | — | No arguments. |
{
"count": 1,
"agents": [
{
"agent_id": "01a1964b-2c01-70a5-b520-78f933cdafad",
"name": "deepseek/deepseek-flash",
"state": "idle",
"provider": "deepseek",
"model": "deepseek-flash",
"context_size": 50000,
"lifecycle": {
"retries": 0,
"rips": 0,
"self_exits": 0,
"ripped_at": null,
"resurrected_at": null
},
"messages": 2,
"tokens": {
"input": 120,
"output": 45,
"total": 165
},
"logs": {
"stdout_bytes": 1234,
"stderr_bytes": 0
},
"updated_at": "2026-11-01T08:46:51Z"
}
]
}In list, messages is an integer total. In get_info, it is an object.
get_info
Get complete metadata for one agent.
Argument | Type | Required | Default | Notes |
| string | yes | — | Agent UUID. |
{
"agent_id": "01a1964b-2c01-70a5-b520-78f933cdafad",
"name": "deepseek/deepseek-flash",
"state": "idle",
"provider": "deepseek",
"model": "deepseek-flash",
"context_size": 50000,
"mounts": {
"write": ["/home/user/project"],
"read": ["/home/user/data"],
"denied": []
},
"cwd": "/tmp",
"session_id": "01a1964b-2c01-70a5-b520-78f933cdafad",
"pid": 4242,
"created_at": "2026-11-01T07:12:51Z",
"updated_at": "2026-11-01T08:46:51Z",
"last_activity_at": "2026-11-01T08:46:51Z",
"messages": {
"total": 2,
"user": 1,
"assistant": 1,
"tool_calls": 0
},
"tokens": {
"input": 120,
"output": 45,
"total": 165
},
"session_bytes": 149799,
"logs": {
"stdout_bytes": 40922,
"stderr_bytes": 0
},
"queue_length": 0,
"last_error": null,
"exit_code": null,
"lifecycle": {
"retries": 0,
"rips": 0,
"self_exits": 0,
"ripped_at": null,
"resurrected_at": null
},
"callback_agent_id": null,
"callback": {
"agent_id": null,
"settle_count": 0,
"notified_seq": 0,
"notified_at": null,
"error": null
},
"artifacts": {
"dir": "/home/user/.local/share/pi-mcp/agents/01a1964b-2c01-70a5-b520-78f933cdafad",
"stdout_log": "/home/user/.local/share/pi-mcp/agents/01a1964b-2c01-70a5-b520-78f933cdafad/stdout.log",
"error_log": "/home/user/.local/share/pi-mcp/agents/01a1964b-2c01-70a5-b520-78f933cdafad/errors.log",
"session": "/home/user/.local/share/pi-mcp/agents/01a1964b-2c01-70a5-b520-78f933cdafad/agent_session.jsonl",
"tmp": "/home/user/.local/share/pi-mcp/agents/01a1964b-2c01-70a5-b520-78f933cdafad/tmp"
},
"last_assistant_text": "Done. I updated the parser."
}state is one of active, idle, ripped, or exited. pid is null when
no process is live.
run
Spawn a caged pi agent and return after its idle handshake. It waits for
send_message.
Argument | Type | Required | Default | Notes |
| string | no |
| Provider name. |
| string | no |
| Model name. |
| string[] | no |
| Absolute host paths mounted writable. |
| string[] | no |
| Absolute host paths mounted read-only. |
| string[] | no |
| Absolute host paths explicitly denied. |
| string | no |
| Existing agent to prompt when this child settles. |
| string | no |
| Starts the child in another token group and patches its MCP config. |
| integer | no |
| Must be at least |
Response: same AgentInfo shape as get_info. A newly started agent has
"state": "idle" and "last_assistant_text": null.
rip
Stop the process while keeping the record, session, logs, forwarded /tmp, and
spawn settings. Idempotent.
Argument | Type | Required | Default | Notes |
| string | yes | — | Agent UUID. |
Returns full AgentInfo plus:
{
"ripped": true,
"already_ripped": false
}state becomes "ripped", pid becomes null, and lifecycle.rips
increments.
resurrect
Restart a ripped or exited agent using its stored settings.
Argument | Type | Required | Default | Notes |
| string | yes | — | Agent UUID. |
| string | no |
| If present, immediately starts a turn. Must not be empty. |
Returns full AgentInfo plus:
{
"resurrected": true,
"prompt_sent": true,
"delivery": "resurrect_prompt",
"disposition": "started"
}Without prompt, prompt_sent is false, and delivery / disposition are
null.
send_message
Send a message to a live agent.
Argument | Type | Required | Default | Notes |
| string | yes | — | Agent UUID. |
| string | yes | — | Message text. |
|
| no |
| Used when the agent is already running. |
{
"agent_id": "01a1964b-2c01-70a5-b520-78f933cdafad",
"delivery": "hot_prompt",
"disposition": "started",
"state": "active"
}Possible deliveries: hot_prompt, hot_follow_up, hot_steer.
remove
Stop the agent if running, then delete its record and artifacts.
Argument | Type | Required | Default | Notes |
| string | yes | — | Agent UUID. |
{
"agent_id": "01a1964b-2c01-70a5-b520-78f933cdafad",
"removed": true
}grep
Run the host's grep against an agent log stream. No shell is used.
Argument | Type | Required | Default | Notes |
| string | yes | — | Agent UUID. |
| string[] | yes | — | Grep argv, without the log path. |
|
| no |
| Selects |
{
"agent_id": "01a1964b-2c01-70a5-b520-78f933cdafad",
"stream": "stdout",
"binary": "/usr/bin/grep",
"cwd": "/home/user/.local/share/pi-mcp/agents/01a1964b-2c01-70a5-b520-78f933cdafad",
"path": "stdout.log",
"args": ["-n", "response"],
"argv": ["-n", "response", "stdout.log"],
"exit_code": 0,
"stdout": "5:{\"type\":\"response\"}\n",
"stderr": "",
"stdout_truncated": false,
"stderr_truncated": false
}The log filename is appended as the final grep operand.
list_tmp
List files under an agent's forwarded /tmp.
Argument | Type | Required | Default | Notes |
| string | yes | — | Agent UUID. |
| string | no |
| Relative directory under the agent tmp. |
| boolean | no |
| Recurse into subdirectories. |
| integer | no |
| Clamped to |
{
"agent_id": "01a1964b-2c01-70a5-b520-78f933cdafad",
"path": ".",
"recursive": true,
"count": 3,
"truncated": false,
"entries": [
{
"path": "notes.txt",
"type": "file",
"size": 21,
"mtime": 1793522881.5025642
},
{
"path": "sub",
"type": "directory",
"size": 4096,
"mtime": 1793522881.5025642
},
{
"path": "sub/data.bin",
"type": "file",
"size": 7,
"mtime": 1793522881.5026317
},
{
"path": "link",
"type": "symlink",
"size": 8,
"mtime": 1793522881.5027,
"target": "../outside.txt"
}
]
}mtime is a Unix timestamp in seconds. Symlinks include target.
grep_tmp
Run the host's grep inside an agent's forwarded /tmp.
Argument | Type | Required | Default | Notes |
| string | yes | — | Agent UUID. |
| string[] | yes | — | Grep argv, with no shell. |
| string | no |
| Relative working directory under tmp. |
| string | no |
| Optional file relative to |
{
"agent_id": "01a1964b-2c01-70a5-b520-78f933cdafad",
"binary": "/usr/bin/grep",
"cwd": "/home/user/.local/share/pi-mcp/agents/01a1964b-2c01-70a5-b520-78f933cdafad/tmp/sub",
"path": "data.bin",
"args": ["-a", "xyz"],
"argv": ["-a", "xyz", "data.bin"],
"exit_code": 0,
"stdout": "abc\u0000xyz\n",
"stderr": "",
"stdout_truncated": false,
"stderr_truncated": false
}Traversal and symlinks escaping the agent tmp are rejected.
read_tmp
Read or download a file from an agent's forwarded /tmp.
Argument | Type | Required | Default | Notes |
| string | yes | — | Agent UUID. |
| string | yes | — | File path relative to the agent tmp. |
| integer | no |
| 0-based start line. |
| integer | no |
| End line, exclusive. |
| integer | no |
| Byte mode start offset. |
| integer | no |
| Byte mode end offset. |
| integer | no |
| Maximum lines to return. |
| integer | no |
| Maximum bytes; capped at 10 MiB. |
Line mode example:
{
"agent_id": "01a1964b-2c01-70a5-b520-78f933cdafad",
"path": "notes.txt",
"mode": "lines",
"size": 21,
"lines_from": 1,
"lines_to": 2,
"line_count": 1,
"next_line": 2,
"next_offset": 21,
"eof": true,
"max_lines": null,
"max_bytes": 1024,
"encoding": "utf-8",
"content": "beta line\n",
"truncated_regions": [],
"warnings": []
}Byte mode replaces lines_from / lines_to / next_line with bytes_from /
bytes_to:
{
"agent_id": "01a1964b-2c01-70a5-b520-78f933cdafad",
"path": "sub/data.bin",
"mode": "bytes",
"size": 7,
"bytes_from": 0,
"bytes_to": 7,
"line_count": 0,
"next_offset": 7,
"eof": true,
"max_lines": null,
"max_bytes": 1024,
"encoding": "base64",
"content": "YWJjAHh5eg==",
"truncated_regions": [],
"warnings": []
}Binary content is base64-encoded. Long lines over 10,000 characters are truncated with warnings and byte regions.
read
Read an agent log stream.
Argument | Type | Required | Default | Notes |
| string | yes | — | Agent UUID. |
|
| no |
| Selects |
| integer | no |
| 0-based start line. |
| integer | no |
| End line, exclusive. |
| integer | no |
| Byte mode start offset. |
| integer | no |
| Byte mode end offset. |
| integer | no |
| Maximum lines to return. |
| integer | no |
| Maximum bytes; capped at 10 MiB. |
{
"agent_id": "01a1964b-2c01-70a5-b520-78f933cdafad",
"stream": "stdout",
"path": "stdout.log",
"mode": "lines",
"size": 40922,
"lines_from": 0,
"lines_to": null,
"line_count": 1,
"next_line": 1,
"next_offset": 145,
"eof": false,
"max_lines": 1,
"max_bytes": 1024,
"encoding": "utf-8",
"content": "{\"type\":\"response\",\"id\":\"rpc-0\",\"success\":true}\n",
"truncated_regions": [],
"warnings": []
}Line and byte ranges are mutually exclusive.
Tests
make lint # Ruff + Prettier checks
make test # unit + stdio MCP round-trip
make e2e # Docker + Testcontainers end-to-end
make real # Cheap real provider checks
make expensive # Real compaction run; only when context/compaction changesDocumentation
Design — MCP surface, RPC lifecycle, persistence, supervision, configuration, security.
pi behavior — verified pi 1.4.0 process, RPC, bash, context, compaction, and record-type behavior.
Sandboxing — write/read/denied mount rules and bwrap behavior.
Development — setup, layout, container, manual tests.
License
MIT. See LICENSE.
This server cannot be deployed
Maintenance
Related MCP Connectors
Hosted runtime for persistent agent teams, durable workflows, memory, schedules, and goals.
Scoped agent execution. Server-side credentials, policy, budgets and verifiable receipts.
Hosted coding agent: start, follow up on and stop tasks that can open GitHub pull requests.
Hosted MCP server and Agent Skills for specs, engineering threads, and project memory.
Related MCP Servers
- AlicenseCqualityBmaintenanceEnables MCP hosts to delegate coding tasks to Pi CLI as a programmable sub-agent with session tracking and process management.74MIT
- FlicenseNot gradedqualityBmaintenanceEnables 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.2 npm-
- AlicenseBqualityBmaintenanceEnables launching and supervising local Codex and Claude Code agent sessions and one-shot tasks, exposing run, list, stop, and output operations over MCP with session-scoped permissions and sandbox controls.2836 npm1MIT
- 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