MCP (Model Context Protocol)
July 21, 2026 · View on GitHub
senpi ships a built-in MCP client. Servers you configure expose their tools, resources, and prompts to the agent with context-efficient defaults: a large catalog costs almost nothing until the model actually needs it.
Quickstart
- Add a server to
<agentDir>/mcp.json(global),.senpi/mcp.json(project), or import from.mcp.json(Claude format, viasettings.importConfigs):
{
"mcpServers": {
"docs": {
"command": "npx",
"args": ["-y", "@example/docs-mcp"],
"env": { "DOCS_TOKEN": "${DOCS_TOKEN}" }
}
}
}
- Start senpi. Run
/mcpfor the status panel,/mcp statusfor a one-line summary,/mcp add <name> <command...>to add servers interactively. - Servers needing OAuth:
/mcp login <name>(see Auth). - Use it: small catalogs register directly; big ones surface through
tool_search(see Exposure tiers).
Configuration reference
Top-level shape: { "settings": { ... }, "mcpServers": { "<name>": { ... } } }.
Server fields (mcpServers.<name>)
type
"stdio" | "http". Default: inferred — http when url is set, else stdio.
url
HTTP(S) endpoint for type:"http" servers (Streamable HTTP with SSE fallback).
command
Executable for type:"stdio" servers. Never passed through a shell.
args
Argument array for command. Default [].
env
Extra environment variables for the child process. Values support ${VAR}
expansion from the trusted parent environment.
cwd
Working directory for the child process. Default: the session cwd.
headers
Extra HTTP headers for type:"http" servers.
auth
"bearer" | "oauth" | false. Default: autodetected — bearerTokenEnv or an
Authorization header implies bearer; a 401 from an HTTP server triggers the
OAuth flow. false disables auth entirely.
bearerTokenEnv
Name of the environment variable holding a bearer token (the value itself never lives in config; literal-looking tokens in config produce a warning).
oauth
OAuth tuning: clientId, callbackPort (default: ephemeral),
scopes, clientMetadataUrl, flow ("code" default, or
"client_credentials" for headless machine-to-machine).
enabled
Default true. Disabled servers stay in config but never spawn.
lifecycle
"lazy" (default: connect on first use), "eager" (connect at session start),
"keep-alive" (eager + 30s pings + automatic reconnect; never idles out).
idleTimeoutMin
Minutes of zero in-flight calls before a connected server is shut down (its
tools stay registered; the next call reconnects transparently). Default 10.
requestTimeoutMs
Per-request timeout. Default 30000.
connectTimeoutMs
Connect + initialize handshake timeout. Default 15000.
startupTimeoutMs
Bounded startup window (ms) that a server's first connect + catalog fetch is
raced against during session attach. A server that does not settle inside the
window keeps connecting in the background and surfaces its tools when ready, so
a slow or wedged server never blocks the first turn. Default 250. Set higher
to wait for tools before the first turn, 0 to never wait. The
SENPI_MCP_STARTUP_TIMEOUT_MS environment variable overrides this for every
server.
includeTools
Glob allowlist (* wildcards) over server-side tool names. Default: all.
excludeTools
Glob denylist applied after includeTools.
directTools
true = every filtered tool active immediately; or an array of names/globs
that stay active while the rest goes behind tool_search. Default: none.
exposure
"auto" (default), "direct", "search", or "proxy". See
Exposure tiers. auto never selects proxy.
logLevel
Minimum RFC-5424 level (debug…emergency) for the server's
notifications/message log stream. Default: record everything (rate-capped).
Settings fields (settings)
toolPrefix
Prefix for registered tool names (<prefix>_<server>_<tool>). Default "mcp".
searchThreshold
Filtered-tool count above which auto switches a server to search mode.
Default 10.
outputGuard
Caps on tool/resource output: maxBytes (default 51200), maxLines
(default 2000), maxTokens. Oversized output is truncated with a notice and
the full artifact is written to disk.
importConfigs
["claude"] imports .mcp.json (Claude Code format) from the project root.
Imported servers require project trust.
oauthCallbackUrl
Override the OAuth loopback callback URL (e.g. behind port forwarding).
stubSwap
Opt-in prompt-cache mitigation for search mode: every inactive tool registers
as a 30-70-token stub so the tools array stays length-stable; activation swaps
the stub for the full schema in place. Default false.
nativeToolSearch
"auto" (default) | true | false. On Anthropic models, defers inactive
MCP tools to the provider's native tool-search; any 400 falls back to the
local tool_search for the session.
Exposure tiers
| Tier | When | Cost profile |
|---|---|---|
| direct | exposure:"direct", directTools:true, or auto at/below searchThreshold | Every tool schema on every request |
| search (Tier-B) | exposure:"search" or auto above the threshold | Full catalog registered, ~135 tokens resident (tool_search only); matches promote next turn; promotions survive resume/compaction |
| proxy (Tier-C) | exposure:"proxy" only — never auto | One mcp_<server> gateway tool (search/describe/call with JSON-string args); cheapest, but no provider-side argument validation |
Skills can carry MCP servers too (an mcp.json sidecar next to SKILL.md, or a
mcp: frontmatter block): those servers register with zero active tools and
reveal their includeTools matches when the skill loads. A name collision
with a configured server resolves in favor of your config.
Server sources
Servers are merged from these sources, in precedence order:
global—<agentDir>/mcp.jsonclaude— imported.mcp.json(requires project trust)project—.senpi/mcp.json(requires project trust)extension— declared by an extension viapi.registerMcpServer()during factory load (see extensions.md)skill— declared in a skill'smcp.jsonsidecar or SKILL.md frontmatter
A name collision resolves as follows: trusted user config (including an
enabled: false entry) wins over extension declarations; an extension
declaration replaces an untrusted placeholder and records a diagnostic.
Extension-declared servers use bare names (no prefix).
Resources and prompts
mcp_list_resources/mcp_read_resourceregister automatically when a connected server lists resources.- Mention
@mcp:<server>/<uri>in your prompt to inline a resource's content into the message; unknown or failing mentions are left untouched with a notice. - Every server prompt registers as
/mcp:<server>:<prompt>; invoking it collects the prompt's arguments and drops the rendered text into the editor. - Servers may ask questions mid-call (elicitation, form mode): senpi walks the requested fields through input dialogs; in non-interactive runs the request is declined cleanly.
Auth
- Bearer: set
bearerTokenEnv(recommended) or anAuthorizationheader. - OAuth (interactive):
/mcp login <name>runs the authorization-code + PKCE flow with a loopback callback; tokens persist under<agentDir>with0600permissions and refresh automatically (single-flight across processes). - OAuth (headless):
flow:"client_credentials"for machine-to-machine, or/mcp login <name> --pasteto complete the code flow by pasting the redirect URL from another browser/machine. /mcp logout <name>clears stored tokens.
Troubleshooting
Symptom (/mcp status) | Meaning | Fix |
|---|---|---|
needs_auth | 401 and no usable token | /mcp login <name> |
suspended | reconnect circuit breaker opened (5 failures/30s) | fix the server, then /mcp reconnect <name> |
degraded | transient failure; auto-reconnect with backoff is running | wait, or /mcp reconnect <name> |
| tools missing | server filtered/disabled, or hidden behind search | check includeTools/excludeTools, ask the model to tool_search |
| child exits at spawn (EOF) | bad command/args/env | /mcp logs <name> shows the captured stderr |
| slow first call | lazy server cold boot (tools attach in the background) | raise startupTimeoutMs, or use lifecycle:"eager" / "keep-alive" |
Security notes
- Config values never pass through a shell;
${VAR}expansion only reads the trusted parent environment. - Project-level and imported configs require project trust before servers spawn; untrusted entries are listed but inert.
- Tokens are stored
0600and never logged; server log streams and tool output are redacted by the same secret scrubber as the rest of senpi and capped byoutputGuard.
Disabling MCP entirely
Add the builtin to your settings' disabled list — no other configuration is needed:
{ "disabledBuiltinExtensions": ["mcp"] }