MCP Servers
What Are MCP Servers
Section titled “What Are MCP Servers”MCP (Model Context Protocol) servers expose additional tools the agent can call alongside its built-in tools like bash, read, write, and edit. Each MCP server is a separate process or remote endpoint that advertises one or more tools over a standardized protocol. Common examples include web search (Tavily, Brave Search), database queries (PostgreSQL, SQLite), extended file system access, GitHub/GitLab integrations, and custom internal APIs. Once connected, the agent sees MCP tools exactly like built-in ones and can call them in any session.
Configuration
Section titled “Configuration”MCP servers are configured in JSON config files:
~/.pizzapi/config.json(global) — servers defined here are available in all sessions..pizzapi/config.json(project-local, at the repo root) — servers defined here are scoped to this project only.
Two config formats are supported:
The mcpServers format uses an object where each key is the server name. This is compatible with Claude Code and VS Code configurations.
{ "mcpServers": { "tavily": { "command": "npx", "args": ["-y", "@tavily/mcp-server"], "env": { "TAVILY_API_KEY": "tvly-..." } }, "filesystem": { "command": "npx", "args": ["-y", "@anthropic/mcp-filesystem"], "env": {} } }}The mcp.servers format uses an array of server objects, each with explicit name and transport fields. It is supported for compatibility; prefer mcpServers for new configurations. Use stdio with command, or http/streamable with url; do not mix command and URL fields.
{ "mcp": { "servers": [ { "name": "tavily", "transport": "stdio", "command": "npx", "args": ["-y", "@tavily/mcp-server"], "env": { "TAVILY_API_KEY": "tvly-..." } }, { "name": "filesystem", "transport": "stdio", "command": "npx", "args": ["-y", "@anthropic/mcp-filesystem"], "env": {} } ] }}Project-local and global MCP servers are deep-merged — servers from both scopes are combined. If a server with the same name appears in both configs, the project-local definition wins. Global-only servers are preserved alongside project-specific ones, so you don’t need to redeclare them.
See the Configuration guide for full details on config file loading and precedence.
STDIO Transport
Section titled “STDIO Transport”STDIO is the most common transport. PizzaPi spawns the MCP server as a child process and communicates over stdin/stdout.
{ "mcpServers": { "tavily": { "command": "npx", "args": ["-y", "@tavily/mcp-server"], "env": { "TAVILY_API_KEY": "tvly-..." } } }}| Field | Type | Required | Description |
|---|---|---|---|
command |
string |
✅ | Executable to run (e.g. npx, node, python). |
args |
string[] |
— | Arguments passed to the command. |
env |
Record<string, string> |
— | Environment variables set for the child process. These are merged with the current environment. |
cwd |
string |
— | Working directory for the child process. Defaults to the current project root. |
Package overlay MCP paths
Section titled “Package overlay MCP paths”A package’s pi.pizzapi.mcp sidecar can use @PACKAGE_ROOT@ in stdio command, cwd, string args entries, and string env values. PizzaPi replaces it with that installed package’s confined root after normal variable expansion, so literal variable-shaped text in the installed path stays unchanged. It is not available in explicit config files or legacy Claude-plugin .mcp.json files, and does not apply to URL-based definitions (even if they also contain stdio fields), URLs, or headers.
For example, the AgentMemory package sidecar should use:
{ "mcpServers": { "godmother": { "command": "@PACKAGE_ROOT@/gm", "args": ["--config", "@PACKAGE_ROOT@/config.json"] } }}HTTP / Streamable HTTP Transport
Section titled “HTTP / Streamable HTTP Transport”For remote MCP servers, use URL-based configuration instead of spawning a local process.
{ "mcpServers": { "remote-tools": { "url": "https://mcp.example.com/v1/mcp", "transport": "http" } }}| Field | Type | Required | Description |
|---|---|---|---|
url |
string |
✅ | Full URL of the MCP server endpoint. |
transport |
"http" | "streamable" |
— | Transport mode. "streamable" uses the Streamable HTTP protocol (text/event-stream). If omitted, plain HTTP (JSON POST) is used. |
headers |
Record<string, string> |
— | Custom HTTP headers sent with every request (e.g. API keys, auth tokens). |
type |
"http" |
— | Setting type to "http" selects Streamable HTTP transport. This matches the Claude Code / VS Code format. |
Elicitation (user input during a tool call)
Section titled “Elicitation (user input during a tool call)”For servers using MCP 2026-07-28, PizzaPi supports form-mode and URL-mode elicitation during tools/call. The existing web approval card displays the requesting server, its message, editable schema fields and defaults, and Accept / Decline / Cancel. The TUI offers a review/edit loop. Validation failures keep the answers local and reopen the form for correction.
Fields support strings (including email, URI, date and date-time), numbers/integers, booleans, and single- or multi-select enums. This initial interface uses text fields: enter true/false for booleans and a JSON array of enum values for multi-select. Optional blank fields are omitted. Enum titles, allowed values, and constraints appear beside each field.
URL mode
Section titled “URL mode”URL mode is for sensitive interactions (credentials, payments, third-party OAuth) that must not pass through PizzaPi. The consent card shows the requesting server, its message as plain text, the site hostname, and the full URL. Nothing is fetched, opened, or inspected until you choose Open in browser; that click opens the URL in a new browser tab with noopener/noreferrer and reports accept (with no content) to the server. Accepting means you consented, not that the flow finished. If the server then answers with a state-only InputRequiredResult, PizzaPi shows a Retry / Cancel card and waits for you; it never retries on its own. If the server re-sends the same URL, the card offers Retry / Open again / Decline / Cancel instead of asking for consent again.
URLs must be https; http is accepted only for localhost, 127.0.0.1 or [::1] (development). URLs with embedded credentials, control, whitespace, bidi or backslash characters, or other schemes are rejected before any card is shown. Punycode (xn--) hostnames and plain http links show a warning. The TUI shows the same information but never opens links: copy the URL into your browser yourself, then choose the matching option. Headless sessions have no elicitation surface, so neither mode is advertised and nothing is ever auto-consented.
URL elicitation is separate from MCP OAuth: it does not change the client’s own bearer token for the server.
PizzaPi handles InputRequiredResult internally: each retry carries the current round’s keyed inputResponses and the exact opaque requestState, if supplied. Answers are not emitted as chat/tool updates, though the MCP server may include them in its final tool result. Cancelling the running tool stops retries; declining or cancelling a form sends the corresponding action to the server.
The client probes server/discover before falling back to legacy initialization. elicitation: { form: {}, url: {} } is declared per modern tool request only when a user-input handler is available. Modern HTTP requests accept JSON or SSE responses in either HTTP configuration mode.
Limitations: This is form/MRTR support, not full implementation of every 2026 feature. Modern HTTP tools with x-mcp-header annotations are excluded with a warning because parameter-header mirroring is not implemented. The deprecated standalone HTTP+SSE transport is not supported. Retry rounds (form, URL and state-only) are bounded at 8 per tool call. Legacy server-initiated elicitation/create requests are unsupported; form support uses the 2026 multi-round-trip result flow rather than a background request channel. No roots or sampling support is advertised. A reachable user interface is required; a busy or unavailable approval surface fails closed. Do not enter passwords, API keys, access tokens, or payment credentials in forms; servers should use URL mode for those.
For local URL-elicitation fixture testing, packages/cli/src/extensions/mcp/elicitation-url-test-server.mjs binds a random port unless PIZZAPI_ELICITATION_URL_PORT is set.
OAuth for HTTP Servers
Section titled “OAuth for HTTP Servers”Some HTTP MCP servers require OAuth authentication. PizzaPi handles the OAuth flow automatically — when a streamable HTTP server returns 401, it starts the MCP OAuth flow, opens or presents the authorization URL, exchanges the callback code, and retries the request with a bearer token. When connected to a relay server (e.g. via pizza web), the OAuth callback routes through the relay so the flow works for remote sessions too.
Tokens, PKCE state, and dynamic client registration data persist in ~/.pizzapi/mcp-auth/. Set PIZZAPI_MCP_AUTH_DIR to use a different directory, which is useful for tests or isolated runners. Files are keyed by a hash of the MCP server URL, so changing the server URL creates a separate credential record.
Client Name Override
Section titled “Client Name Override”Some MCP servers (notably Figma) restrict OAuth dynamic client registration to an allowlist of known client_name values, rejecting unknown clients with 403 Forbidden. You can override the name PizzaPi sends during registration with oauthClientName — either per-server or globally.
Per-server (recommended) — only affects the server that needs it:
{ "mcpServers": { "figma": { "type": "http", "url": "https://mcp.figma.com/mcp", "oauthClientName": "Codex" } }}Global — applies to all MCP servers that don’t set their own override:
{ "oauthClientName": "Codex"}Per-server values take precedence over the global setting.
Headless / Remote OAuth (Paste Fallback)
Section titled “Headless / Remote OAuth (Paste Fallback)”Some MCP servers (e.g., Figma) require localhost redirects for OAuth, which doesn’t work when the runner is remote or headless. PizzaPi provides a paste fallback flow:
- The web UI shows a 2-step prompt: click the auth link to sign in, then paste the callback URL from the browser’s error page
- The runner validates the pasted URL’s state parameter and completes the OAuth flow
- If a server is causing problems, click Disable in the prompt to remove it from the session
This flow activates automatically when the runner detects it can’t open a local browser. Key behaviors:
- Progressive loading: Fast MCP servers (local tools) connect immediately; slow OAuth servers continue connecting in the background with a 10-second grace period
- Late-join support: The paste prompt re-emits every 15 seconds so viewers who connect late still see it
- Session isolation: Paste prompts are scoped to the current session and won’t leak across session switches
Disabling servers
Section titled “Disabling servers”There are two persistent ways to skip an MCP server during initialization:
{ "disabledMcpServers": ["slow-server"], "mcpServers": { "experimental-server": { "url": "https://mcp.example.com/mcp", "transport": "streamable", "disabled": true } }}| Method | Scope | Notes |
|---|---|---|
disabledMcpServers |
Global or project config | Lists server names to skip. Global and project lists are merged. |
Per-entry "disabled": true |
The server definition itself | Works in both mcpServers and mcp.servers[], matching Claude Code-style config. |
Managing Servers at Runtime
Section titled “Managing Servers at Runtime”Use the /mcp slash command to inspect and manage MCP servers without restarting your session:
| Command | Description |
|---|---|
/mcp |
Show status of all connected servers — tool count, connection timing, errors, and which config file each server came from. |
/mcp reload |
Disconnect and reconnect all MCP servers. Useful after editing config files or if a server crashed. |
/mcp disable <name> |
Disable a server by name. The server is disconnected and added to disabledMcpServers in your config so it stays disabled across sessions. |
/mcp enable <name> |
Re-enable a previously disabled server and reconnect it. |
Troubleshooting
Section titled “Troubleshooting”Server timeout on startup — MCP servers must respond within mcpTimeout milliseconds (default: 30000, i.e. 30 seconds). If a server needs more time to initialize, increase the timeout in your config:
{ "mcpTimeout": 60000, "mcpServers": { ... }}Skip all MCP servers — To start a session without any MCP servers (useful for debugging), set the environment variable or flag:
PIZZAPI_NO_MCP=1 pizza# orpizza --no-mcpServer not found — Run /mcp to see which config files were loaded and which servers were discovered. Check that your config file is in the right location (~/.pizzapi/config.json for global, .pizzapi/config.json at the repo root for project-local).
All servers initialize in parallel — Multiple slow servers don’t stack their startup times. If one server takes 10 seconds and another takes 15 seconds, total startup is ~15 seconds, not 25.
Pi 1.0 codemode and tool discovery
Section titled “Pi 1.0 codemode and tool discovery”PizzaPi continues to own MCP configuration, project-trust gating, sandbox URL checks, OAuth, relay approvals, and connection lifecycle. It does not load Pi’s built-in MCP extension, which would connect a second set of servers outside those PizzaPi boundaries. PizzaPi-managed tools remain ordinary direct-call tools by default. When PizzaPi’s toolSearch.enabled is turned on, it registers them with Pi 1.0’s deferred exposure: they remain callable through Pi 1.0 codemode but are not declared to the model until PizzaPi’s search_tools activates them. In either mode, nested calls use Pi’s normal tool pipeline, so PizzaPi tool_call hooks, approvals, and sandbox enforcement still apply.
Pi 1.0’s codemode and tool_search are inactive by default. To opt in to codemode, add "+codemode" to defaultTools in ~/.pizzapi/settings.json; codemode can search and call PizzaPi-managed MCP tools with searchTools(). Pi’s separate tool_search can also be enabled through defaultTools, but PizzaPi’s toolSearch setting and search_tools remain the supported configuration for PizzaPi-managed deferral and /mcp status. These Pi tools do not replace PizzaPi’s connection or trust policy.
MCP tools in plan mode
Section titled “MCP tools in plan mode”Plan mode blocks MCP tools by default. A tool can run in plan mode only if its server marks it read-only (annotations.readOnlyHint: true in tools/list). It can also run if you list its registered name (mcp__<server>__<tool>) in PIZZAPI_PLAN_MODE_ALLOWED_TOOLS. Tools without annotations count as mutating, and so do tools annotated readOnlyHint: false. Calls made through codemode are checked the same way.
Tool search and deferred loading
Section titled “Tool search and deferred loading”When many MCP tools consume too much model context, enable deferred loading in global ~/.pizzapi/config.json:
{ "toolSearch": { "enabled": true, "tokenThreshold": 10000, "maxResults": 5, "keepLoadedTools": true }}If the combined MCP tool definitions exceed the threshold, PizzaPi defers MCP tools and keeps search_tools active. The agent calls search_tools with a plain-language query; PizzaPi matches against deferred tool names, descriptions, and parameter names, returns up to maxResults, and loads matched tools into the active tool set for subsequent responses.
deferLoading: true or false on an individual MCP server entry forces deferral on or off for that server. Built-in tools are never deferred, and the MCP server process or HTTP client stays connected; this is different from PIZZAPI_NO_MCP=1, which skips MCP entirely.
keepLoadedTools controls what happens after discovery:
| Value | Behavior |
|---|---|
true (default) |
Discovered tools stay active for the rest of the session. |
false |
On-demand tools are deactivated after each turn and can be found again with search_tools. |
Use /tool-search status to inspect state and /tool-search reset to re-evaluate deferral. Settings can also be managed from Runner Settings in the web UI.
Examples
Section titled “Examples”Tavily Web Search (STDIO)
Section titled “Tavily Web Search (STDIO)”Gives the agent a tavily_search tool for real-time web search. Get an API key at tavily.com.
{ "mcpServers": { "tavily": { "command": "npx", "args": ["-y", "@tavily/mcp-server"], "env": { "TAVILY_API_KEY": "tvly-your-api-key-here" } } }}Filesystem MCP Server (STDIO)
Section titled “Filesystem MCP Server (STDIO)”Gives the agent tools to list directories, read files, search, and get file metadata outside the working directory. Useful for accessing reference projects or shared assets.
{ "mcpServers": { "filesystem": { "command": "npx", "args": [ "-y", "@anthropic/mcp-filesystem", "/Users/jordan/Documents/reference", "/Users/jordan/Documents/shared-assets" ], "env": {} } }}The paths passed as arguments define the allowed directories the server can access.
Remote HTTP Server with Auth Headers
Section titled “Remote HTTP Server with Auth Headers”Connects to a remote MCP server that requires an API key via HTTP header.
{ "mcpServers": { "internal-tools": { "url": "https://mcp.internal.example.com/v1/mcp", "transport": "streamable", "headers": { "Authorization": "Bearer your-api-token", "X-Team-Id": "engineering" } } }}