ワークフロー

MCP接続トラブル対処|5層で原因特定

更新: AIビルダー編集部
ワークフロー

MCP接続トラブル対処|5層で原因特定

MCPサーバーを追加したのに、接続できない、認証が通らない、ツールが出てこない。そんな詰まり方は、設定を総当たりで触るより、Host・Client・Server・Transport・Authorization のどこで止まっているかを順に切り分けたほうが早く抜けられます。

MCPサーバーを追加したのに、接続できない、認証が通らない、ツールが出てこない。
そんな詰まり方は、設定を総当たりで触るより、Host・Client・Server・Transport・Authorization のどこで止まっているかを順に切り分けたほうが早く抜けられます。
local(stdio)と remote(HTTP 系)の混線はぐっと減ります。

この記事は、Claude DesktopVS CodeでMCPを触り始めた人が、10分で原因の場所を絞り込み、自力で1件は解決できるところまで連れていく実践編です。
実際、私もClaude DesktopにNotionの remote MCP を追加したとき、認証画面が延々と戻ってくるループに詰まりましたが、『Chrome DevTools』の Network で Preserve log を有効にして追い、redirect_uri の不一致を直して抜けました。

remote 側では URL と OAuth 2.1/PKCE、local 側では実行コマンド・PATH・作業ディレクトリ・環境変数が主な分岐点になります。
認証の前に通信が死んでいるのか、通信は通っていて同意やトークン交換で落ちているのかを見分けられれば、ログ確認とチェックリストだけで空振りは減らせます。

接続できないときの最短チェックリスト

事前に確認する6項目

接続トラブルは、深い層から掘るより先に、明白な設定ミスを上から順に消していくほうが早く進みます。
ここでは local(stdio)でも remote(HTTP 系)でも最初に見るべき点を、遠回りしにくい順でそろえます。
MCPは Host・Client・Server が分かれ、通信も JSON-RPC 2.0 ベースで流れるので、どこか一つでも前提が崩れると全体が黙って止まります。
modelcontextprotocol.ioの『MCP Architecture overview』にある構造を頭に置いたうえで、この6項目だけ先に潰すと、切り分けの精度が一段上がります。

  1. 設定ファイルの JSON 構文

末尾カンマ、引用符の閉じ忘れ、配列やオブジェクトの閉じ忘れは、最初に疑う価値があります。
公式サンプルと見比べるだけで見つかることも多いです。
実際、私も一度、設定を何十分も疑っていたのに原因は JSON の末尾カンマひとつでした。
そこを消しただけで接続がその場で復旧し、認証やサーバー実装を触る必要すらありませんでした。
エラー文が不親切なクライアントでは、構文エラーが「接続失敗」に見えることがあります。

  1. コマンドまたは URL の誤記

local なら実行ファイル名、相対パスと絶対パス、拡張子の有無まで見直します。
Windows では実行対象の拡張子関連で外すこともあります。
remote なら ` で始まっているか、末尾スラッシュの有無で実装側のルーティングと食い違っていないかを見ます。
NotionやSupabaseのように接続先 URL が明示されているサービスでも、コピペの途中で 1 文字欠けるだけで別問題に見えます。

  1. 環境変数がプロセスに渡っているか

API トークン、ベース URL、認可設定の値は「設定した」だけでは足りず、起動したプロセスに実際に渡っているかを見ます。
env や簡単な print を仕込んで確認すると早いです。
Claude Desktop系では起動時に継承される環境変数が限定されることがあり、ターミナルから直接起動したときだけ通る、という食い違いが起きます。
値そのものだけでなく、想定した変数名で読まれているかまで確認対象です。

  1. 作業ディレクトリと相対パス

local の stdio サーバーで、設定や証明書、補助ファイルを相対パスで読んでいる場合はここが盲点になります。
クライアント経由の起動では作業ディレクトリが CLI 実行時と一致しないことがあり、./config.json を読んでいるつもりで別の場所を見ているケースがあります。
サーバー単体では動くのにクライアント経由だと落ちるときは、まずこの差を疑うと当たりを引きやすくなります。

  1. PATH と実行権限

実行ファイルが PATH に載っているか、実行ビットや権限が足りているかも見逃せません。
シェルでは動くのにアプリからは見つからない、という症状は PATH の中身が違うと説明がつきます。
Windows なら .exe.cmd の解決、macOS や Linux なら実行権限の付与状況まで範囲に入ります。

  1. 認証の有無とクライアント側ログ

remote は URL が正しくても認証で止まることが多く、OAuth 2.1 と PKCE を前提にしたフローでは、認証ダイアログが開くか、consent 画面に client 名と scope が見えているかが手がかりになります。
modelcontextprotocol.ioの『MCP Authorization』でも PKCE が前提条件として扱われています。
加えて、Claude Desktopなら開発者ツール、VS Codeなら出力パネルや開発者ツールを見て、エラーや警告が出ていないかを先に拾います。
サーバー側ばかり追うより、クライアントが何で止まったと認識しているかを見るほうが早く片づく場面が少なくありません。

ℹ️ Note

設定ファイルの保存場所や、どの環境変数が継承されるかはClaude DesktopとVS Codeで前提が揃いません。local で CLI 単体起動は通るのにクライアント経由だけ失敗するなら、設定内容そのものより起動条件の差を先に見たほうが筋が通ります。

Architecture overview - Model Context Protocol modelcontextprotocol.io

単体起動テストの最小手順

事前チェックで不整合が見つからないときは、クライアントをいったん外して、サーバーだけが動くかを見ます。
ここで通れば Host 側か認証フローの問題に寄せられますし、ここで落ちるならサーバー設定か起動条件に絞れます。
切り分けの価値は、直す場所を一気に減らせる点にあります。

local の stdio サーバーなら、普段クライアントが使っているのと同じコマンドをそのままターミナルで実行します。
起動直後に落ちるなら、missing env、モジュール解決失敗、相対パス誤り、権限不足のどれかに寄ることが多いです。
標準エラーに何も出ない実装でも、簡単なログ出力を足すだけで入口の失敗は追えます。
クライアントに登録した定義を目で追うより、同じコマンドを生で叩いたほうが原因が露出します。

remote なら、まずサーバーが HTTP として起きているかを見ます。
/health/healthz/status/version のような軽いエンドポイントがあれば、それにアクセスして 200 系応答が返るかを確認します。
実装によって名前は違いますが、健康状態やバージョンを返す入口があるなら、MCP の前段で死んでいないことを確かめるには十分です。
簡単な curl で疎通を取り、TLS 証明書やリバースプロキシで落ちていないかも同時に見えます。

認証付き remote では、サーバー自体は生きていても scope 不足で後段だけ止まることがあります。
私が引っかかったのは、initialize までは通るのに tools/list で止まるケースでした。
最初はサーバー実装の不具合を疑いましたが、consent 画面を見直すと必要な scope が足りていませんでした。
追加 scope を付与して認可を取り直したら、その場で tools/list まで通りました。
接続できないように見えても、実際には「通信は通っていて認可だけ足りない」という詰まり方は珍しくありません。

initialize→initialized→tools/list の到達点確認

サーバー単体で生死を見たら、次はMCPの接続フローでどこまで到達したかを三段でメモします。
ここを曖昧にすると、「接続できない」という一言の中に、起動失敗、認証失敗、ツール列挙失敗が混ざってしまいます。
後続の調査でも、この三段のどこで止まるかが軸になります。

initialize に届いていないなら、通信路か起動条件の問題が濃くなります。
local ならコマンド、PATH、作業ディレクトリ、環境変数の順で見直すと筋が通ります。
remote なら URL、TLS、認証開始前の疎通を先に見る場面です。

initialized までは進むのに先へ行かないなら、初期化は通ったが、その後の認可や capability 周りで止まっている可能性が高くなります。
remote では認証ダイアログが出たか、consent UI に client 名と scope が見えたかが手がかりになります。
ここが曖昧だと、実は認証フロー自体が成立していないのに、サーバー側のツール実装を触ってしまいがちです。

tools/list まで到達していれば、少なくとも接続と初期化の大枠は通っています。
ツールが空になる、期待したツールだけ見えない、呼び出しで失敗する、といった症状はここから先の話です。
逆に言えば、ツール一覧が返る前で止まっているのに個別ツールの実装を掘っても、原因には届きません。
クライアント側ログに initializeinitializedtools/list のどこまで出たかを控えるだけで、次に見るべき層がぶれなくなります。

MCP接続トラブルはどこで起きる?まず全体像を5層で整理する

5層の役割と責務

URL を入力して remote MCP につなぐとき、失敗箇所は1つに見えても、実際には層ごとに原因が分かれます。
ここを最初に 5層で切り分けておくと、「URL が違うのか」「認証が始まっていないのか」「サーバーは返しているのにホスト側で止めているのか」が混ざりません。
筆者は会議でこの話をするとき、「MCP は Host が窓口、Client が接続係、Server が機能本体、Transport が通り道、Authorization が入館管理です」とだけ言います。
この言い方にすると図がなくても認識がそろい、誰がどこを見るかをその場で決めやすくなります。

まず Host はClaude DesktopやVS Codeのようなアプリ側の管理レイヤーです。
どの MCP サーバーを登録するか、接続を許可するか、どの UI で consent を見せるかを担います。
ここで見落としやすいのが、ホストは 1 つのサーバーに対して 1 つのクライアント接続を持つ構造だという点です。
1 本の接続を複数サーバーで共有しているわけではないので、ある URL だけ不調なら、その接続単位で切り分けるのが正攻法になります。
この Host / Client / Server の分離が前提として説明されています。

次の Client は、ホストの内部で各サーバーに実際につなぎにいく接続インスタンスです。
remote なら URL に向かって接続を開始し、local ならプロセスを起動して stdio を開きます。
URL 入力型のトラブルでは、この Client がそもそも接続開始できていないケースが目立ちます。
たとえばサーバー URL の typo、末尾スラッシュ込み前提のルーティング差、接続先のホスト名違いなどです。
認証が必要な remote では、Client がブラウザを開いて OAuth フローを開始するところまで進むかも観察点になります。
ここが動かないなら、Authorization 以前に Host 設定や URL 指定が怪しいと読めます。

Server は MCP サーバー本体です。
ツールやリソースを公開し、tools/list や tool 呼び出しに応答します。
ここで押さえたいのは、MCP は既存 API を置き換えるものではなく、上位の標準レイヤーとして API やデータソースをラップする、という位置づけです。
NotionやSupabaseのようなサービスでも、MCP サーバーが背後の API をまとめて「AI クライアントから見える道具箱」に変換しています。
つまり URL が正しくても、Server 側でツール登録漏れや scope 不足があれば、接続後に何も出てこない状態は普通に起こります。

Transport は通信の通り道です。
local なら stdio、remote なら HTTP 系です。
ここは「アプリが悪い」「認証が悪い」と誤解されやすい層ですが、実務では TLS 前提であること、プロキシやロードバランサをまたぐこと、長時間接続やストリーミングでセッション維持が必要になることが効いてきます。
remote は URL さえ開けば終わりではなく、HTTP over TLS が素直に通るか、接続途中のミドルウェアがストリームやセッションを壊していないかまで見ないといけません。
2026-03-05 更新の roadmap でも scalable session handling が重点課題に入っており、スケーリングやセッション管理に起因する不安定さは設計上の論点として扱われています。

Authorization は OAuth 2.1 と PKCE を中心にした認可レイヤーです。
remote 接続でブラウザ同意が入るタイプでは、ここを独立した層として見ると混乱が減ります。
接続ボタンを押したあとにブラウザが立ち上がるか、consent 画面が表示されるか、認可後にホストへ戻れるかで、止まっている位置がわかります。
PKCE 非対応ならクライアントは先に進めない扱いになっており、OAuth 対応だけ見て「認証はあるはず」と判断すると外します。
URL 入力型の接続で詰まるときは、サーバー URL の正しさ、認証フロー開始の有無、OAuth/PKCE 対応可否を別々に見るほうが、原因の見落としが減ります。

セキュリティ面では、どの層でも「何でも登録できる」運用が事故につながります。
とくに remote は URL を貼れば接続候補に見えるため、allowlist や trusted registry を前提にした運用が効きます。
接続先を信頼済みの提供元に絞っておけば、誤った URL、なりすまし、意図しない認可画面への誘導を減らせます。
local 側の任意コード実行リスクほど目立ちませんが、remote でも接続先の出自管理を外すと、調査時間より先に安全性が崩れます。

local(stdio) vs remote(HTTP系) の比較

local と remote は、同じ「MCP 接続」でも詰まり方がまったく違います。
local はマシン上でプロセスを起動し、標準入力・標準出力で JSON-RPC を流す形です。
remote は URL 先のサーバーへ HTTP 系 transport でつなぎ、必要ならブラウザ同意を経てトークンやセッションを確立します。
見た目はどちらも「サーバーを追加する」操作ですが、調べるポイントは別物です。

表にすると次のように整理できます。

項目local(stdio)remote(HTTP系)
接続の起点ローカルコマンド起動サーバー URL への接続開始
主な失敗点実行コマンド、PATH、依存関係URL 誤り、TLS、認証開始失敗
認証の中心環境変数やローカル資格情報OAuth 2.1、PKCE、ブラウザ同意
通信の観察場所ターミナル、stderr、クライアントログブラウザ、Network、サーバーログ
不安定化しやすい要因起動条件の差セッション維持、ロードバランサ、スケーリング

transport に関する表記は記事や SDK によって「HTTP with SSE」や「Streamable HTTP」といった語が混在しています。
複数の実装や SDK、実務記事で 'Streamable HTTP' と呼ばれるトランスポートパターンが採用される例は確認できますが、modelcontextprotocol の一次仕様ドキュメントで formal な定義が示されているかは実装ごとに差があります。
実運用では各実装の挙動を個別に確認し、MDN(EventSource)や該当 SDK の実装ノート、RFC 等を参照して扱いを決めてください。
また、EventSource(SSE)のブラウザ実装は差があるため、認証付き SSE を採る場合は Cookie やクエリトークン、あるいは Fetch ベースで EventSource 相当の振る舞いを自前実装するなどの代替を検討し、対象ブラウザでの動作試験を推奨します。
allowlist や trusted registry の話も、remote では地味に効きます。
URL 手入力は柔軟ですが、そのぶん接続先の正当性を人間が毎回判断することになります。
信頼済みレジストリや組織内 allowlist を前提にしたほうが、「URL は合っているのに別環境だった」「テスト用エンドポイントを本番用と思い込んでいた」といった初歩的な事故を減らせます。
Supabaseの MCP でも development/testing 向けという前提があり、接続成功だけを見て本番運用前提だと思い込むと、使い方の層でずれます。

接続フロー(initialize→tools/list)を言語化

流れをテキストで並べると、次の順になります。

  1. Host 内の Client が接続を開始する
  2. 必要なら remote 側で OAuth フローが始まり、認可を終える
  3. client.initialize を送る
  4. Server が初期化完了を返す(initialized 相当の到達点)
  5. Client が tools/list を呼ぶ
  6. 利用可能なツール一覧を受け取り、必要に応じて tool を呼び出す
  7. リソース参照や購読がある場合は、その後の通信へ進む

この順番で見ると、URL 入力型の失敗は 3 つに分けられます。
接続開始前で止まる失敗認証直後で止まる失敗初期化後にツール列挙で止まる失敗です。
たとえばサーバー URL が間違っていれば 1 から 2 に進みません。
OAuth は始まるのに PKCE が成立しなければ 2 から 3 に乗れません。
initialize が通っているのにツールが空なら、Server 側のツール公開条件、scope、内部エラーの線が濃くなります。

ここで誤解しやすいのが、「ブラウザ認証が終わったなら接続済み」という見方です。
実際には、認証完了は通信全体の途中にすぎません。
OAuth が通っても、そのあとで initialize が失敗したり、tools/list で権限不足が出たりします。
consent 画面に表示される client 名、scope、redirect_uri の整合が取れていても、Server 側がそのトークンで必要なツールを列挙できるとは限らないからです。
接続の成否を 1 つの出来事で判断せず、どのメッセージまで到達したかで見るほうが実務に合います。

remote の transport が HTTP 系になると、ここに TLS とセッション維持の論点が重なります。
最初の initialize は通るのに、その後のストリーミング応答やセッション継続で崩れるケースでは、アプリ画面では「たまにつながらない」としか見えません。
けれどフローで言うと、初期化後の後続通信が不安定になっている状態です。
スケールアウトした構成やセッション共有が弱い構成では、ある接続だけツール一覧取得後に失敗する、といった振る舞いが出ます。
こうした揺れは Host や Server の単体バグに見えがちですが、Transport とセッション管理まで含めて初めて説明できます。

MCP は API を置き換えるのではなく、ツールとデータへの入口を標準化する層です。
そのため、接続フローの後半で起きる失敗も、実際には背後の API 権限やデータアクセス制御が顔を出します。
URL を入れた、ログインした、なのに何も使えない、という詰まり方は珍しくありません。
そういうときほど、「どの層で」「どのメッセージまで」進んだかを言葉にするだけで、調査の射程が急に狭まります。

local MCP(stdio)がつながらない場合の切り分け手順

実行コマンド/パス/権限の確認

local の stdio 接続で最初に疑うべきなのは、MCP サーバーそのものがクライアントから起動できているかです。
ターミナルで動くのにClaude Desktopやローカル IDE からはつながらないときも、実際には「そのコマンド名を解決できていない」「実行権限がない」「Windows で拡張子込みの実体を見つけられていない」のどれかに収まることが多いです。

Linux や macOS では which サーバー名、Windows では where サーバー名 を実行して、設定に書いたコマンドが本当に見つかるかを確認します。
見つからない場合は、クライアント設定の command が間違っているか、PATH に配置先が含まれていません。
Windows では .exe / .cmd / .bat の違いで挙動が変わる点にも注意してください。

設定に書くコマンドは、名前解決に頼らず絶対パスで一度固定すると切り分けが速くなります。
nodeuvxpython のようなランタイム名も、クライアント側の PATH 解決に依存するからです。
MCP Architecture overviewで整理されている通り、Host の中で Client がサーバープロセスを起動するので、手元のシェルで見えている環境とは PATH 解決や実行コンテキストが異なるため、挙動が一致しないことがあります。
local の初期トラブルは、この「起動主体の違い」を忘れた瞬間に増えます。

作業ディレクトリと相対パス

コマンドが存在しているのに起動後すぐ失敗するなら、まず作業ディレクトリを確認します。
MCP サーバーが ./config.json./.env、相対パス指定の SQLite ファイルや証明書を読み込む実装だと、クライアント経由の起動時に想定と違う場所を見て落ちることがあります。

ここで厄介なのは、クライアントによっては作業ディレクトリが明示されておらず、プロセスの起点がユーザープロファイル配下だったり、アプリ本体の実行位置寄りだったりすることです。
Claude Desktopでは「今開いているプロジェクト直下から起動される」と思い込みがちですが、そう決め打ちすると相対パスの設定を誤ります。
VS Codeでも user 設定なのか workspace 設定なのか、さらに remote context 上で動いているのかで、参照されるファイル位置が変わります。

相対パス問題の見つけ方は単純で、起動直後に process.cwd()pwd 相当をログへ出します。
あわせて、読みに行く設定ファイルの絶対パスもその場で記録すると、どこでずれているか一目で分かります。
筆者は local MCP の設定を組むとき、相対パスを残すのは開発初期だけにして、継続運用では設定ファイルや補助スクリプトを絶対パスに寄せます。
ターミナルではたまたま成功していた参照が、クライアント経由に変わった途端に崩れるのを何度も見てきたからです。

ℹ️ Note

ターミナル単体での再現時も、普段の作業ディレクトリで起動するのではなく、cd / のように別の場所から同じコマンドを叩いてみると、相対パス前提の実装かどうかがすぐ露出します。

環境変数の継承とwrapperスクリプト

local MCP では、環境変数の継承が想像より細いことがあります。
USER HOME PATH だけ見えて、シェル初期化で読み込まれる独自変数や .env の内容は入ってこない、という形です。
ターミナルで直接起動すると成功するのに、クライアント経由だと API キー未設定で落ちるなら、この線が濃くなります。

💡 Tip

この wrapper は、単に変数を注入するだけでなく、PATH の補正や作業ディレクトリ固定にも使えます。
たとえばシェルでは pyenvasdf が入れてくれる PATH 断片を、クライアント起動時には持っていないことがあります。
その場合、wrapper の冒頭で PATH を明示的につなぎ、本体へ渡すと再現性が上がります。
設定ファイルに生のワンライナーを詰め込むより、run-mcp.shrun-mcp.cmd のように入口を一本化したほうが、トラブル時の観察点も減ります。

即終了プロセスのログ取得

「つながらない」の中には、接続処理で詰まっているのではなく、サーバープロセスが起動直後に死んでいるだけのケースが混ざります。
依存ライブラリの import 失敗、設定ファイルの JSON パース失敗、権限不足、秘密情報未設定などで即終了すると、クライアント側には漠然とした接続失敗しか見えません。

このとき頼りになるのは stderr です。
local stdio サーバーは標準出力をプロトコル通信用に使うので、診断ログは stderr かファイルへ逃がしたほうが安全です。
JSON-RPC メッセージと雑多な print が混ざると、今度は通信自体を壊します。
起動直後の例外スタック、読もうとした設定パス、認識した PATH、現在ディレクトリ、必須環境変数の有無だけでも stderr に出せば、無言で落ちる状態から一歩進めます。

ログは「成功時には静か、失敗時だけ詳細」より、起動直後の自己申告を一定量出すほうが切り分けに向きます。
筆者は最初の数行に、実行ファイルパス、カレントディレクトリ、設定ファイル位置、主要環境変数の存在有無を書かせています。
そうしておくと、依存不足で落ちたのか、読み込み先を間違えたのか、権限で弾かれたのかが短時間で分かれます。
構造化ログまで用意できるなら、起動フェーズだけ JSON で保存しておくと比較もしやすくなります。

クライアントごとの実行場所差異

同じ local MCP でも、Claude DesktopとVS Codeでは「どこでそのコマンドが動くか」が違います。
ここを一括りにすると、CLI では成功、A では失敗、B では別の失敗、という現象を説明できません。
VS Codeでは 『VS Codeで MCP servers を追加・管理する』 にある通り、user 設定・workspace 設定・remote 接続先という実行コンテキストが分かれており、ローカルファイルを読んでいるつもりが remote 側で走っていた、というずれが起きます。

Claude Desktopではユーザープロファイル配下の設定とアプリ起動時の環境が基準になるため、ターミナルで開いているプロジェクトの状態はそのまま反映されません。
VS Codeではワークスペース単位で見えているファイルや拡張機能ホストの場所が絡みます。
リモート開発中なら、/Users/... を参照するつもりの設定が実際にはコンテナや SSH 先で評価されることもあります。
local のつもりで作った相対参照が、実際には別マシン文脈で解決されているわけです。

こういう差は、CLI とクライアント経由で環境ダンプを並べて比べると露出します。
cwdPATHHOME、実行ユーザー、見えている設定ファイル一覧をそれぞれ出し、同じサーバー本体を直接実行した結果と見比べます。
筆者はこの比較を一度やってから、クライアントごとに「どの層まで同じか」を先に決めるようになりました。
local MCP の初期設定は設定 JSON の書き方で詰まる印象がありますが、実際にはどこでそのプロセスが生え、何を見て起動しているかを押さえるだけで、迷路の半分は消えます。

Add and manage MCP servers in VS Code code.visualstudio.com

remote MCP がつながらない場合の切り分け手順

URL/TLS/ネットワーク前提の確認

remote MCP の切り分けは、まず「その URL に対して、認証前の土台が成立しているか」を見ます。
ここが崩れていると、OAuth の失敗に見えても実際は単なる到達不能です。
remote 側は https 前提で考えるのが基本で、ホスト名の typo、ポート違い、/mcp などのパス違い、リバースプロキシ配下での転送設定漏れが初期不良の中心になります。
MCPの公式ドキュメントであるConnect to remote MCP Serversでも、remote 接続は URL と認証の組み合わせで成立する前提になっています。

ここでは local のときと違って、実行コマンドの有無や PATH は主役ではありません。
ただし、読者がつまずきやすいのは「local で動いたサーバーをそのまま remote 化したのに通らない」という場面です。
その場合、元のサーバープロセスが安定して起動しているかはまだ確認対象に入ります。
CLI から直接起動すると問題ないのに、実際に公開経路を通すと失敗するなら、差分はアプリ本体より手前にあります。
逆に、裏側のサーバーが起動直後に即終了していれば、外からは TLS や認証の問題に見えても、実態はアプリが死んでいるだけです。
local セクションで触れた通り、作業ディレクトリや環境変数の継承制限で設定ファイルが読めず、バックエンドだけ落ちていたという流れは remote 化した後でも普通に起きます。

証明書まわりでは、ブラウザで consent 画面に飛ぶ前に警告が出ないか、証明書の有効性が通っているかを先に見たほうが早いです。
リバースプロキシを挟んでいるなら X-Forwarded-Proto や Host ヘッダの扱いがずれると、アプリ側が http と誤認してリダイレクト URL を壊すことがあります。
認証が始まったように見えても、戻り先がずれて失敗する典型です。
『Chrome DevTools』の Network パネルで Preserve log を有効にしておくと、認可ページへの遷移から戻り先までのリダイレクト列を追えます。
ページ遷移をまたぐとログが消えるので、ここを有効にしてから試すだけで観察の精度が上がります。

筆者が一度はまったのは、API Gateway の背後に置いたオリジンで CORS と Authorization ヘッダが途中で落ち、tools/list が空になったケースです。
認証自体は通っていたので最初は権限不足を疑いましたが、レスポンスログを Gateway 経由とオリジン直叩きで並べると、前者だけヘッダが欠けていました。
MCP サーバーの実装より、手前の転送条件を見直したら解消しました。
remote の不具合はアプリコードよりネットワーク境界で起きる比率が高く、ログの比較対象を一つ増やすだけで景色が変わります。

developer.chrome.com

認証フロー開始とconsent UI

URL に到達できたら、次は「認証フローが本当に始まっているか」を確認します。
remote MCP では、ブラウザが開いて consent UI まで進むかどうかが最初の関門です。
ブラウザが立ち上がらない、認可画面に飛ばない、開いてもすぐエラーになるなら、接続先 URL ではなく認可エンドポイントの設定やクライアント登録が怪しくなります。

MCP Security Best Practicesでは、consent UI に少なくとも client 名、scope、redirect URI が表示されるべきだと整理されています。
ここでクライアント名が期待したものになっているか、scope が広すぎたり欠けていたりしないか、redirect URI が登録済みの値と一致しているかを見ると、設定ミスをかなりの確率で拾えます。
見た目が出たから安心ではなく、表示内容が合っているかまで見ないと、通ってはいけない認可が通るか、必要な権限が足りずに接続後の tools/list だけ空になることがあります。

statenonce の検証が途中で落ちると、ユーザー目線では「同意したのに戻ってこない」挙動になります。
ここもブラウザ側の Network ログが効きます。
認可リクエストに含めた値と、戻りのクエリに現れた値が一致しているかを追うと、クライアント側で弾かれたのか、認可サーバーが意図しない値を返したのかが分かれます。
Notionの manual URL 接続のように、手入力でサーバーを追加する方式では、同意画面に出るクライアント名と scope の読み合わせを雑にすると、別環境のアプリを許可していたという事故が起きます。
Supabaseでも、接続時は redirect URI と要求 scope の整合が崩れると、認証が通ったつもりで後段の操作だけ失敗します。

ここで local との違いとして意識したいのが、CLI から直接起動した場合との差です。
local はターミナルでそのまま環境変数を積んで起動できますが、remote はブラウザ往復が入るため、クライアントアプリの内部状態と認可サーバー側の登録情報が噛み合っていないと前に進みません。
ローカルでは問題なかった設定が、クライアント名や redirect URI の固定値のせいで remote だけ詰まる、という流れは珍しくありません。

PKCE対応の必須性

ここは「対応していないなら直すしかない」と割り切って見る判断材料になります。
remote MCP の認可は OAuth 2.1 前提で、PKCE の code_challengeS256 が必須です。
MCP Authorizationの仕様に沿っていないサーバーが接続拒否になるのは不具合ではなく、正しいふるまいです。
古い OAuth 実装や簡易な検証用実装だと、authorization code は返せても PKCE を見ていないことがあり、その場合は Claude Desktop や IDE 側が拒否して止まります。

このとき「ブラウザまで開いたのに最後で失敗する」ので、TLS や URL は通っているように見えます。
ですが原因は認証サーバーの能力不足です。
Network を見ると、認可リクエストに code_challengecode_challenge_method=S256 が載っているのに、戻りで整合が取れない、あるいは token 交換で弾かれている、という形で出ます。
ここを scope や cookie の問題と混同すると遠回りになります。

認証で失敗する場合は、まず認可リクエストとコールバックのパラメータを突き合わせてください。
次に token 交換のログで code_verifier の処理や invalid_grant の発生を確認します。
これらを順に確認すると、scope や cookie、PKCE 周りの問題を混同せずに切り分けられます。

認証が一度は通るのに、その後の initializetools/list が不安定なら、アプリ本体よりセッション管理とスケーリングの層を疑います。
MCPのアーキテクチャでは 1 サーバーにつき 1 クライアント接続が基本なので、認証直後の状態やセッション情報が途中で別ノードに飛ぶと、接続済みのつもりなのに次のリクエストだけ未認証扱いになります。
sticky session がない、共有ストアにトークンやセッション情報を書いていない、ロードバランサの timeout が短い、といった条件が重なると再現します。

HTTP ストリーミングや SSE 系の実装では、長めの接続を前提にしているのに、ゲートウェイ側が短い idle timeout で切ってしまうことがあります。
切断後にクライアントが再接続しても、前のセッションを継げなければ一時的な 4xx や 5xx が混ざります。
こういうときは単発の失敗コードを責めるより、同じタイミングでどのノードに到達したか、直前に再認証が走っていないか、レート制限に触れていないかを見るほうが筋がいいです。
生きた接続が並ぶ環境では、認可基盤、MCP サーバー、API Gateway、アプリバックエンドの各 timeout が噛み合っていないだけで、不規則な切断に見えます。

production 設定でコネクション数やレート制限を引き上げているつもりでも、手前のプロキシだけ低い値のまま残っていることがあります。
特に検証環境から本番相当へ上げた直後は、ヘルスチェックは通るのに本接続だけ落ちる、という歪みが出やすいのが利点です。
/health/version が正常でも、認証付きの長時間接続まで同じ条件で流れているとは限りません。
local セクションで説明した確認手順はここでも有効です。
まずはバックエンドの即終了や環境変数の継承不足といった、ノード単位の起動条件差を優先して確認してください。

allowlistとレジストリ運用

接続できることと、つないでよいことは別です。
manual URL で remote MCP を追加できるクライアントでは、未審査サーバーをそのまま許可すると、認証画面に見慣れた名前が出ただけで信頼してしまいがちです。
allowlist や trusted registry を持っているなら、接続失敗の切り分けでも「その URL は運用対象か」を先に見たほうが早く、同時に事故も減ります。
企業環境では、個別 URL を各端末にばらまくより、ゲートウェイやプロキシで出口を一本化し、登録済みサーバーだけ通す構成のほうが運用の整合が取りやすいのが利点です。

NotionやSupabaseのように、手動で URL を追加して consent を進めるサービスでは、同意画面のクライアント名、要求 scope、redirect URI を見れば、正規の接続かどうかを判定できます。
ここが allowlist 運用と噛み合うと、現場では「URL を知っていれば追加できる」状態から抜けられます。

筆者はSupabase MCPを触るとき、本番プロジェクトではなく検証用プロジェクトにつなぐ運用にしてから安心感が増しました。
MCP は便利なぶん、AI から触れる面積がそのまま広がるので、接続試験の段階で本番データを避けるポリシーが効きます。
認証フローの確認、必要 scope の読み合わせ、tool の列挙、失敗時の再試行までを検証環境で済ませると、接続トラブルの切り分けと権限設計を同時に進められます。
Supabase MCPの公式ドキュメントにも、用途に応じて認証情報の扱いを分ける発想があり、この種のサーバーは「つながるか」だけでなく「どのデータに触れさせるか」まで含めて運用設計したほうが後戻りが減ります。

認証・認可エラーの見方

エラーパターン別チェック

remote 接続で詰まると、全部を「認証が通らない」でまとめてしまいがちですが、実際は失敗点ごとに見る場所が違います。
MCP Authorizationの仕様では OAuth 2.1 ベースの認可フローと PKCE が前提で、code_challengecode_verifier の往復が成立していない接続は通らないのが正しい挙動です。
code_challenge_method=S256 で認可リクエストが出て、トークン交換で code_verifier が送られているかを見れば、実装の土台が合っているかを早い段階で切り分けられます。
未対応なのに接続側の設定で何とかしようとすると、症状だけが散らかります。
ここは 『MCP Authorization』 に沿って、未対応なら接続不可が正と捉えたほうが判断を誤りません。

代表的なエラーは、画面の文言と HTTP ステータス、認可サーバーのログを対にして読むと整理できます。
401 Unauthorized は、アクセストークンが付いていない、期限切れ、対象 API が期待する audience と違う、といった場面で出ます。
MCP サーバーの前段に別 API があり、そこが resource パラメータで対象リソースを切り替える設計だと、認可は終わっていても意図した resource 向けのトークンになっておらず 401 になることがあります。
403 Forbidden は認証は通ったが権限が足りない状態で、scope 不足や tenant 制約のことが多いです。
invalid_scope は要求した scope 名そのものが違うか、その client では付与できない scope を求めています。
invalid_grant は認可コードの再利用、期限切れ、PKCE の code_verifier 不一致でよく出ます。
redirect_uri_mismatch は登録値と実際の callback が 1 文字でも異なると起きますし、state 不一致は別タブでの再試行やセッションの取り違いで表面化します。

筆者が実際に時間を取られたのは redirect_uri_mismatch でした。
見た目では同じ URL に見えたのに、末尾スラッシュの有無だけが違っていました。
先に『Chrome DevTools』の Network で Preserve log を有効にして認可フロー全体を残し、callback に戻る直前のリクエストで redirect_uri のクエリ文字列を確認しました。
その後、認可サーバー側のログで登録済み URI と実際に受けた URI を並べると、 の差がそのまま弾かれていたと分かりました。
ブラウザ側だけ見ていると「だいたい同じ」に見えますが、認可基盤はそこを別物として扱います。
Network ログとサーバーログの両方を突き合わせると、文字列比較で落ちているのか、途中の reverse proxy が書き換えたのかまで追えます。

scope 不足も、見え方が独特です。
接続自体は成功しているのに tools/list の結果が空で、クライアント側では「サーバーはいるが何も出ない」ように見えます。
以前、これに遭遇したときはサーバーの故障を疑いましたが、実際は consent UI に出ていた scope が足りませんでした。
不足していた scope を同意画面で追加したところ、その場で再認可が走り、直後の tool 一覧に必要な項目が現れました。
tool が空のときはサーバー実装より先に、consent UI に表示されている scope 名と、サーバーログで認識されている権限セットを見たほうが早いです。
画面で見える scope 表示と、ログ上の claims や granted scope が一致しているかが分岐点になります。

Authorization - Model Context Protocol modelcontextprotocol.io

consent/scope/redirect_uri/PKCE の4観点

認証・認可エラーは、consent、scope、redirect_uri、PKCE の 4 観点で並べると原因がほぼ散ります。
しかも画面だけ、ログだけでは足りません。
認可ダイアログに何が表示されたかと、実際に送受信されたパラメータの両方が必要です。
MCP Security Best Practicesでも consent UI には少なくとも client 名、scope、redirect_uri が見えるべきとされており、この 3 つが UI に出ていないと利用者側も運用者側も誤接続を見抜きにくくなります。

consent UI では、どの client が何を要求しているかを見ます。
client 名が期待したアプリ名か、scope 表示が広すぎないか、redirect_uri が見覚えのある callback か。
この 3 点がそろっていれば、誤ったアプリや別環境の client に同意していないかをその場で判定できます。
ChatGPT 系の文脈では用語変更にも注意が必要で、OpenAI 側では 2025-12-17 に connectors から apps へ呼称が変わっています。
'apps' と、2026年1月に公式公開されたMCP Appsは同じ語を含みますが、文脈が違います。
前者はChatGPT側の連携 UI の用語、後者は MCP 側のアプリ表示機構です。
認証画面や設定画面の説明でこの二つを混ぜると、どの client に consent したのかを読み違えます。

次に scope は、要求された scope、同意された scope、実際に token に入った scope を切り分けます。
画面では scope 表示が短縮されることがあるので、認可リクエストのクエリとトークン交換後のログを見比べたほうが確実です。
MCP サーバー側で tool 公開条件が scope に結び付いている場合、scope 不足は UI の失敗ではなく機能非表示として現れます。
認可ダイアログが出て完了したのに tool が空、あるいは一部だけ欠けるなら、scope 名の typo より「同意されたが付与されていない」「期待した resource に対する scope ではない」を疑う局面です。
resource パラメータを使う基盤では、scope だけ合っていても対象 resource が違うと結果が空振りになります。

redirect_uri では、登録値、リクエスト値、最終的にブラウザが戻った URL の 3 点をそろえます。
ここは文字列一致で落ちるので、末尾スラッシュ、URL エンコード、http/https、サブドメイン差分を曖昧に扱えません。
『Chrome DevTools』なら Network で該当リクエストを選び、Headers からクエリ文字列をそのまま確認できます。
ページ遷移をまたぐ OAuth フローでは Preserve log を先に入れておくと、認可開始から callback までの連なりを失わず追えます。
ブラウザから見えた値とサーバーが受け取った値のどちらか一方だけでは、途中書き換えの有無を判定できません。

PKCE は、フロー全体が成功したように見えても token 交換で崩れるので、認可ダイアログの有無だけでは判断できません。
見たいのは、認可開始時の code_challengecode_challenge_method=S256、token 交換時の code_verifier、そして invalid_grant 発生時のサーバーログです。
ここが噛み合っていないと、ユーザーは「ブラウザ同意までは進んだのに接続できない」と感じます。
実際には認可コードと verifier の対応関係が崩れているだけで、画面の見え方と裏側の失敗地点がずれているわけです。

💡 Tip

consent UI の client 名、scope、redirect_uri を見て、Network で実際の code_challenge(S256) と callback を追い、サーバーログで code_verifier と付与済み scope を照合する。この順で見ると、認証エラーが UI 側か、認可サーバー側か、MCP サーバー側かを切り分けやすくなります。

client_id/issuerの確認

複数環境をまたぐと、認証エラーの原因はコードより設定ドリフトのほうに寄ります。
dev、stg、prod で似た client を量産していると、client_id は合っているのに issuer が違う、issuer は合っているのに tenant が違う、といった食い違いが起きます。
症状としては 401、403、invalid_grant、scope 不足のどれにも化けるので、エラーメッセージだけでは決まりません。
ここでは client_id を「どのアプリの認可要求かを識別する値」、issuer を「そのトークンを誰が発行したか」、tenant を「どの組織境界に属するか」で分けて見る必要があります。

実務では、環境ごとに設定表を 1 枚作っておくと取り違いが減ります。
見るべき列は多くありません。
環境名、issuer、tenant、client_id、redirect_uri、要求 scope、resource の 7 つが並んでいれば、認証トラブルの大半はその表との照合で進められます。
たとえば prod 用の client_id で stg の issuer に投げている、dev の redirect_uri を prod client に登録している、といったズレは、個別ログを読む前に表で見つかることがあります。
逆にこの表がないと、画面に出るアプリ名だけを頼りに consent してしまい、別 tenant の client に権限を与えていたことに気づきません。

とくに issuer は、同じベンダーの認可基盤でも URL が少し違うだけで別発行者として扱われます。
iss claim と設定値が一致していないとトークン検証で落ちますし、tenant 切り替え型の基盤では audience や discovery の取得先まで連動して崩れます。
client_id の識別では、管理画面で表示される client 名だけでなく ID 自体を記録しておくほうが確実です。
client 名は似た命名になりがちですが、client_id は誤接続を見破る最後の手掛かりになります。

ChatGPT系の接続画面や各種クライアント設定では、説明文に古い「connectors」という用語が残っている記事がまだ流通しています。
現行の UI やドキュメントでは apps という語が前に出るので、OpenAI 文脈の apps とMCP Appsの区別をつけたまま設定表に書くと混乱が減ります。
表の列名も「ChatGPT app 側 client」「MCP App 表示機能」など用途で分けておくと、client_id の紐付け先を見失いません。

APIキー運用の注意

OAuth 2.1 と PKCE の話をしていると見落としがちですが、周辺 API の呼び出しで API キーを併用する構成も珍しくありません。
このとき一番危ないのは、検証の勢いでキーを平文の設定ファイルや共有メモに置いてしまうことです。
MCP サーバーは AI から触れる経路を増やすので、平文保存されたキーが流出すると、単体 API 以上に影響範囲が読みにくくなります。
ローカルの .env をそのままリポジトリに混ぜる、サーバー設定 JSON に直書きする、チャットに貼る、といった運用は事故の入口になります。

代替としては、サーバー側ではシークレットストア、端末側では OS キーチェーンに寄せるのが筋です。
加えて、キー自体の権限は最小化し、用途ごとに分け、短期有効期限のものを使う構成だと、漏えい時の被害を狭くできます。
CI 向けに PAT を分離するSupabaseの運用例が参考になるのは、同じ資格情報を人手操作と自動処理で共有しない発想が明確だからです。
MCP サーバーから外部 API を叩く場合も、開発者の万能キー 1 本で全部まかなうより、読み取り専用と更新系を分離したほうが後の監査で追えます。

API キー運用では、401 の意味も OAuth と少し変わります。
Bearer token の期限切れではなく、単にキーが無効、ローテーション後の旧キーを使っている、許可 IP や対象 API が違う、という落ち方をします。
OAuth 側の consent UI や scope 表示を見てもここは解決しないので、どのリクエストが OAuth トークンで、どれが API キー認証なのかをログで分けて読む必要があります。
MCP サーバーが裏側で複数の資格情報を扱うと、表面上は同じ 401 でも発生源が別です。

認証情報の保存場所を分ける運用は、デバッグでも効きます。
OS キーチェーンやシークレットストアを使っていれば、クライアント再設定時に「何が更新され、何が残っているか」を追跡できますし、平文ファイルを探し回る手間も減ります。
認可フローが整っているのに一部 API だけ失敗する場面では、OAuth の設定より API キーの残骸を疑ったほうが話が早いことがあります。
認証・認可エラーは画面の派手さで OAuth に意識が向きますが、実際の障害点は古い API キー 1 本ということもあります。

ログと開発者ツールで原因を特定する

Claude Desktopのログ/DevTools

Claude Desktopでは、まず接続先ごとのステータス表示を起点にすると、勘ではなく段階で止まり方を見分けられます。
接続済みなのか、認証待ちなのか、ツール取得で止まっているのかが UI に出ているなら、その表示を最初の証拠として扱います。
ここで「つながっていそう」に見えても、実際には initialize の返答までは終わっていて、その後の tools/list が返っていないだけということが珍しくありません。
ステータス表示は、どこまで進んだかを切り分けるための入口です。

その次に開くのが『Chrome DevTools』です。
Chrome for Developersの説明どおり、Network パネルではリクエストが時系列で並び、Preserve log を有効にしておくとページ遷移をまたいでもログを保持できます。
remote 接続の認証でブラウザ遷移が入る場面では、この設定がないとリダイレクトの途中経過が消えます。
私が見る順番はほぼ固定で、まず Console で CORS エラーやスクリプト例外を拾い、次に Network で認証開始からコールバックまでの流れを追い、Storage で Cookie や Local Storage、Session Storage に認証関連の状態が残っているかを確認します。

OAuth と PKCE が絡む remote 接続では、Network を Document 系リクエスト中心に見ていくと流れを追いやすくなります。
認可エンドポイントへの移動、認証後のリダイレクト、コールバック先への戻りが個別リクエストとして並ぶので、Headers で Location、クエリ文字列、レスポンスヘッダを読みます。
PKCE では code_challenge_method=S256 が送られているかをここで確認できます。
途中で 302 が連続しているのに戻り先で失敗するなら、リダイレクトチェーンのどこかで redirect_uri や state の整合が崩れています。
CORS の失敗は Console に先に出ることが多く、Network 側ではリクエストが飛んでいてもレスポンスをアプリが読めていない構図が見えます。

Storage パネルも見逃せません。
remote 認証後にセッション Cookie がセットされていない、あるいは古い値が残っていると、見た目はログイン成功でも MCP 接続だけ失敗します。
Cookie の有無だけでなく、認証直後に値が更新されたかまで追うと、ブラウザ側とサーバー側の認識差が見えてきます。

ℹ️ Note

remote 認証を追うときは、先に『Chrome DevTools』を開いて Network の Preserve log を有効にし、認可フローを走らせると、リダイレクトチェーン、Location ヘッダ、クエリ中の認可コードまで一続きで読めます。

local の stdio 接続でも、Claude Desktop側の情報だけで止めず、サーバーの stderr やスタックトレースと突き合わせると原因が早く固まります。
以前、接続自体は始まっているのにツールが一向に出てこないケースで、サーバー側のエラースタックを読むと、作業ディレクトリが未定義のまま相対パスを解決しようとして落ちていました。
UI だけ見ていると「サーバー起動失敗」にしか見えませんでしたが、実際には起動後の設定読込で ./config 解決に失敗していたわけです。
こういう差は、クライアント表示とサーバーログを並べて初めて見えます。

VS Codeの出力と実行場所差分

VS Codeでは、MCP の失敗が設定ミスなのか実行場所の違いなのかを分けて見る必要があります。
『VS Codeで MCP servers を追加・管理する』 でも、ユーザー設定、ワークスペース設定、remote 環境という実行文脈の違いが前提になっています。
同じ設定値を書いたつもりでも、実際にはローカル PC で動いているのか、Dev Container や SSH 先で動いているのかで、参照される PATH、環境変数、ファイルパスが変わります。

ℹ️ Note

実行場所差分を見るときは、設定ファイルの置き場所そのものより、「その設定がどのプロセスに効いているか」を基準に読むと迷いません。
workspace 設定に書いた相対パスが、remote 側のファイルシステムを指しているのか、ローカルのワークスペースを前提にしているのかで結果が変わります。
ユーザー設定では動くのに workspace 設定では失敗するなら、相対パス解決か trust の境界を疑います。
remote でだけ失敗するなら、そこで実際に見えているバイナリ、証明書、環境変数がローカルと違います。

私が比較するときは、同じサーバー定義を user と workspace で並べて見るより、ログに出た実行コマンド、作業ディレクトリ、環境変数の差分を書き出します。
たとえばローカルでは node が見えているのに remote では見えていない、workspace の相対パスがコンテナ内で存在しない、といったズレはその時点で証拠になります。
設定 JSON を眺めているだけでは見落としやすいところです。

最低限そろえたい標準項目は、timestamplevelrequest_idmethoddurationerror です。
これに接続単位の識別子や user/session 相当の情報を加えると、1 回の接続で何が起きたかをまとめて追えます。
request_id がないと、同時に飛んだ複数リクエストのどれがどのエラーに対応するかが曖昧になりますし、duration がないと失敗ではなく遅延が原因のケースを見落とします。

実際、以前 tools/list が返らずにクライアント側で固まって見える案件がありました。
JSON ログの duration を見ると、initialize は短時間で終わっているのに tools/list だけ閾値を超えて伸びていました。
エラーは出ていなかったので最初は通信を疑いましたが、メソッド別の時間分布を追うとサーバー内部の遅延だと分かります。
さらに下流のログをたどると、ツール定義の組み立て時に DB アクセスが詰まり、接続プール不足で待たされていました。
クライアントからは「ツール一覧が来ない」だけでも、サーバー側で duration を持っていれば、失敗ではなく待ち時間の問題だと切り分けられます。

error フィールドも、単にメッセージ文字列を積むより、codemessage、必要なら stack を分けて持たせたほうが後で読みやすくなります。
JSON-RPC のエラーとアプリ内部エラーを混ぜないためです。
method=tools/listduration 高止まり、error=null なら遅延系、error.code が埋まっていればプロトコルまたは業務ロジックの失敗、というふうに見分けがつきます。

JSON-RPCメッセージの読む順序

MCP のやり取りは、画面の見た目より JSON-RPC 2.0 の request/response として読むと整理できます。
順番としては、まず request の method を見て、次に id が response と対応しているかを確認し、失敗しているなら error.codeerror.message を読む、という流れです。
initializeinitializedtools/list のどこまで進んだかが見えれば、障害点はだいぶ絞れます。

initialize の request が送られて response が返っているなら、少なくとも初期ハンドシェイクの一部は成立しています。
そこから initialized 相当の通知まで進み、さらに tools/list の request が出ているなら、起動不能ではなくツール列挙段階の問題と判断できます。
逆に initialize 自体の response が返っていない場合は、認証前や接続開始、サーバー起動直後の失敗を疑ってください。
id の対応は見落としがちですが、非同期で複数メッセージが並ぶ場面ではここを外すと誤読します。
id=7 の request に対する response を読まずに、別の通知や別 request の結果を見てしまうと、原因の位置がずれます。
エラーがある場合は error.codeerror.message を必ずセットで読みます。
message だけ拾うと人間向け文言に引っ張られますが、code があるとプロトコル違反なのか、メソッド未実装なのか、アプリ内処理失敗なのかを揃えて分類できます。

remote 接続では、HTTP の request/response 観点も並行して持っておくと理解が早まります。
HTTP レベルで 401 や 302 が起きているのか、その先で JSON-RPC エラーが返っているのかは別問題です。
HTTP は通っているが JSON-RPC の tools/list 応答がエラー、という形もあれば、そもそも HTTP の段階で認証リダイレクトに吸われて JSON-RPC 本文まで届いていない形もあります。
プロトコルを一段ずつ分けて読むと、「ネットワークは生きているのにツールが出ない」状態の説明がつきます。

停止ポイント別の観測項目

止まり方を分類するときは、段階ごとに見る場所を固定すると迷走しません。
まず観測したいのは、initialized まで進んだかです。
ここまで到達していなければ、認証、接続開始、サーバー起動、初期ハンドシェイクのどこかに問題があります。
Claude Desktopならステータス表示、DevTools の Console/Network、サーバー起動ログを並べて、initialize request と response があるかを確認します。

次に見るのが、tools/list で止まっているかです。
initialize は返るのにツールが出てこないなら、この段階が本命です。
クライアント側では tools/list request が出ているか、response が返っているか、返っているなら空配列なのかエラーなのかを見ます。
サーバー側では method=tools/list のログ、duration、DB や外部 API 呼び出しの内訳を追います。
ここで遅延が伸びていれば、接続不能ではなくバックエンド処理待ちです。

その先の tool 実行で失敗しているか も別枠で見ます。
ツール一覧が出ているなら、接続そのものは成立しています。
この段階では request payload、引数バリデーション、外部 API 呼び出し、認可スコープ不足を中心に読みます。
クライアント UI では「ツール実行失敗」とひとまとめに見えても、サーバーログには入力値不正、権限不足、下流 5xx など別の原因が残っています。

観測項目を段階別に並べると、実務では次の順で見ることが多いです。

  1. クライアントのステータス情報で、接続開始後にどこまで進んだ表示になっているかを確認する
  2. DevTools の Console で CORS、認証、フロント側例外を拾う
  3. Network で認証リダイレクト、HTTP ステータス、JSON-RPC の request/response を追う
  4. initialize の応答有無、initialized 到達、tools/list 送信と応答有無を切り分ける
  5. サーバー構造化ログで request_id ごとに methoddurationerror を照合する
  6. ツール一覧取得後の失敗なら、tool 実行時の入力と下流依存先ログを読む

この順で見ていくと、「認証で落ちているのか」「initialized までは行ったのか」「tools/list で止まっているのか」「tool 実行だけが落ちているのか」が、感触ではなく記録で分かれてきます。
MCP は Host、Client、Server の層があるので複雑に見えます。
『MCP Architecture overview』 の整理どおり、どの層でどのメッセージが止まったかを証拠で追えば、原因は思ったより狭い範囲に収まります。

よくある症状別: 原因と対処

表示されない/認証ループ/ツール空

「追加したのに出てこない」は、接続以前の単純な設定ミスがまだ残っていることが多いです。
まず疑う順番は、設定 JSON の構文、実行パスまたは URL、allowlist の未登録、クライアントの再読込漏れです。
local ならコマンド名は合っていても実際の実行ファイルが見つからず、remote なら URL の末尾やプロトコル違いで登録だけ通って接続には進んでいない、という形がよくあります。
JSON は見た目では気づきにくいので、整形ツール任せではなく一度パーサに通したほうが早いです。
local のパス確認なら which 、絶対パス指定なら ls -l /path/to/server、remote の死活確認なら curl -i や curl -i のように、一発で存在確認できるコマンドに寄せると迷いません。
allowlist を持つクライアントでは、URL やドメインを足したつもりでも許可側に入っていなければ UI 上は静かに弾かれます。
ここで再設定を繰り返すより、設定ファイルと許可設定を並べて読むほうが早道です。

認証ループは、見た目は同じでも原因が分かれます。
多いのは state 不一致、redirect_uri のミスマッチ、ブラウザ側のサードパーティ Cookie 制限、複数タブで認証を同時に進めたケースです。
『Chrome DevTools』の Network で Preserve log を有効にした状態で認証を開始すると、認可画面への遷移、コールバック、再リダイレクトの流れが残るので、どこで往復しているかを追えます。
MCP Security Best Practicesでは consent UI に client 名、scope、redirect_uri の表示を少なくとも含めるべきだとされていて、実際この3点を見比べると不一致を見つけやすくなります。
対処の順番としては、まず1タブだけでやり直す、次に consent 画面の client 名と redirect_uri を確認する、その後で callback URL の登録値をサーバー側設定と突き合わせる、まだ回るなら Cookie と Storage を消して再試行、という流れが崩れません。
MCP Authorizationでも OAuth 2.1 と PKCE 前提での整合が土台になっているので、認証サーバー側だけ直してクライアント設定が古いまま、という片手落ちが残ると同じ場所で回り続けます。

接続済みに見えるのにツール一覧が空なら、問題は tools/list 周辺に寄っています。
典型例は scope 不足、tools/list の response が空配列、サーバーが capability を宣言していても実装が伴っていない、の3つです。
ログでは initialize は成功しているのに、その後の tools/listresult: [] で返るか、あるいは method 未実装のエラーになっています。
たとえばサーバーログに method=tools/list result=[] と出ていれば接続は通っていて、列挙対象がないか権限条件で落ちている可能性が高いですし、error.codeerror.message に未実装系の内容が出ていれば capability と実装のずれです。
修正は、認可サーバーで付与 scope を見直す、MCP サーバー側で tools/list の戻り値を直接ログに出す、initialize response に含める capability と実装済みメソッドをそろえる、の順で進めると詰まりません。

この手の表示不全では、用語の古さも地味に効きます。
ChatGPT系の Apps まわりで、古い記事の connector 表記を前提に手順を追ってしまい、設定画面の言葉が合わずに遠回りしたことがありました。
2025年12月に connectors から apps へ名称が切り替わっていて、最新版ドキュメントの表記に合わせた途端、どの画面で何を追加するのかがつながりました。
設定項目名が一致しないと、それだけで「表示されない」の原因が別物に見えてしまいます。

クライアント依存の不具合

同じサーバーなのにClaude Desktopでは動くのにVS Codeだけ失敗する、あるいはChatGPT系だけ認証後に無反応になる、という差は珍しくありません。
ここで見るべきなのは、クライアントの好みではなく実行場所、環境変数、trust ポリシー、拡張の互換性です。
MCP は Host と Client の境目で動作が変わるので、「サーバーが壊れている」と一括りにすると外します。

比較観測は、同じサーバーを別クライアントに登録して、起動コマンド、作業ディレクトリ、環境変数、認証方法、ログの停止位置を並べると差が見えます。
VS Codeでは user settings に置いた構成と workspace settings に置いた構成で実行主体が変わることがあり、Remote-SSH を使うとその差がさらに表面化します。
私は一度、VS Codeの Remote-SSH ワークスペースでだけ local MCP が落ちるのに、同じ定義をユーザー設定へ移すと成功することがありました。
原因を追うと、設定ファイルの書き方ではなく「どこでコマンドが起動されるか」が違っていました。
これを実行場所の問題として整理すると、PATH の参照先、依存モジュールの解決先、読んでいる .env の位置まで一気につながります。
VS Codeの公式ドキュメントでも user、workspace、remote の管理軸が分かれていて、同じ JSON に見えても起動環境は同一ではありません。

trust も見逃しやすい点です。
ワークスペースが制限モードに入っていると、拡張や外部プロセス起動が止まり、表面上は「接続失敗」に見えます。
拡張の互換性も同様で、MCP 本体ではなくクライアント側拡張が古い API 前提で動いていると、接続後の一覧表示や approval UI だけ壊れることがあります。
比較するときは、片方で成功した request/response の並びを基準にし、失敗側でどのメッセージが欠けているかを見ると、サーバー共通の問題かクライアント固有の問題かを切り分けられます。

localはNG/remoteはOK

remote はつながるのに local だけ落ちるなら、ネットワークより前の起動条件で止まっています。
local MCP の本質は「クライアントがローカルコマンドを起動できるか」なので、ディレクトリ、実行権限、依存解決の3点をまず見ます。
chmod 不足で実行できない、想定した作業ディレクトリで起動しておらず相対パスが崩れる、node_modules や Python 仮想環境をその場所から読めない、といった問題が代表例です。
remote で成功している事実は、サーバー機能そのものより、配布形態と起動条件の差を示しています。

切り分けは、クライアントを経由せずローカルで単体起動するのが最短です。
ターミナルで実際のコマンドをそのまま実行し、stderr に何が出るかを見ます。
ここで落ちるならクライアントではなく起動コマンドの問題ですし、単体では立ち上がるのにクライアント経由で落ちるなら、環境差分が本命です。
たとえばシェルでは読まれている PATH が GUI アプリ起動では入っていないことがありますし、ワークスペース起動では cwd がプロジェクトルートではないことがあります。
私は remote では動くのに local だけ失敗する案件で、コード側を疑う前に単体起動へ戻したことで、相対パスで読んでいた設定ファイルが見つかっていないだけだと気づけました。
local は「同じマシン上だから単純」という印象に反して、起動者が誰か、どのディレクトリから起動されたかで挙動が変わります。

比較の観点を固定すると、local と remote の差は読み解きやすくなります。
remote は URL と認証の整合が中心で、local はコマンド実行とローカル依存の整合が中心です。
したがって remote 成功をもって「サーバー実装は正しい」と言い切るのではなく、「HTTP 配布の経路では成立している」と捉えたほうが切り分け精度が上がります。

💡 Tip

local 単体起動で見るべきは、終了コード、stderr の先頭数行、起動時の cwd、読み込まれた環境変数です。ここが分かると、クライアント設定を何度触るより早く詰まりが取れます。

Apps対応クライアントの前提条件

MCP に接続できているのに Apps の UI が出ない場合、まず疑うべきはクライアントがMCP Appsに対応しているかどうかです。
MCP 自体の接続と、接続後に UI をレンダリングできることは別要件です。
2026年1月にMCP Appsが公式公開されて以降は、ツール呼び出しだけでなく UI ホスト機能の有無がクライアント差としてはっきり出るようになりました。
つまり、サーバーが app 用メタデータや UI を返せても、クライアント側にその表示機能がなければ何も出ません。

UI が出る前提を満たしていても、iframe 通信と権限ポリシーで止まることがあります。
Apps 系 UI は sandboxed iframe と postMessage をまたぐ構成になりやすく、ホスト側の許可設定と埋め込み側の通信条件が揃わないと、接続は見えているのに画面だけ空になります。
ここでは Network より Console に手掛かりが出やすく、埋め込み拒否、メッセージ受信失敗、必要オリジンの不一致といった形で現れます。
2026年以降は、MCP 接続の有無だけでなく、Apps ホストとしての要件を満たしているかまで見ないと、UI 表示の不具合を説明しきれません。

ChatGPT系の画面で Apps を探しているのに、古い connector 前提の記事を見ていると、そもそも探す場所がずれます。
私自身、表記が切り替わった後もしばらく古い用語の感覚を引きずっていて、接続設定と UI 機能の境界を誤解していました。
最新版の apps 表記に揃えて読み直すと、どこまでが MCP 接続設定で、どこからが Apps ホスト機能なのかが分かれ、UI が出ない原因を「接続失敗」ではなく「クライアント前提未充足」として捉え直せました。
ここが整理できると、ツールは見えるのに UI は出ない、という一見ちぐはぐな症状にも筋が通ります。

安全にデバッグするための運用ルール

本番回避と最小権限

デバッグで最初に固定したいのは、本番データにつながらない状態を先に作ることです。
Supabaseが development や testing 用の分離環境で検証する運用を勧めているのと同じで、MCP でも production のNotionワークスペースや本番 DB を直接触る構成は、原因調査の段階から外しておいたほうが事故を減らせます。
接続不良を追っている最中は、URL の打ち分け、OAuth の再同意、権限の付け替えを短時間で何度も試すので、正常系より誤操作が入り込みやすいからです。

本番データを避ける・最小権限での検証

私も一度、認証先の切り替えを見ている最中に、検証用のつもりで開いたフローがプロダクションのNotionワークスペースへ向きかけたことがありました。
そのときは専用の検証環境を別に持っていて、しかもトークンを閲覧系の最小権限に絞っていたので、接続が成立しても更新操作まで進まずに止まりました。
接続確認の場面では「通るかどうか」だけに意識が寄りがちですが、実際には通ってしまった先がどこかのほうが被害を左右します。

権限設計も同じ発想で、まず必要最小の scope だけを与え、期限付きトークンを使います。
読み取り確認の段階で書き込み権限まで渡す理由は薄く、トークンの有効期間も長く持たせないほうが、流出時の影響範囲を狭められます。
運用では短期トークンを基本にして、更新時にローテーションし、だれがいつ発行・失効させたかを追える状態にしておくと、接続不良の調査と権限管理が分離されません。
MCP の認可画面では、少なくとも client 名、scope、redirect_uri が見えていることに意味があり、ここで「検証用クライアントなのに本番 redirect_uri を踏んでいないか」を毎回確認できる形にしておくと、事故の芽を早い段階で摘めます。

trusted sourceとsandbox

MCP サーバーを増やすときは、動けばよいではなく、どこから来たサーバーかを先に確認します。
local MCP はローカルコマンドを実行し、remote MCP は認証済みの外部接続を張るので、どちらも追加時点で攻撃面が広がります。
個人利用でも配布元リポジトリやメンテナの実体を確かめ、企業なら trusted registry や allowlist に載ったものだけ通す形に寄せたほうが、トラブルシュートより前の段階で危険な混入を止められます。

組織運用では、クライアントごとに自由追加させるより、ゲートウェイやプロキシを通して接続先を制御したほうが整います。
認証方式、TLS、許可ドメイン、ログ取得点を一か所に集められるためです。
MCP は接続経路が Host、Client、認証、転送方式にまたがるぶん、野良サーバーが混じると「つながらない」の調査と「つないではいけない」が同時発生して、切り分けが崩れます。
trusted source に限定するのは利便性を削るためではなく、デバッグ時の変数を減らすためでもあります。

local サーバーは sandbox 前提で扱うほうが安全です。
コンテナ、仮想環境、専用ユーザーでの権限分離を使い、読めるディレクトリと書けるディレクトリを限定すると、誤ったサーバーを起動しても被害が広がりません。
ファイルシステム全体に触れられる状態で stdio サーバーを試すと、単なる接続検証のつもりがローカル資産の露出につながります。
私の手元でも、検証用の local サーバーは作業ディレクトリだけを bind mount したコンテナに寄せたほうが、設定ファイルの参照先が明確になり、権限エラーの意味も読み取りやすくなりました。
安全性だけでなく、どこまで見えているかがはっきりするので、デバッグ結果の解像度も上がります。

⚠️ Warning

local MCP を sandbox に入れると、接続失敗時に「依存不足なのか」「権限不足なのか」「想定外のファイル参照なのか」が切り分けやすくなります。広い権限で通してしまうより、失敗の意味が明確に出ます。

トークン保護・監査・ドリフト監視

トークンは設定値ではなく秘密情報として扱います。
平文の環境変数に長期間置きっぱなしにするより、OS のキーチェーンやシークレット管理基盤に入れ、取り出し経路を限定したほうが漏えい面を減らせます。
Claude系クライアントやVS Code周辺で local 実行を試していると、環境変数で手早く通したくなる場面がありますが、そのままシェル履歴、設定ファイル、スクリーンショットに残ることがあるので、暗号化ストアへ寄せたほうが後始末が軽く済みます。

監査ログも、セキュリティ用の付加物ではなくデバッグの一次情報です。
だれがどのサーバーを追加し、どの scope で認可し、接続試行が成功したのか失敗したのか、その記録がないと「昨日までは動いていた」の中身を特定できません。
接続試行、認証失敗、トークン更新、サーバー設定変更は少なくとも残しておきたいところです。
失敗ログだけでなく成功ログも必要で、成功条件の変化が見えると、障害なのか設定変更の影響なのかを切り分けられます。

加えて、設定ドリフト監視が効きます。
MCP の不調は、サーバー実装の破綻より、URL、redirect_uri、許可 scope、起動コマンド、プロキシ設定の小さなずれで起きることが多いからです。
構成をコード化して差分検知し、想定外の変更が入ったらアラートを出す運用にしておくと、「誰かが直したつもりで別の箇所を変えた」状態を見逃しません。
接続試行の急増や失敗率の上昇も合わせて監視すると、トークン失効や認証基盤側の変更を早めに拾えます。

将来の変化点

ℹ️ Note

そのため、今のベストプラクティスは個別ツールの小技として覚えるより、本番を避ける、権限を削る、信頼済みソースに限定する、実行領域を閉じる、変更を記録するという軸で捉えたほうが崩れません。
セッション管理やゲートウェイの実装が進んでも、この軸に沿った運用はそのまま移植できます。
逆に、手元で一度通った設定を恒久運用へ持ち込むやり方は、新しい接続方式や管理面の進化に追従できず、デバッグの成功体験がそのまま事故の入口になります。

詰まったら、10分だけ切り分け順を固定してください。
最初に local か remote かを決め、次にサーバー単体起動、クライアント設定、認証(consent、scope、redirect_uri、PKCE)、最後に tools/list の順で確認します。
運用面では、OpenAI系の connectors→apps への呼称変更やMCP Apps公開後の表記更新を前提に、remote のトランスポート名も公式ドキュメントに合わせてそろえてください。
継続運用では structured logging と /health・/version の導入を先に進め、企業では gateway/proxy と registry を整えると、次の障害で迷う場所が一段減ります。

What is the Model Context Protocol (MCP)? - Model Context Protocol modelcontextprotocol.io

この記事をシェア

A
AIビルダー編集部

AIビルダーの編集チームです。AI開発ツールの最新情報と使い方を発信しています。

関連記事

ワークフロー

MCPサーバー設定 入門|AI連携の始め方

ワークフロー

MCPは、AIアプリと外部ツールやデータソースをつなぐ共通規格で、チャットの外にあるファイル、API、業務システムを同じ作法で扱えるようにする土台です。私も最初はローカルのMarkdownメモをFilesystem系サーバーで読ませるところから始めましたが、設定を足して再起動するとツール一覧に現れ、

Claude Code

Claude Code VS Code連携の設定と使い方

Claude Code

『VS Code』で『Claude Code』を使い始めるなら、いちばん近道なのはVisual Studio Marketplaceから公式拡張を入れて、そのままパネルを開く流れです。

Claude Code

Claude Code vs Cursor 比較|違いと使い分け

Claude Code

Claude Codeと『Cursor』はどちらもAIで開発を加速できる道具ですが、触ってみると得意な仕事がはっきり違います。VS Code歴の長い筆者は両方を試してきましたが、細かな実装修正をテンポよく詰める場面では『Cursor』の即応性が気持ちよく、

Claude Code

Claude Code 使い方|インストールと基本操作【2026】

Claude Code

Claude Codeは、ターミナルでコードを読み、編集し、コマンド実行まで任せられるぶん、最初の導入順を間違えると「便利そうだけど怖い」で止まりがちです。ここでは、Mac・Windows・Linuxそれぞれでの安全な入れ方から、初回起動、