Repository navigation
pi-dispatch v4.2.0
pi-dispatch 4.2.0 lets a job use MCP servers. You offer the servers in the overlay, and a trigger names the ones its jobs connect. A github or gitlab trigger can also give its jobs the forge's own MCP server, running as the token the job already holds.
A trigger that names no server runs as before. Three things change for everyone: a pi started inside a job gets --no-mcp, import-pi now also stages your MCP servers, and the triggers file refuses a misspelled run.mcp (see Changed).
New
-
MCP servers in the overlay. Put them in
mcp.jsonin the overlay folder, in pi's ownmcpServersshape. Streamable HTTP servers and stdio servers both work. The legacy SSE transport,auth,oauthandcwdare refused.pi-dispatch import-pistages the file from your~/.pi/agent/mcp.json. It keeps only the servers a job can use, prints each one it staged, and names each one it left behind with the reason. It never copiesmcp-auth.json. Use--no-mcpto stage none.- A header, an env value and a url's query value are always a reference,
${NAME}. The job fills it in from the trigger'srun.secretsor fromPI_FORWARD_ENV. A literal argument or a literal part of a url's path is copied as written, and every job can read the overlay, so put a credential in a reference. - A stdio command must be in the job image or the overlay (a bare name on
PATH, or a path under/usr,/bin,/sbin,/optor/app). A package runner such asnpxoruvxneeds"allowPackageRunner": trueon the server. pi-dispatch doctorreads the file, names each refused server and why, offers to delete anmcp-auth.json, and lists package runners. With the egress policy on, it names each HTTP server and each package registry the proxy would not let a job reach.
-
run.mcpin a trigger names the servers its jobs connect, for example"mcp": ["docs"], up to 8. You write it intriggers.jsonby hand: the panel's trigger tools refuse it. A chained child does not inherit it. Before anything is spent, the worker refuses a job when:- the overlay does not offer the name, turns it off, or refuses it (
mcp-server-unknown), or itsmcp.jsonis missing or cannot be read (mcp-config-invalid); - the server references a variable the trigger does not pass (
mcp-secret-unmapped), or one the job keeps for itself, such as a provider key or the forge token (mcp-secret-reserved); - with the egress policy on, the proxy would not let the job reach the server (
mcp-egress-unreachable) or the registry a package runner fetches from (mcp-package-runner); - the trigger's
run.excludeToolsleaves the server no way in (mcp-excluded); - the list the job carries is not one the loader writes (
mcp-list-invalid).
The issue gets a short comment that names the server and the rule, never a host or a variable. The worker's log has the detail.
- the overlay does not offer the name, turns it off, or refuses it (
-
pi's MCP works in a job as in the pi CLI. That covers the
codemode,deferred(throughtool_search),directandhiddenexposures, and resources. A job never signs in: an HTTP 401 ends it. There is no/mcpcommand. The job waits for every server before its prompt: 15 seconds, or a server's owntimeoutup to 120. -
Codemode's
models.classify()andmodels.generateImages()are on. Each call is metered with the job and held to its model list. Under a cost cap, every image call is refused, and so is a classifier with an output price, because neither cost can be bounded in advance. A classifier priced on input only is admitted. A refused call stops the whole job, as any refused model call does. Set"codemodeModels": falseat the top of the overlay'smcp.jsonto turn them off for every job. -
run.excludeToolson a trigger withrun.mcpcan also namecodemode,tool_search,list_mcp_resources,list_mcp_resource_templates,read_mcp_resource, and one server's tools as a whole asmcp__<server>__*(with-in the name written as_). A single MCP tool cannot be named. A list that would leave a named server no way in is refused before spend (mcp-excluded). -
The forge's own MCP server. A github or gitlab trigger may add
forgetorun.mcp. The job then gets GitHub'sgithub-mcp-server2.0.2 or the community@zereight/mcp-gitlab2.1.67, both baked into the job image. Each runs as the forge token the job already holds forghorglab, so there is no new credential and no entry in the overlay.- On GitHub the server runs its
defaulttoolsets (Actions stays off) in lockdown mode. - The servers' dedicated merge and approval tools are withheld, so the job is not handed a ready-made one. The two Copilot tools, and GitLab's
discover_toolsandexecute_graphql, are withheld too. - With the egress policy on, a github job needs
api.github.cominegress-allowlist.conf. A gitlab job needs the host of yourGITLAB_URLthere on port 443, or declared inmodel-endpoints.json. - A cron, Forgejo or Azure DevOps trigger that names
forgeis refused when the triggers file loads.
- On GitHub the server runs its
-
The panel shows an
[mcp ...]badge on a trigger that names servers. Its detail view says, per server, how the overlay offers it now or why a job would be refused. It is read-only.dispatch_triggerslistsrun.mcp. -
A stop inside the job is named. When the job's MCP stops it before its prompt (
mcp-unavailable,mcp-config-invalid,mcp-config-mismatch,mcp-secret-missing,mcp-command-missingormcp-shadowed), the run record and the panel carry that reason, the failure hook is called with it, and the issue gets a fixed sentence for it. A server that had not answered in time ismcp-unreachable, retried like any infrastructure failure.
See docs/mcp.md.
Changed
- A
pistarted inside a job gets--no-mcp. Before, a pi CLI the job ran could connect the servers in its agent folder'smcp.jsonand, when it trusted the project, in the repo's.pi/mcp.json. Now such a child loads no built-in MCP. Code the job runs can still connect a server on its own, for example withpi mcp listor by loading pi's MCP extension by its path. docs/costs.md lists these. This needs the 4.2.0 job image. import-pialso writes the overlay'smcp.json. Each import replaces it with the servers from your host, and removes it under--no-mcpor when your host offers none. A file you wrote into the overlay by hand is replaced too. A staged server does nothing until a trigger names it.- The triggers file refuses a near miss of
run.mcp, such asrun.mcpServers,run.Mcporon.mcp. An older version dropped such a key silently. - Doctor and MCP endpoints. An endpoint in
model-endpoints.jsonthat only an overlay MCP server uses is reported as an "MCP server endpoint". It passes once anything answers through the proxy, instead of warning that it does not answer as a model server. - The job image is a little larger: it carries both forge servers. It still runs pi 1.1.0.
Before you upgrade
- Update the job image to 4.2.0 before a trigger names a server. It declares the
mcpandforgeMcpcapabilities. On an older image, a job that names a server is refused before it costs anything (job-image-mcp-unsupported, orjob-image-forge-mcp-unsupportedforforge). - Upgrade every worker before any trigger names a server. A 4.1.0 worker drops
run.mcpfrom the triggers file without a word. It runs such a job without its servers, and the record reads like a clean run. That holds for a job it takes from the shared queue and for the cron jobs it schedules. A fleet with older workers is safe only while no trigger names a server. - Upgrade the receiver to 4.2.0 with the workers. A 4.1.0 receiver also drops
run.mcp. A 4.2.0 worker then refuses each such job astrigger-skewinstead of running it without its servers, but only when it can read the triggers file itself. A worker that cannot read it runs the job without its servers. - Update the panel with
pi install npm:@edgehero/pi-dispatch-adminto see the servers a trigger names. The panel's setup wizard now installs worker and receiver 4.2.0. - Adding
run.mcpto a trigger refuses its jobs that were already queued (trigger-skew). Re-run them. - Run
pi-dispatch import-piagain if you want your host's MCP servers in the overlay, thenpi-dispatch doctor. - An HTTP MCP server on a port other than 443, with the egress policy on, needs its exact host and port in
model-endpoints.json, the same as a model server on your network. Then runpi-dispatch egress renderand reload the proxy. A declared host and port is reached as declared, overhttp://orhttps://. A host inegress-allowlist.confis reached on port 443 overhttps://only. With the egress policy off, the route is not checked. - GitLab: give the token the Developer role, not Maintainer, and leave "Allowed to merge" on protected branches at Maintainers. A Maintainer token lets the job, and the forge's MCP server, unprotect a branch or change who may merge. Withholding the merge tools is not a boundary: a GitLab quick action in a note, or the job's own
ghorglab, can still do what the token's role allows. See docs/gitlab.md.
Upgrading from 3.x? Read Before you upgrade in the v4.0.0 release first.
Full changelog: v4.1.0...v4.2.0