「Ableton Live MCP」のShow HN投稿が今週初め、118ポイントと78件のコメントを獲得しました。Model Context Protocol(MCP)は、Anthropic専用の実験から、1年足らずでエージェント統合の事実上のレイヤーへ広がりました。今では、Claude Desktop、Cursor、独自エージェントから外部ツール・リソース・プロンプトを呼び出すための実装対象として扱われています。
一方で、MCPサーバーのテスト方法はまだ整っていません。stdio経由でJSON-RPCを手動実行する方法は「hello world」には十分ですが、ツールが12個、プロンプトが3個、外部API依存があるサーバーではすぐに破綻します。
この記事では、MCPサーバーを手動で検証し、その後 Apidog で自動テスト化する手順をまとめます。目的は、MCPサーバーを通常のAPIと同じように、契約、モック、回帰テスト付きで出荷できる状態にすることです。
より広いエージェント設計の文脈から来ている場合は、agents.mdガイド も合わせて読むと、MCPサーバーの契約をチームで共有しやすくなります。
TL;DR
- MCP はAnthropicのModel Context Protocolであり、stdioまたはHTTP上のJSON-RPC 2.0として動作します。
- 主なプリミティブは、ツール、リソース、プロンプトです。
- MCPサーバーのテストでは、主に
initialize、tools/list、tools/call、resources/read、prompts/getの応答を検証します。 - 最初はMCP Inspectorやstdioで手動確認し、ワイヤーレベルのリクエスト・レスポンスを把握します。
- 次に Apidog にJSON-RPCリクエストを保存し、JSONPathアサーション、モック、CI実行を追加します。
- 外部API依存はApidogのモックサーバーで置き換え、CIを高速かつ決定論的にします。
- リクエストコレクション、モック、CIランナーをまとめて使う場合は、Apidogをダウンロード してください。
MCPの正体を短く整理する
Model Context Protocol仕様 は、AIクライアントと外部機能をつなぐJSON-RPC 2.0ベースのプロトコルです。
クライアントには、たとえば次のようなものがあります。
- Claude Desktop
- Cursor
- 独自のAIエージェント
- MCP対応IDEや開発ツール
MCPクライアントはサーバーを起動し、最初に initialize ハンドシェイクを実行します。その後、ツール、リソース、プロンプトに対してJSON-RPC呼び出しを行います。
テストで特に重要なのは次の呼び出しです。
| メソッド | 目的 |
|---|---|
initialize |
プロトコルバージョンと機能のネゴシエーション |
tools/list |
公開ツールと入力スキーマの一覧取得 |
tools/call |
ツールの実行 |
resources/list |
読み取り可能なリソース一覧の取得 |
resources/read |
URI指定リソースの読み取り |
prompts/list |
プロンプトテンプレート一覧の取得 |
prompts/get |
プロンプトテンプレートのレンダリング |
トランスポートは主に2種類です。
- stdio:stdin/stdoutで改行区切りのJSON-RPCフレームをやり取りする
- HTTP:通常は
POST /とSSEを使ってストリーミングする
ローカルMCPサーバーはstdio、リモートMCPサーバーはHTTPを使うことが多いです。
MCPでは、tools/list のレスポンス形状が壊れるだけで、Claude DesktopやCursorなど複数のクライアントが同時にツールを認識できなくなります。したがって、MCPサーバーは通常のAPIと同じく契約テストが必要です。
MCPサーバーでテストすべき項目
堅牢なMCPサーバーテストでは、少なくとも次の6領域を確認します。
1. プロトコル準拠
initialize が正しい protocolVersion を返すか確認します。
例:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2026-04-01",
"capabilities": {
"tools": {},
"resources": {},
"prompts": {}
},
"serverInfo": {
"name": "example-mcp-server",
"version": "1.0.0"
}
}
}
確認ポイント:
-
protocolVersionが期待値と一致する - 実際に対応している機能だけを
capabilitiesに出している -
serverInfo.nameとserverInfo.versionが存在する
2. スキーマの正確性
tools/list 内の各ツールに、正しい inputSchema があるか確認します。
見るべき項目:
-
nameが一意である -
descriptionが空ではない -
inputSchemaが有効なJSON Schemaである - 必須引数が
requiredに入っている - 型が実装と一致している
悪い例:
{
"name": "get_weather",
"description": "",
"inputSchema": {
"type": "object",
"properties": {
"city": {}
}
}
}
改善例:
{
"name": "get_weather",
"description": "指定した都市の現在の天気を取得します。",
"inputSchema": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "天気を取得する都市名。例: Tokyo"
}
},
"required": ["city"]
}
}
3. ツールの動作
tools/call が期待通りのコンテンツブロックを返すか確認します。
成功時の例:
{
"jsonrpc": "2.0",
"id": 42,
"result": {
"content": [
{
"type": "text",
"text": "Tokyo: 24°C, cloudy"
}
]
}
}
確認ポイント:
-
contentが配列である - 各ブロックに
typeがある -
text、image、resourceなどの型が仕様に沿っている - 成功時に
isError: trueが付いていない
4. ツールのエラー処理
MCPでは、ツール実行時の失敗はJSON-RPCエラーではなく、通常の結果として返します。
推奨されるエラー例:
{
"jsonrpc": "2.0",
"id": 43,
"result": {
"isError": true,
"content": [
{
"type": "text",
"text": "city is required"
}
]
}
}
避けるべき例:
{
"jsonrpc": "2.0",
"id": 43,
"error": {
"code": -32602,
"message": "city is required"
}
}
JSON-RPCエラーは、プロトコルレベルの失敗を示します。ツール内部の入力エラーや外部APIエラーに使うと、クライアントが接続を閉じる場合があります。
5. リソースアクセス
resources/list で返したURIが、resources/read で実際に読めるか確認します。
確認ポイント:
- URIが安定している
-
resources/readが同じURIを解決できる - ページネーションがある場合、2ページ目以降も読める
- 存在しないURIに対して適切なエラーを返す
6. プロンプトのレンダリング
prompts/get が正しい messages 配列を返すか確認します。
確認ポイント:
-
messagesが配列である - 各メッセージに
roleとcontentがある - 引数が正しい位置に展開される
- 必須引数が不足した場合にエラーになる
stdioでMCPサーバーを手動テストする
まずは自動化せず、MCPサーバーがワイヤーレベルで何を返すか確認します。
まだサーバーを作っていない場合は、PythonまたはTypeScriptで 公式MCP SDKクイックスタート を使って最小構成を作成します。
MCP Inspectorで起動確認する
npx @modelcontextprotocol/inspector node your-server.js
MCP Inspectorは、MCPサーバーと通信するローカルWeb UIを起動します。
ここで確認すること:
- サーバーが起動する
-
initializeが成功する -
tools/listが表示される - 各ツールを手動実行できる
- エラー時のレスポンス形状が正しい
生のstdioでJSON-RPCを送る
Inspectorで問題がなければ、次は生のJSON-RPCを送ります。
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2026-04-01","capabilities":{}}}' | node your-server.js
次に、tools/list を送ります。
echo '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' | node your-server.js
ツール呼び出しの例:
echo '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"get_weather","arguments":{"city":"Tokyo"}}}' | node your-server.js
この段階で、次のリクエスト・レスポンスを保存しておきます。
initializetools/list- 主要ツールの正常系
tools/call - 主要ツールの異常系
tools/call resources/listresources/readprompts/listprompts/get
これらが、後で Apidog に登録する契約テストの元になります。
手動テストからApidogでの自動化へ移行する
手動テストは、初期の形状バグを見つけるには有効です。しかし、次のような状態になったら自動化が必要です。
- ツール数が増えてきた
- 外部API依存がある
- PRごとに回帰確認したい
-
tools/listのスキーマ変更を検知したい - Claude Desktopで毎回手動確認するのが遅い
基本パターンは次の通りです。
- 手動で取得したJSON-RPCリクエストをApidogに保存する
- 各レスポンスにJSONPathアサーションを追加する
- 外部APIをモックする
- CIでスイートを実行する
1. MCPサーバー用のApidogプロジェクトを作成する
Apidogで新しいプロジェクトを作成します。
HTTP MCPサーバーの場合は、ベースURLにMCPサーバーのHTTPエンドポイントを設定します。
例:
http://localhost:3000
stdioのみのMCPサーバーの場合は、テスト用に薄いHTTPラッパーを用意します。公式Inspectorを使うか、HTTPで受け取ったJSON-RPCをstdioに転送する小さなNode.jsスクリプトを使います。
たとえば、テスト用ラッパーの構成は次のようになります。
Apidog
-> HTTP POST /rpc
-> wrapper.js
-> stdio
-> your-server.js
非HTTPバックエンドをHTTP経由でテストする考え方は、2026年のPostmanなしのAPIテスト でも同じです。
2. 標準リクエストを保存する
Apidogに、MCPの主要メソッドをリクエストとして保存します。
最低限、次を登録します。
initializetools/list-
tools/call正常系 -
tools/call異常系 resources/listresources/readprompts/listprompts/get
tools/call のリクエストボディ例:
{
"jsonrpc": "2.0",
"id": 42,
"method": "tools/call",
"params": {
"name": "get_weather",
"arguments": {
"city": "Tokyo"
}
}
}
異常系も必ず保存します。
{
"jsonrpc": "2.0",
"id": 43,
"method": "tools/call",
"params": {
"name": "get_weather",
"arguments": {}
}
}
3. JSONPathアサーションを追加する
自動テストで重要なのは、リクエストを送ることではなく、レスポンスの契約を検証することです。
initialize のアサーション例
確認項目:
-
$.result.protocolVersionが期待値と一致する -
$.result.capabilitiesが存在する -
$.result.serverInfo.nameが存在する
例:
$.result.protocolVersion == "2026-04-01"
$.result.capabilities exists
$.result.serverInfo.name exists
tools/list のアサーション例
確認項目:
-
$.result.toolsが存在する -
$.result.toolsが空でない - 各ツールに
nameがある - 各ツールに
descriptionがある - 各ツールに
inputSchemaがある
例:
$.result.tools exists
$.result.tools.length > 0
$.result.tools[0].name exists
$.result.tools[0].description exists
$.result.tools[0].inputSchema exists
ツール数が固定されている場合は、件数も検証できます。
$.result.tools.length == 10
tools/call 正常系のアサーション例
確認項目:
-
$.result.contentが配列である -
$.result.content[0].typeが期待値と一致する -
$.result.isErrorが存在しない、またはfalse
例:
$.result.content exists
$.result.content[0].type == "text"
$.result.isError != true
tools/call 異常系のアサーション例
確認項目:
-
$.result.isErrorがtrue - エラーメッセージが存在する
- JSON-RPCの
errorではなくresultとして返る
例:
$.result.isError == true
$.result.content[0].text exists
$.error not exists
エラーメッセージの完全一致は避け、安定したエラーコードや正規表現で検証する方が安全です。
4. アップストリームAPIをモックする
多くのMCPサーバーは、外部APIをラップします。
例:
- 天気API
- GitHub
- Linear
- Notion
- 社内データベース
- インシデント管理API
CIで毎回ライブAPIを呼ぶと、次の問題が起きます。
- レート制限に引っかかる
- ネットワーク障害でテストが落ちる
- テスト結果がデータ状態に依存する
- 外部API利用コストが増える
この部分はApidogのモックサーバーで置き換えます。
手順:
- Apidogで外部APIのエンドポイントを定義する
- レスポンス例を登録する
- モックサーバーを起動する
- テスト時だけMCPサーバーの外部API URLをモックURLに向ける
- 本番実行時は実APIのURLに戻す
設定例:
# CI
WEATHER_API_BASE_URL=https://mock.apidog.com/project/xxx
# production
WEATHER_API_BASE_URL=https://api.weather.example.com
モックを使うことで、MCPサーバーのテストは次のようになります。
- 外部ネットワークに依存しない
- 数秒で終わる
- 毎回同じ結果になる
- スキーマ回帰を早期に検知できる
モックを使った契約優先のワークフローは、契約優先のAPI開発 でも詳しく説明しています。
5. CIでMCPテストスイートを実行する
ApidogプロジェクトはCLIランナーで実行できます。apidog run は保存済みリクエストを実行し、アサーションを評価し、失敗時に非ゼロで終了します。
GitHub Actionsの最小構成例:
name: MCP server tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci
- name: Start MCP HTTP wrapper
run: node test/wrapper.js &
- name: Run Apidog suite
run: npx apidog run --project-id $APIDOG_PROJECT --env ci
env:
APIDOG_PROJECT: ${{ secrets.APIDOG_PROJECT }}
APIDOG_TOKEN: ${{ secrets.APIDOG_TOKEN }}
これで、すべてのpushとpull requestでMCP契約テストが実行されます。
tools/list のスキーマ変更、必須引数の欠落、ツールのエラー形式の崩れは、リリース前に検知できます。
良いMCPテストカバレッジの目安
Apidog でMCPサーバーをテストする場合、現実的なスイートは次の構成になります。
-
initialize:1件 -
tools/list:1件 - 各ツールの
tools/call:2〜4件- 正常系
- 必須引数不足
- 型不正
- 外部APIエラー
- 各リソースファミリーの
resources/list:1件 - 各リソースファミリーの
resources/read:1件 - 各プロンプトの
prompts/list:1件 - 各プロンプトの
prompts/get:1件
たとえば、次のMCPサーバーを考えます。
- ツール:10個
- リソース:3種類
- プロンプト:4個
この場合、テストスイートはおおよそ50〜70リクエストになります。モックサーバーを使えば、ローカルでもCIでも短時間で実行できます。
MCPサーバーテストでよくある間違い
initialize を省略する
一部のMCPサーバーは、initialize のタイミングでツールレジストリや設定を初期化します。
そのため、いきなり tools/list を呼ぶと落ちる場合があります。
必ず次の順序でテストします。
initializetools/listtools/callresources/*prompts/*
エラーメッセージを完全一致で検証する
エラーテキストは変更されやすいため、完全一致は壊れやすいです。
避けたい例:
$.result.content[0].text == "city is required"
より安定した例:
$.result.isError == true
$.result.content[0].text contains "city"
可能であれば、ツール側で安定したエラーコードを含める設計にします。
モックと本番レスポンスが乖離する
モックが実APIと違う形状を返していると、テストは成功しても本番で壊れます。
対策:
- リリースごとに実APIレスポンスからモックを更新する
- 重要な外部APIは契約テストを別途持つ
- モックフィクスチャをレビュー対象にする
ストリーミングをテストしない
HTTP MCPサーバーはSSEでツール結果をストリーミングすることがあります。
確認ポイント:
- SSEを有効にしたリクエストでテストする
- 組み立て後の結果に対してアサーションする
- タイムアウトや途中切断も確認する
Apidogは保存済みリクエストでSSEを扱えます。HTTP MCPサーバーをテストする場合は、リクエスト設定でストリーミングを有効にしてください。
並行実行をテストしない
MCPクライアントは、エージェントループ内で複数の tools/call を並行実行することがあります。
単一リクエストでは成功しても、共有状態やキャッシュが原因で本番だけ壊れるケースがあります。
確認すべきこと:
- 同じツールを同時に複数回呼ぶ
- 異なるツールを同時に呼ぶ
- 外部APIモックに遅延を入れる
- タイムアウト時の挙動を見る
プロトコルエラーとツールエラーを混同する
MCPでは、プロトコルレベルの失敗とツール実行の失敗を分けます。
- JSON-RPCエラー:不正なメソッド、無効なJSON-RPC、プロトコル違反
-
isError: true:ツール実行時の入力エラー、外部APIエラー、業務エラー
これを混同すると、Claude Desktopなどのクライアントが接続を閉じる場合があります。同様の契約バグについては、APIプラットフォームの契約優先開発 でも扱っています。
実世界のユースケース
社内インシデント管理MCPサーバー
あるチームは、社内インシデント管理API用のMCPサーバーを構築しました。
Apidogで tools/list の形状にアサーションを追加したところ、1週間で3件の回帰を検出しました。いずれも、Claude Desktopを使うエンジニア全員に壊れたツール定義が配布される可能性がある変更でした。
Notion用オープンソースMCPサーバー
Notion向けMCPサーバーを公開している開発者は、Apidogモックを使ってCI中のNotion API呼び出しを置き換えています。
結果:
- Notionのレート制限に依存しない
- すべてのPRでテストが走る
- コントリビューターがNotion APIキーを持つ必要がない
- テストが数秒で完了する
複数MCPサーバーを運用するプラットフォームチーム
14個の内部MCPサーバーを運用するチームでは、各サーバーの契約を共有Apidogワークスペースに置いています。
新しいMCPサーバーはベーステストスイートを継承し、レビュー担当者はマージ前にスキーマ差分を確認できます。
特に tools/list のアサーションにより、引数名変更による大規模なクライアント影響を事前に検知できました。
実装チェックリスト
MCPサーバーのテストを始めるなら、次の順で進めるのが実用的です。
- MCP Inspectorでサーバーを起動する
-
initializeが成功することを確認する -
tools/listのレスポンスを保存する - 各ツールの正常系
tools/callを実行する - 各ツールの異常系
tools/callを実行する -
resources/*とprompts/*を確認する - ApidogにJSON-RPCリクエストを登録する
- JSONPathアサーションを追加する
- 外部APIをApidogモックに置き換える
- CIで
apidog runを実行する
結論
MCPは急速に普及しましたが、テスト方法はまだ手動中心です。しかし、MCPサーバーはJSON-RPC APIです。REST APIと同じように、契約、モック、CIによる回帰テストを持つべきです。
要点は次の5つです。
- MCPサーバーはJSON-RPC APIとして扱う
- 最初はMCP Inspectorとstdioで手動確認する
- 手動で得たリクエストを Apidog に保存する
-
initialize、tools/list、tools/call、resources/read、prompts/getにアサーションを追加する - 外部APIは Apidog のモックで置き換え、CIを安定させる
次のステップはシンプルです。Apidog でプロジェクトを作成し、手動でキャプチャした tools/list のリクエストを貼り付け、JSONPathアサーションを1つ追加してください。そこから、MCPサーバーの契約テストを段階的に拡張できます。
よくある質問
MCPとは何ですか?
MCP(Model Context Protocol)は、AIクライアントが外部ツール、リソース、プロンプトを呼び出すためのAnthropicのオープン仕様です。stdioまたはストリーミング可能なHTTP上でJSON-RPC 2.0として動作します。
MCPの完全な仕様 は modelcontextprotocol.io で公開されています。
HTTPラッパーなしでMCPサーバーをテストできますか?
手動テストなら可能です。公式MCPインスペクター はstdioと直接通信できます。
ただし、Apidog で自動テストする場合は、CI中だけstdioを薄いHTTPラッパーで包む構成が実用的です。本番トラフィックは引き続きstdioのままで問題ありません。
MCPサーバーが呼び出す外部APIはどうモックしますか?
Apidogプロジェクトで外部APIエンドポイントをモックとして定義します。テスト中はMCPサーバーの設定をモックURLに向け、本番では実URLに切り替えます。
同じパターンは QAエンジニア向けのAPIテストツール でも説明しています。
ツール結果のストリーミングはどうテストしますか?
HTTP MCPサーバーはSSEでツール結果をストリーミングすることがあります。Apidogの保存済みリクエストでSSEを有効にし、組み立て後の結果に対してアサーションを実行します。
プロトコルバージョンはテストすべきですか?
はい。initialize の protocolVersion は必ず固定してアサーションしてください。ここがずれると、クライアントとの互換性問題がサイレントに発生します。
実際のClaude Desktopに対してテストすべきですか?
はい。リリース前のスモークテストとしては有効です。
ただし、Claude Desktopを回帰テストの中心に置くべきではありません。手動で遅く、非決定論的だからです。回帰テストは Apidog で自動化し、Claude Desktopは最終確認に使うのが実用的です。
実際のMCPサーバー実装はどこで見られますか?
公式MCPサーバーリポジトリ に、ファイルシステム、GitHub、Slack、Postgresなどのリファレンス実装があります。ツール定義、入力スキーマ、エラー処理を見ると、良いMCPサーバーの形状を理解しやすくなります。
Top comments (0)