
MCPの正体を理解する:Tool Useのエージェントループから JSON-RPC 2.0 の二層エラーハンドリングまで
はじめに
Claude API で Tool Use を使ったエージェントを実装しているとき、ふと疑問が湧きました。
「MCP(Model Context Protocol)って結局何なのか?Tool Use と何が違うのか?」
調べていくと、LLM は MCP の存在すら知らないという事実にたどり着き、そこから MCP のアーキテクチャ思想、さらには通信基盤である JSON-RPC 2.0 の仕組みまで、一連の疑問が連鎖的に解けていきました。
この記事では、その探求の過程を共有します。
Tool Use エージェントループの正しいメッセージ構造
まず基本から。Claude API でツール使用を有効にしたエージェントを実装する場合、正しいエージェントループの設計を理解する必要があります。
ループの流れ
Claude が stop_reason: "tool_use" を返したとき、開発者はローカルでツールを実行し、結果を API に返す必要があります。このとき守るべきルールは明確です。
直前の assistant メッセージを含む会話履歴全体と、対応する tool_use_id を持つ tool_result コンテンツブロックを含む新しい user メッセージを送信すること。

{
"messages": [
{
"role": "user",
"content": "現在の東京の天気を調べて。"
},
{
"role": "assistant",
"content": [
{
"type": "tool_use",
"id": "toolu_xyz123",
"name": "get_weather",
"input": { "city": "Tokyo" }
}
]
},
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_xyz123",
"content": "晴れ、気温 22度"
}
]
}
]
}
なぜ user ロールなのか?
「ツールを呼び出したのは assistant なのに、結果を返すのが user なのはなぜ?」と疑問に思うかもしれません。
Claude API における role は、データの流れる方向を表しています。
| ロール | 意味 |
|---|---|
assistant |
モデルから出てくるデータ(ツール呼び出し命令を含む) |
user |
モデルへ送り込むデータ(ツール実行結果を含む) |
アプリケーションコードはユーザーの「代理人(プロキシ)」として振る舞い、外部世界から得た事実をモデルに伝達する役割を担います。

エラー時の扱い
ツール実行がタイムアウトなどで失敗した場合でも、tool_result の送信は省略できません。省略すると API エラー(400 Bad Request)になります。エラー時は is_error: true を設定し、content にエラー詳細を記述します。
{
"type": "tool_result",
"tool_use_id": "toolu_01A2B3C4D5",
"content": "Error: The external API request timed out after 10 seconds.",
"is_error": true
}
これにより、Claude はエラーの事実を認識し、ユーザーへの説明やリトライの判断を自律的に行えます。
LLM にとって MCP は存在しない
ここからが本題です。上記の Tool Use ルールは、MCP を使った場合でもまったく同じです。
なぜなら、Claude は MCP の存在を一切知らないからです。
クライアントアプリが「幻術師」
MCP のアーキテクチャでは、クライアントアプリ(Claude Desktop、Claude Code、自作のエージェントなど)が中間に立ち、以下の処理を行います。
Phase 1: ツール一覧の統合
クライアントアプリは接続先の MCP サーバー群に「どんなツールを持っていますか?」と問い合わせ、取得したツール定義を標準の tools 配列にまとめて Claude API に送信します。
"tools": [
{ "name": "github_create_issue", "description": "..." },
{ "name": "postgres_query", "description": "..." },
{ "name": "slack_post_message", "description": "..." }
]
Claude にとっては、これらが MCP サーバーから来たものか、ローカルに書かれた関数かは区別がつきません。
Phase 2: Claude は標準の Tool Use を呼ぶだけ
{
"type": "tool_use",
"name": "postgres_query",
"input": { "query": "SELECT * FROM users WHERE active = true" }
}
Claude は「どうやって実行されるか」を知りません。構造化された JSON の意図を出力して停止するだけです。
Phase 3: クライアントアプリがルーティングを決定
クライアントアプリが tool_use の name を見て判断します。
- ローカル関数なら → 直接実行
- MCP サーバーのツールなら → JSON-RPC で該当サーバーに転送
Phase 4: 結果を tool_result で返す
MCP サーバーから受け取った結果を、標準の tool_result メッセージブロックに包んで Claude に返します。

MCP = AI 時代の HTTP、LLM のためのマイクロサービス
では、LLM が MCP を知らないなら、なぜ Anthropic は MCP を作ったのか?
答えはデカップリングです。HTTP がウェブブラウザとウェブサーバーを分離したように、MCP は AI クライアントとツール・データソースを分離します。
HTTP との構造的類似性
| 観点 | HTTP | MCP |
|---|---|---|
| クライアントは何を知らなくていい? | サーバーの実装言語・DB種別 | ツールの実装言語・認証フロー |
| サーバーは何を知らなくていい? | クライアントのOS・デバイス | どのLLMが呼んでいるか |
| 共通の契約 | HTTP メソッド・ステータスコード | JSON-RPC 2.0・3つのプリミティブ |
マイクロサービスとのアナロジー
従来のシステム設計では、巨大なアプリを マイクロサービス(認証サービス、決済サービス、配送サービスなど)に分割し、メインアプリはネットワーク呼び出しで必要な機能を使います。
MCP はまったく同じことを AI の世界で行っています。ただし、サービスを呼び出す「ボス」がハードコードされたアプリケーションではなく、LLM である点が異なります。
MCP はマイクロサービスに似ている。マイクロサービスがアプリケーションのためのものなら、MCP は LLM のためのもの。

MCP 以前の問題
MCP がなかった時代、5つの異なる AI コーディングアシスタント(Cursor、Claude Desktop、Continue.dev、Aider、社内エージェントなど)が GitHub 連携を実装したい場合、5回とも同じ車輪を再発明する必要がありました。しかもそれぞれの実装は互換性がありません。
MCP があれば、ある開発者が優れた GitHub MCP サーバーを1つ作れば、すべての AI クライアントアプリが即座に利用可能になります。
エコシステムの現実
ただし、「100人の開発者が同じアプリに対して100個の MCP サーバーを作る」ことを MCP は止められません。npm に文字列パディングのパッケージが50個あるのと同じです。
重要なのは、努力が注がれる場所が変わったことです。各アプリ開発者が同じ基本ツールを非公開で書き直す」のではなく、 「コミュニティが最高の公開 MCP サーバーを競い合う」 形になりました。
JSON-RPC 2.0:通信プロトコルのスキーマ強制
MCP の通信基盤を支えているのが JSON-RPC 2.0 です。
JSON と JSON-RPC 2.0 の違い
JSON はデータフォーマット、JSON-RPC 2.0 は厳格な契約です。 JSON が「アルファベット」なら、JSON-RPC 2.0 はそのアルファベットで書かれた「法的契約書」のようなものです。
JSON は構文のみを規定し、意味についてのルールはありません。
{ "coffee": "iced", "temperature": 2, "is_good": true }
一方、JSON-RPC 2.0 はリモートプロシージャコール(RPC)のための厳密な構造を強制します。
リクエスト契約
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": { "name": "fetch_file", "path": "./src/main.ts" },
"id": "request-abc-123"
}
3つの必須キー:jsonrpc(バージョン)、method(実行する関数名)、id(リクエスト識別子)。params(引数)はオプショナルだが、MCP では通常含まれる。
レスポンス契約
{
"jsonrpc": "2.0",
"result": { "content": "console.log('hello world');" },
"id": "request-abc-123"
}
エラー契約
{
"jsonrpc": "2.0",
"error": { "code": -32602, "message": "File not found at specified path" },
"id": "request-abc-123"
}
スキーマ違反時の自動拒否
JSON-RPC 2.0 はスキーマ違反を自動で検出・拒否します。
| 違反の種類 | エラーコード | 発生条件 |
|---|---|---|
| Parse error | -32700 |
JSON 構文エラー(閉じ括弧忘れなど) |
| Invalid Request | -32600 |
必須キー欠落(jsonrpc や method がない) |
| Method not found | -32601 |
存在しないメソッドの呼び出し |
| Invalid params | -32602 |
パラメータの型・構造が不正 |
なぜ LLM にとって重要か
LLM はツールパラメータを動的に生成するため、ツール名のハルシネーションやパラメータの欠落が起こりえます。JSON-RPC 2.0 のスキーマ強制があることで、MCP サーバーはクラッシュせず、標準化されたエラーコードを返します。クライアントアプリはそのエラーを Claude にフィードバックし、Claude が自己修正して再リクエストする、という堅牢なループが実現します。
HTTP ステータスコードと JSON-RPC エラーコードの二層構造
JSON-RPC 2.0 は HTTP のステータスコード(400, 500 など)を置き換えるのではなく、共存します。
二層のセキュリティモデル
装甲トラックに例えると分かりやすいです。
- HTTP ステータスコード → トラックそのものを管理(配送が物理的に成功したか?)
- JSON-RPC エラーコード → トラック内の金庫を管理(中身の検査に合格したか?)

パターン1:ネットワーク成功 + ツール実行成功
HTTP/1.1 200 OK
{
"jsonrpc": "2.0",
"result": { "status": "Slack message sent successfully!" },
"id": "mcp-msg-99"
}
パターン2:ネットワーク成功 + ツール実行失敗
HTTP は 200 OK を返すのに、内部的にはエラー。ネットワークの配送は成功したが、中身の検査で不合格だったケースです。
HTTP/1.1 200 OK
{
"jsonrpc": "2.0",
"error": { "code": -32602, "message": "Invalid params: 'path' must be an absolute string." },
"id": "claude-bad-request-1"
}
パターン3:ネットワーク自体が失敗
MCP サーバーがクラッシュした場合、JSON-RPC のレスポンスすら返せません。
HTTP/1.1 504 Gateway Timeout

| レイヤー | 何を意味するか | 例 |
|---|---|---|
| HTTP(4xx, 5xx) | パイプが壊れている | サーバー到達不能、タイムアウト |
| JSON-RPC(-32xxx) | パイプは正常だが、流したデータが検査不合格 | メソッド不在、パラメータ不正 |
まとめ
この探求を通じて得られた理解を整理します。
| 概念 | 本質 |
|---|---|
| Tool Use のエージェントループ | assistant: tool_use → user: tool_result の厳密な交互パターン。会話履歴の完全保持が必須 |
| MCP と LLM の関係 | LLM は MCP を知らない。クライアントアプリがルーティングを判断する |
| MCP の価値 | AI 時代の HTTP。プラットフォーム・言語を問わないデカップリング標準 |
| JSON-RPC 2.0 | 通信プロトコルのスキーマ強制層。HTTP ステータスコードとは別レイヤーで共存 |
実務的な判断基準としては、MCP を「LLM のためのマイクロサービス」と捉えるのが最も実用的です。従来のマイクロサービスがアプリケーションのためのものなら、MCP は LLM のためのもの。HTTP の上に JSON-RPC 2.0 という契約を重ね、3つのプリミティブ(Prompts, Resources, Tools)で構成される。この理解があれば、MCP の設計判断で迷うことは少なくなるはずです。






