pi-delegate-mcp
Servidor MCP que transforma o agente de codificação pi em um worker de segundo plano controlável - delegue uma tarefa, redirecione-a durante a execução e mantenha o contexto dele fora do seu.
Documentação
██████╗ ██╗ ██████╗ ███████╗██╗ ███████╗ ██████╗ █████╗ ████████╗███████╗
██╔══██╗██║ ██╔══██╗██╔════╝██║ ██╔════╝██╔════╝ ██╔══██╗╚══██╔══╝██╔════╝
██████╔╝██║ ██║ ██║█████╗ ██║ █████╗ ██║ ███╗███████║ ██║ █████╗
██╔═══╝ ██║ ██║ ██║██╔══╝ ██║ ██╔══╝ ██║ ██║██╔══██║ ██║ ██╔══╝
██║ ██║ ██████╔╝███████╗███████╗███████╗╚██████╔╝██║ ██║ ██║ ███████╗
╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚══════╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝
███╗ ███╗ ██████╗██████╗
████╗ ████║██╔════╝██╔══██╗
██╔████╔██║██║ ██████╔╝
██║╚██╔╝██║██║ ██╔═══╝
██║ ╚═╝ ██║╚██████╗██║
╚═╝ ╚═╝ ╚═════╝╚═╝
Servidor MCP que expõe o agente de codificação pi como um trabalhador delegável e direcionável.
Aponte o Claude Code (ou qualquer host MCP) para ele e delegue trabalho a qualquer um dos ~38 provedores do pi (DeepSeek, Grok, GLM, Kimi, Qwen, Codex, OpenRouter, llama.cpp local), mantendo o contexto do sub-agente fora da sua conversa principal.
Para que serve
Seu harness principal roda em um modelo caro, com uma janela de contexto que você se importa. Muito do que ele faz não precisa desse modelo e prejudica ativamente esse contexto: pesquisar um repositório em busca de cada ponto de chamada, ler um arquivo de 2000 linhas para responder uma pergunta, auditar o que um refactor deixou para trás.
Entregue esse trabalho a um delegado:
- Custo. O trabalho braçal roda em DeepSeek, GLM, Kimi, Qwen ou llama.cpp local. Você paga preços de fronteira apenas pelo raciocínio que realmente precisa deles.
- Contexto. O delegado lê os arquivos com seu próprio orçamento e retorna um resultado. Os 200 KB que ele leu nunca entram na sua conversa.
- Raio de impacto. Delegados são somente leitura por padrão (
read, grep, find, ls), aplicado na construção da sessão. Um modelo barato fazendo trabalho exploratório não pode tocar sua árvore, a menos que você o opte.
O delegado é sempre o agente pi. Codex, Grok, DeepSeek e os demais fornecem o modelo por trás dele; isto não é um wrapper em torno de seus CLIs.
Por que pi, e não opencode ou um wrapper de CLI?
Um delegado só é direcionável se dois canais permanecerem abertos: você deve poder redirecioná-lo no meio da tarefa, e ele deve poder perguntar algo a você e bloquear até que você responda. A maioria das formas de dirigir um agente de codificação a partir de outro programa fecha ambos.
pi -p / wrappers de CLI | opencode SDK | este servidor | |
|---|---|---|---|
| Roda em processo | não (subprocesso) | não (cliente HTTP para opencode serve) | sim (createAgentSession) |
| Redirecionar uma execução em andamento | não | abort apenas | steer |
| Agente pode perguntar algo a você | não (ctx.hasUI falso) | não na API de sessão | status → answer * |
| Modelo por chamada | não | sim | argumento model |
pi -p e --mode json definem ctx.hasUI = false. Um delegado iniciado dessa forma é fire-and-forget
por construção: ele não pode levantar uma pergunta, e você não pode redirecioná-lo.
O SDK do opencode é um cliente tipado para um processo de servidor separado: createOpencode() inicia
opencode serve e fala HTTP com ele. Design limpo, mas significa um segundo processo para supervisionar,
e a superfície de sessão que ele expõe (prompt, abort, revert, messages) não tem direcionamento
no meio da execução e nenhum caminho para o agente perguntar algo ao chamador.
O pi fornece createAgentSession como uma biblioteca incorporável. Este servidor mantém o objeto de sessão
em processo, então session.steer() pode enviar uma mensagem após a chamada de ferramenta atual e antes da
próxima chamada de modelo, e um uiContext sintético captura as perguntas do agente e as estaciona para
answer. Nada é terceirizado; nada precisa ser supervisionado.
* Perguntas vêm de extensões do pi, então esse canal está aberto apenas para delegados gerados com
extensions: true. Veja Pesquisa na web e outras ferramentas de extensão.
(A tabela compara o canal de delegação, não o sandboxing; o opencode tem sua própria configuração de permissões. Veja Somente leitura por padrão para o que este servidor aplica e não aplica.)
Ferramentas
| Ferramenta | Propósito |
|---|---|
init | Chame primeiro. Relata modelos alcançáveis, ferramentas permitidas e como dirigir um delegado. Toda outra ferramenta recusa até que ela rode uma vez. |
spawn | Delega em segundo plano. Retorna sessionId imediatamente. Use por padrão. |
spawn_batch | Distribui até 10 delegados em uma chamada. Validado como lote, então nada inicia se uma tarefa for ruim. |
run | Delega e bloqueia até terminar. Apenas para perguntas rápidas. |
status | Estado, turnos, ferramentas usadas, texto mais recente e perguntas pendentes. |
steer | Redireciona um agente em execução. Chega após sua chamada de ferramenta atual. |
follow_up | Dá a um delegado concluído outro turno. Ele mantém tudo o que leu, então você não reexplica a tarefa. |
answer | Responde a uma pergunta levantada por status. Só alcançável com extensions: true, pois apenas extensões podem perguntar. |
abort | Para uma sessão; a saída parcial permanece legível. |
models | Lista modelos que este delegado pode usar. |
sessions | Lista sessões, em execução e concluídas. Filtre por state, expanda com verbose. |
forget | Remove uma sessão concluída do histórico, liberando seu id. |
Instalação
Requer Node.js 22.19+ e uma instalação funcional do pi que tenha sido autenticada uma vez
(pi, depois /login).
Claude Code
claude mcp add pi -e PI_DELEGATE_MODEL=openrouter/stealth/ox-alpha -- npx -y pi-delegate-mcp
Qualquer host MCP, via .mcp.json
{
"mcpServers": {
"pi": {
"command": "npx",
"args": ["-y", "pi-delegate-mcp"],
"env": { "PI_DELEGATE_MODEL": "openrouter/stealth/ox-alpha" },
"timeout": 1800000
}
}
}
npx resolve o pacote a cada inicialização. Para fixá-lo, instale globalmente e chame o binário
diretamente:
npm install -g pi-delegate-mcp
{ "mcpServers": { "pi": { "command": "pi-delegate-mcp", "timeout": 1800000 } } }
Mantenha a chave do servidor curta, pois ela prefixa cada nome de ferramenta (mcp__pi__spawn).
A partir do código-fonte
git clone https://github.com/howznguyen/pi-delegate-mcp && cd pi-delegate-mcp
npm install && npm run build && npm link
Primeira execução
Peça ao seu agente para delegar algo. Ele chama init uma vez para aprender o que este servidor pode alcançar,
depois 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 retorna imediatamente. Consulte com status para o rastreamento ordenado de ferramentas e a resposta, ou
sessions quando vários estiverem em andamento. Se init falhar, ele diz exatamente o que está faltando: pi não
instalado, nenhum provedor autenticado ou um escopo de modelo que não corresponde a nada.
Os nomes de modelos nos exemplos abaixo são ilustrativos. Execute models para ver o que sua própria instalação do pi
pode realmente alcançar.
Rastreabilidade
spawn e run ambos aceitam seu próprio id e um label de texto livre:
{
"id": "search-audit-01",
"label": "what ONNX removal left behind",
"prompt": "...",
"model": "opencode-go/deepseek-v4-flash"
}
Ids são [A-Za-z0-9._:-], 1-64 caracteres, devem começar alfanuméricos e devem ser únicos entre sessões
ativas. Omita para um UUID.
Sessões concluídas permanecem legíveis via status e sessions em vez de desaparecerem, então você pode voltar
e verificar o que um delegado realmente fez. As PI_DELEGATE_HISTORY mais recentes (padrão 50) são
mantidas; forget remove uma antecipadamente.
status retorna um rastreamento ordenado de toolCalls: cada ferramenta que o delegado executou, com argumentos e
tempo. Adicione verbose: true para ids de chamada e resultados:
{
"seq": 1,
"id": "call_467b4bb4…",
"name": "bash",
"state": "ok",
"ms": 10,
"args": "{\"command\":\"echo hello-trace\"}",
"result": "hello-trace\n"
}
Argumentos e resultados são truncados (PI_DELEGATE_TRACE_ARGS, PI_DELEGATE_TRACE_RESULT) com o
comprimento descartado registrado, então um único read de um arquivo grande não pode inundar seu contexto.
Dando a um delegado outro turno
Um delegado concluído não está gasto. O pi mantém sua sessão em memória, então follow_up re-prompta
o mesmo agente com tudo o que ele já leu ainda em contexto:
{ "sessionId": "search-audit-01", "prompt": "Now check whether the build files reference it too" }
{ "sessionId": "search-audit-01", "state": "running", "turnsSoFar": 1 }
O delegado continua de onde parou. Ele ainda mantém os arquivos que leu no primeiro turno, então a segunda pergunta custa uma chamada de modelo em vez de uma sessão nova relendo o repositório.
Esta é a forma barata de ter uma conversa com um delegado. Gerar um novo significa reexplicar a tarefa e pagar para ele reler os mesmos arquivos, e sua resposta chega sem nenhum do raciocínio que levou até lá.
follow_up recusa um delegado que ainda está trabalhando, porque redirecionar um no meio da tarefa é
para isso que steer serve. Os dois não são intercambiáveis: steer chega entre chamadas de ferramenta em um
agente em execução, follow_up inicia um novo turno em um concluído.
Distribuindo em lote
spawn_batch inicia um lote inteiro em uma chamada. As tarefas herdam o model, cwd,
tools e extensions do nível do lote, e os substituem individualmente onde precisam:
{
"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"
}
]
}
Isso os nomeia audit-01, audit-02, audit-03 e retorna em alguns milissegundos, pois
iniciar um delegado não espera ele pensar.
O lote é validado antes de qualquer coisa começar: formato do id, ids duplicados dentro do lote, ids já ativos, ferramentas bloqueadas e cada nome de modelo. Uma tarefa ruim falha a chamada e não inicia nada. Meia distribuição é o pior resultado, porque você paga pelos delegados que iniciaram e ainda precisa descobrir quais não iniciaram.
Consulte o lote inteiro com uma chamada sessions em vez de uma status por delegado. Reduza para
status apenas para o delegado que você realmente quer ler. steer e abort permanecem por sessão.
Escolhendo um modelo por chamada
model em qualquer chamada substitui PI_DELEGATE_MODEL. Um nome não resolvível é um erro grave, nunca um
fallback silencioso para o modelo padrão, porque um fallback silencioso é como você acaba cobrando um modelo
que nunca pediu.
Quais nomes resolvem é decidido pelo próprio escopo enabledModels do pi, que este servidor aplica
em vez de meramente exibir:
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)
Provedores personalizados ignoram o escopo. Qualquer modelo servido por um provedor declarado em
~/.pi/agent/models.json é oferecido mesmo quando enabledModels não o nomeia, com a justificativa
de que declarar um provedor manualmente já é uma intenção de usá-lo. É por isso que a lista pode ser
muito mais longa que enabledModels: três entradas no escopo mais dois provedores personalizados podem facilmente
significar quinze modelos oferecidos. init diz isso explicitamente em models.scopeNote quando se aplica.
Duas opções mudam isso:
| Efeito | |
|---|---|
PI_DELEGATE_STRICT_SCOPE=1 | Honra enabledModels exatamente. A exceção de provedor personalizado é descartada. |
PI_DELEGATE_IGNORE_SCOPE=1 | Descarta o escopo completamente. Todo modelo autenticado é utilizável. |
Chame models para ver o que é realmente alcançável sob a configuração em vigor.
Linha de status
O Claude Code permite exatamente um comando statusLine, então pi-delegate-statusline envolve o que
você já executa e anexa um segmento mostrando os delegados deste workspace:
{
"statusLine": {
"type": "command",
"command": "PI_DELEGATE_STATUSLINE_WRAP=ccstatusline pi-delegate-statusline",
"refreshInterval": 10
}
}
Remova PI_DELEGATE_STATUSLINE_WRAP para imprimir apenas o segmento do 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
Quais delegados pertencem a qual sessão
Filtrar por diretório não é suficiente: duas sessões do Claude Code abertas no mesmo repositório
mostrariam os delegados uma da outra. A atribuição usa a linhagem de processos.
O host MCP inicia um servidor por sessão, então o servidor registra process.ppid, o
pid do host. A linha de status, iniciada por esse mesmo host, percorre sua própria ancestralidade e mantém apenas os
arquivos de estado cujo hostPid encontra ali. Mesmo repositório, duas sessões, sem interferência. O
filtro de diretório permanece como fallback para arquivos de estado gravados antes disso existir.
O estado vive em $XDG_STATE_HOME/pi-delegate-mcp/<pid>.json (PI_DELEGATE_STATE_DIR para
realocar). Os arquivos são podados quando o processo desaparece, ESRCH apenas, já que EPERM significa que o
processo está vivo sob outro usuário. Os servidores também saem por conta própria quando o stdin fecha ou o
pid do host desaparece, então um host que morre sem fechar o transporte não deixa nada para trás.
Somente leitura por padrão
As ferramentas são bloqueadas para read, grep, find, ls na construção da sessão. Qualquer outra coisa é recusada
antes mesmo de uma sessão ser criada.
Para ampliar isso, nomeie as ferramentas extras no servidor:
"env": { "PI_DELEGATE_ALLOW_TOOLS": "bash" }
ou PI_DELEGATE_ALLOW_WRITE=1 para permitir tudo.
bash não é um meio-termo. O pi não inclui sistema de permissões, então um delegado com bash
pode gravar arquivos, excluí-los e acessar a rede independentemente de write e edit estarem
na sua lista. Recusar esses dois enquanto permite bash registra sua intenção; não impõe
nada. Os prompts de permissão e hooks do Claude Code nunca veem o que o pi faz. Se você precisar de um
limite real, execute este servidor dentro de um contêiner.
Busca na web e outras ferramentas de extensão
As ferramentas próprias do pi são read, grep, find, ls, bash, powershell, write, edit. Não há
busca nem fetch entre elas. Essas vêm das extensões do pi, que registram suas próprias ferramentas, e um
delegado pode usá-las.
Defina extensions: true na chamada e permita os nomes das ferramentas no 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}" }
É assim que você dá a um delegado alcance de rede sem entregar bash. web_search pode buscar
e nada mais, e passa pela mesma allowlist que qualquer outra ferramenta, então o padrão
somente leitura permanece inalterado para chamadas que não o solicitam.
Quais ferramentas existem dependem do que o usuário que executa o servidor tem instalado. pi-web-access fornece
web_search, fetch_content, source_check e get_search_content. pi-mcp-adapter faz a ponte entre os
servidores MCP em ~/.pi/agent/mcp.json e os expõe como mcp. O pi não tem cliente MCP próprio, então
essa extensão é a única rota para um.
extensions: true confia em toda extensão instalada, não apenas na que você queria. Elas carregam como um
conjunto, rodam com todos os privilégios do processo deste servidor, e algumas abrem sockets e timers
que sobrevivem à sessão. Ative por chamada, para os delegados que precisam, em vez de deixar
ativado por padrão. Também custa tempo real de inicialização, por isso fica desligado a menos que seja solicitado.
Configuração
| Variável de ambiente | Padrão | Significado |
|---|---|---|
PI_DELEGATE_MODEL | padrão do pi | Modelo usado quando uma chamada omite model |
PI_DELEGATE_ALLOW_TOOLS | não definido | Lista separada por vírgulas de ferramentas extras a permitir, ex.: bash |
PI_DELEGATE_ALLOW_WRITE | não definido | 1 permite todas as ferramentas |
PI_DELEGATE_HISTORY | 50 | Sessões concluídas mantidas para revisão |
PI_DELEGATE_TRACE_ARGS | 400 | Máx. de caracteres dos argumentos de ferramenta mantidos no trace |
PI_DELEGATE_TRACE_RESULT | 600 | Máx. de caracteres dos resultados de ferramenta mantidos no trace |
PI_DELEGATE_BATCH_MAX | 10 | Teto de tarefas por chamada de spawn_batch |
PI_DELEGATE_LIST_CAP | 60 | Acima disso, init resume modelos por provedor em vez de listá-los |
PI_DELEGATE_STATE_DIR | diretório de estado XDG | Onde o estado da linha de status é publicado |
PI_DELEGATE_STATUSLINE_WRAP | não definido | Comando da linha de status a envolver e anexar |
PI_DELEGATE_STATUSLINE_LOG | não definido | Arquivo para anexar um timestamp a cada renderização da linha de status, para depuração |
PI_DELEGATE_PROGRESS_MS | 15000 | Intervalo de notificação de progresso durante run |
PI_DELEGATE_IGNORE_SCOPE | não definido | 1 ignora o escopo de enabledModels do pi, permitindo qualquer modelo configurado |
PI_DELEGATE_STRICT_SCOPE | não definido | 1 honra enabledModels exatamente, removendo o bypass de provedor personalizado |
PI_CODING_AGENT_DIR | ~/.pi/agent | De onde o auth.json e a configuração do pi são lidos |
Trabalho de longa duração
O SDK MCP TypeScript usa por padrão um timeout de requisição de 60 segundos, que uma tarefa real vai estourar. Três defesas, em ordem de preferência:
- Use
spawn+status. Nada bloqueia, então nenhum timeout se aplica. runemite notificações periódicas de progresso, que resetam o timeout do host.- Aumente o teto com
"timeout"em.mcp.jsonouMCP_TOOL_TIMEOUTno ambiente.
CLAUDE_AUTO_BACKGROUND_TASKS=1 faz o Claude Code colocar chamadas MCP longas em segundo plano após ~2 minutos.
Note que notificações de progresso são descartadas uma vez que a chamada vai para segundo plano, então escolha (1) ou (3),
não ambos.
Autenticação
O servidor não lida com credenciais. O pi autentica a si mesmo a partir de ~/.pi/agent/auth.json,
depois variáveis de ambiente. Hosts MCP frequentemente iniciam servidores com um ambiente reduzido, então
prefira auth.json (execute pi uma vez e /login) em vez de exportar chaves em um perfil de shell.
Desenvolvimento
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 é o que o CI executa e o que prepublishOnly usa como porta de entrada, porque não precisa de credenciais e
nem de rede. npm test aciona delegados reais contra provedores reais, então custa dinheiro e só
funciona onde pi foi autenticado.
| Caminho | O que vive ali |
|---|---|
src/config.ts | Cada variável de ambiente, lida em um só lugar |
src/permissions.ts | A allowlist de ferramentas e a porta que a impõe |
src/registry.ts | Mapa de sessões, reivindicação de id, evicção de histórico |
src/tools/ | Um módulo por grupo de ferramentas MCP |
src/pi/ | Tudo que toca o SDK do pi |
src/statusline/ | Publicação de arquivos de estado e o binário da linha de status |
Lançamentos são dirigidos por tags. npm version patch && git push --follow-tags executa o build e os testes,
depois publica via OIDC trusted publishing, então nenhum token npm é armazenado em qualquer lugar do repositório.
Issues e pull requests são bem-vindos. Se você está relatando um delegado que se comportou mal, o
trace toolCalls de status com verbose: true é a coisa útil para anexar.
Trabalho anterior
abatilo/pi-mcp-bridge segue o caminho mais simples:
iniciar pi --mode json -p --session-id <uuid> e deixar o pi persistir sessões no disco, então a ponte
não guarda estado algum. Elegante, e vale a leitura. Ele troca direção, perguntas e controle
de ferramentas para chegar lá.
Licença
MIT