MCP in depth
Everything about connecting Claude Code to MCP servers: transports, scopes, authentication, limits, tool search, plugin servers and running Claude Code as a server.
The Model Context Protocol (MCP) is an open standard for plugging tools and data into AI applications. In Claude Code, an MCP server adds tools Claude can call: your issue tracker, error monitoring, a database, a design tool, an internal API.
My rule of thumb: the moment I catch myself pasting data from another system into the chat, that system deserves an MCP server. Then Claude reads it directly rather than working from my copy.
New to MCP? Do the MCP quickstart first. This page is the full reference.
What it unlocks
Some requests that become possible once the right servers are connected:
- "Pick up LIN-842, implement it and open a PR."
- "Look at last night's Sentry errors for the checkout service and tell me which release introduced them."
- "From the analytics database, how many trial accounts converted in September?"
- "Rebuild the pricing card to match the new Figma frame."
- "Draft follow-up emails to the five customers who reported this bug."
An MCP server can also act as a channel, pushing events (chat messages, CI results, webhooks) into a running session so Claude can react while you are away.
Finding and building servers
The Anthropic Directory at claude.ai/directory lists reviewed connectors; any remote server listed there can be added with claude mcp add.
Warning: Only connect servers you trust. A server that pulls in outside content (web pages, tickets, emails) can carry prompt injection. See security.
To build your own, the protocol guides at modelcontextprotocol.io cover the fundamentals. The fastest start is the official mcp-server-dev plugin, which has Claude scaffold a server with you:
/plugin install mcp-server-dev@claude-plugins-official
/mcp-server-dev:build-mcp-server
If the install says Marketplace "claude-plugins-official" not found, run /plugin marketplace add anthropics/claude-plugins-official and try again. If the summary says Run /reload-plugins to apply., Claude Code runs it for you; use /reload-plugins --force if it warns about re-reading the conversation. In VS Code or the desktop app, install the plugin through their UI instead (see installing plugins).
Adding servers
There are four transports. Pick by where the server runs and how it talks.
Remote HTTP (the default choice)
claude mcp add --transport http <name> <url>
claude mcp add --transport http billing https://mcp.billing.internal/mcp \
--header "Authorization: Bearer $BILLING_TOKEN"
In JSON config, type accepts streamable-http as an alias for http, so snippets copied from server docs work unchanged.
A JSON entry with a url but no type is an error, because a missing type means stdio. Claude Code skips it with MCP server "<name>" has a "url" but no "type"; add "type": "http" (or "sse" / "ws") to this entry.
"type": "sdk" entries can only be registered by an SDK host such as an Agent SDK app or the desktop app. In a config file they are skipped with a message saying so. With --output-format stream-json, skipped --mcp-config entries are also listed in the system/init event's mcp_server_errors field (v2.1.219 or later).
Remote SSE (deprecated)
Server-Sent Events is on its way out. From v2.1.265, --transport http tries HTTP first and falls back to SSE automatically, so you rarely need anything else. On older versions, or to force it:
claude mcp add --transport sse legacy-crm https://crm.example.com/sse \
--header "X-API-Key: $CRM_KEY"
Local stdio
A stdio server is a process on your machine. Use it for anything that needs local access.
claude mcp add [options] <name> -- <command> [args...]
claude mcp add --env PGHOST=localhost --transport stdio pg-local \
-- npx -y @example/postgres-mcp --read-only
The -- matters. Everything before it is for Claude Code (--transport, --env, --scope); everything after is passed untouched to the server. Without it, a server flag such as --read-only would be parsed as a Claude Code option.
--env accepts several KEY=value pairs, so if the server name comes straight after --env it is read as another pair and rejected. Put another option, like --transport stdio, between them.
Claude Code sets CLAUDE_PROJECT_DIR in the server's environment to the project root, the same value hooks receive. It does not change when working directories are added. A server that restricts itself to allowed folders should implement the MCP roots/list request instead: Claude Code answers with the launch directory plus every directory granted through --add-dir, /add-dir or additionalDirectories, and sends notifications/roots/list_changed when that set changes (v2.1.203 or later).
Because CLAUDE_PROJECT_DIR lives in the server's environment rather than Claude Code's, referencing it in command or args of a .mcp.json or ~/.claude.json entry needs a default: ${CLAUDE_PROJECT_DIR:-.}. Plugin configs substitute it directly.
Remote WebSocket
WebSocket suits servers that push events unprompted. It supports neither OAuth nor the --transport flag, so use HTTP if the server only answers requests. Configure it in JSON:
claude mcp add-json live-feed \
'{"type":"ws","url":"wss://feed.example.com/mcp","headers":{"Authorization":"Bearer abc123"}}'
ws entries accept the same url, headers, headersHelper, timeout and alwaysLoad fields as http. Auth is header-only: a static token or a headersHelper.
Translating instructions written for other clients
Many servers document setup for Claude Desktop or Cursor only. Look for one of three things:
| You find | It means | Do this |
|---|---|---|
A URL like https://.../mcp | Remote server | claude mcp add --transport http <name> <url> (or SSE if stated; wss:// needs add-json with "type":"ws") |
A command like npx -y pkg or uvx pkg | Local stdio | claude mcp add <name> --env KEY=val -- npx -y pkg |
An mcpServers JSON block | Another client's config | claude mcp add-json <name> '<the inner object>' |
For a JSON block, pass the object inside mcpServers, not the wrapper. Fix two things first: add a type to any entry with a url, and rename any key containing characters other than letters, digits, hyphens and underscores. For example:
claude mcp add-json todo-sync '{"command":"uvx","args":["todo-sync-mcp"]}'
Every command writes to local scope unless you add --scope. Success prints an Added ... line; claude mcp get <name> then confirms the connection.
Managing servers
claude mcp list # all servers with health
claude mcp get billing # details for one
claude mcp remove billing # delete it, plus stored OAuth tokens and client registration
Inside a session, /mcp is the control panel: status, tools, authentication, reconnect and disable.
If add or remove prints was not saved or may not have been saved, see errors.
Status values
claude mcp list shows a health marker per server, such as ✔ Connected, ! Needs authentication or ✘ Failed to connect. A failure means that server could not be reached, not that the command failed. Three statuses come from configuration rather than a connection attempt:
| Status | Shown in | Meaning |
|---|---|---|
⏸ Pending approval (run `claude` to approve) | list and get | A .mcp.json server you have not approved |
✘ Rejected (see disabledMcpjsonServers in settings) | get only | Blocked by disabledMcpjsonServers |
⊘ Disabled for this project (re-enable via /mcp) | list and get | Named in the project's disabledMcpServers |
For ✘ Failed to connect, list appends detail and get shows an Issue: line: HTTP status or error code plus any server message (v2.1.219 or later). Credential-like text is redacted and the expanded URL is never shown. ✘ Connection error never carries detail, because the exception text could include that URL. When an authentication attempt from /mcp still fails with a code, the message adds the URL's origin (scheme, host, and port if given), never the path or query, and with ${VAR} references unexpanded.
A remote server with an empty url shows not configured and is not contacted. Plugins use this for placeholder connectors; the /mcp detail reads No URL configured for this server.
The discovery cache
From v2.1.221, a remote server you have used before can show something like cached 2h ago · connects on first use · 5 tools. Its tool list came from a saved cache, so startup skips connecting and the server connects when Claude first calls one of its tools. Tools are available from your first message regardless. Since v2.1.238 the cache is off unless a rollout enabled it for you; set MCP_DISCOVERY_CACHE=1 to turn it on or 0 to keep it off. Disable or Clear authentication in /mcp discards the entry, as does Reconnect on a connected or failed server. Reconnect on a cached server connects now and keeps the entry.
Configuration warnings
| Warning | What triggers it | Fix |
|---|---|---|
| Hidden whitespace | Leading or trailing spaces in command, url, args, or env / headers keys and values. Shown as e.g. Leading or trailing whitespace in: headers.Authorization | Edit the value; Claude Code does not trim it |
| Same name in several scopes | Different endpoints under one name. OAuth sign-ins are stored per endpoint, so you may need to sign in per project | claude mcp remove <name> --scope <scope> on the ones you do not want |
| Reserved name | Using a built-in name such as workspace, claude-in-chrome, computer-use, Claude Preview or Claude Browser. The entry is skipped; claude mcp add rejects it | Rename |
| Missing variable | ${VAR} with no value and no :-default. The server loads with the literal text | Set it or add a default |
Project approvals and workspace trust
.mcp.json servers need approval before they run in interactive sessions. Reset your past answers with claude mcp reset-project-choices.
From v2.1.196, approvals committed to the repository are ignored until you have trusted the folder by running claude there and accepting the dialog. A cloned repo cannot pre-approve its own servers through enableAllProjectMcpServers or enabledMcpjsonServers in .claude/settings.json. Approvals from your user ~/.claude/settings.json, managed settings and --settings still apply in an untrusted folder. An untracked .claude/settings.local.json counts only after trust (from v2.1.207), except in your configuration home. disabledMcpjsonServers in any file always wins.
In claude -p, the Agent SDK and cloud sessions there is nobody to ask, so project servers load without approval. Same for a bypassPermissions session when skipDangerousModePermissionPrompt is set in user or managed settings. To keep a server out anyway, list it in disabledMcpjsonServers, exclude project settings with --setting-sources (or the SDK's settingSources), or use --strict-mcp-config so only --mcp-config servers load (from v2.1.246 that also skips the approval wait). See managed MCP.
Switching a server off without deleting it
Toggle it in /mcp. The choice is stored per project in ~/.claude.json, in one of two lists that never overlap:
disabledMcpServers: opt-out for user, plugin, organisation-provided and claude.ai connectors you fetch, plus built-ins that default to on. Connectors are recorded by display name, such asclaude.ai Slack.enabledMcpServers: opt-in for built-ins that default to off, such ascomputer-use.
Each server is checked against exactly one list, so a regular server in enabledMcpServers is ignored. These are unrelated to enabledMcpjsonServers and disabledMcpjsonServers, which govern .mcp.json approval.
Scopes
| Scope | Loads in | Shared | Stored in |
|---|---|---|---|
local (default) | This project | No | ~/.claude.json, under projects["<path>"].mcpServers |
project | This project | Yes, via git | .mcp.json at the root |
user | Every project | No | ~/.claude.json, top-level mcpServers |
Choose with -s or --scope. Local suits experiments and anything with private credentials. Project is for what the whole team should have. User is for personal utilities you want everywhere.
Note: MCP "local scope" lives in
~/.claude.json, which is not the same place as local settings (.claude/settings.local.json). See settings.
Which definition wins
If a server is defined more than once, Claude Code connects once using the highest source, taking that whole entry with no merging:
- Local
- Project
- User
- Plugin servers
- claude.ai connectors
The three scopes match duplicates by name. Plugins and connectors match by endpoint. URLs count as the same when they differ only in scheme or host letter case, a default port such as :443, or a trailing slash. A server from the managedMcpServers managed setting outranks all of them (v2.1.259 or later). In the desktop app's Code tab, a stdio name in both user scope and .mcp.json resolves to the user definition.
Environment variables in config
.mcp.json supports ${VAR} and ${VAR:-default} in command, args, env, url and headers:
{
"mcpServers": {
"reports": {
"type": "http",
"url": "${REPORTS_HOST:-https://reports.example.com}/mcp",
"headers": { "Authorization": "Bearer ${REPORTS_TOKEN}" }
}
}
}
An unset variable with no default leaves the literal text and produces a warning.
In a remote server's url and headers, credential variables always read as empty, so a project file or plugin cannot ship your secrets to a server it names. That covers Claude Code's own (ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN), cloud provider ones (AWS_BEARER_TOKEN_BEDROCK) and others like HTTPS_PROXY and NPM_TOKEN. Defaults on those names are ignored too. ANTHROPIC_BASE_URL still expands unless its value embeds credentials. To pass one deliberately, copy it into a variable with your own name. A debug log line, never expanded toward a remote server, names any you referenced (claude --debug-file /tmp/claude-debug.log).
/mcp detail (from v2.1.268), claude mcp list and claude mcp get show ${VAR} references by name rather than value. Organisation-provided servers show the URL host only.
Worked examples
GitHub with a personal access token
Create a fine-grained token at your GitHub token settings with access to the right repositories, then:
claude mcp add --transport http github https://api.githubcopilot.com/mcp/ \
--header "Authorization: Bearer $GITHUB_PAT"
The add command does not validate credentials, so check /mcp. A bad token shows as failed with the HTTP status, usually 401. Then ask things like "summarise the open PRs touching the billing module" or "file an issue for the race condition we just found".
A read-only database
DBHub (@bytebase/dbhub) connects Claude to a relational database. Always use a read-only database user:
claude mcp add --transport stdio warehouse -- npx -y @bytebase/dbhub \
--dsn "postgresql://analyst_ro:secret@db.internal:5432/warehouse"
Confirm in /mcp, then ask: "Which ten products had the highest return rate last quarter?"
Authentication
Most hosted servers use OAuth 2.0. A remote server is flagged as needing authentication when it returns 401 or 403, with a few exceptions:
- A claude.ai connector whose
401comes from claude.ai rejecting your session token shows the session-token-rejected state instead. - A server where you configured
Authorizationyourself (inheadersor viaheadersHelper) is reported as failed, since your credential is what needs fixing. Check you have not referenced a variable that reads as empty. - Connectors delivered to a cloud session are authenticated by the session proxy; reauthorise them at claude.ai/customize/connectors.
When a signed-in server returns 401, Claude Code refreshes the token, reconnects and retries once before flagging it. If the refresh token itself is rejected you get an immediate notice pointing to /mcp, where Re-authenticate signs you in again. Servers that return a WWW-Authenticate header pointing at their authorisation server are discovered automatically.
At startup a notice lists servers needing sign-in, once per server, counting only ones you can authenticate from Claude Code. In -p and SDK runs there is no panel, so (from v2.1.196, with tool search on) Claude is told which server needs authorising. Sign in interactively first.
Signing in
Add the server, then run /mcp, select it and choose Authenticate:
claude mcp add --transport http linear https://mcp.linear.app/mcp
Tokens are stored securely and refreshed automatically. Clear authentication in /mcp revokes them. If the browser does not open, copy the URL; if the redirect fails after you approve, paste the full callback URL from the address bar into Claude Code's prompt.
From the shell, without opening a session:
claude mcp login linear
claude mcp logout linear
On a machine with no browser (SSH, headless Linux), login prints the URL; open it locally and paste the redirect URL back. It needs an interactive terminal, so use ssh -t. --no-browser forces this mode.
Fixed callback port
Claude Code normally picks a random port for the OAuth callback. If the server requires a pre-registered redirect URI of the form http://localhost:PORT/callback, fix the port:
claude mcp add --transport http --callback-port 8765 crm https://mcp.crm.example.com/mcp
Pre-registered OAuth clients
If you see Incompatible auth server: does not support dynamic client registration, the server needs credentials you register yourself. (Servers using a Client ID Metadata Document are discovered automatically.) Register an app in the server's developer portal with redirect URI http://localhost:PORT/callback, then:
claude mcp add --transport http \
--client-id crm-cli-123 --client-secret --callback-port 8765 \
crm https://mcp.crm.example.com/mcp
--client-secret prompts with masked input. The JSON route puts the client details in an oauth object:
claude mcp add-json crm \
'{"type":"http","url":"https://mcp.crm.example.com/mcp","oauth":{"clientId":"crm-cli-123","callbackPort":8765}}' \
--client-secret
Set oauth.callbackPort alone to fix the port while still registering dynamically. In CI, provide the secret through MCP_CLIENT_SECRET to skip the prompt.
Points to remember:
- The secret goes to the system keychain (macOS) or a credentials file, never the config.
- It can only be set when adding.
loginand/mcpuse the stored one and ignoreMCP_CLIENT_SECRET. To change it, remove and re-add with the same--scope. - Public clients take
--client-idonly. - These flags only affect HTTP and SSE servers.
claude mcp get <name>shows whether OAuth credentials are configured.- v2.1.229 briefly sent
http://127.0.0.1:PORT/callback, which broke exact-match servers; v2.1.231 restoredlocalhost.
Overriding metadata discovery
By default Claude Code tries RFC 9728 protected resource metadata at /.well-known/oauth-protected-resource, then RFC 8414 at /.well-known/oauth-authorization-server. To skip that, set oauth.authServerMetadataUrl (must be https://). Its scopes_supported then overrides the server's advertised scopes.
{
"mcpServers": {
"crm": {
"type": "http",
"url": "https://mcp.crm.example.com/mcp",
"oauth": { "authServerMetadataUrl": "https://sso.example.com/.well-known/openid-configuration" }
}
}
}
Pinning scopes
oauth.scopes fixes the scopes requested, as one space-separated string. It beats both the metadata URL and discovered scopes. This is how a security team limits a server to an approved subset:
{
"mcpServers": {
"drive": {
"type": "http",
"url": "https://mcp.drive.example.com/mcp",
"oauth": { "scopes": "files.read files.metadata" }
}
}
}
Unpinned, Claude Code requests the scope from the server's WWW-Authenticate header or protected resource metadata, or no scope at all (from v2.1.196; it no longer asks for the whole scopes_supported list, which upset some identity providers). If the authorisation server lists offline_access, it is appended so tokens can refresh. A later 403 insufficient_scope fails the call with a needs additional permissions message naming the scope; add it to your pinned list before re-authenticating, otherwise the new token will still lack it.
Generating headers at connect time
For Kerberos, short-lived tokens or internal SSO, headersHelper runs a command and merges its JSON output into the request headers:
{
"mcpServers": {
"intranet": {
"type": "http",
"url": "https://mcp.intranet.example.com",
"headersHelper": "/usr/local/bin/intranet-mcp-headers"
}
}
}
The rules:
- Output must be a JSON object of string pairs on stdout.
- It runs in a shell with a 10-second limit, fresh on every connect and reconnect, with no caching (your script handles reuse).
- Its headers override static
headersof the same name. - On a
401or403from a tool call it is re-run, the connection rebuilt and the call retried once. - An
Authorizationheader from the helper disables OAuth fallback for that server; if the server rejects it while connecting, the connection is reported as failed. Auth errors are retried when the helper is the only source ofAuthorization. - It receives
CLAUDE_CODE_MCP_SERVER_NAME,CLAUDE_CODE_MCP_SERVER_URLand, for plugin servers,CLAUDE_PLUGIN_ROOT, so one script can serve several servers. - Plugin helpers cannot use
${user_config.*}(since v2.1.207); put those inheadersinstead.
Working directory depends on where the server was configured, so use absolute paths:
| Configured in | Runs in |
|---|---|
| A plugin | The plugin root |
Project .mcp.json or local scope | That project directory |
A project agent file, SDK mcpServers / setMcpServers(), or --mcp-config | The session's primary working directory |
| User scope, managed MCP, a claude.ai connector, or an agent file from outside the project | Your config directory (~/.claude or CLAUDE_CONFIG_DIR) |
Helpers supplied by a repository or plugin are code you did not write, so they run without credential-like variables (any name containing TOKEN, SECRET, PASSWORD, KEY or AUTH in any case, plus a fixed list such as ANTHROPIC_CUSTOM_HEADERS; git's GIT_CONFIG_KEY_<n> are kept). That applies to project .mcp.json, plugins and inline servers in project or --add-dir agent files. User, local, managed, connector, SDK and --mcp-config servers keep your environment. Read credentials from a file or keychain instead.
Project and local-scope helpers only run once you have accepted the trust dialog for that exact folder (a parent folder's trust does not count). Until then the server connects with static headers only, and -p / SDK runs print headersHelper not run to stderr. You can pre-trust by setting projects["<path>"].hasTrustDialogAccepted to true in ~/.claude.json. Inline agent-file servers from an untrusted project are not loaded at all.
Adding from JSON and importing
claude mcp add-json <name> '<json>'
claude mcp add-json stock-api '{"type":"http","url":"https://stock.example.com/mcp","headers":{"X-Key":"k_123"}}'
claude mcp add-json img-tools '{"type":"stdio","command":"/opt/img-mcp","args":["--cache","/tmp/img"]}'
Watch your shell quoting. Add --scope user for user scope.
To pull servers from the Claude Desktop chat app (macOS and WSL only):
claude mcp add-from-claude-desktop
You pick servers in a dialog. Names must use only letters, digits, hyphens and underscores; others are reported and skipped while the rest import (from v2.1.205). Clashing names get a numeric suffix such as server_1.
Connectors from claude.ai
Signed in with a claude.ai account, the connectors you added at claude.ai/customize/connectors appear in Claude Code automatically, marked as coming from claude.ai in /mcp. On Team and Enterprise plans only admins can add them. Some connectors are provided by Anthropic with no setup, such as claude.ai Claude Docs where it is available (see artifacts); turn that off through deniedMcpServers or the /mcp toggle.
Connectors whose auth your organisation manages are marked managed. Ones you have never signed in to sit behind a Show unused connectors row.
Connectors are only fetched when your active authentication is a claude.ai subscription. They are not loaded when ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN or apiKeyHelper is active, when a cloud provider like Bedrock is in use, when an Anthropic profile or ANTHROPIC_PROFILE supplies the credential, or when CLAUDE_CODE_OAUTH_TOKEN holds a claude setup-token token. /status shows which method is active.
Other behaviour:
- A network failure fetching the list is retried three times in the background.
session token rejectedmeans claude.ai rejected your login, not the connector. Run/login, then reconnect from/mcp.- A server you added yourself with the same URL wins; the connector shows as hidden.
- Some Anthropic-hosted services (Microsoft 365, Gmail, Google Calendar) cannot do local OAuth. Adding them yourself gives
is Anthropic-hosted and doesn't support local OAuth; connect them on claude.ai instead.
Where connectors come from by surface
| Session | How connectors arrive | What controls them |
|---|---|---|
| Terminal, VS Code, JetBrains, Agent SDK | Claude Code fetches them | The settings below plus managed MCP |
| Cloud sessions | The cloud host passes them in | Your claude.ai org settings, allow/deny lists reaching the session, and any host managed-mcp.json (which drops them entirely) |
| Desktop local and SSH | Delivered in-process as sdk servers | Only blocked tool controls; disconnect on claude.ai to remove |
In cloud sessions the proxy rewrites connector URLs, so serverUrl patterns for the original URL will not match. Connectors are not yet available in the desktop app's WSL sessions.
Organisation tool controls
Organisations can set each connector tool to ask or blocked. ask prompts on every call with Your organization requires approval for this tool, even in acceptEdits, auto and bypassPermissions, never offers to remember, and ignores allow rules; dontAsk mode denies instead. blocked removes the tool before Claude sees it (/mcp shows it as disabled by your organization). In desktop local and SSH sessions, ask does not apply and ordinary permission rules are used.
Turning connectors off
{ "disableClaudeAiConnectors": true }
This affects only connectors Claude Code fetches itself. true from any settings source wins, so a project can opt out but cannot re-enable what a user or policy disabled. --mcp-config servers are unaffected. For one shell, ENABLE_CLAUDEAI_MCP_SERVERS=false claude does the same. To block individual connectors, add them to deniedMcpServers by name ("claude.ai Slack") or URL, or toggle them per project in /mcp.
Runtime behaviour
Client runtimes
There are two MCP client runtimes. v1 is built on the MCP TypeScript SDK 1.x; v2 uses SDK 2.0 and adds protocol revision 2026-07-28. The runtime is chosen at startup. Sessions that fetch feature flags use v2 from v2.1.232. Others (Bedrock, Claude Platform on AWS, Google Cloud's Agent Platform, Foundry, apps gateway sign-ins, sessions with telemetry or flag fetching off) use v2 by default from v2.1.274, unless a host sets CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST.
On v2, Claude Code negotiates the newer revision with HTTP and stdio servers that support it, holds a stream open for their list_changed notifications, refuses to register channel servers on the new revision (it cannot carry channel messages), fails OAuth when the authorisation response names an unexpected issuer, and only sends OAuth credentials to HTTPS token endpoints or localhost, 127.0.0.1 and ::1. Choose the runtime with MCP_SDK_GENERATION (v1 or v2) and negotiation with MCP_PROTOCOL_NEGOTIATION (auto or legacy; legacy keeps stdio channel servers working).
Changing tool lists
Servers can send list_changed when their tools, prompts or resources change. Interactive sessions refetch everything; -p and SDK runs refresh tools only. A failed refresh keeps the previous lists (from v2.1.214). On v2, a notification stream that closes within 10 seconds is reopened up to three times; one that stays open longer is reopened up to five times an hour, then left for about six hours. Reconnect from /mcp to refresh sooner.
Reconnection and retries
| Situation | Behaviour |
|---|---|
| Remote server drops mid-session | Up to five reconnects with exponential backoff from one second. Interactive sessions show it as pending, then failed with MCP server "<name>" disconnected · open /mcp to reconnect |
| First connection to HTTP or SSE fails transiently (5xx, refused, timeout) | Up to three retries |
| WebSocket first connection, auth errors, not-found | No retry (except auth errors when a headersHelper supplies Authorization) |
Discovery requests (tools/list and friends) hit a transient error | Up to three retries; not for auth, 4xx or timeouts |
| Stdio server dies | Not reconnected automatically |
/mcp reconnect all retries every failed or unauthenticated server (v2.1.284 or later in the terminal). With tool search on, Claude is told which servers failed and why; without it, Claude is not told.
If Claude needs a server that is still connecting, it waits: inside the ToolSearch call normally, or through the WaitForMcpServers tool when tool search is off. After a resume, a call to a still-connecting server is held up to 10 seconds, then fails with No such tool available. /mcp shows tool counts and flags servers that claim tools but list none.
Timeouts
| Setting | Effect |
|---|---|
MCP_TIMEOUT | Server startup timeout in ms (default 30 seconds) |
timeout on a server entry | Hard wall-clock limit per tool call in ms, overriding MCP_TOOL_TIMEOUT for that server. Values under 1000 are ignored. Progress notifications do not extend it |
MCP_TOOL_TIMEOUT | Global per-call limit (about 28 hours if unset) |
CLAUDE_CODE_MCP_TOOL_IDLE_TIMEOUT | Abort a call that sends nothing for this long. Default five minutes for remote and connector servers, 30 minutes for stdio. 0 disables. Not applied to IDE or SDK in-process servers |
HTTP, SSE and connector servers also have a per-request timer up to the first response byte, set to the largest of 60 seconds, the server's tool timeout and MCP_TIMEOUT. A per-server timeout of 1000 or more is also a floor on the idle timeout (v2.1.203 or later).
Long calls move to the background
From v2.1.212, a main-conversation MCP call still running after two minutes becomes a background task. Claude gets a task ID and carries on, the result arrives as a notification, and /tasks lets you watch or stop it. It does not survive exiting. Change the threshold with CLAUDE_CODE_MCP_AUTO_BACKGROUND_MS (0 disables); CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1 disables all background tasks. Subagent calls, IDE servers and non-interactive runs (unless CLAUDE_AUTO_BACKGROUND_TASKS=1) are never backgrounded, and nothing moves while an elicitation dialog is open.
Output limits
| Limit | Default | Notes |
|---|---|---|
| Warning | 10,000 tokens | Fixed |
| Maximum | 25,000 tokens | MAX_MCP_OUTPUT_TOKENS changes it for tools without their own limit; image results always obey it |
| Text saved to file | 50,000 characters | Applies whatever the token count; a tool can raise it with _meta["anthropic/maxResultSizeChars"] up to 500,000 |
| Error text | About 11,000 characters | Longer isError results keep the first and last 5,000 characters |
Oversized results are written to the session's tool-results folder under ~/.claude/projects/ and replaced by a file reference. Image results (PNG, JPEG, GIF, WebP) are shown inline, possibly downscaled, and from v2.1.283 the original is also saved there unless session persistence is off.
{
"name": "dump_schema",
"description": "Returns every table and column",
"_meta": { "anthropic/maxResultSizeChars": 300000 }
}
Schemas the API would reject
The API refuses root-level anyOf, oneOf or allOf in a tool's input schema. Claude Code flattens those into one object, merging properties, and adds a sentence to the description explaining which parameters go together (allOf keeps each branch's required; for anyOf and oneOf the requirements are described, not enforced). Validate on the server. If it cannot produce an acceptable schema, it skips that one tool.
It also drops tools whose top-level property names are not 1 to 64 characters of ASCII letters, digits, _, . and -, or whose schema fails the JSON Schema draft 2020-12 meta-schema, and tells Claude which were excluded. Where feature flags are not fetched, it only logs the problem and the API returns a 400 naming the tool by position.
Server author features
Forcing approval for a tool
Set _meta["anthropic/requiresUserInteraction"] to the boolean true and that tool prompts on every call, in every mode, with no "don't ask again" and no allow-rule bypass. dontAsk mode denies it. A --permission-prompt-tool cannot approve it (MCP tool requires user interaction; not supported via --permission-prompt-tool), but an SDK canUseTool callback can. Remote Control and SDK one-tap approval are withheld for it (v2.1.214 or later).
{
"name": "approve_refund",
"description": "Issues a refund to a customer",
"_meta": { "anthropic/requiresUserInteraction": true }
}
Elicitation
Servers can ask you for input mid-task. Form mode shows fields; URL mode asks to open a link, typically for sign-in. URLs are passed to your system's URL handler and capped at about 8,000 characters (each character needing escaping counts four times, so heavily percent-encoded URLs hit the cap near 4,000). Use the Elicitation hook to answer automatically. On revision 2026-07-28 Claude Code declares elicitation: {form: {}, url: {}}.
Server instructions and tool search
With tool search, only tool names and server instructions load at start. Write instructions that say what tasks your tools cover and when Claude should look for them. Tool descriptions and instructions are cut at 2,048 characters; change that with CLAUDE_CODE_MAX_MCP_DESCRIPTION_LENGTH (v2.1.280 or later).
Resources and prompts
Resources with @
Type @ to see resources from connected servers alongside files, and reference one as @server:protocol://path:
Compare @linear:issue://LIN-842 with the behaviour in @docs:file://billing/refunds
Referenced resources are fetched and attached; Claude also gets tools to list and read them. MCP Apps UI resources (ui:// or text/html;profile=mcp-app) are hidden from suggestions but readable by URI.
Prompts as commands
Server prompts appear in the / menu as /servername:promptname (MCP) and can also be typed as /mcp__servername__promptname. Arguments are space-separated single tokens:
/mcp__linear__triage LIN-842 urgent
Server names have characters outside A-Z, a-z, 0-9, _ and - replaced with _. A server named anthropic-skills has its prompts hidden, as that name is reserved for synced skills.
Tool search
Tool search defers MCP tool definitions until needed, so many servers barely dent your context window. There is no per-server tool cap. It needs a model that supports tool_reference blocks (Sonnet 4.5, Haiku 4.5, Opus 4.5 and later).
ENABLE_TOOL_SEARCH | Behaviour |
|---|---|
| unset | Deferred, except with a non-first-party ANTHROPIC_BASE_URL, pre-4.5 models on Google Cloud's Agent Platform, or Azure-hosted Foundry deployments |
true | Deferred (still not on Azure-hosted Foundry or pre-4.5 Agent Platform models); the beta header is sent through proxies |
auto | Load upfront while definitions are under 10% of context, defer once they reach it |
auto:N | Same with your own percentage, 0 to 100 |
false | Everything upfront |
CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS forces it off and cannot be overridden by ENABLE_TOOL_SEARCH (organisations can keep it on via managed settings from v2.1.227). You can also deny the ToolSearch tool in permissions.deny.
Always loading a server
"alwaysLoad": true on any server entry loads all its tools upfront regardless. A server can do the same for single tools with "anthropic/alwaysLoad": true in _meta. Startup then waits for that server, up to the 5-second connect timeout, unless it has a valid cache entry. Other servers connect in the background; MCP_CONNECTION_NONBLOCKING=0 makes startup wait for all.
Plugin servers
Plugins can bundle servers in .mcp.json at the plugin root or inline under mcpServers in plugin.json:
{
"mcpServers": {
"ledger": {
"command": "${CLAUDE_PLUGIN_ROOT}/bin/ledger-mcp",
"args": ["--data", "${CLAUDE_PLUGIN_DATA}/ledger.db"],
"env": { "LEDGER_REGION": "${LEDGER_REGION}" }
}
}
}
${CLAUDE_PLUGIN_ROOT}, ${CLAUDE_PLUGIN_DATA} and ${CLAUDE_PROJECT_DIR} are substituted in stdio command, args and env, and in remote url, headers and headersHelper. Servers start when the plugin is enabled and stop when it is disabled, applying at the next reload; /cd reconciles them from v2.1.246. Reloads keep unchanged connections. In cloud sessions a call to a not-yet-connected plugin server starts it on demand. You add and remove these by installing the plugin, though /mcp can toggle them off. claude mcp get redacts their env values.
Tool names include the plugin: mcp__plugin_<plugin>_<server>__<tool>, for example mcp__plugin_finance-kit_ledger__post_entry. Use that full form in permission rules, a skill's allowed-tools, subagent tools and hook matchers; mcp__ledger__.* will never match. The server itself is plugin:<plugin>:<server> wherever a server name is expected, such as an mcp_tool hook's server field. More in plugin components.
Claude Code as an MCP server
claude mcp serve
This exposes Claude Code's own tools over stdio. It prints nothing and waits for a client, which is normal. To use it from the Claude Desktop chat app, add to claude_desktop_config.json:
{
"mcpServers": {
"claude-code": {
"type": "stdio",
"command": "/Users/you/.local/bin/claude",
"args": ["mcp", "serve"],
"env": {}
}
}
}
Use the full path from which claude if it is not on the client's PATH, otherwise you get spawn claude ENOENT. The client is responsible for confirming individual tool calls.
Managed configuration
Organisations can deploy a fixed set with managed-mcp.json, provide servers to everyone with managedMcpServers, and restrict with allowedMcpServers and deniedMcpServers. See managed MCP.