mcp · claude-code · ai-governance · revops
Claude Code MCP Scopes: Local vs Project vs User (Complete Reference)
A full reference for how Claude Code decides which MCP servers to load: the four config layers, where each lives on disk, precedence order, why transport is a separate axis, and how to keep secrets out of a committed .mcp.json.
On this page
This is the reference half of a pair. The other post is the opinionated one about keeping client data apart. This one just explains the machinery, because most of what is written about MCP scopes online collapses four layers into three and gets the definition of local scope backwards.
If you only remember one thing: local scope is not "your machine." It is one folder.
There are four layers, not three.
Claude Code documents three scopes. In practice a fourth thing can put tools in front of the model, so plan around four.
Local scope. The default. A local-scoped server loads only in the project where you added it and stays private to you. It is stored in ~/.claude.json under that project's exact path. Add a server in ~/clients/acme and it does not exist in ~/clients/northwind.
claude mcp add --transport http stripe https://mcp.stripe.com
claude mcp add --scope local stripe --transport http https://mcp.stripe.com
Those two lines do the same thing. The resulting shape in ~/.claude.json:
{
"projects": {
"/Users/you/clients/acme": {
"mcpServers": {
"stripe": { "type": "http", "url": "https://mcp.stripe.com" }
}
}
}
}
Project scope. A .mcp.json file at the project root. Built to be checked into version control so everyone who clones the repo gets the same servers. It loads in that folder and, importantly, in every folder beneath it.
claude mcp add --transport http paypal --scope project https://mcp.paypal.com/mcp
User scope. Stored in ~/.claude.json and available across all projects on that machine, private to your account. It does not sync between machines. Two laptops means two setups.
claude mcp add --transport http hubspot --scope user https://mcp.hubspot.com/anthropic
claude.ai connectors. Not a Claude Code scope at all, which is why most write-ups miss it. Anything connected at claude.ai is automatically available in Claude Code, in every folder. Run /mcp in a session and they appear flagged as coming from claude.ai.
One catch worth knowing: connectors are only fetched when your active authentication is a claude.ai subscription login. Set ANTHROPIC_API_KEY, or use a third-party provider like Bedrock, and they silently do not load. If a connector is missing, run /status and check which auth method is actually live.
To turn them off entirely, set disableClaudeAiConnectors to true in any settings scope, or run with ENABLE_CLAUDEAI_MCP_SERVERS=false.
There is also a fifth source, plugin-bundled servers, which ship inside a plugin and register under a scoped name like plugin:my-plugin:database-tools. Worth knowing they exist so you are not surprised by tools you cannot find in any config file.
Precedence: which definition wins.
When the same server is defined more than once, Claude Code connects once and uses the definition from the highest-precedence source. The entire entry from that source is used. Fields are not merged across scopes, so you cannot define a URL at one level and headers at another.
- Local scope
- Project scope
- User scope
- Plugin-provided servers
- claude.ai connectors
The three scopes match duplicates by name. Plugins and connectors match by endpoint, so a plugin pointing at the same URL as a configured server is treated as a duplicate rather than a second server.
Two consequences that catch people out.
Precedence runs opposite to blast radius. The widest layer, connectors, sits last. It never overrides anything, so it never announces itself. It just adds.
And a narrow definition silently shadows a wide one. A crm at local scope beats a crm at user scope with no warning. That is usually what you want. It is occasionally the reason you spent an hour wondering why your edits to the wrong file did nothing.
claude mcp list shows what actually resolved. claude mcp get <name> shows which scope a single server came from. When something is loading that you cannot account for, start there.
Transport is a separate axis from scope.
This is the conflation that makes the whole topic feel murkier than it is. Scope answers which folders a server loads in. Transport answers how Claude Code talks to it. They are independent. Any transport can sit at any scope.
stdio. Runs as a local process on your machine. Good for tools needing direct system access or custom scripts. Note the -- separator, which divides Claude Code's own flags from the command being run:
claude mcp add --env AIRTABLE_API_KEY=KEY --transport stdio airtable -- npx -y airtable-mcp-server
Without the --, Claude Code tries to parse the server's flags as its own.
HTTP. The recommended transport for remote servers, and the most widely supported for cloud services. In JSON config the type field accepts streamable-http as an alias for http, so configs copied from vendor docs generally work unmodified.
SSE. Deprecated. Use HTTP where available. If you are getting connection errors with --transport sse, switching to --transport http is the first thing to try.
WebSocket. For remote servers that push events to Claude unprompted. Configure it through claude mcp add-json, since the --transport flag does not accept ws. Authentication is header-only, so no OAuth.
One failure mode worth memorizing: a JSON entry with a url but no type is a configuration error. Claude Code reads a typeless entry as stdio, skips the server, and tells you to add "type": "http".
Keeping secrets out of a committed .mcp.json.
Project scope is designed to be committed, which means anything in its env block lands in git history. Claude Code supports variable expansion so you do not have to put it there.
{
"mcpServers": {
"api-server": {
"type": "http",
"url": "${API_BASE_URL:-https://api.example.com}/mcp",
"headers": { "Authorization": "Bearer ${API_KEY}" }
}
}
}
${VAR} expands to the variable's value. ${VAR:-default} falls back when it is unset. Expansion works in command, args, env, url, and headers.
If a referenced variable is unset with no default, the config still loads. Claude Code warns in claude mcp list and passes the literal ${VAR} text through, which usually surfaces as a confusing auth failure rather than an obvious config error. Set a default so the failure is legible.
For anything genuinely per-client, local scope is the better answer than clever expansion. Nothing to commit means nothing to leak.
Project-scoped servers need approval before they run.
Because .mcp.json arrives with cloned code, Claude Code prompts for approval before using project-scoped servers. Until you approve, they show in claude mcp list as pending.
some-server: npx -y some-package - Pending approval (run `claude` to approve)
Run claude interactively to review and approve. To reset your choices, use claude mcp reset-project-choices.
This is a real safety net and worth understanding rather than clicking through. A repo cannot approve its own servers: settings committed into a project's .claude/settings.json are ignored until you trust the workspace. Approvals from your own user settings, managed settings, or --settings still apply.
Context cost and tool search.
Every loaded server used to cost context whether you used it or not. Tool search is now on by default and defers tool definitions until Claude needs them, so only names and server instructions load at session start.
That takes the sting out of running many servers, but it does not make scope irrelevant. Deferred tools are still discoverable, which means a server loaded in the wrong folder is still a server the model can reach for.
If a server's tools genuinely need to be visible every turn, set alwaysLoad: true on it. Use it sparingly, since each upfront tool spends context you would otherwise have for the conversation.
Which scope should you use?
Local for anything client-specific, anything with credentials you do not want committed, and anything you are testing. It is the default, it is the narrowest, and it is the only one that does not inherit.
Project for genuine team tooling that belongs to a repo and that every contributor should get on clone. Use variable expansion for anything sensitive.
User for personal utilities that are safe everywhere. Your own notes system, a scratch filesystem server. If you would not be comfortable with it firing while you work on a client, it does not belong here.
Connectors for the chat surfaces, understanding that they reach into Claude Code too.
The short version for anyone running more than one client: default to local, audit user scope and connectors regularly, and keep client folders out from under any parent that has a .mcp.json. The reasoning behind that last one, and the test that proves it, is in the companion post.
- Jaron
Frequently asked questions
What is the difference between local, project, and user scope in Claude Code?
Local scope is the default and loads a server only in the single folder where you added it, stored in ~/.claude.json under that project's exact path. Project scope stores servers in a .mcp.json file at the project root, is designed to be committed to version control, and loads in that folder and every folder beneath it. User scope stores servers in ~/.claude.json and loads them in every project on that machine, but does not sync between machines.
What is the MCP scope precedence order in Claude Code?
Local scope, then project scope, then user scope, then plugin-provided servers, then claude.ai connectors. When the same server is defined in more than one place Claude Code uses the entire entry from the highest-precedence source and does not merge fields across scopes. The three scopes match duplicates by name, while plugins and connectors match by endpoint.
Is MCP transport the same thing as MCP scope?
No. Scope determines which folders a server loads in. Transport determines how Claude Code communicates with it: stdio for local processes, HTTP for remote servers, SSE which is deprecated, and WebSocket for servers that push events. The two are independent axes, so any transport can be configured at any scope.
How do I keep API keys out of a committed .mcp.json file?
Use environment variable expansion. Claude Code expands ${VAR} and ${VAR:-default} inside the command, args, env, url, and headers fields, so you can commit the config while keeping values in your shell. If a variable is unset with no default the config still loads but passes the literal text through, which usually appears as an auth failure, so set a default. For per-client credentials, local scope avoids the problem entirely because there is nothing to commit.
Why is my MCP server showing as pending approval?
Project-scoped servers defined in .mcp.json require explicit approval before Claude Code will use them, because that file arrives with cloned code. Run claude interactively to review and approve them. Use claude mcp reset-project-choices to reset your approval decisions. A repository cannot approve its own servers through committed settings until you trust the workspace.
Why don't my claude.ai connectors appear in Claude Code?
Connectors are only fetched when your active authentication method is a claude.ai subscription login. If ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN, an apiKeyHelper, or a third-party provider such as Bedrock is active, connectors are not loaded even if you previously logged in. Run /status to confirm which authentication method is active, then run /login to select your claude.ai account.