Blog · October 20, 2026 · 8 min read · by Vilva Athiban P B

Claude Code .mcp.json Explained: Where Project-Scoped MCP Servers Are Read From, What Happens in Subfolders and Monorepos, and Why a Committed Server Shows Pending Approval

The most common MCP question that is not really about MCP goes like this. You added a server to the repo's .mcp.json, it works when you start Claude Code at the root, and then a teammate opens a terminal in packages/api, runs claude, and the server is nowhere to be found. Or the opposite: the server appears, but with a “pending approval” label, in a headless run that nobody can approve. Or .mcp.json is there, committed, correct, and /mcp lists a different definition of the same server from somewhere else entirely.

All three are the same subject: what project scope means, where Claude Code reads .mcp.json from, and how that interacts with subfolders, monorepos and the approval step. This post is only about that. For the broader picture of the three scopes, secrets and a sane team setup, start with our MCP servers guide; for keeping big servers from eating your context, see MCP context usage.

What .mcp.json is, in one paragraph

Claude Code stores MCP servers in three scopes. Local scope is the default and lives in ~/.claude.json, keyed to the current project, so it is yours alone. User scope also lives in ~/.claude.json but applies to every project you open. Project scope is the one that travels with the code: a .mcp.json file at the project's root directory, which the docs tell you to check into version control so everyone on the team gets the same tools. The file is a plain JSON object with an mcpServers map:

{
  "mcpServers": {
    "shared-server": {
      "type": "http",
      "url": "https://example.com/mcp"
    },
    "db": {
      "command": "npx",
      "args": ["-y", "@example/db-mcp"],
      "env": { "DATABASE_URL": "${DATABASE_URL}" }
    }
  }
}

You do not have to write it by hand. The same CLI that adds local servers writes to .mcp.json when you pass the scope:

claude mcp add --transport http shared-server --scope project https://example.com/mcp

Subfolders: the root is where you start Claude, not where the repo is

Here is the part that trips up monorepos. “Project root” in the project-scope definition is the directory Claude Code treats as the project for this session, which is the directory you launched it from. It is not the git root, and in current versions Claude Code does not walk up the tree looking for a .mcp.json in a parent folder the way a build tool might look for a config file. Start claude in packages/api and the project is packages/api; a .mcp.json two levels up is simply not in play.

That is also why the local-scope entries in ~/.claude.json are per project directory: a server you added with the default scope while sitting at the repo root is attached to the root path, and opening a subfolder gives you a different project with its own, empty, local list. If the behaviour feels inconsistent, check what claude mcp list says from each directory before changing anything; it reports which servers are loaded and warns about unresolved variables, and /mcp inside a session shows ${VAR} references by name (v2.1.268 or later).

There are four reasonable ways to set up a monorepo, in rough order of preference:

  1. Start Claude at the root and widen access, not the other way round. Launch from the repo root, where the shared .mcp.json lives, and point Claude at the package you are working in. Claude can read and edit anywhere under the root, so most of the time nothing more is needed; if you also want a directory outside the root in play, --add-dir (or permissions.additionalDirectories in settings) adds it without changing which .mcp.json is read.
  2. A .mcp.json per package. If people genuinely work from inside packages/web and packages/api and the two need different servers, give each its own file. The cost is duplication of anything shared; keep the shared entries identical and let reviewers notice drift.
  3. Pass the file explicitly with --mcp-config. claude --mcp-config ../../.mcp.json loads a specific file regardless of where you started. Combined with --strict-mcp-config, Claude Code uses only the servers in the files you passed and ignores every other scope, which makes it the right tool for CI and scripted runs where you want no surprises.
  4. Move the shared servers to user scope. A server everyone on the team uses in every repo, such as a company docs server, arguably does not belong in any single project. User scope follows you into every directory. It is the least discoverable option for new teammates, which is the trade-off.

The same server in two scopes

When a server name appears in more than one place, Claude Code connects once and uses the highest-precedence definition: local beats project, project beats user, user beats plugin-provided servers, which beat claude.ai connectors. Managed managedMcpServers entries from an administrator outrank all of them (v2.1.259 or later). The entire entry is taken from the winning source; fields are not merged. So if your .mcp.json says url: https://staging... and a stale local entry with the same name says production, you are talking to production and the committed file is being ignored. The fix is claude mcp remove <name> for the local one, after checking claude mcp get <name> to see which definition is active.

Environment variables, and the ones that are deliberately empty

Because .mcp.json is shared, anything secret or machine-specific has to come from the environment. The file supports ${VAR} and ${VAR:-default} in command, args, env, url and headers. An unset variable with no default does not stop the file loading: Claude Code warns about the missing variable in claude mcp list and uses the literal ${VAR} text, which is why a server can appear “configured” and still fail to connect.

Two details are specific to the shared file. First, a handful of credential variables, including ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN and AWS_BEARER_TOKEN_BEDROCK, read as empty when referenced in a remote server's url or headers, and a :-default on them is ignored. That is on purpose: a committed .mcp.json must not be able to send your Anthropic credentials to a server somebody added in a pull request. Second, CLAUDE_PROJECT_DIR in a project-scoped file needs a default, as in ${CLAUDE_PROJECT_DIR:-.}, when used in command or args. Plugin configs can use it bare; project files cannot.

The approval step, and why headless runs skip it

A .mcp.json is code that someone else may have committed, and an MCP server can run commands on your machine, so Claude Code prompts for approval in interactive sessions before using project-scoped servers. Your answer is remembered per project; claude mcp reset-project-choices clears it so you are asked again, which is the fix when a server you rejected weeks ago is now one you want.

The prompt cannot appear where there is nobody to answer it. In claude -p runs, Agent SDK sessions and cloud sessions, Claude Code loads project-scoped servers without asking. The same applies to a session started in bypassPermissions mode when skipDangerousModePermissionPrompt is set. If that is not what you want for a particular server, you have three documented options:

  • Add it to disabledMcpjsonServers in a settings file; that blocks it in every permission mode, and an entry in any settings file is enough.
  • Exclude project settings with --setting-sources, or the SDK's settingSources option.
  • Start with --strict-mcp-config and pass only the servers you trust via --mcp-config.

Going the other way, teams that want no prompt at all can set enableAllProjectMcpServers: true, or list the approved names in enabledMcpjsonServers. Note the trust rule: those two keys, when committed in a project's .claude/settings.json, are ignored in a folder you have not marked as trusted, while disabledMcpjsonServers still applies. Approvals from ~/.claude/settings.json, managed settings and --settings still count in an untrusted folder; an untracked .claude/settings.local.json only counts in a trusted one (v2.1.207 or later). The claude mcp list labels tell you which case you are in: Pending approval (run claude to approve) versus Rejected (see disabledMcpjsonServers in settings).

One easy confusion: disabledMcpServers and enabledMcpServers, without the json, are different keys. They are per-project toggles stored in ~/.claude.json for turning servers on and off locally, not the approval list for the shared file.

A checklist for the next time a server goes missing

  1. Which directory did you start claude in? That is the project. Run claude mcp list from there.
  2. Is the server labelled pending approval, rejected, or listed with a missing-variable warning? Each has a different fix above.
  3. Does claude mcp get <name> show the definition you expect, or a local or user-scope copy with the same name?
  4. Is this a headless or CI run? Then there was no prompt, and the server either loaded silently or was blocked by disabledMcpjsonServers.
  5. Still stuck? claude --mcp-config ./.mcp.json --strict-mcp-config removes every other variable from the experiment.

Most of the time the answer is step one. The repo is not the project; the folder you are standing in is.

Keep reading

Desk Exercises for Programmers: 15 Moves to Do While Your AI Agent Codes

7 min read

Exercise While Coding: Turn Claude Code Wait Time Into Micro-Workouts

7 min read

Claude Code Notch Notifications on Mac: See When Claude Is Done Without Watching the Terminal

7 min read

Claude Code Tips and Tricks: 12 Ways to Use Claude Code Effectively

8 min read

Claude Code Multiple Sessions: How to Run Agents in Parallel Without Losing Track

6 min read

A Claude Code Workflow That Doesn't Involve Watching the Terminal

5 min read

Claude Code Hooks: A Practical Guide to Automating Your Agent Workflow

7 min read

Claude Code Auto Mode: Fewer Permission Prompts Without Living Dangerously

7 min read

Claude Code Subagents: How to Delegate Work to Specialized Agents

7 min read

Claude Code Context Management: Treat the Context Window Like a Budget

6 min read

Per-Subagent Model Selection: Route the Grunt Work Down, Keep the Judgment Up Top

7 min read

Skills and Plugins: How to Teach Claude Code Your Way of Working

7 min read

Claude Code Background Tasks: Run Long Commands Without Blocking Your Session

6 min read

Codex CLI Notifications: How to Get a Ding When Codex Is Done or Needs Input

7 min read

Cursor Notification When Done: Every Way to Get Notified When Cursor Finishes

6 min read

Claude Code Notifications: How to Get Notified When Claude Code Finishes or Needs Your Input

6 min read

Want to Be Notified When Claude Responds? How Claude Notifications Work on Web, Desktop, and Mobile

5 min read

Claude Code Notification Scripts: Copy-Paste Recipes for Every Platform

6 min read

Get a Ding the Moment Codex Needs Your Response

6 min read

Get Notified the Moment Claude Code Is Waiting for Your Input

6 min read

“Notifications Are Turned Off for Claude” — Here Is the Fix

6 min read

Codex Sound When Done: Make Codex CLI Play a Sound When It Finishes

6 min read

terminal-notifier + Claude Code: Native macOS Alerts When Your Agent Finishes

6 min read

Gemini CLI Notifications: How to Get a Sound or Alert When Gemini Finishes

6 min read

Get Claude Code Notifications on Your Phone

6 min read

Claude Code Remote Control, Explained

7 min read

preferredNotifChannel: Claude Code's Built-In Notification Setting

6 min read

Claude Code Notifications in tmux and Over SSH

7 min read

Claude Code Effort Levels: Why Your Setting Keeps Getting Ignored

8 min read

Codex vs Claude Code Notifications: How Each One Tells You It Is Done

8 min read

Claude Code Notifications Not Working: A Diagnostic Checklist

10 min read

Claude Code Notifications Inside Your Editor's Terminal

7 min read

CLAUDE_CODE_DISABLE_BACKGROUND_TASKS Explained

7 min read

CLAUDE_CODE_DISABLE_BG_SHELL_PRESSURE_REAP Explained

8 min read

Cursor Alerts Explained: Every Alert Cursor Raises, and How to Control Them

7 min read

Claude Code Environment Variables: The Groups That Actually Matter

10 min read

Claude Code Notifications on WSL and Windows

9 min read

Claude Code Checkpoints and /rewind

8 min read

Claude Remote Control: How It Works, Turning It Off, and Auto Mode

7 min read

Claude Code /loop: Poll a Deploy, Babysit a PR, and Get Told When It's Done

8 min read

Setting Up NotchFit With OpenAI Codex CLI: Hooks, $notchfit-plan, and the Daily Flow

7 min read

Claude Mods Explained: What the New Plugin Layer Can Do, and How to Turn On 'You Should Know'

9 min read

NotchFit vs Mac Break Reminder Apps: A Clock or a Signal From Your Coding Agent?

8 min read

Claude Code Headless Mode: Running claude -p in Scripts and CI, and the Three Signals That It Finished

10 min read

Building a Movement Habit at a Desk: Use the Agent Run as the Cue, One Set as the Unit, and Streaks Carefully

8 min read

Claude Code MCP Servers Explained: Adding Servers, Choosing a Scope, Handling Secrets, and Why Long Tool Calls Go to the Background

10 min read

/notchfit:plan Explained: How Claude Code or Codex Writes a Weekly Workout Plan the Notch Can Actually Run

8 min read

Claude Code Spinner Verbs Explained: What “Pondering” and “Brewing” Mean, How to Change Them, and Why You Should Stop Watching the Spinner

7 min read

Why NotchFit's Done and Permission Alerts Wait Until Your Set Ends, When That Costs You, and How to Turn the Workout Off

7 min read

How to Resume a Claude Code Session: --continue vs --resume, Naming Sessions, the Session Picker, Branching, and What Comes Back

9 min read

NotchFit on a Mac Without a Notch: How Pill Mode Works on Mac mini, iMac, Mac Studio and a MacBook With the Lid Closed

6 min read

How to Run a Claude Code Session in the Background: /background, claude --bg, Agent View, and Getting Back to It

9 min read

What a Notch App Can See: NotchFit's Local-First Design, What the Claude Code and Codex Hooks Send, and What Stays on Your Mac

7 min read

Why Codex CLI Asks for Permission Every Time, and How to Tune the Sandbox and Approval Policy Without Going Full Access

8 min read

What to Do While Claude Code or Codex Works: A Simple Protocol for the Two-to-Twenty-Minute Gap in Every Agent Run

7 min read

Is It Possible to Get a Notification When Claude Code or Codex Hits a Reset? Yes for Claude Code: the quota_auto_resume Hooks, Explained

8 min read

What a MacBook Notch App Can Actually Do: The Five Kinds of Notch Utility, Their Trade-offs, and Where NotchFit Fits

7 min read

How to Make MCP Servers Not Take Up as Much Context in Claude Code: Tool Search, alwaysLoad, Scopes and Output Caps

8 min read

The notchfit Command Line: Drive Your Workout, Read Status as JSON, Validate Plans and Test the Notch Without Leaving the Terminal

7 min read

How to Audit MCP Tool Calls Made by Claude Code: A Five-Minute Hook Audit Log, OpenTelemetry Tool Events, and the Transcript for Investigations

9 min read

Exercise Snacks: What the Research Actually Says About One-Minute Vigorous Breaks, What It Does Not, and How NotchFit's Agent-Run Sets Compare

8 min read

How Long Until a Claude Code MCP Tool Call Times Out? The Four Timers (Startup, Two-Minute Backgrounding, Idle, Wall-Clock), Their Defaults, and Which One to Change

8 min read

Sitting Too Long as a Developer: The Posture Problems Coding Agents Made Worse, What the Research Says About Breaking Up Sitting, and How NotchFit Turns an Agent Run Into the Break

8 min read

Standing Desk Exercises for Developers: Why Standing Still Is Not the Goal, Eight Movements That Fit a Thirty-Second Agent Run, and How NotchFit Cues Them From the Notch

8 min read