Connect an MCP server
You need: a recipe (Create a recipe) and the server’s URL and token. For the cloud steps, a deployed runtime (Deploy your agent).
A session gets a tool only when all three gates agree:
| Gate | Where | What it does |
|---|---|---|
| Package grant | pi.mcp in package.json | Servers the recipe may use, and the most tools from each. |
| Agent selection | mcp: in agent YAML | The subset this agent uses. It cannot add anything. |
| Binding | .pi/mcp.local.json locally, a binding in the cloud | Endpoint and credentials for a declared server id. It never adds tools. |
The examples use a contacts server.
1. Grant the server
{
"pi": {
"mcp": {
"servers": [
{ "id": "contacts", "required": true, "tools": { "include": ["search_contacts", "get_contact"] } }
]
}
}
}| Field | Meaning |
|---|---|
id | The name agents and bindings use. |
required | Default false. With mode: tools, a task stops at startup when a required server is down; with mode: cli, it starts and that server’s mcp commands fail. A required server with no binding adds no tools in the cloud, and stops the session locally. An optional server that is missing or down adds no tools. |
discovery | "require" (default) or "defer", for a required server. With mode: tools, a build fails when a required server is down. "defer" lets the build finish without this server, for one that is reachable only where tasks run; a task still needs it. |
tools.include | Exact names, or ["*"] for all. Not a glob: search_* is invalid. No include means no tools. |
tools.exclude | Exact names to remove. Always wins. |
2. Select tools per agent
tools: [bash]
mcp:
mode: cli
servers:
contacts:
include: [search_contacts]| Mode | The agent gets | Use it when |
|---|---|---|
cli (default) | One mcp shell command (search, list, call, run). Needs bash. | The server has many tools. |
tools | Each tool registered with Pi. defer: ["*"] hides tools until needed; eager: lists exceptions. | The model should see real schemas, or the agent has no bash. |
A child agent’s mcp: block replaces the parent’s whole block; restate mode
and servers.
3. Bind it locally
Keep this file out of Git:
{
"servers": [
{
"id": "contacts",
"transport": "streamable_http",
"url": "https://contacts.example.com/mcp",
"headers": { "Authorization": "Bearer ${CONTACTS_MCP_TOKEN}" }
}
]
}4. Bind it in the cloud
Store the token, then connect the endpoint. Single-quote the header so your
shell leaves ${CONTACTS_TOKEN} alone.
introspection bindings credentials create --name CONTACTS_TOKEN --from-env CONTACTS_TOKEN -r support-agent
introspection bindings mcp connect --mcp-server-id contacts \
--endpoint-url https://contacts.example.com/mcp \
--header 'Authorization=Bearer ${CONTACTS_TOKEN}' \
-r support-agent -e stagingLeave out -e to cover every environment, including development
(Connect services and secrets).
Use OAuth instead of a token
Create a custom connector from the server URL; discovery finds the OAuth
endpoints. Pass --scope for each scope the server’s scopes_supported
metadata lists, since the connector otherwise requests none. authorize opens
your browser and stores one org-owned connection every task uses.
introspection connectors create --name "Contacts" --slug contacts-oauth --provider custom \
--auth-mode oauth-stored --issuer https://contacts.example.com/mcp --scope mcp
introspection bindings mcp connect --mcp-server-id contacts \
--endpoint-url https://contacts.example.com/mcp --connector contacts-oauth -r support-agent
introspection connectors authorize contacts-oauthFor each end user’s own sign-in, see Per-user tools.
Verify
export CONTACTS_MCP_TOKEN=...
introspection local -p "Run mcp list contacts and tell me which tools you have."
introspection bindings mcp list -r support-agent -e staging
introspection tasks create -r support-agent -e staging --prompt "List the contacts tools you can use."Expect search_contacts only, locally and in staging, and a contacts row in mcp list.
Limits
| Symptom or limit | Fix |
|---|---|
| Agent has no tools from the server | --mcp-server-id differs from pi.mcp.servers[].id, the agent’s mcp: omits it, or the server has no binding for this runtime and environment. Check bindings mcp list. |
| Build fails on a required MCP server | A required server was down during the build. Bring it up and push again, or set "discovery": "defer" on a server that is reachable only where tasks run. |
| Required server stops the task | The server was down when the task started, with mode: tools. Check the endpoint is up, then start a new task. Locally, a required server missing from .pi/mcp.local.json stops the session as well (Bind it locally). |
endpoint_credential_missing, or 401 from the server | The header names a credential not stored for this runtime and environment (first), or the shell expanded ${...} (second). |
| Endpoint refused | Must be HTTPS on a public DNS name, not an IP address or localhost (The sandbox). |
| Connector create returns 422 “client_id is required” | The provider has no automatic client registration. Register an OAuth app there and put client_id and client_secret in a connectors create @connector.yaml document. |
Or ask your agent: “Give my main agent read-only search access to our contacts MCP server and bind it in staging.”