Claude Codeセルフホスト環境にMCPサーバーを届ける3つの経路
セルフホスト環境の全セッションにMCPサーバーを届ける3つの経路と、claude.aiコネクタが自動では届かない理由をまとめます。
Claude Codeのセルフホスト環境は、クラウドセッションの実行場所を自社のマシンやコンテナに置き換える機能です。ただしMCPサーバーは自動では引き継がれません。Runnerが起動するセッションは、開発者のマシンにある~/.claude/の設定を丸ごと持ち込むわけではないからです。全セッションに届ける経路は、ビルド時の組み込み・管理者配布・リポジトリのコミットの3系統です。設定の書き込み先は経路ごとに違います。管理者配布は/etc/claude-code/managed-mcp.jsonなどの標準パスです。
Claude Codeセルフホスト環境でMCPサーバーが自動で使えない理由
セルフホスト環境のRunnerは、セッションごとに隔離された設定ディレクトリを起動時にゼロから作ります。ローカル端末でclaude mcp addを実行していても、その設定はローカル端末の~/.claude.jsonにしかなく、Runnerのセッションには存在しません。アカウント状態やプロジェクト履歴も引き継がれず、セッションはRunnerホスト側に用意された設定だけを見ます。
「Runnerホスト側に事前にMCPサーバーを登録しておく」か「セッションに設定ファイルを渡す」かのどちらかが要ります。優先順位や適用範囲は経路ごとに違います。
経路1: イメージビルド時にclaude mcp add --scope userで組み込む
もっとも標準的な経路は、コンテナイメージのビルド時にclaude mcp addをそのまま実行しておくことです。デスクトップ環境で追加するときと同じコマンドが、Dockerfile内でも使えます。
RUN claude mcp add --scope user sidecar -- /usr/local/bin/mcp-sidecar
RUN claude mcp add --scope user --transport http internal http://mcp-gateway.svc.cluster.local:8080Runnerがベアプロセス(コンテナを介さない直接起動)の場合は、同じコマンドをRunnerを動かすユーザーの権限でホスト上に実行し、Runnerを再起動します。Runnerはホスト設定を起動時に1回だけ読み込むため、コマンドを流し込んだだけでは反映されません。
--scope userを省くと、設定はどこに書かれるか
--scope userは必須です。claude mcp add --help(v2.1.287)では、-s, --scopeの既定値はlocalと表示されます。空のHOMEとCLAUDE_CONFIG_DIRに差し替えた作業ディレクトリで、スコープ違いの2コマンドを実行して.claude.jsonを見比べました。
$ claude mcp add --scope user --transport http internal http://mcp-gateway.svc.cluster.local:8080
Added HTTP MCP server internal with URL: http://mcp-gateway.svc.cluster.local:8080 to user config
$ claude mcp add sidecar -- /usr/local/bin/mcp-sidecar
Added stdio MCP server sidecar with command: /usr/local/bin/mcp-sidecar to local config
File modified: .../.claude.json [project: <作業ディレクトリ>]{
"mcpServers": {
"internal": { "type": "http", "url": "http://mcp-gateway.svc.cluster.local:8080" }
},
"projects": {
"<作業ディレクトリ>": {
"mcpServers": {
"sidecar": { "type": "stdio", "command": "/usr/local/bin/mcp-sidecar", "args": [], "env": {} }
}
}
}
}userスコープはトップレベルのmcpServersに、スコープ指定なしはディレクトリのパスをキーにしたprojectsの下に入ります。Runnerがセッションへ写すのはトップレベルのmcpServersだけなので、2つ目のsidecarは届きません。コマンドは成功メッセージを返すため、失敗に気づけない点が厄介です。スコープの一般的な違いはClaude Code MCP設定ガイドにあります。
Runnerはホストの設定を起動時に1回だけスナップショットする
Runnerは起動時に、ホストの.claude.json(~/.claude/の内部ではなく隣に置かれるファイル)からmcpServersキーだけを読み取り、各セッションの隔離された設定に書き写します。アカウント情報やプロジェクト履歴は含まれません。
MCPサーバーがセッションに届いたかを確かめる流れ
- 1
ホストの設定を確認する
Runnerホストの
.claude.jsonを開き、トップレベルのmcpServersにサーバーがあるかを見ます。 - 2
Runnerを再起動する
変更したら再起動します。
- 3
セッションを立てて尋ねる
環境上でセッションを1つ作り、使えるMCPツールを一覧にするようClaudeに頼みます。
- 4
足りないときはRunnerのログを見る
認識できない
typeのエントリは、起動時の警告としてログに残ったうえでセッションから除外されます。
ホスト設定の読み込み先はSELF_HOSTED_RUNNER_HOST_CONFIG_DIRで変更できます。空のディレクトリを指しておくと、MCPサーバーのシーディングそのものを止められます。
経路2: 管理者がホストに置いて配布する
管理者が配る方法は2種類あり、他のサーバーを締め出すかどうかが分かれ目です。
管理者配布の2つの方法
managed-mcp.json
ホストの標準パスに置くファイルです。許可するのはファイルに書いたサーバー、managedMcpServersで配ったサーバー、アプリが登録する組み込みサーバーなどに限られます。ユーザーが足したサーバーやプラグインのサーバー、--mcp-configで渡したサーバーは使えません。
managedMcpServers
managed settingsのキーです。HTTPとSSEのサーバーだけを書けて、ユーザー自身が足したサーバーはそのまま使えます。command実行型のエントリは読み込まれません。
managed-mcp.jsonの置き場所と、コネクタへの影響
/etc/claude-code/managed-mcp.json # Linuxランナー
/Library/Application Support/ClaudeCode/managed-mcp.json # macOSランナーこのファイルがRunnerホストにあると、Claude Codeはコントロールプレーンが配信するMCPサーバー(claude.aiコネクタを含む)をスキップします。名前はセッション子プロセスのstderrに警告として出て、Runnerはdebugログレベルで記録します。セッション内には何も表示されないので、コネクタが消えた理由に気づきにくい構造です。
v2.1.229より前は、この状況でセッションが起動時に終了していました。エラーはYou cannot dynamically configure MCP servers when an enterprise MCP config is presentです。今は警告に留まり、セッションは続行します。--mcp-configで渡したサーバーも同じ扱いです。
このファイルがあるホストでは、claude mcp add自体も失敗します。イメージビルドで経路1を使う場合も同じです。
Cannot add MCP server: enterprise MCP configuration is active and has exclusive control over MCP serversallowedMcpServersの扱いはv2.1.259で変わりました。それ以前は、許可リストを設定するとmanaged-mcp.jsonのサーバーも許可リストを通る必要がありました。v2.1.259以降は許可リストの対象外で、${VAR}展開を使うサーバーだけが例外としてチェックされます。許可リストで一部のサーバーを意図的に弾いていた場合、アップグレード後は通知なしに読み込まれ始めます。弾くならdeniedMcpServersを使います。許可リストと拒否リストの運用はManaged MCPで許可リスト・拒否リストを組織管理するで扱っています。
managedMcpServersに書ける条件
エントリは次の条件をすべて満たすときだけ読み込まれます。1つでも外れたエントリは落ち、他のエントリは読み込まれます。
{
"managedMcpServers": {
"search": { "type": "http", "url": "https://search.example.com/mcp" }
}
}typeはhttpかsse(streamable-httpはhttpの別名)urlはhttps://で始まる。localhost向けでも平文のhttp://は不可command・args・env・headersHelperを持たない- 値に
${VAR}参照を含めない。環境変数は展開されない - サーバー名は英数字・ハイフン・アンダースコアだけ
先のsidecarのようなstdioサーバーや、ゲートウェイの平文http://のURLは、この方法では配れません。その場合はイメージビルドかmanaged-mcp.jsonを選びます。同名のサーバーがある場合は、managedMcpServersの定義がlocal・project・userスコープより優先されます。managed-mcp.jsonと併用すると、同名ではファイル側が優先されます。
経路3: チームで共有するなら.mcp.jsonをリポジトリにコミットする
プロジェクトスコープの.mcp.jsonをリポジトリのルートに置いてコミットする方法もあります。この経路は個々のRunnerホストの設定に依存しないため、リポジトリを扱うすべてのRunnerと開発者に届きます。
{
"mcpServers": {
"shared-server": {
"type": "http",
"url": "https://example.com/mcp"
}
}
}ローカル端末のインタラクティブセッションでは、.mcp.json由来のサーバーを使う前に承認ダイアログが出ます。クラウドセッション(セルフホスト環境のセッションも含む)では.mcp.jsonのサーバーは自動承認で読み込まれます。承認プロンプトが出る前提で運用していると、この違いが効きます。
claude.aiコネクタはセルフホスト環境に自動では届かない
組織でconnector deliveryが有効な場合、claude.ai上で設定したコネクタは、インタラクティブに作成されたセッションにはAnthropicのコントロールプレーンからapi.anthropic.com経由で自動的に届きます。ところが、CLIディスパッチのようにプログラムから作成されたセッションは配信の対象外です。セッション子プロセスのOAuthトークンにはコネクタを取得するスコープがなく、取得を試みる動作もしません。
プログラムから作るセッションにコネクタ相当のサーバーを渡すなら、ホストのスナップショット・managed-mcp.json・managedMcpServers・.mcp.jsonのいずれかを使います。「claude.aiで設定したのに使えない」ときは、そのセッションがインタラクティブに作られたかプログラムから作られたかを先に確かめます。ホストにmanaged-mcp.jsonがあれば、インタラクティブなセッションでも配信されたコネクタはスキップされます。allowAllClaudeAiMcps: trueを入れても戻りません。
設定していないのにMCPサーバーが1つ付いてくる
コントロールプレーンは、クラウドセッションに「Claude Code Remote」という独自のMCPサーバーを付けます。Claudeはこれを使って、ルーティンの予約、他のクラウドセッションの起動、リポジトリの追加、プルリクエストの追跡を行います。セッションの作り方によって、サーバー名はClaude_Code_Remote・claude-code-remote・UUID形式の3種類のどれかで登録されます。
止めたいときは、サーバー単位の拒否ルールを3つの名前すべてに書きます。ルールは大文字小文字を含めて名前と完全一致で照合されるためです。
拒否ルールはserver-managed settingsに置けば、Runnerを変えずに全セッションへ届きます。Runner上の~/.claude/settings.jsonに置く方法もあります。
{
"permissions": {
"deny": [
"mcp__Claude_Code_Remote",
"mcp__claude-code-remote",
"mcp__bf7c680d-5fdc-5ef4-b4a0-abadb619bf0a"
]
}
}ツール単位で止める書き方はmcp__Claude_Code_Remote__add_repoの形です。接続自体を止めるなら、deniedMcpServersのserverNameにmcp__を付けない名前を書きます。ルールが効いたかは、セッションでMCPツールの一覧を尋ね、拒否したツールが答えに出ないことで確認します。
複数のMCPサーバーを配布するならツール検索の設定も見ておく
イメージビルドで複数のMCPサーバーを組み込むと、セッションが最初に読み込むツール定義の量も増えます。Claude Codeにはこれを抑える「ツール検索」があり、既定ではツール名とサーバーのinstructions(サーバー側の使い方メモ)だけを開始時に読み込みます。実際に使うツールの詳細は、Claudeが必要になった時点で検索して取得します。セッションはRunnerの環境変数を引き継ぐので、ENABLE_TOOL_SEARCHをRunner側に設定すれば、そのRunnerが生成する全セッションに効きます。
| 値 | 挙動 |
|---|---|
| 未設定 | 挙動既定で全ツールを遅延読み込み。ただしANTHROPIC_BASE_URLがAnthropic以外のホストを指す場合などは先読みにフォールバックする |
true | 挙動全ツールを遅延読み込みにする(Azure上のFoundryなど一部環境を除く)。非対応のプロキシ経由では要求が失敗する |
auto | 挙動ツール定義の合計がコンテキストウィンドウの10%未満なら先読み、10%に達したら遅延読み込みに切り替える閾値モード |
auto:N | 挙動閾値をNパーセント(0〜100)で指定する閾値モード |
false | 挙動全ツールを起動時に先読みする(遅延読み込みなし) |
毎セッションで確実に使うサーバーがあるなら、そのサーバーの設定にalwaysLoad: trueを立てます。ENABLE_TOOL_SEARCHの設定にかかわらず、そのサーバーのツールは起動時から読み込まれます。
claude mcp addにalwaysLoadを指定するフラグはありません。イメージビルドではclaude mcp add-jsonでJSONごと渡します。
RUN claude mcp add-json core '{"type":"http","url":"https://mcp.example.com/mcp","alwaysLoad":true}' --scope useralwaysLoadを立てたHTTP・SSEサーバーには、セッション開始時に既定で最大5秒待ちます。接続が間に合わないサーバーのツールは、最初のターンに出ないことがあります。待ち時間はMCP_CONNECT_TIMEOUT_MSで変えられます。基本構文とスコープの全体像はClaude Code MCP設定ガイドにあります。
ラッパースクリプトから--mcp-configで渡す場合
ラッパースクリプトを使うRunnerでは、"$@"の後ろに--mcp-configを足してファイルのサーバーを渡す方法もあります。stdio・HTTP・SSEのどれも書けます。
exec "$CLAUDE_RUNNER_CLAUDE_BIN" "$@" --mcp-config /etc/claude/mcp.jsonコントロールプレーンが配信するサーバーがセッション中に更新されると、そのファイルのサーバーが止まる場合があります。v2.1.299より前は、更新に名前が含まれないサーバーが最初の更新で止まりました。v2.1.299以降は、ラッパーが子プロセスの環境を作り直すときにCLAUDE_CODE_REMOTE_MCP_CONFIGを引き継ぐ必要があります。execで環境ごと渡すなら引き継がれます。
settings.jsonにMCPサーバーは書けない
settings.jsonとmanaged-settings.jsonにはMCPサーバーの定義を書けません。設定スキーマにトップレベルのmcpServersフィールドが無いためです。設定ファイルで配りたいときは、managed settingsのmanagedMcpServersキーに書き直します。それ以外の経路は、イメージビルド・managed-mcp.json・.mcp.jsonです。
ラッパースクリプトやライフサイクルフックで認証情報やチェックアウト処理をカスタマイズしたいときは、Claude Codeセルフホスト環境をラッパースクリプトとフックで拡張するが向いています。
まとめ
stdioサーバーを全セッションに配るならイメージビルドです。HTTPサーバーを管理者から配るだけならmanagedMcpServers、他を締め出すならmanaged-mcp.json、プロジェクト固有の共有なら.mcp.jsonを選びます。