Where a Claude Code MCP server can live
Highest precedence first
- Local (the default)
~/.claude.json, this project; only you - Project
.mcp.jsonin the repo; the whole team - User
~/.claude.jsontop level; you, all projects - Plugin-providedservers bundled with installed plugins
- Claude.ai connectorsloaded when signed in to that account
Choosing a scope
claude mcp add registers a server, and its --scope flag decides where the definition is written. Without a flag the server goes to local scope: private to you and active only in the current project. User scope is still private but follows you into every project. Project scope writes to .mcp.json at the repository root, which you commit, so everyone who clones the project gets the same servers.
| Scope | Stored in | Who gets it | Good for |
|---|---|---|---|
local | ~/.claude.json, under this project | Only you, only here | Trying a server out; a personal key for one repo |
project | .mcp.json at the project root | Everyone who clones the repo | Tools the whole team relies on |
user | ~/.claude.json, top-level mcpServers | Only you, every project | Personal utilities you use everywhere |
Which scope does this server need?
- The whole team, this repo
--scope projectcommit.mcp.json - Just me, every project
--scope userpersonal utilities - Just me, trying it heredefault
localno flag needed
# Team-shared: writes .mcp.json in the project root
claude mcp add --scope project --transport http docs https://code.claude.com/docs/mcp
# Personal, every project: writes ~/.claude.json (top level)
claude mcp add --scope user --transport http sentry https://mcp.sentry.dev/mcp
# Local stdio server: everything after -- is the command to run
claude mcp add playwright -- npx -y @playwright/mcp@latest
claude mcp list # status of every configured serverProject scope has a safety step. The first time Claude Code sees a server from .mcp.json, it asks you to approve it, so a repository you clone cannot launch processes on your machine without consent. If you rejected one by mistake, claude mcp reset-project-choices clears those decisions. Non-interactive runs such as claude -p and the Agent SDK load project servers without the prompt, which is one reason to review .mcp.json changes like code.
Sharing a server without sharing the secret
A shared .mcp.json must never contain a token. Claude Code expands environment variables in it: ${VAR} takes the value from each person’s environment, and ${VAR:-default} supplies a fallback. Expansion works in command, args, env, url and headers. Each teammate sets their own token locally; the committed file holds only the placeholder.
Committing configuration, not credentials
Token in the repo
{
"mcpServers": {
"jira": {
"type": "http",
"url": "https://mcp.example.com",
"headers": {
"Authorization":
"Bearer 7f3Kx9…real token"
}
}
}
}Placeholder per person
{
"mcpServers": {
"jira": {
"type": "http",
"url": "https://mcp.example.com",
"headers": {
"Authorization":
"Bearer ${JIRA_TOKEN}"
}
}
}
}Two details prevent surprises. If a variable is unset and has no default, Claude Code warns in claude mcp list and /mcp and leaves the literal ${VAR} in place, so the server fails to authenticate rather than silently using nothing. And for remote servers, Claude Code deliberately reads certain credential variables, such as ANTHROPIC_API_KEY, as empty inside url and headers, so a shared file cannot be used to send your Anthropic key to a third-party server.
The same servers in the Agent SDK
Agents built with the Agent SDK use MCP the same way. Pass servers in the mcpServers option (mcp_servers in Python), or let the SDK pick up the project’s .mcp.json, which it loads when the project setting source is enabled — as it is by default. Tools appear to Claude as mcp__<server>__<tool>, and must be permitted: list them, or a server wildcard such as mcp__github__*, in allowedTools. The docs recommend this over broad permission modes, since a wildcard grants exactly one server.
import asyncio, os
from claude_agent_sdk import query, ClaudeAgentOptions, SystemMessage
options = ClaudeAgentOptions(
mcp_servers={
"github": {
"type": "http",
"url": "https://api.githubcopilot.com/mcp/",
"headers": {"Authorization": f"Bearer {os.environ['GITHUB_TOKEN']}"},
}
},
allowed_tools=["mcp__github__list_issues"], # permit only what the task needs
)
async def main():
async for message in query(prompt="List the 3 newest issues", options=options):
if isinstance(message, SystemMessage) and message.subtype == "init":
for server in message.data.get("mcp_servers", []):
if server.get("status") in ("failed", "needs-auth"):
print("Unavailable:", server["name"])
asyncio.run(main())Check the connection status. The init message reports each server as pending, connected, failed, needs-auth or disabled. The docs warn that when a server is unavailable Claude can fall back to built-in tools, so a failed connection does not stop the run — it quietly changes how the answer was produced.
Build, reuse, or expose as a resource
For a standard system — GitHub, Sentry, a Postgres database, a browser — an existing server is usually the right choice: someone else maintains it, and the Anthropic Directory and the MCP servers repository list many. Build your own when the workflow is specific to your team: an internal deploy pipeline, a house data model, a tool that combines several internal APIs into one task. Whichever you use, remember that Claude also has built-in tools. If an MCP tool’s description does not say clearly what it offers beyond them — for instance, that a code-search tool searches every repository in the organisation, not just the checkout — Claude may reach for Grep instead.
Not everything should be a tool. MCP servers can also expose resources: readable content identified by a URI, such as a database schema, a documentation index or a list of open incidents. The MCP specification calls resources application-driven, while tools are model-controlled — the application or user decides to include a resource, where a tool is something the model chooses to call. In Claude Code you can reference a server’s resources with @ mentions. Offering a catalogue as a resource lets the agent start from a map instead of spending tool calls exploring.
| Need | Better as | Why |
|---|---|---|
| Look up one customer, create a ticket | Tool | An action with inputs that the model decides to take |
| The database schema, the docs hierarchy | Resource | Reference context; avoids exploratory calls |
| Standard SaaS integration | Existing server | Maintained elsewhere; no code to own |
| Team-specific multi-step workflow | Custom server | No off-the-shelf server fits the job |
Traps the wrong answers are built from
| Tempting but wrong | Do this instead |
|---|---|
Committing a token inside .mcp.json | Use ${VAR} expansion and let each developer set the variable. |
| Putting team tooling in one person’s user or local scope | Define shared servers at project scope in .mcp.json. |
Adding personal or experimental servers to .mcp.json | Use user scope (all projects) or local scope (this project only). |
| Building a custom server for a standard integration | Use a maintained existing server; build only for team-specific workflows. |
| Making the agent explore a catalogue with many tool calls | Expose the catalogue as an MCP resource it can start from. |
You should now be able to
- Choose local, project or user scope for an MCP server based on who needs it.
- Share a server in
.mcp.jsonusing${VAR}and${VAR:-default}instead of secrets. - Connect MCP servers to an Agent SDK agent, permit their tools and check connection status.
- Decide between an existing server and a custom one for a given integration.
- Expose reference content as MCP resources to cut exploratory tool calls.