Room 42, home

Share MCP servers with your team

Check MCP server config into the repo so everyone gets the same tools, and keep secrets out of the file with environment variable expansion.

MCPadvanced6 min readUpdated 13 Jun 2026

In short

  1. 01Adding MCP servers at project scope writes them to a committed file, so every teammate who clones the repo gets the same tools.
  2. 02Local and user scopes stay private to you, while project scope is the only one that travels with version control.
  3. 03Reference secrets with environment variables like ${VAR}, never literal keys, so credentials stay on each machine and out of git.
  4. 04Each teammate approves the shared servers on first use and sets their own variable values, so onboarding is one pull and a few exports.
Contents · 9 sections
  1. 1What are the MCP scopes, and which one is shared with the team?
  2. 2How do I add a server at project scope?
  3. 3What goes into .mcp.json?
  4. 4Why does Claude Code ask teammates to approve shared servers?
  5. 5How do I keep secrets out of the committed file?
  6. 6Where teammates set the values
  7. 7Inspect and remove shared servers
  8. 8Precedence when scopes collide
  9. 9A checklist for a clean shared setup

Add your servers at project scopeA configuration level in Claude Code that stores servers in a .mcp.json file at the project root, designed to be committed to version control and shared with the whole team. so Claude Code writes them into a .mcp.json file at the repository root, then commit that file. Every teammate who clones the repo gets the same set of tools. One file, one shared toolset. Keep API keys and tokens out of it by referencing environment variables instead of pasting the literal values.

If you have not connected a server yet, read MCP fundamentals and MCP configuration first. This guide is about the team part: one shared definition, no leaked secrets.

What are the MCP scopes, and which one is shared with the team?

When you run claude mcp add, Claude Code stores the server at one of three scopes. The scope decides where the config lives and who else sees it.

ScopeLoads inShared with teamStored in
LocalCurrent project onlyNo~/.claude.json
ProjectCurrent project onlyYes, via version control.mcp.json in project root
UserAll your projectsNo~/.claude.json

Local scope is the default. It stays private to you.Common gotchaBecause local is the default, running claude mcp add without --scope project writes the server to your private ~/.claude.json, so nothing lands in .mcp.json and teammates never see it.Run claude mcp list after adding to confirm it shows as project-scoped before you tell the team to pull. User scope follows you across every project on your machine. Only project scope writes to a file inside the repository, and that file is the one you commit. So pick project scope whenever the whole team needs the same server.

How do I add a server at project scope?

Pass --scope project (or the short -s project) to claude mcp add. Claude Code creates or updates .mcp.json at the repository root for you.

For a remote HTTP server:

claude mcp add --transport http --scope project linear https://mcp.linear.app/mcp

For a local stdioStandard input/output: the host launches the server as a child process and talks to it over plain text streams, no network involved. server, put the command after a -- separator. Everything after the -- is the command Claude Code runs, untouched:

claude mcp add --scope project --transport stdio db -- npx @acme/db-mcp-server

The -- matters. It tells the CLI where its own flags stop and the server command begins. Drop it and the CLI tries to read the command as one of its own options.

What goes into .mcp.json?

The command above writes a standardised shape. A project-scoped .mcp.json holds an mcpServers object keyed by server name:

{
  "mcpServers": {
    "linear": {
      "type": "http",
      "url": "https://mcp.linear.app/mcp"
    },
    "db": {
      "command": "npx",
      "args": ["@acme/db-mcp-server"],
      "env": {}
    }
  }
}

You can edit this file by hand too. The type field accepts http (or streamable-http as an alias), and stdio servers use command, args, and env. Commit the file like any other config:

git add .mcp.json
git commit -m "Add Linear and database MCP servers for the team"

Now a teammate runs git pull, opens Claude Code in the repo, and the servers are already defined. No copying a command off a wiki. No guessing a URL.

Why does Claude Code ask teammates to approve shared servers?

Claude Code does not silently trust a .mcp.json that arrives from version control. A committed file is something other people can change. So the first time a teammate opens the project, Claude Code prompts them to approve the project-scoped servers before it uses any of them.

Until they approve, the servers show as pending. You can see this state from the command line:

claude mcp list

A server awaiting approval appears as ⏸ Pending approval. Running claude interactively brings up the prompt to review and approve. If someone needs to redo their decision later, this resets the stored choices for the project:

claude mcp reset-project-choices

This is why project scope is safe to share. The config travels with the repo, but each person consents before the servers run on their machine.

How do I keep secrets out of the committed file?

The committed .mcp.json is readable by everyone with repo access, and it lives in git history forever. Never put a literal API key, token, or password in it. A pasted secret in a tracked file is a leaked secret.Common gotchaThe trap is fixing this too late: people paste a real key, commit, then swap in ${VAR} in a later commit and assume they are safe.The old value still sits in git history for anyone who runs git log -p .mcp.json, so a follow-up edit does not undo the leak. Treat any key that ever touched a commit as compromised, rotate it, and only then rewrite history if you must. For the wider question of what to allow and what to block when you share servers, read the MCP security basics.

Claude Code expands environment variables inside .mcp.json, so you commit a reference and each person supplies the actual value from their own environment.

Two forms are supported:

  • ${VAR} expands to the value of the environment variable VAR.
  • ${VAR:-default} expands to VAR if it is set, otherwise uses default.

Expansion works in the command, args, env, url, and headers fields. That covers both a bearer token on a remote server and a key passed to a local server's environment:

{
  "mcpServers": {
    "api-server": {
      "type": "http",
      "url": "${API_BASE_URL:-https://api.example.com}/mcp",
      "headers": {
        "Authorization": "Bearer ${API_KEY}"
      }
    },
    "db": {
      "command": "npx",
      "args": ["@acme/db-mcp-server"],
      "env": {
        "DATABASE_URL": "${DATABASE_URL}"
      }
    }
  }
}

The file names API_KEY and DATABASE_URL, but the values stay on each machine. The ${API_BASE_URL:-...} default lets one teammate point at a staging host without editing the shared file.

If a referenced variable has no value and no default, Claude Code fails to parse the config rather than connecting with a blank credential. That failure is the correct outcome. It tells the teammate to set the variable.

Where teammates set the values

The shared file declares which variables exist. Each person sets them once, locally, in whatever way suits their shell or secret store. A direct export works for a quick start:

export API_KEY="sk-the-real-value"
export DATABASE_URL="postgres://localhost:5432/app"

For something durable, teams keep these in a .env file that is listed in .gitignore, or in a secret manager that injects them into the shell. The rule is the same either way. The value never enters a tracked file. Add the patterns that hold secrets to .gitignore so they cannot be committed by accident:

echo ".env" >> .gitignore
echo ".env.local" >> .gitignore

A short note in your docs/onboarding.md listing the variables each server needs saves every new hire from reverse-engineering the config.

Inspect and remove shared servers

These commands work the same for project-scoped servers as for any other:

claude mcp list
claude mcp get linear
claude mcp remove linear

claude mcp get linear prints the resolved config for one server, which is the quick way to confirm a variable expanded the way you expected. claude mcp remove linear deletes it from .mcp.json, and you commit that removal so it leaves everyone's setup at the same time.

Precedence when scopes collide

A teammate might already have a personal server with the same name as one in the committed file. Claude Code connects to each server once, using the definition from the highest-precedence source. It does not merge fields across scopes; the winning entry is used whole. The order is:

  1. Local scope
  2. Project scope
  3. User scope

So a personal local-scoped linear overrides the shared project-scoped linear for that one person. This is useful when someone needs to point at a fork or a local build while the team stays on the shared definition. It also explains a common puzzle. When a teammate reports that their server behaves differently, check whether they have a local or user entry shadowing the shared one.

A checklist for a clean shared setup

  • Add team servers with --scope project so they land in .mcp.json.
  • Commit .mcp.json to version control.
  • Reference every secret with ${VAR}, never a literal value.
  • Add .env and similar secret files to .gitignore.
  • Document which environment variables each server needs.
  • Confirm a fresh clone prompts for approval and connects after the variables are set.

Get these right and onboarding a teammate to your tools is one git pull, a few exported variables, and one approval prompt. The next stop is wiring those servers into repeatable workflows: see Claude Code workflows.