【クイックサマリー】Claude Code に MCP(Model Context Protocol)サーバーを登録し claude mcp list で「✔ Connected」と表示されても、既定の非対話実行ではAIはツールを一度も呼び出せず、しかも終了コードは0のまま——この挙動を実測ログ付きで検証した Zenn 記事の内容を、再現手順つきで整理した実践ガイドです。対象CLIは Claude Code(検証バージョン 2.1.220)です。
「接続済みと出ているのに動かない」「exit 0 なのにファイルが1バイトも書かれていない」——MCP連携の自動化で、こうした原因の見えない挙動に消耗していませんか?原因はサーバー側の不具合ではなく、Claude Code の権限(パーミッション)レイヤーにある可能性が高いと考えられます。
この記事では、Zenn の検証記事「MCPサーバーは「接続済み」と表示されたのに、AIは既定では一度も呼べなかった」(著者: numarn 氏)の実測結果をもとに、MCP連携の仕組みと安全な自動化の方法を解説します。なお MCP は 公式サイト で仕様が公開されているオープンな共通規格で、検証に使われたリファレンスサーバー群も GitHub リポジトリ でOSSとして公開されています。
- 「接続済み」なのにツール呼び出しが拒否される仕組み(接続・可視・許可の3段階)
- 拒否されても終了コード0・is_error false になる「成功の顔をした失敗」の見抜き方
--allowedToolsの許可粒度(サーバー丸ごと/ツール1個)と再現コマンド- CI・自動実行パイプラインに組み込むときの検証ポイントとセキュリティ上の注意
「接続済み」と「呼び出せる」は別物——MCP連携の3段階
検証記事によると、手順自体はシンプルです。公式リファレンス実装の @modelcontextprotocol/server-memory(バージョン 2026.7.4、AIに覚え書きを保存させるサーバー)を次のように登録します。
claude mcp add memory -- node /path/to/server-memory/dist/index.js
claude mcp list
# memory: node .../index.js - ✔ Connected
起動直後のイベントログでも mcp_servers = [{"name": "memory", "status": "connected"}] と表示され、利用可能なツール52個のうち mcp__memory__create_entities など mcp__ で始まる9個が一覧に載っていました。ここまでは期待どおりです。
ところが非対話モード(claude -p)でAIにエンティティ作成を頼むと、返ってきたのは「Claude requested permissions to use mcp__memory__create_entities, but you haven’t granted it yet.(まだ許可されていない)」という拒否でした。保存先の実ファイル memory.jsonl は生成されず、所要時間は 8,246ms(実測1回目)。つまりMCPの配線は「繋がったか」「AIから見えているか」「呼んでよいか」の3段階に分かれていて、claude mcp list が保証するのは1段目だけ、ということになります。
いちばん怖いのは「失敗に見えない」こと
この拒否された実行、プロセスの終了コードは 0、結果JSONの is_error も false でした。異常の痕跡は、結果JSON内の permission_denials(拒否記録の配列)に1件残ることと、途中経過の tool_result の is_error が true になっていることだけです。シェルスクリプトで if [ $? -eq 0 ] だけを見ていると、何もしていない実行が「成功」として通過します。自動化に組み込む人ほど知っておきたい挙動です。
再現手順——許可の付け方と設定の置き場所
–allowedTools の許可粒度は2段階
呼び出しを通すには --allowedTools を付けます。検証記事の実測では、書き方によって許可範囲が変わりました。
# ツール1個だけを許可(他のツールは拒否される)
claude -p "memoryのcreate_entitiesでzenn-testを作って" \
--allowedTools "mcp__memory__create_entities"
# サーバー丸ごと許可(同サーバーの別ツールも通る)
claude -p "memoryのread_graphで中身を見せて" \
--allowedTools "mcp__memory"
ツール名まで指定した場合はその1個だけが通り、別のツール(read_graph)を頼むと同じ「まだ許可されていない」文言で拒否されます。サーバー名だけの指定では、試した範囲で同サーバーの別ツールも通ったと報告されています。成功した回では memory.jsonl に {"type":"entity","name":"zenn-test",...} が実際に書き込まれており(所要 6,932ms/5,562ms)、返答テキストではなくファイルの実在で成功を確認できたことになります。
登録が「消える」ように見える場所
claude mcp add の既定スコープ「local」は、~/.claude.json 内の projects["実行フォルダの絶対パス"].mcpServers というフォルダ別の区画に書き込まれます。そのため1つ下のフォルダに移動して claude mcp list を叩くと「No MCP servers configured.」と返ってきます。登録が消えたのではなく、そのフォルダには登録が無いだけです。検証記事の著者も「ここでいちばん混乱した」と述べています。
その場かぎりで渡す –mcp-config
登録なしで使いたい場合は、設定をJSON文字列として直接渡せます。
claude -p "..." --mcp-config '{"mcpServers":{"mem2":{"type":"stdio","command":"node","args":["/path/to/index.js"]}}}'
ツール名は設定のキー名に追従して mcp__mem2__* になります。既に登録があるフォルダで使うと両方が読み込まれてツールは18個(9個+9個)になり、--strict-mcp-config を足すと他の設定を全部無視して渡した分だけ(9個)に絞られます。使い捨ての実行やCI上での実行に向いた指定と考えられます。
自動実行フローに組み込む方法——「成功の顔をした失敗」を検知する
実測結果を踏まえると、MCPを使う自動実行パイプラインで終了コードだけを成否判定に使うのは危険です。検証記事が推奨するチェックポイントは次の2つです。
- 起動直後イベントの mcp_servers:
--output-format stream-json --verboseを付けて実行し、各サーバーの status が connected か(起動失敗時は “failed” とだけ記録されます) - 結果JSONの permission_denials: 拒否記録が0件か。1件でもあれば、そのツール呼び出しは実行されていません
たとえば結果JSONを jq で処理するなら jq '.permission_denials | length' のように拒否件数を数え、0以外なら失敗扱いにする、といった判定が一例として考えられます。加えて、返答テキストではなく最終成果物(ファイル・DBレコード)の実在確認をパイプラインの最後に入れると確実です。検証記事自体も、成功判定を一貫して「memory.jsonl にレコードが書かれたか」で行っています。
職種別の適用例としては、①ブログ記事生成や集計レポートを cron で回している自動化エンジニアが「実は1週間ずっと空振りだった」事故を防ぐ、②社内ナレッジボットを開発する情報システム担当者が許可リストを最小権限で設計する、③受託開発でクライアントのCIにAIエージェントを組み込むエンジニアが --strict-mcp-config で実行環境の構成を固定する、といった場面で今回の知見がそのまま活きると考えられます。
MCP設定3方式の比較——local・project・インライン
検証で確認された3つの設定経路を整理します。
| 方式 | 保存先 | 他フォルダからの可視性 | 実測で確認された挙動 | 向いている用途 |
|---|---|---|---|---|
| local(claude mcp add 既定) | ~/.claude.json の projects 区画(フォルダ別) | 見えない | 既定では呼び出し拒否。–allowedTools で許可が必要 | 個人の日常利用 |
| project(.mcp.json) | リポジトリ直下の .mcp.json | チームで共有可能 | 一覧では「Pending approval」表示でも、非対話 -p では承認なしに接続され副作用も発生 | チーム共有(後述の注意点を必ず確認) |
| インライン(–mcp-config) | 保存されない(実行1回かぎり) | — | 登録ゼロのフォルダでも動作。–strict-mcp-config で他設定を全無視できる | CI・使い捨て実行・検証 |
注意点・セキュリティ——静かに起きる4つの挙動
① 禁止ツールは「拒否」ではなく「非表示」になる。 --disallowedTools "mcp__memory__create_entities" を指定すると、起動時のツール一覧が9個から8個に減り、対象ツールはそもそも一覧に載りません。permission_denials も0件のままです。「拒否記録が無い=何も止めていない」とは限らない点に注意が必要です。
② 起動に失敗したサーバーも終了コード0で終わる。 存在しないコマンドをサーバーとして指定した実験では、3回とも終了コード0・標準エラー出力0行で、手がかりは起動直後イベントの status: "failed" だけでした。所要時間でも判別できなかった(正常時 3,514〜3,775ms に対し失敗時 3,654〜4,701ms でレンジが重なる)と報告されています。
③ .mcp.json は非対話実行では承認なしに読み込まれる。 一覧上「Pending approval(承認待ち)」のサーバーが、claude -p では connected になり、許可指定を与えると実ファイルへの追記まで成功しています。.mcp.json はリポジトリに入れて共有できるファイルのため、レビューを通っていない .mcp.json が置かれたリポジトリで自動実行を回すと、意図しないサーバーが動く余地があります。対策としては --strict-mcp-config の併用で .mcp.json を無視できることが確認されています。
④ MCPサーバーは任意のプログラムである。 今回の server-memory はローカルファイルへの書き込みのみですが、MCPサーバー一般は外部通信を含む任意の処理を実行できます。導入前にリポジトリの中身と通信先を確認する運用をおすすめします。なお本記事の実測値はすべて Claude Code 2.1.220・Node.js v22.22.2 環境のもので(記録上の応答モデルは claude-sonnet-5)、将来のバージョンで挙動が変わる可能性があります。最新仕様は公式ドキュメントで確認してください。
まとめ
- MCP連携は「繋がった」「見えている」「呼んでよい」の3段階に分かれており、
claude mcp listの Connected は1段目の保証にすぎません - 呼び出しが拒否されても終了コードは0・is_error は false のまま。自動化では mcp_servers の status と permission_denials、そして最終成果物の実在を確認するのが確実です
- .mcp.json は非対話実行で承認なしに読み込まれるため、共有リポジトリでは
--strict-mcp-configによる構成の固定が有効です
MCPの設定項目や権限まわりの最新仕様については、Claude Code 公式ドキュメントと MCP 公式サイトを参照してください。
コメント