pi-delegate-mcp
Servidor MCP que convierte al agente de codificación pi en un trabajador en segundo plano dirigible: delega una tarea, redirígela a mitad de ejecución y mantén su contexto fuera del tuyo.
Documentación
██████╗ ██╗ ██████╗ ███████╗██╗ ███████╗ ██████╗ █████╗ ████████╗███████╗
██╔══██╗██║ ██╔══██╗██╔════╝██║ ██╔════╝██╔════╝ ██╔══██╗╚══██╔══╝██╔════╝
██████╔╝██║ ██║ ██║█████╗ ██║ █████╗ ██║ ███╗███████║ ██║ █████╗
██╔═══╝ ██║ ██║ ██║██╔══╝ ██║ ██╔══╝ ██║ ██║██╔══██║ ██║ ██╔══╝
██║ ██║ ██████╔╝███████╗███████╗███████╗╚██████╔╝██║ ██║ ██║ ███████╗
╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚══════╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝
███╗ ███╗ ██████╗██████╗
████╗ ████║██╔════╝██╔══██╗
██╔████╔██║██║ ██████╔╝
██║╚██╔╝██║██║ ██╔═══╝
██║ ╚═╝ ██║╚██████╗██║
╚═╝ ╚═╝ ╚═════╝╚═╝
Servidor MCP que expone el agente de codificación pi como un trabajador delegable y dirigible.
Apunta Claude Code (o cualquier host MCP) hacia él y delega trabajo a cualquiera de los ~38 proveedores de pi (DeepSeek, Grok, GLM, Kimi, Qwen, Codex, OpenRouter, llama.cpp local) manteniendo el contexto del sub-agente fuera de tu conversación principal.
Para qué sirve
Tu harness principal se ejecuta en un modelo caro, con una ventana de contexto que te importa. Gran parte de lo que hace no necesita ese modelo, y daña activamente ese contexto: buscar en un repositorio cada punto de llamada, leer un archivo de 2000 líneas para responder una pregunta, auditar lo que dejó un refactor.
Entrega ese trabajo a un delegado en su lugar:
- Costo. El trabajo pesado se ejecuta en DeepSeek, GLM, Kimi, Qwen o un llama.cpp local. Pagas precios de frontera solo por el razonamiento que realmente los necesita.
- Contexto. El delegado lee los archivos con su propio presupuesto y devuelve un resultado. Los 200 KB que leyó nunca entran en tu conversación.
- Radio de impacto. Los delegados son de solo lectura por defecto (
read, grep, find, ls), aplicado en la construcción de la sesión. Un modelo barato haciendo trabajo exploratorio no puede tocar tu árbol a menos que lo habilites.
El delegado es siempre el agente pi. Codex, Grok, DeepSeek y los demás suministran el modelo detrás de él; esto no es un wrapper alrededor de sus CLIs.
Por qué pi, y no opencode o un wrapper de CLI
Un delegado solo es dirigible si dos canales permanecen abiertos: debes poder redirigirlo a mitad de tarea, y debe poder preguntarte algo y bloquearse hasta que respondas. La mayoría de las formas de conducir un agente de codificación desde otro programa cierran ambos.
pi -p / wrappers de CLI | SDK de opencode | este servidor | |
|---|---|---|---|
| Se ejecuta en proceso | no (subproceso) | no (cliente HTTP a opencode serve) | sí (createAgentSession) |
| Redirigir una ejecución en curso | no | abort solamente | steer |
| El agente puede preguntarte algo | no (ctx.hasUI falso) | no en la API de sesión | status → answer * |
| Modelo por llamada | no | sí | argumento model |
pi -p y --mode json establecen ctx.hasUI = false. Un delegado iniciado de esa manera es de tipo disparar-y-olvidar
por construcción: no puede plantear una pregunta, y no puedes redirigirlo.
El SDK de opencode es un cliente tipado para un proceso de servidor separado: createOpencode() arranca
opencode serve y habla HTTP con él. Diseño limpio, pero significa un segundo proceso que supervisar,
y la superficie de sesión que expone (prompt, abort, revert, messages) no tiene dirección
a mitad de turno ni una vía para que el agente pregunte algo al llamador.
pi incluye createAgentSession como una biblioteca incrustable. Este servidor mantiene el objeto de sesión
en proceso, así que session.steer() puede enviar un mensaje después de la llamada de herramienta actual y antes de la
siguiente llamada al modelo, y un uiContext sintético captura las preguntas del agente y las estaciona para
answer. No se ejecuta nada en un shell externo; no hay nada que supervisar.
* Las preguntas provienen de extensiones de pi, así que ese canal está abierto solo para delegados generados con
extensions: true. Ver Búsqueda web y otras herramientas de extensión.
(La tabla compara el canal de delegación, no el sandboxing; opencode tiene su propia configuración de permisos. Ver Solo lectura por defecto para lo que este servidor aplica y no aplica.)
Herramientas
| Herramienta | Propósito |
|---|---|
init | Llámalo primero. Informa modelos alcanzables, herramientas permitidas y cómo conducir un delegado. Cada otra herramienta se niega hasta que se haya ejecutado una vez. |
spawn | Delega en segundo plano. Devuelve sessionId inmediatamente. Úsalo por defecto. |
spawn_batch | Distribuye hasta 10 delegados en una sola llamada. Validado como lote, así que nada comienza si una tarea es mala. |
run | Delega y bloquea hasta terminar. Solo para preguntas rápidas. |
status | Estado, turnos, herramientas usadas, texto más reciente y preguntas pendientes. |
steer | Redirige un agente en ejecución. Aterriza después de su llamada de herramienta actual. |
follow_up | Dale a un delegado terminado otro turno. Conserva todo lo que leyó, así que no tienes que reexplicar la tarea. |
answer | Responde una pregunta que status haya sacado a la superficie. Solo alcanzable con extensions: true, ya que solo las extensiones pueden preguntar. |
abort | Detiene una sesión; la salida parcial sigue siendo legible. |
models | Lista los modelos que este delegado puede usar. |
sessions | Lista sesiones, en ejecución y terminadas. Filtra por state, expande con verbose. |
forget | Elimina una sesión terminada del historial, liberando su id. |
Instalación
Requiere Node.js 22.19+ y una instalación funcional de pi que haya iniciado sesión una vez
(pi, luego /login).
Claude Code
claude mcp add pi -e PI_DELEGATE_MODEL=openrouter/stealth/ox-alpha -- npx -y pi-delegate-mcp
Cualquier host MCP, vía .mcp.json
{
"mcpServers": {
"pi": {
"command": "npx",
"args": ["-y", "pi-delegate-mcp"],
"env": { "PI_DELEGATE_MODEL": "openrouter/stealth/ox-alpha" },
"timeout": 1800000
}
}
}
npx resuelve el paquete en cada lanzamiento. Para fijarlo, instálalo globalmente y llama al binario
directamente:
npm install -g pi-delegate-mcp
{ "mcpServers": { "pi": { "command": "pi-delegate-mcp", "timeout": 1800000 } } }
Mantén la clave del servidor corta, ya que prefija cada nombre de herramienta (mcp__pi__spawn).
Desde el código fuente
git clone https://github.com/howznguyen/pi-delegate-mcp && cd pi-delegate-mcp
npm install && npm run build && npm link
Primera ejecución
Pide a tu agente que delegue algo. Llama a init una vez para que aprenda qué puede alcanzar este servidor,
luego spawn:
{ "id": "audit-01", "label": "who still imports onnxruntime",
"prompt": "Search this repo for anything still importing onnxruntime and list the files.",
"cwd": "/path/to/repo" }
{ "sessionId": "audit-01", "state": "running", "model": "opencode-go/deepseek-v4-flash",
"activeTools": ["read", "grep", "find", "ls"] }
spawn devuelve inmediatamente. Consulta con status para el rastro ordenado de herramientas y la respuesta, o
sessions cuando haya varias en vuelo. Si init falla, dice exactamente qué falta: pi no
instalado, ningún proveedor con sesión iniciada, o un alcance de modelo que no coincide con nada.
Los nombres de modelo en los ejemplos a continuación son ilustrativos. Ejecuta models para ver qué puede alcanzar
realmente tu propia instalación de pi.
Trazabilidad
spawn y run aceptan ambos tu propio id y un label de texto libre:
{
"id": "search-audit-01",
"label": "what ONNX removal left behind",
"prompt": "...",
"model": "opencode-go/deepseek-v4-flash"
}
Los ids son [A-Za-z0-9._:-], de 1 a 64 caracteres, deben comenzar alfanuméricos y deben ser únicos entre sesiones
activas. Omítelos para un UUID.
Las sesiones terminadas siguen siendo legibles vía status y sessions en lugar de desaparecer, así que puedes volver
y comprobar qué hizo realmente un delegado. Las PI_DELEGATE_HISTORY más recientes (50 por defecto) se
conservan; forget elimina una antes de tiempo.
status devuelve un rastro ordenado de toolCalls: cada herramienta que ejecutó el delegado, con argumentos y
tiempos. Añade verbose: true para ids de llamada y resultados:
{
"seq": 1,
"id": "call_467b4bb4…",
"name": "bash",
"state": "ok",
"ms": 10,
"args": "{\"command\":\"echo hello-trace\"}",
"result": "hello-trace\n"
}
Los argumentos y resultados se recortan (PI_DELEGATE_TRACE_ARGS, PI_DELEGATE_TRACE_RESULT) con la
longitud descartada registrada, así que una sola read de un archivo grande no puede inundar tu contexto.
Darle otro turno a un delegado
Un delegado terminado no está agotado. pi mantiene su sesión en memoria, así que follow_up vuelve a pedir
al mismo agente con todo lo que ya leyó todavía en contexto:
{ "sessionId": "search-audit-01", "prompt": "Now check whether the build files reference it too" }
{ "sessionId": "search-audit-01", "state": "running", "turnsSoFar": 1 }
El delegado retoma donde lo dejó. Todavía conserva los archivos que leyó en el primer turno, así que la segunda pregunta cuesta una llamada al modelo en lugar de una sesión nueva que relea el repositorio.
Esta es la forma barata de tener una conversación con un delegado. Generar uno nuevo significa reexplicar la tarea y pagar para que relea los mismos archivos, y su respuesta llega sin el razonamiento que llevó hasta allí.
follow_up rechaza un delegado que todavía está trabajando, porque redirigir uno a mitad de tarea es
para lo que sirve steer. Los dos no son intercambiables: steer aterriza entre llamadas de herramienta en un
agente en ejecución, follow_up inicia un nuevo turno en uno terminado.
Distribución en lote
spawn_batch inicia un lote completo en una sola llamada. Las tareas heredan el model, cwd,
tools y extensions a nivel de lote, y los anulan individualmente donde lo necesiten:
{
"idPrefix": "audit",
"model": "opencode-go/deepseek-v4-flash",
"cwd": "/repo",
"tools": ["ls"],
"tasks": [
{ "prompt": "What still imports onnxruntime?", "label": "imports" },
{ "prompt": "Which build files still reference ONNX?", "label": "build" },
{
"prompt": "Any ONNX model files left on disk?",
"label": "artifacts",
"model": "opencode-go/ox-alpha-free"
}
]
}
Eso los nombra audit-01, audit-02, audit-03 y devuelve en unos pocos milisegundos, ya que
lanzar un delegado no espera a que piense.
El lote se valida antes de que comience cualquier cosa: formato de id, ids duplicados dentro del lote, ids ya activos, herramientas bloqueadas y cada nombre de modelo. Una tarea mala hace fallar la llamada y no lanza nada. La mitad de una distribución es el peor resultado, porque pagas por los delegados que sí comenzaron y aún tienes que averiguar cuáles no.
Consulta todo el lote con una sola llamada a sessions en lugar de una status por delegado. Baja a
status solo para el delegado que realmente quieres leer. steer y abort siguen siendo por sesión.
Elegir un modelo por llamada
model en cualquier llamada anula PI_DELEGATE_MODEL. Un nombre irresoluble es un error grave, nunca una
retrocesión silenciosa al modelo por defecto, porque una retrocesión silenciosa es cómo terminas facturando un modelo
que nunca pediste.
Qué nombres se resuelven lo decide el propio alcance enabledModels de pi, que este servidor aplica
en lugar de simplemente mostrar:
opencode-go/deepseek-v4-flash -> ok (listed in enabledModels)
opencode-go/glm-5.3 -> refused (out of scope)
knowns-hub/claude-opus -> ok (custom provider, see below)
Los proveedores personalizados omiten el alcance. Cualquier modelo servido por un proveedor declarado en
~/.pi/agent/models.json se ofrece incluso cuando enabledModels no lo nombra, con el argumento
de que declarar un proveedor a mano ya es una intención de usarlo. Por eso la lista puede ser
mucho más larga que enabledModels: tres entradas en el alcance más dos proveedores personalizados pueden fácilmente
significar quince modelos ofrecidos. init lo dice explícitamente en models.scopeNote cuando aplica.
Dos interruptores cambian eso:
| Efecto | |
|---|---|
PI_DELEGATE_STRICT_SCOPE=1 | Respeta enabledModels exactamente. Se elimina la omisión del proveedor personalizado. |
PI_DELEGATE_IGNORE_SCOPE=1 | Elimina el alcance por completo. Cada modelo autenticado es usable. |
Llama a models para ver qué es realmente alcanzable bajo la configuración que esté en vigor.
Línea de estado
Claude Code permite exactamente un comando statusLine, así que pi-delegate-statusline envuelve lo que
ya ejecutas y añade un segmento que muestra los delegados de este espacio de trabajo:
{
"statusLine": {
"type": "command",
"command": "PI_DELEGATE_STATUSLINE_WRAP=ccstatusline pi-delegate-statusline",
"refreshInterval": 10
}
}
Elimina PI_DELEGATE_STATUSLINE_WRAP para imprimir solo el segmento de pi.
π ▸ audit engine·t1·12s audit index·t2·8s running, with turn counts and elapsed time
π ▸ migrate·t7·3m04s ?1 waiting one delegate is blocked on a question
π ✓2 finished, nothing running
Qué delegados pertenecen a qué sesión
Filtrar por directorio no es suficiente: dos sesiones de Claude Code abiertas en el mismo repositorio
mostrarían los delegados de la otra. La atribución usa el linaje de procesos en su lugar.
El host de MCP genera un servidor por sesión, por lo que el servidor registra process.ppid, el pid del host. La línea de estado, generada por ese mismo host, recorre su propia ascendencia y conserva solo los archivos de estado cuyo hostPid encuentra allí. Mismo repositorio, dos sesiones, sin interferencias. El filtro de directorio permanece como respaldo para archivos de estado escritos antes de que esto existiera.
El estado vive en $XDG_STATE_HOME/pi-delegate-mcp/<pid>.json (PI_DELEGATE_STATE_DIR para reubicar). Los archivos se eliminan cuando su proceso desaparece, ESRCH solamente, ya que EPERM significa que el proceso está vivo bajo otro usuario. Los servidores también salen por sí solos cuando stdin se cierra o el pid del host desaparece, por lo que un host que muere sin cerrar el transporte no deja nada atrás.
Solo lectura por defecto
Las herramientas están bloqueadas a read, grep, find, ls en la construcción de la sesión. Cualquier otra cosa se rechaza antes de que se cree una sesión.
Para ampliar eso, nombre las herramientas adicionales en el servidor:
"env": { "PI_DELEGATE_ALLOW_TOOLS": "bash" }
o PI_DELEGATE_ALLOW_WRITE=1 para permitir todo.
bash no es un punto intermedio. pi no incluye un sistema de permisos, por lo que un delegado que tenga bash puede escribir archivos, eliminarlos y acceder a la red independientemente de si write y edit están en su lista. Rechazar esos dos mientras se permite bash registra su intención; no hace cumplir nada. Los avisos de permisos y los hooks de Claude Code nunca ven lo que hace pi. Si necesita un límite real, ejecute este servidor dentro de un contenedor.
Búsqueda web y otras herramientas de extensión
Las herramientas propias de pi son read, grep, find, ls, bash, powershell, write, edit. No hay búsqueda ni fetch entre ellas. Esas provienen de extensiones de pi, que registran sus propias herramientas, y un delegado puede usarlas.
Establezca extensions: true en la llamada y permita los nombres de las herramientas en el servidor:
"env": { "PI_DELEGATE_ALLOW_TOOLS": "web_search,fetch_content" }
{ "prompt": "Find the current Node LTS version and tell me just the number",
"extensions": true, "tools": ["read", "grep", "find", "ls", "web_search"] }
{ "seq": 1, "name": "web_search", "state": "ok", "ms": 2568,
"args": "{\"query\":\"latest stable Node.js LTS version\",\"numResults\":5}" }
Así es como se le da a un delegado acceso a la red sin entregarle bash. web_search puede buscar y nada más, y pasa por la misma lista de permitidos que cualquier otra herramienta, por lo que el valor predeterminado de solo lectura no cambia para llamadas que no lo solicitan.
Qué herramientas existen depende de lo que el usuario que ejecuta el servidor tenga instalado. pi-web-access proporciona web_search, fetch_content, source_check y get_search_content. pi-mcp-adapter conecta los servidores MCP en ~/.pi/agent/mcp.json y los expone como mcp. pi no tiene un cliente MCP propio, por lo que esa extensión es la única ruta hacia uno.
extensions: true confía en cada extensión instalada, no solo en la que quería. Se cargan como un conjunto, se ejecutan con todos los privilegios del proceso de este servidor, y algunas abren sockets y temporizadores que sobreviven a la sesión. Actívelo por llamada, para los delegados que lo necesiten, en lugar de dejarlo activado por defecto. También cuesta tiempo de inicio real, por lo que está desactivado a menos que se solicite.
Configuración
| Variable de entorno | Predeterminado | Significado |
|---|---|---|
PI_DELEGATE_MODEL | El predeterminado de pi | Modelo usado cuando una llamada omite model |
PI_DELEGATE_ALLOW_TOOLS | sin establecer | Lista separada por comas de herramientas adicionales a permitir, p. ej. bash |
PI_DELEGATE_ALLOW_WRITE | sin establecer | 1 permite todas las herramientas |
PI_DELEGATE_HISTORY | 50 | Sesiones terminadas conservadas para revisión |
PI_DELEGATE_TRACE_ARGS | 400 | Máx. de caracteres de argumentos de herramientas conservados en el rastro |
PI_DELEGATE_TRACE_RESULT | 600 | Máx. de caracteres de resultados de herramientas conservados en el rastro |
PI_DELEGATE_BATCH_MAX | 10 | Límite de tareas por llamada a spawn_batch |
PI_DELEGATE_LIST_CAP | 60 | Por encima de esto, init resume modelos por proveedor en lugar de listarlos |
PI_DELEGATE_STATE_DIR | Directorio de estado XDG | Dónde se publica el estado de la línea de estado |
PI_DELEGATE_STATUSLINE_WRAP | sin establecer | Comando de línea de estado a envolver y añadir |
PI_DELEGATE_STATUSLINE_LOG | sin establecer | Archivo al que añadir una marca de tiempo en cada render de línea de estado, para depuración |
PI_DELEGATE_PROGRESS_MS | 15000 | Intervalo de notificación de progreso durante run |
PI_DELEGATE_IGNORE_SCOPE | sin establecer | 1 ignora el ámbito enabledModels de pi, permitiendo cualquier modelo configurado |
PI_DELEGATE_STRICT_SCOPE | sin establecer | 1 respeta enabledModels exactamente, eliminando la omisión de proveedor personalizado |
PI_CODING_AGENT_DIR | ~/.pi/agent | De dónde se leen el auth.json y la configuración de pi |
Trabajo de larga duración
El SDK de MCP TypeScript tiene un tiempo de espera de solicitud predeterminado de 60 segundos, que una tarea real superará. Tres defensas, en orden de preferencia:
- Use
spawn+status. Nada bloquea, por lo que no se aplica ningún tiempo de espera. runemite notificaciones de progreso periódicas, que restablecen el tiempo de espera del host.- Aumente el límite con
"timeout"en.mcp.jsonoMCP_TOOL_TIMEOUTen el entorno.
CLAUDE_AUTO_BACKGROUND_TASKS=1 hace que Claude Code ponga en segundo plano llamadas MCP largas después de ~2 minutos.
Tenga en cuenta que las notificaciones de progreso se descartan una vez que una llamada se pone en segundo plano, así que elija (1) o (3), no ambos.
Autenticación
El servidor no maneja credenciales. pi se autentica desde ~/.pi/agent/auth.json, luego variables de entorno. Los hosts de MCP a menudo lanzan servidores con un entorno reducido, así que prefiera auth.json (ejecute pi una vez y /login) en lugar de exportar claves en un perfil de shell.
Desarrollo
npm install
npm run build # tsc, src/*.ts -> dist/
npm run typecheck # tsc --noEmit, strict
npm run test:ci # offline: boots the server over stdio and lists its tools
npm test # full suite: needs a logged-in pi, makes real model calls
test:ci es lo que CI ejecuta y lo que prepublishOnly exige, porque no necesita credenciales ni red. npm test impulsa delegados reales contra proveedores reales, por lo que cuesta dinero y solo funciona donde pi ha iniciado sesión.
| Ruta | Qué vive allí |
|---|---|
src/config.ts | Cada variable de entorno, leída en un solo lugar |
src/permissions.ts | La lista de permitidos de herramientas y la puerta que la hace cumplir |
src/registry.ts | Mapa de sesiones, reclamación de id, expulsión de historial |
src/tools/ | Un módulo por grupo de herramientas MCP |
src/pi/ | Todo lo que toca el SDK de pi |
src/statusline/ | Publicación de archivos de estado y el binario de línea de estado |
Los lanzamientos se impulsan por etiquetas. npm version patch && git push --follow-tags ejecuta la compilación y las pruebas, luego publica mediante publicación confiable OIDC, por lo que no se almacena ningún token npm en el repositorio.
Los problemas y las solicitudes de extracción son bienvenidos. Si está informando un delegado que se comportó mal, el rastro toolCalls de status con verbose: true es lo útil para adjuntar.
Trabajo previo
abatilo/pi-mcp-bridge toma la ruta más simple: generar pi --mode json -p --session-id <uuid> y dejar que pi persista sesiones en disco, por lo que el puente no mantiene ningún estado. Elegante, y vale la pena leerlo. Intercambia dirección, preguntas y control de herramientas para llegar allí.
Licencia
MIT