Claude Code MCPのスコープ管理—.mcp.jsonでチーム共有する設計

Claude Code MCPのスコープ管理—.mcp.jsonでチーム共有する設計 | mohablog

Claude Code に MCP サーバーを足すとき、各自が手元で claude mcp add を叩く運用だと、チーム内で構成がずれます。.mcp.json をリポジトリに置けば、clone した全員が同じ定義を読み込む。ただ、どのスコープに書くか・秘密情報をどう渡すか・clone した側の承認をどう通すかで、扱いが分かれます。

目次

3つのスコープはどこに保存されるか

MCP サーバーの設定は local / project / user の3スコープに分かれます。違いは保存先と、チームに共有されるかどうか。公式ドキュメントの “MCP installation scopes” にある対応表がそのまま基準になります。

local / project / user の保存先

スコープ有効範囲チーム共有保存先
local(既定)追加したプロジェクトのみされない~/.claude.json
projectそのプロジェクトのみされる(VCS経由)プロジェクト直下の .mcp.json
user自分の全プロジェクトされない~/.claude.json

保存先は実質2ファイルです。local と user は ~/.claude.json に入り、他人には渡らない。リポジトリに乗るのは project の .mcp.json だけ。local は追加したプロジェクトのパス単位で書かれるので、別リポジトリを開くと出てきません。

旧バージョンの scope 名とずれている点

古い記事をなぞるときに引っかかるのが、スコープ名の変更です。今の local は、旧バージョンで project と呼ばれていた。今の user は旧 global。名前が1つずつずれています。--scope project と書いた古い手順をそのまま真似ると、個人設定のつもりが .mcp.json に書き出されてチーム全員に配られる、という取り違えが起きます。今のバージョン(v2.1系)では、共有したいときだけ明示的に --scope project を選ぶ。

claude mcp add –scope project でチームに配る

共有の起点は1コマンドです。--scope project を付けると、~/.claude.json ではなくプロジェクト直下の .mcp.json に書き込まれます。

HTTP サーバーを project スコープで追加する

claude mcp add --scope project --transport http claude-code-docs https://code.claude.com/docs/mcp

実行すると、次の .mcp.json がプロジェクト直下に生成されます。

{
  "mcpServers": {
    "claude-code-docs": {
      "type": "http",
      "url": "https://code.claude.com/docs/mcp"
    }
  }
}

接続状態は claude mcp list で確認します。

claude mcp list
claude-code-docs: https://code.claude.com/docs/mcp (HTTP) - ✓ Connected

生成された .mcp.json をコミットする

あとは普通にバージョン管理へ乗せるだけ。

git add .mcp.json
git commit -m "MCP: docs サーバーを project スコープで共有"

clone した相手が Claude Code を起動すると、同じサーバー定義を読み込みます。手元で claude mcp add を叩き直す必要はなくなる。設定がコード側に固定されるので、メンバー間で「自分の環境では動くのに」というズレが消えます。

stdio サーバーの場合の書き方

ローカルのプロセスとして動く stdio サーバーは、起動コマンドを -- の後ろに置きます。-- は Claude 側のオプション(--scope--env)と、サーバーを起動する実コマンドの区切りです。

claude mcp add --scope project playwright -- npx -y @playwright/mcp@latest
{
  "mcpServers": {
    "playwright": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@playwright/mcp@latest"]
    }
  }
}

HTTP は url、stdio は commandargs--transport を省くと既定の stdio 扱いになります。

同名サーバーが複数スコープにあるとどうなるか

local に個人用、project に共有用、両方に同じ名前でサーバーを定義したとき、Claude Code はどちらを使うのか。片方が黙って優先され、もう片方は無視されます。

優先順位は local が最優先

“Scope hierarchy and precedence” セクションが順番を定めています。同じ名前が複数箇所にあると、優先度の高い1つだけに接続する。

1. local スコープ
2. project スコープ
3. user スコープ
4. プラグイン提供のサーバー
5. claude.ai コネクタ

3スコープ + プラグイン + コネクタで5段階。スコープ同士は名前で重複を判定し、プラグインとコネクタは接続先(URL やコマンド)で判定します。local に同名を置けば、それが project の共有定義を上書きして勝つ。

フィールドはマージされない

マージされない、という一点でつまずきます。優先度の高い定義が採用されるとき、採用元のエントリ全体がそのまま使われ、スコープをまたいでフィールドが合成されることはありません。project 側で headers に認証トークンを、local 側で url だけを書いて、両方が混ざることを期待するとハマる。local が勝てば、local に書いた url だけが使われ、project の headers は無視されます。共有定義を確実に効かせたいなら、同名の local エントリを claude mcp remove <name> --scope local で消しておく。

.mcp.json に API キーを直書きしない

.mcp.json はリポジトリに乗ります。そこへ値で書いたトークンは、ファイルから消してもコミット履歴に残る。共有するファイルと秘密情報は、分けて扱います。

直書きは履歴に残る

やりがちなのが、認証ヘッダーにトークンをそのまま書く形。

{
  "mcpServers": {
    "sentry": {
      "type": "http",
      "url": "https://mcp.sentry.dev/mcp",
      "headers": { "Authorization": "Bearer sk-live-abc123..." }
    }
  }
}

これを push すると、以降そのトークンは Git 履歴に残ります。ファイルから消しても、過去のコミットには残る。以前、検証用に project スコープの .mcp.json へトークンを直書きしたまま push してしまい、履歴から抜くために git filter-repo を回す羽目になりました。最初から変数展開にしていれば、コミットに残るのは変数名だけで済んだはずです。

${VAR} と ${VAR:-default} で外から渡す

“Environment variable expansion in `.mcp.json`” セクションが、この直書きを避ける仕組みを用意しています。値そのものではなく、環境変数の参照を書く。

{
  "mcpServers": {
    "api-server": {
      "type": "http",
      "url": "${API_BASE_URL:-https://api.example.com}/mcp",
      "headers": {
        "Authorization": "Bearer ${API_KEY}"
      }
    }
  }
}

構文は2つ。${VAR} は環境変数 VAR の値に展開される。${VAR:-default}VAR があればその値、無ければ default に展開されます。上の例なら、各自が手元で API_KEY を export しておけば、ヘッダーは起動時に実際のトークンへ差し替わる。リポジトリに残るのは ${API_KEY} という文字列だけ。API_BASE_URL はマシンごとに切り替えたい値の受け皿で、未設定なら既定の URL が入ります。

展開できる場所と、未設定時の挙動

展開が効くのは commandargsenvurlheaders の5フィールド。ここを押さえれば、パスもエンドポイントもトークンも外出しできます。ただし1点だけ気をつけてください。必須の変数が未設定で、しかも既定値を書いていないと、Claude Code は設定のパースに失敗します。${API_KEY} のように既定を持たせない変数は、export し忘れると起動時にそのサーバーが読み込まれない。逆に言えば、秘密情報には既定値を付けず、参照必須にしておくのが安全側です。

clone した側で承認を求められる理由

.mcp.json をコミットしても、clone した相手の環境で即座に有効になるわけではありません。stdio サーバーはローカルでプロセスを起動する。リポジトリを開いただけで任意のコマンドが走ると危ないので、初回は本人の承認を挟みます。

承認プロンプトと Pending approval

“For security reasons, Claude Code prompts for approval before using project-scoped servers” と公式が明記しています。未承認のサーバーは claude mcp list⏸ Pending approval と表示される。

sentry: https://mcp.sentry.dev/mcp (HTTP) - ⏸ Pending approval

対話セッションで claude を起動すると承認を聞かれ、通せばそのサーバーが繋がります。承認は初回1回。以降は記憶されます。

承認をリセットする reset-project-choices

一度拒否したサーバーを後から有効にしたい、あるいは承認状態をやり直したいときは、専用コマンドで選択をリセットします。

claude mcp reset-project-choices

これで .mcp.json のサーバーが未承認状態に戻り、次の起動時にもう一度承認を聞かれます。

信頼していないフォルダでは自動承認が効かない

チームで enableAllProjectMcpServers.claude/settings.json にコミットしておけば、承認をまとめて通せます。ただし v2.1.196 以降、clone 直後の未信頼フォルダではこの設定が無視される。claude を起動してワークスペースの信頼ダイアログを承諾するまで、サーバーは ⏸ Pending approval のまま繋がりません。clone してきたリポジトリが自分自身のサーバーを勝手に承認できないようにする設計です。MCP サーバーの追加そのものの流れは Claude CodeのMCPサーバー連携ガイドでも扱っています。

.mcp.json を直したのに反映されないとき

Claude Code が .mcp.json を読むのはセッション開始時です。起動中に編集しても効かないので、セッションを抜けて入り直す。それでもサーバーが出てこないなら、/mcp を開いてパース警告を探します。壊れたエントリは読み飛ばされ、問題のフィールドがそこに表示される。過去に一度拒否していた場合は claude mcp reset-project-choices で承認をやり直せば戻ります。

まとめ

  • MCP のスコープは local / project / user。共有したいときだけ --scope project を選び、.mcp.json をコミットする
  • project は今の local、旧 global は今の user。古い手順の scope 名はずれている
  • 同名が複数スコープにあると local が勝ち、採用元のエントリ全体が使われる。フィールドはマージされない
  • トークンは直書きせず ${API_KEY} で外出しする。展開先は command / args / env / url / headers の5箇所
  • 秘密情報の変数には既定値を付けない。未設定なら起動が止まり、漏れに気づける
  • clone 側は初回承認が要る。v2.1.196 以降は信頼ダイアログを通すまで enableAllProjectMcpServers も効かない
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次