メインコンテンツまでスキップ

MCPのテストとデバッグ

MCP サーバー・クライアントをテストし、問題を切り分けるための手法とツールを解説します。プロトコルの仕組みは MCPプロトコル基礎 - JSON-RPCとステートレス設計、HTTP のヘッダーは トランスポート - stdioとStreamable HTTP、ツールの設計は ツール設計パターンとアンチパターン を参照してください。ここでは「作ったものが正しく動くことを、どの層で、どの道具で確かめるか」に絞ります。

1. このドキュメントで扱うこと​

  • MCP Inspector
  • CI での利用
  • ツールハンドラーの単体テスト
  • スキーマの検証
  • コンフォーマンステスト
  • curl での再現
  • 認可のテスト
  • stdio のデバッグ
  • LLM を含む評価
  • セキュリティテスト

MCP Inspector​

MCP Inspector は、MCP サーバーをテスト・デバッグするための参照ツールです。@modelcontextprotocol/inspector という 1 つのパッケージが、1 つのバイナリで 3 つのクライアントを提供します。Node 22.19.0 以降が必要で、npx で直接実行できます。

クライアント起動用途
Web (既定)npx @modelcontextprotocol/inspectorブラウザ上の GUI。最も機能が豊富
CLInpx @modelcontextprotocol/inspector --cliスクリプト、CI、コーディングエージェント向けの機械可読なクライアント
TUInpx @modelcontextprotocol/inspector --tuiブラウザを使えない・使いたくないときの端末 UI

3 つは共通のコアの上に作られているため、トランスポート、設定ファイル、ディスク上の OAuth の状態、プロトコルの世代 (レガシーか 2026-07-28 か) の交渉が、どのクライアントでも同じに動きます。パッケージには、接続を張ったまま複数のコマンドを実行できる実験的な mcpdo も含まれます。

# ローカルの stdio サーバーを起動して Web UI で開く
npx @modelcontextprotocol/inspector node path/to/server/index.js

# リモートの HTTP サーバーを指定する
npx @modelcontextprotocol/inspector --server-url https://api.example.com/mcp --transport http

Web の起動時には、起動ごとの API トークン付きの URL が表示されます。Inspector は OAuth トークンや stdio の env: の値を、OS のキーチェーンがあればそこに、なければ ~/.mcp-inspector/secrets.json に保存します。後者は鍵を指定しない限り暗号化されません。共有マシンや CI では保存先に注意してください。

プロトコルの世代 (Protocol Era)​

2026-07-28 版でプロトコルが大きく変わったため、Inspector はプロトコルの世代を、トランスポートとは独立したサーバーごとの設定として扱います。legacy、auto、modern の 3 値があり、CLI と TUI では --protocol-era <legacy|auto|modern> で 1 回の実行に限って上書きできます。

値接続時の動作
legacy (既定)通常の initialize。プローブは行わない
autoまず server/discover を試し、新世代として認識できなければ initialize にフォールバックする
modern2026-07-28 に固定する。フォールバックしないため、旧世代のサーバーには明示的に失敗する

既定が auto ではないのは、デバッグ用のツールが勝手にプローブを送らないためです。server/discover のプローブは、何も返さない旧世代の stdio サーバーで止まり、読みたい通信の記録も汚します。新世代のサーバーを確認するときは、modern か auto を自分で選ぶ必要があります。既定のまま新世代のサーバーに接続して「動かない」と判断しないよう注意してください。

世代によって、Web の各タブの動きも変わります。

機能レガシー新世代 (2026-07-28)
ログlogging/setLevel でセッション単位に設定リクエストごとに _meta の io.modelcontextprotocol/logLevel を付ける
リソースの購読resources/subscribesubscriptions/listen (ストリームの状態がバッジで見える)
Taskscapabilities.tasks、tasks/list、tasks/result拡張 io.modelcontextprotocol/tasks。tasks/get のポーリングで結果が埋め込まれる
サーバーからの質問サーバーからのリクエストinput_required の結果とリトライ (MRTR)
キャンセルnotifications/cancelledStreamable HTTP ではそのリクエストの応答ストリームを閉じる
セッションMcp-Session-Id と DELETEセッションなし。リクエストごとに完結

Tools タブは、x-mcp-header の注釈を持つツールに「city -> Mcp-Param-City」のようなヘッダーの対応を表示し、注釈が不正で除外されるツールには、その理由を表示します。Network タブは HTTP の層 (ミラーされた Mcp-* ヘッダーの強調表示)、Protocol タブは JSON-RPC の層 (-32020 HeaderMismatch、-32021 MissingRequiredClientCapability、-32022 UnsupportedProtocolVersion を型付きのエラーとして) を見せます。HTTP のステータスと JSON-RPC のエラーを、層を分けて確認できます。

CI での利用​

CLI クライアントは、1 回の実行で「接続し、--method で指定した 1 つのリクエストを送り、結果を出力して終了する」ように動きます。そのため CI のジョブやシェルのパイプライン、コーディングエージェントに向いています。

# ツールの一覧を取得して終了する
npx @modelcontextprotocol/inspector --cli node build/index.js --method tools/list

# リモートの HTTP サーバーのツールを呼び、jq に渡す
npx @modelcontextprotocol/inspector --cli https://api.example.com/mcp --transport http \
--method tools/call --tool-name get_weather --tool-arg city=Boston --format json | jq .result

主なメソッドは initialize (接続だけの確認)、tools/list、tools/call、resources/list、resources/read、prompts/list、prompts/get などです。--format json は、バナーなしで JSON のオブジェクトを 1 つだけ標準出力に出すため、そのままパイプで処理できます。--tool-arg key=value は値を JSON として解釈できれば数値などに変換し、できなければ文字列として送ります。文字列のままにしたいときは、引数のオブジェクト全体を渡す --tool-args-json を使います (2 つのフラグは同時に使えません)。

終了コード​

CI で原因別に分岐できるよう、失敗の種類ごとに終了コードが決まっています。

コード意味
0成功
1使い方の誤りや想定外のエラー
3サーバーが認証を要求している (HTTP 401/403、認可エラー)
4サーバーに到達できない (DNS、接続拒否、タイムアウト)
5ツールのエラー。tools/call が isError: true を返した、またはツールが存在しない
6--strict が、ツールのスキーマの移植性に関するエラーを検出した

ほかに、MCP Apps の確認用 (--app-info) や Skills の検証用 (--verify) の終了コードがあります。0 以外で終了するときは、標準エラーに {"error": {"code": "auth_required", ...}} の形の JSON が 1 行出るため、2>&1 | tail -1 | jq .error のように機械的に解析できます。isError: true のツール結果は、ペイロードを出力したうえで終了コード 5 になるため、&& でつないだ後続のコマンドが失敗した呼び出しの後に進みません。

CI では、対話的な OAuth を絶対に開始させないことが重要です。--stored-auth-only を付けると、対話的な OAuth もステップアップも始めず、ブラウザも開かず、保存済みのトークンがなければ直ちに auth_required で失敗します。付けない場合も、標準入力と標準エラーのどちらも TTY でなければ、コールバックを 15 分待ち続けるのではなく、即座に auth_required で失敗します。

set -euo pipefail

# サーバーに到達でき、期待するツールが公開されていなければビルドを失敗させる
mcp-inspector --cli --config ./ci-servers.json --server my-server \
--stored-auth-only --method tools/list --format json \
| jq -e '.result.tools | map(.name) | index("get_weather")' > /dev/null

tools/list --strict は、ツールのスキーマの移植性の問題 (パス、問題点、修正案) を標準エラーに出し、エラー相当のものがあれば終了コード 6 で失敗します。スキーマの検証を CI のゲートにするときに使えます。

ツールハンドラーの単体テスト​

MCP のテストの大部分は、プロトコルではなくビジネスロジックのテストです。ツールのハンドラーを、「入力を受け取り、結果を返す関数」として、プロトコルの層から切り離して書くと、言語標準のテストフレームワークだけで素早く検証できます。

  • ビジネスロジック (API を呼ぶ、計算する) は、MCP の型を知らない関数に置く
  • ツールの登録部分は、その関数を呼んで content / structuredContent / isError に詰め替える薄い層にする
  • 外部 API は、モックではなく fake やテスト用のサーバー (Go なら httptest) に差し替える。方針は Goのテスト を参照

プロトコルの層は、SDK のテストクライアントや Inspector の CLI で、少数の代表的な呼び出しだけを確認します。

スキーマの検証​

ツールは inputSchema を必須で持ち、outputSchema を任意で持ちます。outputSchema がある場合、サーバーはスキーマに適合する構造化結果を返さなければならず、クライアントは検証することが推奨されます。テストでは次を確かめます。

  • inputSchema が有効な JSON Schema のオブジェクトである (null は不可)。引数のないツールは { "type": "object", "additionalProperties": false } が推奨される
  • スキーマの方言を $schema で指定しない場合は 2020-12 として扱われる
  • 実際のツールの結果の structuredContent が、outputSchema に適合する
  • 構造化結果を返すツールは、互換性のため、シリアライズした JSON を TextContent にも入れる
  • x-mcp-header の注釈は、プリミティブ型 (文字列、整数、真偽値) のプロパティにだけ付け、number 型や配列の中のプロパティには付けない。不正な注釈を持つツールは、Streamable HTTP のクライアントによって tools/list から除外される

outputSchema への適合は、ツールを実際に呼んだ結果を JSON Schema の検証ライブラリに通すテストとして、単体テストの中に入れておくと、スキーマと実装のずれを早く見つけられます。

コンフォーマンステスト​

仕様への準拠は、SEP (仕様の変更提案) の標準化の過程にも組み込まれています。SEP-2484 により、観測できるプロトコルの挙動を変える標準化トラックの SEP は、Accepted から Final へ進む条件として、適合性のテストのシナリオが conformance リポジトリにマージされていることが求められます。シナリオには、SEP の仕様部分にある MUST / SHOULD などの要件を、確認項目か、除外の理由に対応づけるトレーサビリティのファイル (sep-NNNN.yaml) が付きます。

実装する側から見ると、次の意味があります。

  • コンフォーマンスのスイートは、公式 SDK に限らず、コミュニティの SDK や独自の実装でも実行でき、適合率を報告できる
  • 仕様の文章とテストが食い違う場合は、仕様が優先され、テストの側が不具合として扱われる
  • SDK の階層 (tier) の評価は、リポジトリの最新ではなく、固定されたコンフォーマンスのリリースに対して行われる

実行の手順や CLI のオプションは、conformance リポジトリ (modelcontextprotocol/conformance) の README と CONTRIBUTING を参照してください。自作の SDK やゲートウェイを新世代に対応させるときは、このスイートを回帰テストに組み込むと、仕様の解釈のずれを早く見つけられます。

curl での再現​

2026-07-28 版の Streamable HTTP は、1 つのリクエストが 1 つの POST で完結するステートレスな形です。そのため、セッションを張らずに curl で直接、問題を再現できます。ただし、必須のヘッダーと _meta のフィールドを、すべて付ける必要があります。

  • Accept に application/json と text/event-stream の両方を含める
  • MCP-Protocol-Version: 2026-07-28。本文の _meta の io.modelcontextprotocol/protocolVersion と一致させる (不一致は 400 と HeaderMismatch)
  • Mcp-Method: JSON-RPC の method
  • Mcp-Name: tools/call なら params.name、resources/read なら params.uri、prompts/get なら params.name
  • 本文の _meta に、io.modelcontextprotocol/protocolVersion と io.modelcontextprotocol/clientCapabilities (必須)、io.modelcontextprotocol/clientInfo (推奨)

ヘッダーの値が ASCII として安全に表せない場合は、=?base64?...?= の形式で Base64 エンコードします。

curl -sS -i https://example.com/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-H 'MCP-Protocol-Version: 2026-07-28' \
-H 'Mcp-Method: tools/call' \
-H 'Mcp-Name: get_weather' \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_weather",
"arguments": { "location": "Seattle, WA" },
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientInfo": { "name": "curl", "version": "0" },
"io.modelcontextprotocol/clientCapabilities": {}
}
}
}'

応答が text/event-stream の場合、通知に続いて最終の応答が SSE のイベントで届きます (-N を付けるとバッファリングを避けられます)。診断に役立つ応答は次のとおりです。

症状HTTPJSON-RPC のエラー原因の例
ヘッダーと本文が食い違う、必須ヘッダーがない400-32020 HeaderMismatchMcp-Name と params.name の不一致
_meta の必須フィールドがない400-32602protocolVersion や clientCapabilities の欠落
必要なクライアントの機能が宣言されていない400-32021Elicitation を要求するのに clientCapabilities に宣言がない。data.requiredCapabilities に不足分が入る
サーバーが対応しない版400-32022 UnsupportedProtocolVersiondata.supported に対応する版の一覧が入る
未実装のメソッド404-32601Method not found
Origin が不正403(なし、または id のない応答)DNS rebinding の対策で拒否された

Mcp-Session-Id や Last-Event-ID、GET でのストリームの取得は、この版では廃止されています。新世代のみ対応のサーバーは、GET や DELETE に 405 を返し、2025 年版以前のヘッダーは無視します。古い手順を curl で試して失敗しても、サーバーの不具合とは限りません。

サーバーが旧世代の initialize と新世代の両方を受けるかどうかを調べるには、新世代のリクエストを最初に送ります。400 のときは本文を読み、認識できる新世代のエラーなら新世代のサーバー、本文が空か認識できないエラーなら旧世代のサーバーです。サポートするバージョンを知りたいだけなら、server/discover を呼びます。

認可のテスト​

認可は、成功する経路よりも失敗する経路のテストが重要です。仕様の詳細は MCPの認可 - OAuth 2.1とメタデータ探索 を参照し、ここでは確認の観点を挙げます。

  • トークンなしのリクエストが 401 になり、WWW-Authenticate ヘッダーの resource_metadata が保護リソースのメタデータを指している
  • スコープが不足したリクエストが 403 (insufficient_scope) になり、必要なスコープが WWW-Authenticate の scope で返る
  • 別のリソース向けに発行された (audience が異なる) トークンが拒否される。トークンの素通し (token passthrough) を許さない
  • 期限切れ、失効済みのトークンが 401 になる
  • tools/list が、リクエストに提示された認可に応じた結果を返す (接続ごとの状態に依存しない)
  • 一覧に見えないツールでも、tools/call のたびにスコープと権限を検証している

Inspector は、全クライアントで OAuth の流れ全体 (401 → メタデータの探索 → クライアントの登録または識別 → ブラウザでの認可 → コールバック → トークンの交換と再試行) を実装しています。接続中に 401 や 403 insufficient_scope が返ったときは、再認可やスコープのステップアップを行い、失敗したリクエストを再試行します。Web では確認のダイアログ、CLI では標準エラーでの Proceed with step-up authorization? [y/N] の確認が出ます。接続情報のパネルで、認可の状態、クライアントの登録の種類、付与されたスコープを確認できます。

CLI と TUI の OAuth のコールバックは、既定で http://127.0.0.1:6276/oauth/callback です。リダイレクト URI の事前登録が必要な IdP では、これを登録します。localhost と 127.0.0.1 は認可サーバーから見て別の URI なので、完全一致させてください。コールバックは、loopback のホストにしか bind できません。

stdio のデバッグ​

stdio のサーバーは、クライアントが子プロセスとして起動し、標準入出力で通信します。次の点が、デバッグの勘所です。

  • stdout にログを書かない: stdout は MCP のメッセージ専用です。print や console.log を混ぜると、プロトコルが壊れます。ログは stderr に出し、ホストアプリケーションに取り込ませます
  • 作業ディレクトリは未定義のことがある: クライアントの設定から起動すると、作業ディレクトリが / (macOS) などになり得ます。設定ファイルと .env では絶対パスを使います
  • 環境変数は一部しか引き継がれない: 引き継がれる集合はプラットフォーム依存です。必要なものは、クライアントの設定の env で明示します
  • 起動の失敗: 実行ファイルのパス、必要なファイルの欠落、権限、設定の JSON の構文エラー、環境変数の欠落を順に確認します。command は絶対パスにして試します
  • 設定やコードを変えたら、クライアントを再起動する: Claude Desktop では、ウィンドウを閉じるだけでは足りず、完全に終了して開き直します

Claude Desktop のログは、macOS が ~/Library/Logs/Claude、Windows が %APPDATA%\Claude\logs に書かれます (mcp*.log)。developer_settings.json に {"allowDevTools": true} を置くと、Chrome の開発者ツールで、クライアント側のエラーやメッセージのペイロードを確認できます。他のクライアントは、それぞれのドキュメントでログの場所を確認してください。

接続に失敗するときは、次の順に切り分けます。

  1. クライアントのログを確認する
  2. サーバーのプロセスが動いているか確認する
  3. Inspector で、サーバーを単独で起動して接続する
  4. server/discover で、サーバーが対応するプロトコルのバージョンを確認する。UnsupportedProtocolVersionError (-32022) は、data に対応するバージョンの一覧を持つ
  5. リクエストの _meta に、必須の io.modelcontextprotocol/protocolVersion と io.modelcontextprotocol/clientCapabilities があるか確認する。欠けていると -32602 で拒否される

HTTP のサーバーでは、stderr がクライアントに取り込まれません。サーバー側のログ基盤や OpenTelemetry (詳細は MCPの可観測性と監査) と、curl やブラウザの開発者ツールの Network パネルで確認します。

LLM を含む評価​

ここまでのテストは、決定的に結果が決まるものです。一方、ツールが実際に使われるかどうかは、モデルがツールの名前と description を読んで選ぶため、決定的ではありません。ツールの設計の良し悪しは、モデルを含めて評価します。

  • 評価用のプロンプトの集合を用意し、期待するツールが選ばれるか、引数が正しく組み立てられるかを記録する
  • description やスキーマを変更したとき、同じプロンプトの集合で再実行し、選択の変化を比較する。変更の前後で正答率が下がっていないかを確かめる
  • 似た役割のツールを増やしたときに、誤って別のツールが選ばれないかを確かめる
  • 結果のサイズ (トークン数) も記録する。ツールの定義と結果は、モデルのコンテキストを消費する (コンテキスト肥大問題と対策)
  • モデルの出力は揺れるため、1 回の成否ではなく、複数回の試行の割合で判断する

評価は、ツールの追加や description の変更のたびに回す回帰テストとして、CI に組み込める形にしておきます。モデルを使う評価は、コストと時間がかかるため、単体テストやプロトコルのテストとは分けて、頻度を変えて実行します。

セキュリティテスト​

セキュリティは、機能のテストとは別の観点で確認します。詳細な脅威は AIエージェント特有の脅威 - プロンプトインジェクションとツール汚染 と MCPセキュリティベストプラクティス - 攻撃と対策 を参照してください。

  • ツール汚染: ツールの description や結果に、モデルへの指示が紛れ込んでいないか。Inspector で、モデルを介さずにツールの一覧を読んで確認する
  • 過剰なスコープ: scopes_supported にすべてのスコープを載せていないか。最小のスコープから始め、必要になったときに WWW-Authenticate でステップアップできるか
  • 入力の検証: 不正な型、範囲外の値、パストラバーサル、巨大な入力に対して、ツールがエラー (isError: true) を返し、クラッシュしないか
  • 出力の取り扱い: ツールの結果に、秘密情報や内部の情報が含まれないか
  • ヘッダーと本文の不一致: Mcp-Name や Mcp-Param-* と本文が食い違うリクエストを 400 で拒否できるか。ゲートウェイがヘッダーで判断し、サーバーが本文で実行する構成では、この拒否が前提になる
  • Origin の検証: 不正な Origin を 403 で拒否できるか (ローカルの HTTP サーバー)
  • SSRF: 認可のメタデータの探索で、クライアントが内部のアドレスに接続しないか (サーバーサイドで動くクライアントの場合)

2. テストのレベル​

レベル対象ツール・手法
単体ツールのロジック言語標準のテストフレームワーク
プロトコルリクエスト・レスポンスの形式MCP Inspector、curl、SDK のテストクライアント
認可メタデータ、トークン検証、スコープInspector の認可機能、OAuth テストクライアント
適合性仕様への準拠コンフォーマンステスト
エンドツーエンドLLM がツールを正しく使えるか実モデルでの評価プロンプト

下のレベルほど速く、安く、決定的に動きます。バグの大半は単体テストで見つけ、プロトコルと認可のテストは、代表的な経路と失敗の経路に絞り、モデルを使う評価は、ツールの定義を変えたときに回します。

3. 開発者がつまずきやすいポイント​

Inspector の既定が新世代ではない​

Inspector の既定のプロトコルの世代は legacy です。2026-07-28 の新世代のサーバーを legacy で試すと、initialize が拒否されて接続できません。--protocol-era modern (または auto) を指定します。逆に、auto を既定にしていると、旧世代の stdio サーバーでプローブが止まることがあります。

stdout にログを出して、プロトコルを壊す​

stdio のサーバーで標準出力に書くと、JSON-RPC のメッセージに混ざって、クライアントが解釈できなくなります。デバッグの print は、stderr に出します。

新世代の curl で、ヘッダーか _meta を付け忘れる​

Mcp-Method、MCP-Protocol-Version、Accept (両方の型)、本文の _meta のどれが欠けても 400 で拒否されます。2025 年版で使っていた Mcp-Session-Id を付けても、無視されるだけです。エラーの本文の code で、何が不足しているかを読みます。

ヘッダーと本文の値を食い違わせる​

Mcp-Name と params.name、MCP-Protocol-Version と _meta の protocolVersion は、一致していなければなりません。Base64 の形式 (=?base64?...?=) を使うときは、サーバーがデコードしてから本文と比較します。手で組み立てるときに、エンコードを間違えやすい箇所です。

CI で対話的な OAuth が始まって、ジョブが止まる​

TTY のない環境では、CLI は auth_required で直ちに失敗しますが、TTY のあるランナーでは、ブラウザの認可を待ち続けます。CI では --stored-auth-only を付け、事前に取得した認証情報を使います。

失敗したツール呼び出しを、成功として扱う​

tools/call が isError: true を返しても、JSON-RPC としては成功です。CLI は終了コード 5 を返しますが、SDK のテストクライアントでは、結果の isError を自分で検査します。プロトコルのエラー (不明なツール、不正な形式) と、ツールの実行エラー (入力の検証、API の失敗) は、区別して期待値を書きます。

モデルの 1 回の出力で、評価の良し悪しを決める​

モデルを含む評価は、同じ入力でも結果が揺れます。1 回の成否ではなく、複数回の試行の割合と、変更の前後の差で見ます。

4. コード例​

Inspector の CLI で、サーバーを検査する​

# 接続だけを確認する (serverInfo、protocolVersion、capabilities)
mcp-inspector --cli node build/index.js --method initialize

# 新世代のリモートサーバーを、世代を固定して確認する
mcp-inspector --cli https://api.example.com/mcp --transport http \
--protocol-era modern --method tools/list --format json | jq '.result.tools[].name'

# 構造化結果を返すツールを呼ぶ (引数は JSON で渡す)
mcp-inspector --cli https://api.example.com/mcp --transport http \
--method tools/call --tool-name get_weather \
--tool-args-json '{"location":"Seattle, WA"}' --format json

# スキーマの移植性をゲートにする (エラー相当があれば終了コード 6)
mcp-inspector --cli node build/index.js --method tools/list --strict > /dev/null

終了コードで分岐するシェルスクリプト​

if out=$(mcp-inspector --cli "$URL" --transport http \
--stored-auth-only --method tools/list 2>err.json); then
echo "$out" | jq '.result.tools | length'
else
case $? in
3) echo "認証が必要: 事前に Web の Inspector でサインインする" ;;
4) echo "サーバーに到達できない" ;;
*) jq .error < err.json ;;
esac
fi

ツールのハンドラーの単体テスト (Go)​

ビジネスロジックを、MCP の型を知らない関数に置いておけば、テーブル駆動テストで、プロトコルなしに検証できます。

package weather

import (
"context"
"errors"
"testing"
)

var ErrInvalidLocation = errors.New("location is required")

type Result struct{ Summary string }

type Client interface {
Fetch(ctx context.Context, location string) (Result, error)
}

// Forecast はビジネスロジック。MCP の型に依存しない。
func Forecast(ctx context.Context, client Client, location string) (Result, error) {
if location == "" {
return Result{}, ErrInvalidLocation
}
return client.Fetch(ctx, location)
}

type fakeClient struct{ result Result }

func (f fakeClient) Fetch(context.Context, string) (Result, error) { return f.result, nil }

func TestForecast(t *testing.T) {
tests := []struct {
name string
location string
wantErr error
}{
{"正常", "Seattle, WA", nil},
{"空の入力はエラー", "", ErrInvalidLocation},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := Forecast(context.Background(), fakeClient{}, tt.location)
if err != tt.wantErr {
t.Fatalf("err = %v, want %v", err, tt.wantErr)
}
})
}
}

ツールの登録側は、この関数を呼び、エラーを isError: true の結果 (モデルが自己修正できる文面) に詰め替える薄い層にします。その層は、Inspector の CLI か SDK のテストクライアントで、代表的な呼び出しを 1 つ確認します。

outputSchema への適合を検証するテスト (TypeScript)​

import Ajv2020 from "ajv/dist/2020";
import { expect, test } from "vitest";

test("get_weather の structuredContent が outputSchema に適合する", async () => {
const tools = await client.listTools();
const tool = tools.tools.find((t) => t.name === "get_weather")!;
const result = await client.callTool({
name: "get_weather",
arguments: { location: "Seattle, WA" },
});

const validate = new Ajv2020().compile(tool.outputSchema!);
expect(validate(result.structuredContent)).toBe(true);
});

client は、使用している SDK のテストクライアントを指します。メソッド名は SDK のバージョンによって異なるため、お使いの SDK のドキュメントを確認してください。

保護リソースの 401 を確認する​

# トークンなしで呼び、WWW-Authenticate を確認する
curl -sS -i https://example.com/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-H 'MCP-Protocol-Version: 2026-07-28' \
-H 'Mcp-Method: tools/list' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{"_meta":{
"io.modelcontextprotocol/protocolVersion":"2026-07-28",
"io.modelcontextprotocol/clientCapabilities":{}}}}' \
| grep -i -E '^(HTTP/|www-authenticate)'

HTTP/... 401 と、resource_metadata を含む WWW-Authenticate が返ることを確認します。

5. まとめ​

  • MCP のテストは、単体、プロトコル、認可、適合性、モデルを含む評価の 5 つのレベルに分ける。下のレベルほど速く決定的で、バグの大半はそこで見つける
  • Inspector は、Web、CLI、TUI を 1 つのパッケージで提供する。既定のプロトコルの世代は legacy なので、2026-07-28 のサーバーでは --protocol-era modern か auto を指定する
  • CI では CLI を使い、--format json と終了コード (3 は認証、4 は到達不能、5 はツールのエラー、6 はスキーマ) で判定する。--stored-auth-only で対話的な OAuth を止める
  • ビジネスロジックをプロトコルの層から切り離し、通常の単体テストで検証する。outputSchema への適合は、実際の結果を検証するテストで確かめる
  • 新世代は、ステートレスな POST なので curl で再現できる。Mcp-Method、Mcp-Name、MCP-Protocol-Version、_meta の必須フィールドが必要で、ヘッダーと本文の不一致は -32020 で拒否される
  • 認可は、失敗する経路 (401、403、audience の不一致、スコープ不足) を重点的にテストする
  • stdio は、stdout をプロトコル専用にし、ログを stderr に出す。作業ディレクトリと環境変数は引き継がれないことがある
  • ツールの選択は、description の変更で変わる。モデルを含む評価を、複数回の試行の割合で回帰テストにする
  • SEP-2484 により、観測できる挙動を変える標準化トラックの SEP には、適合テストが必要になった。自作の実装も、conformance のスイートで確認できる

関連ドキュメント​

参考資料​