aiagent.
aiagent5 min read

MCP Config Scopes in Claude Code: User vs Project vs Local

How .mcp.json scope resolution works in Claude Code, where to put env vars, and a verification flow that catches broken servers before they break your session.

MCP Config Scopes in Claude Code: User vs Project vs Local

The Model Context Protocol turns Claude Code from a chat box into a tool-calling agent that can read your filesystem, query Postgres, hit a private API, or drive a browser. The catch: where you declare an MCP server determines who can run it, what gets committed to git, and which environment variables it sees at boot. Get the scope wrong and you either leak credentials into a public repo or spend twenty minutes wondering why a tool isn't showing up in /mcp.

Three scopes exist. They resolve in a fixed order, they overlap on purpose, and the rules for env var injection differ at each layer.

The three scopes, ranked by precedence

Claude Code loads MCP servers from three locations and merges them with local > project > user precedence. A server defined at local scope wins over the same name at project, which wins over user.

ScopeLocationCommitted to git?Typical use
user~/.claude.json (global)No (lives in your home dir)Personal tools you want everywhere: a notes server, a personal GitHub PAT
project.mcp.json at the repo rootYes \u2014 checked inTeam-shared servers: project-specific database, internal API client
local.claude/settings.local.json in the repoNo (gitignored by default)Per-developer overrides: your local Postgres password, a feature-flagged dev server

Run claude mcp list to see which scope each server resolved from. The CLI prints the source path next to the server name, which is the fastest way to debug "why is the wrong version of this server loading."

Adding servers without hand-editing JSON

The CLI handles the scope flag for you. These three commands write to the three locations above:

# user scope \u2014 available in every project
claude mcp add --scope user notes \
  -- npx -y @modelcontextprotocol/server-filesystem ~/notes

# project scope \u2014 committed for the team
claude mcp add --scope project postgres \
  -- npx -y @modelcontextprotocol/server-postgres \
  postgresql://localhost/myapp

# local scope \u2014 your machine only
claude mcp add --scope local postgres-dev \
  -- npx -y @modelcontextprotocol/server-postgres \
  postgresql://localhost/myapp_dev

The -- separator is mandatory. Everything after it is the launch command Claude Code runs to spawn the server's stdio process. Forget the -- and the CLI will try to interpret your server's flags as its own.

If you prefer hand-editing, the schema is the same across all three files:

{
  "mcpServers": {
    "postgres": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-postgres", "postgresql://localhost/myapp"],
      "env": {
        "PGSSLMODE": "require"
      }
    }
  }
}

Where to put credentials

This is the part that bites people. The .mcp.json file at project scope gets committed, so anything in its env block ends up in git history. Two patterns avoid leaking secrets.

Pattern 1: shell expansion in args. Claude Code expands ${VAR} references inside args and env values at server launch. Set the variable in your shell before running claude:

{
  "mcpServers": {
    "github": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"],
      "env": {
        "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_TOKEN}"
      }
    }
  }
}

The committed file references ${GITHUB_TOKEN}. The actual token lives in your ~/.zshrc or a .envrc loaded by direnv. Teammates set their own values; nothing sensitive ships to git.

Pattern 2: local scope override. Define the server at project scope with a placeholder, then override at local scope with the real credential. The local file is gitignored, so the override never leaves your machine.

A weaker third option is to define the whole server at user scope, but that breaks reproducibility \u2014 a new contributor cloning the repo won't know the server exists until someone tells them, and your README has to document something that should have been declarative.

Scope resolution gotchas

A few behaviors that aren't obvious from the docs:

  1. No partial merging within a server. If postgres exists at both project and local scope, local scope replaces the entire definition. You can't override just the env block while keeping project-scope args. Copy the whole entry when you override.
  2. User scope loads even when you cd into a project. This is a feature for tools like a notes server that should follow you everywhere. It's a footgun if you defined a server at user scope months ago and forgot \u2014 claude mcp list is the cure.
  3. Project scope requires claude to be launched from the repo root or a subdirectory. Run claude from /tmp and your project-scope servers won't load even if .mcp.json exists somewhere on disk.
  4. Local scope lives in .claude/settings.local.json, not .mcp.local.json. Easy to miss because the project-scope filename suggests a parallel naming convention that doesn't exist.

Verification flow

A working install verification catches three failure modes: the server didn't start, the server started but tools didn't register, or tools registered but they error on first call. Run this sequence after adding a new server:

# 1. Did the server appear in the list with the right scope?
claude mcp list

# 2. Did the launch command actually work?
claude mcp test postgres
# Prints stdout/stderr from the spawned process. Look for
# "Server started" or framework-specific ready messages.

# 3. Inside a Claude Code session, did tools register?
# Type /mcp and inspect the tool list for that server.

# 4. Smoke-test one tool with a low-impact call.
# In Claude Code: "Use postgres.query to run SELECT 1"

Step 2 is where most install failures surface. A common one: the package isn't published yet, so npx -y @some-org/some-server hangs forever waiting for a registry entry that doesn't exist. Set a 30-second timeout on claude mcp test and treat anything longer as a failed install.

When to choose which scope

A practical decision rule:

  • User scope when the server is personal and stateless across projects (notes, your own GitHub account, a sandbox filesystem)
  • Project scope when the server is part of the project's contract \u2014 a teammate cloning the repo and running claude should get it without setup beyond their own credentials
  • Local scope when you need to override a project-scope server temporarily, run a development variant, or test a server before committing its config to the team

Mix them deliberately. A typical project ships postgres at project scope pointing at production read-replica config, and individual developers override it at local scope to point at their dev database. The team config stays declarative; nobody's local credentials leak.

References: