MCP tool declarations
The three gates
Access to MCP tools passes through three independent gates, and the effective tool set of a session is their intersection: a tool is available only when the package permits it, the agent selects it, and a binding makes its server reachable.
- The package grant.
package.jsondeclares, underpi.mcp, the MCP server ids the recipe may use and the maximum tools permitted from each server. Nothing outside this grant can ever be reached. - The agent selection. Each agent’s
mcp:block in its YAML picks a mode and narrows the package grant to the subset that role needs. An agent cannot add capability the package did not declare. - The endpoint binding. A package MCP manifest,
<recipe-root>/.pi/mcp.local.json, or a host/platform binding maps a declared server id to its endpoint and credentials. A binding never expands authorization — it only makes an already-authorized server reachable.
Every gate is fail-closed: a server with no include, a server left out of an
agent’s servers, and an omitted mcp: block all mean no access, so a recipe
can never grant more than it wrote down.
The package grant
{
"pi": {
"mcp": {
"manifests": ["mcp.json"],
"servers": [
{
"id": "contacts",
"required": true,
"tools": {
"include": ["*"],
"exclude": ["delete_contact"]
}
}
]
}
}
}Each entry in servers takes exactly three fields:
id— the server’s name as agents and bindings will refer to it.required— optional, defaultfalse. A server marked"required": truemust resolve to a bound endpoint when the session is materialized, or the session fails closed rather than starting without the capability. A non-required server with no binding simply contributes no tools.tools— the maximum tool set, asincludeand optionalexcludelists.
include names what may be reached: ["*"] means the server’s whole tool set
(including tools the server adds later), and a list of exact names means just
those. "*" is a whole-toolset sentinel, not a glob — a pattern such as
search_* is invalid. exclude subtracts exact names after inclusion and
always wins. Omitting include allows no tools. Prefer exact tool names in the
package grant; reserve "*" for servers the recipe genuinely trusts wholesale.
manifests optionally lists recipe-relative paths or globs of portable MCP
manifest files that ship endpoint configuration with the package. It is always
an array; singular manifest and string shorthand are invalid. Like every
other binding source, a packaged manifest supplies connections, not additional
tools.
Example: resolve one tool for an agent
Assume a contacts MCP server exposes search_contacts, get_contact, and
delete_contact. This recipe permits the first two tools, while its agent
selects only search_contacts.
Package grant
{
"name": "contacts-agent",
"version": "0.1.0",
"type": "module",
"pi": {
"agents": ["agents/*.yaml"],
"mcp": {
"servers": [
{
"id": "contacts",
"required": true,
"tools": {
"include": ["search_contacts", "get_contact"]
}
}
]
}
}
}The three gates resolve like this:
| Gate | Result |
|---|---|
| Package grant | contacts.search_contacts and contacts.get_contact are permitted. delete_contact is outside the recipe boundary. |
| Agent selection | The agent narrows the package set to contacts.search_contacts. |
| Endpoint binding | The contacts id gains a reachable endpoint and credential reference, but no additional tools. |
| Effective session | Only contacts.search_contacts is available. |
For an ordinary local run, <recipe-root> is the directory containing the
recipe’s package.json. Run introspection local from that directory so the
binding and recipe resolve together. A host may supply the same binding shape
explicitly instead.
If a required user-owned binding is missing, explain that exact recipe-relative path and let the user create it before retrying the real recipe. Do not move a machine-specific URL or credential into a package MCP manifest merely to make a local proof pass. Validate the binding through the runner without printing or repeating credential values.
Keep .pi/mcp.local.json untracked and ignored by Git when it contains local
endpoints or credential references. .recipeignore does not prevent Git from
tracking the file and does not exclude it from source validation. Managed
runtimes receive their endpoint and credential configuration through
bindings, not this local file.
Two modes
Every resolved agent owns its mode independently; with no mode anywhere in a
from: chain, an agent gets CLI.
mode: cligives the agent onemcpcommand containing only its authorized tools. A large server costs one tool definition instead of forty: the agent discovers narrowly (mcp search), reads one schema (mcp list --schema), then calls. Sessions expose onlysearch,list,call, andrun— authentication, admin commands, and config overrides are rejected. Keep a shell tool such asbashintools, since the command runs there.mode: toolsregisters each authorized tool with Pi directly, for when the model should see real tool schemas.defer: ["*"]hides authorized tools at session start andeager:lists exceptions, which buys the token saving of deferred loading without implementing it; neither can authorize anythinginclude/excludedid not.
In CLI mode, the session’s mcp command supports exactly four subcommands:
| Command | What it does |
|---|---|
mcp search "what you need" [--limit N] [--regex] | Search only the tools authorized in this session; results give the exact tool ref, description, and required fields. |
mcp list [server | server.tool] | Compact inventory of the session’s tools. --schema shows one exact tool’s input/output contract; --all-parameters, --verbose, --status, and --timeout <ms> adjust the view. |
mcp call <server>.<tool> key=value … | Call one exact tool with schema-aware argument coercion. key=@path passes a file’s contents as an exact UTF-8 string (use @@ for a literal @); --json <json | -> supplies a structured object directly or from stdin; --output text|markdown|json|raw and --timeout <ms> control the result. |
mcp run [--var KEY=value] [--json-errors] [file] | Run a short JavaScript workflow — from a file or a quoted stdin heredoc — that batches or composes calls through tools["server"]["tool"](args), with dynamic input passed via --var and read as vars.KEY. |
mcp run is resource-bounded, not a separate security boundary — the script
runs with the same OS privileges as the agent’s shell sandbox. By default a
workflow is killed after 120 seconds, each tool call is capped at 60 seconds,
and a workflow may make at most 100 tool calls with at most 16 running
concurrently; extra calls queue FIFO and inherit the remaining workflow
deadline, and a synchronous busy-loop is force-killed at the deadline. Every
tool-call chain must be awaited or returned before the script exits — a
detached .then/.catch that is still pending fails the run. The defaults
are overridable through PI_RECIPES_MCP_RUN_TIMEOUT_MS,
PI_RECIPES_MCP_RUN_CALL_TIMEOUT_MS, PI_RECIPES_MCP_RUN_MAX_CALLS, and
PI_RECIPES_MCP_RUN_MAX_CONCURRENCY.
A declared agent mcp block replaces the inherited one whole, so a derived
agent must restate its mode and servers — a capability change is always
visible in the diff of the agent that made it.
Validation
An invalid MCP policy fails closed: a task will not launch the agent with
guessed access. Validate at author time with introspection check, which
reports an agent naming a server the package never declared, an unknown
mode, and a selector that is neither an exact tool name nor "*". Note what
it does not flag, because these are policy rather than mistakes: a server
with no include and a selection that resolves to no tools both validate, and
both mean no access at run time.
Run it
After replacing the example URL with your server and exporting
CONTACTS_MCP_TOKEN, run the recipe. local validates it before Pi starts:
introspection localBare introspection local auto-selects the runtime when the workspace has
exactly one manifest (or one whose recipe contains the current directory);
with several manifests, name one with --runtime <slug>.
Inside that Pi session, the resolved tool is exposed via the mcp CLI:
mcp list contacts
mcp list contacts.search_contacts --schema
mcp call contacts.search_contacts query="Ada Lovelace"See MCP and federation for the managed side — bindings, connector-supplied servers, credentials, and identity on the Introspection platform — and the Recipes documentation for the complete format contract.