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。最も機能が豊富 |
| CLI | npx @modelcontextprotocol/inspector --cli | スクリプト、CI、コーディングエージェント向けの機械可読なクライアント |
| TUI | npx @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 にフォールバックする |
modern | 2026-07-28 に固定する。フォールバックしないため、旧世代のサーバーには明示的に失敗する |
既定が auto ではないのは、デバッグ用のツールが勝手にプローブを送らないためです。server/discover のプローブは、何も返さない旧世代の stdio サーバーで止まり、読みたい通信の記録も汚します。新世代のサーバーを確認するときは、modern か auto を自分で選ぶ必要があります。既定のまま新世代のサーバーに接続して「動かない」と判断しないよう注意してください。
世代によって、Web の各タブの動きも変わります。
| 機能 | レガシー | 新世代 (2026-07-28) |
|---|---|---|
| ログ | logging/setLevel でセッション単位に設定 | リクエストごとに _meta の io.modelcontextprotocol/logLevel を付ける |
| リソースの購読 | resources/subscribe | subscriptions/listen (ストリームの状態がバッジで見える) |
| Tasks | capabilities.tasks、tasks/list、tasks/result | 拡張 io.modelcontextprotocol/tasks。tasks/get のポーリングで結果が埋め込まれる |
| サーバーからの質問 | サーバーからのリクエスト | input_required の結果とリトライ (MRTR) |
| キャンセル | notifications/cancelled | Streamable 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 のmethodMcp-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 を付けるとバッファリングを避けられます)。診断に役立つ応答は次のとおりです。
| 症状 | HTTP | JSON-RPC のエラー | 原因の例 |
|---|---|---|---|
| ヘッダーと本文が食い違う、必須ヘッダーがない | 400 | -32020 HeaderMismatch | Mcp-Name と params.name の不一致 |
_meta の必須フィールドがない | 400 | -32602 | protocolVersion や clientCapabilities の欠落 |
| 必要なクライアントの機能が宣言されていない | 400 | -32021 | Elicitation を要求するのに clientCapabilities に宣言がない。data.requiredCapabilities に不足分が入る |
| サーバーが対応しない版 | 400 | -32022 UnsupportedProtocolVersion | data.supported に対応する版の一覧が入る |
| 未実装のメソッド | 404 | -32601 | Method 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 の開発者ツールで、クライアント側のエラーやメッセージのペイロードを確認できます。他のクライアントは、それぞれのドキュメントでログの場所を確認してください。
接続に失敗するときは、次の順に切り分けます。
- クライアントのログを確認する
- サーバーのプロセスが動いているか確認する
- Inspector で、サーバーを単独で起動して接続する
server/discoverで、サーバーが対応するプロトコルのバージョンを確認する。UnsupportedProtocolVersionError(-32022) は、dataに対応するバージョンの一覧を持つ- リクエストの
_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 のスイートで確認できる
関連ドキュメント
- Goのテスト
- MCPプロトコル基礎 - JSON-RPCとステートレス設計
- トランスポート - stdioとStreamable HTTP
- MCPの認可 - OAuth 2.1とメタデータ探索
- MCPの可観測性と監査
- AIエージェント特有の脅威 - プロンプトインジェクションとツール汚染