MCPプロトコル基礎 - JSON-RPCとステートレス設計
MCP のワイヤーフォーマットと、2026-07-28 で導入されたステートレスなベースプロトコルの仕組みを解説します。全体像は MCP入門 - Model Context Protocolとは で扱い、ここでは個々のメッセージに何が載るかを、JSON-RPC のレベルで確認します。バージョン間の変更点と移行は MCP最新仕様 2026-07-28 の変更点と移行、トランスポート固有の事項は トランスポート を参照してください。
1. このドキュメントで扱うこと
- JSON-RPC 2.0 の基本
- ステートレス設計
_metaで運ぶ情報- server/discover
- resultType
- ケイパビリティと extensions
- エラーコードの割り当て
- バージョニングと互換性
- 廃止されたユーティリティ
JSON-RPC 2.0 の基本
MCP のメッセージは、すべて JSON-RPC 2.0 に従い、UTF-8 で符号化します。種類は 3 つです。
| 種類 | 送り手 | 必須の項目 | 備考 |
|---|---|---|---|
| Request | クライアント | jsonrpc、id、method、(任意で params) | id は文字列か整数。JSON-RPC と違い null は不可。未応答の他のリクエストと重複させない |
| Response (result) | サーバー | jsonrpc、id、result | result に resultType が必須 |
| Response (error) | サーバー | jsonrpc、id、error (code、message、任意で data) | code は整数。リクエストを読めなかった場合のみ id を省略できる |
| Notification | 双方向 | jsonrpc、method、(任意で params) | id を持たない。受け手は応答を返さない |
2026-07-28 では、サーバーが独自の JSON-RPC リクエストをクライアントへ送ることはありません。サーバーがクライアントの入力を必要とする場合は、結果の中に入力要求を埋め込みます (Multi Round-Trip Requests。ElicitationとMulti Round-Trip Requests を参照)。クライアントが JSON-RPC の Response を送ることもありません。メッセージの流れは、「クライアントのリクエストに対するサーバーの応答」と「通知」に絞られます。
ステートレス設計
MCP はステートレスなプロトコルになりました。リクエストを処理するのに必要な情報は、すべてそのリクエスト自身に含まれます。サーバーは各リクエストを独立に処理し、同じ接続や同じストリーム上の過去のリクエストから、状態を推測してはいけません。
initializeとnotifications/initializedのハンドシェイクは廃止された。接続を開いた直後から、任意のリクエストを送れる- サーバーは、プロトコルバージョン、ケイパビリティ、クライアントの識別を、過去のリクエストから補ってはいけない。すべて、そのリクエストの
_metaから読む - 開いている接続 (たとえば stdio のプロセス) は、会話でもセッションでもない。クライアントは無関係なリクエストを同じトランスポート上に混在させてよい。サーバーは、プロセスや接続の同一性を、会話の継続性の代わりに使ってはいけない
- 複数のリクエストにまたがる状態 (長時間のタスク、アプリケーションが持つハンドルなど) は、クライアントが毎回渡す明示的な識別子で参照する
subscriptions/listen のように長く開いたままのリクエストも、プロトコル上はリクエストとレスポンスです。応答が、通知を流し続けるストリームになっているだけで、その状態はリクエスト自身に閉じています。接続には属しません。
「ステートレスなプロトコル」は「ステートレスなアプリケーション」を意味しません。アプリケーションが状態を必要とするなら、サーバーが発行したハンドルを、ツールの引数として渡す設計にします。詳しくは MCP入門 - Model Context Protocolとは を参照してください。
_meta で運ぶ情報
_meta は、クライアントとサーバーがメッセージにメタデータを添えるための項目です。キーの形式は「任意のプレフィックス + / + 名前」です。プレフィックスは、ドットで区切ったラベルの並びで、逆ドメイン表記 (com.example/) が推奨されます。2 番目のラベルが modelcontextprotocol か mcp のプレフィックスは、MCP の予約です。たとえば io.modelcontextprotocol/ や dev.mcp/ は予約済みですが、com.example.mcp/ は予約ではありません。
仕様が予約しているキーは次のとおりです。
| キー | 載せる場所 | 必須 | 内容 |
|---|---|---|---|
io.modelcontextprotocol/protocolVersion | リクエスト | 必須 | そのリクエストのプロトコルバージョン (例: "2026-07-28") |
io.modelcontextprotocol/clientCapabilities | リクエスト | 必須 | そのリクエストに関係するクライアントのケイパビリティ |
io.modelcontextprotocol/clientInfo | リクエスト | 任意 (送ることが推奨) | クライアントの名前とバージョン |
io.modelcontextprotocol/serverInfo | 結果 | 任意 (含めることが推奨) | サーバーの名前とバージョン |
io.modelcontextprotocol/logLevel | リクエスト | 任意 | サーバーが出力してよい最小のログレベル |
io.modelcontextprotocol/subscriptionId | subscriptions/listen の通知 | 必須 | 通知を、元の購読リクエストに結びつける |
progressToken | リクエスト | 任意 | 進捗通知を要求する |
traceparent、tracestate、baggage | 双方向 | 任意 | OpenTelemetry のトレースコンテキスト。値は W3C Trace Context / Baggage の形式。プレフィックスの規則の例外 |
必須のフィールドが欠けたリクエストは、不正な形式です。サーバーは JSON-RPC のエラーコード -32602 (Invalid params) で拒否し、HTTP では 400 Bad Request を返します。また、サーバーは、クライアントが宣言していないケイパビリティに依存してはいけません。処理にそのケイパビリティが必要なら、MissingRequiredClientCapabilityError (-32021) を返し、data.requiredCapabilities に不足しているものを列挙します。
clientInfo と serverInfo は、送り手が自己申告する値で、プロトコルは検証しません。表示、ログ、デバッグのために使い、挙動の切り替えやセキュリティ上の判断には使いません。
server/discover
server/discover は、サーバーの対応バージョン、ケイパビリティ、識別情報を 1 回のリクエストで返します。サーバーは必ず実装しなければならず、クライアントが呼ぶかどうかは任意です。結果にはキャッシュの項目 (ttlMs、cacheScope) が付きます。
クライアントが呼ぶ意味があるのは、主に 2 つの場面です。
- サーバー情報の提示:
tools/list、prompts/list、resources/listを別々に呼ばずに、サーバーの対応範囲を 1 回で表示したい - stdio の後方互換プローブ: HTTP のようなステータスコードがない stdio で、新旧どちらのサーバーかを見分ける (後述)
接続の準備として毎回呼ぶ必要はありません。クライアントは任意の RPC をそのまま送り、バージョンが合わなければ UnsupportedProtocolVersionError を受けて再試行できます。
resultType
すべての結果は resultType を持ちます。クライアントは、この値で result の解釈を決めます。
| 値 | 意味 |
|---|---|
complete | リクエストが完了し、result に最終的な内容がある |
input_required | 処理を進めるには追加の入力が必要。result は InputRequiredResult で、inputRequests に必要な入力を載せる |
クライアントは、resultType が欠けた結果を complete として扱わなければなりません。2025-11-25 以前のサーバーは、このフィールドを返さないためです。一方、クライアントが知らない値は、不正として扱います。拡張が resultType の値を足すことはできますが、有効になるのは、ケイパビリティで広告された拡張の値だけです。input_required を受けたときの、クライアントの再試行の手順は ElicitationとMulti Round-Trip Requests で扱います。
ケイパビリティと extensions
ケイパビリティは、ClientCapabilities と ServerCapabilities で宣言します。クライアントのものは、リクエストごとに _meta の clientCapabilities として送ります。サーバーのものは、server/discover の結果に含まれます。
コアのプロトコルに含まれないオプションの機能は、extensions として交渉します。ケイパビリティの extensions フィールドは、拡張の識別子から、その拡張固有の設定オブジェクトへの対応表です。識別子は _meta のキーと同じ規則に従い、プレフィックスが必須です。設定が不要なら、空のオブジェクトで対応を示します。
{
"capabilities": {
"tools": {},
"extensions": {
"io.modelcontextprotocol/tasks": {}
}
}
}
片方だけが拡張に対応している場合、対応している側は、コアのプロトコルの挙動に戻すか、適切なエラーでリクエストを拒否しなければなりません。各拡張は、フォールバックの挙動を文書にすることが推奨されています。拡張の一覧と使い方は MCP拡張機能 - Tasks・MCP Apps・Skills を参照してください。
エラーコードの割り当て
MCP は、一般的なプロトコルの失敗には、JSON-RPC 標準のコード (-32700、-32600〜-32603) を使います。JSON-RPC が実装定義のために予約する -32000〜-32099 は、次のように分割されています。
| 範囲 | 扱い |
|---|---|
-32000〜-32019 | 過去に実装が使っていたコード (レガシー)。新しいコードの割り当ては禁止で、新しい実装は使わないことが推奨される。-32002 を除き、特定の意味を仮定してはいけない |
-32020〜-32099 | MCP 仕様が予約。仕様が定義したコードだけを、定義された意味で使う |
2026-07-28 が定義するコードは 3 つです。
| コード | 名前 | 用途 |
|---|---|---|
-32020 | HeaderMismatch | HTTP ヘッダーが本文と一致しない、または必須ヘッダーが欠けている |
-32021 | MissingRequiredClientCapability | 処理に必要なクライアントのケイパビリティが宣言されていない |
-32022 | UnsupportedProtocolVersion | 要求されたバージョンにサーバーが対応していない |
過去の版のコードのうち、次の 2 つは、この版の実装が送出してはいけません。
-32002: リソース未検出 (2025-11-25 以前)。-32602(Invalid params) に置き換わった。クライアントは、旧版のサーバーが返す-32002を、引き続き受け入れることが推奨される-32042: URL 形式の Elicitation が必要 (2025-11-25 のみ)
アプリケーション固有のエラーに、独自のコードを割り当てるなら、JSON-RPC の予約範囲 (-32768〜-32000) の外を使います。
バージョニングと互換性
交渉のためのハンドシェイクはありません。すべてのリクエストが自分のバージョンを宣言し、サーバーはリクエストごとに受け入れるか拒否するかを決めます。HTTP では、MCP-Protocol-Version ヘッダーにも同じ値を載せます。
サーバーが要求されたバージョンに対応しない場合 (未知のバージョンでも、知っているが対応しないバージョンでも)、UnsupportedProtocolVersionError を返し、対応するバージョンを列挙します。クライアントは、一覧から共通のバージョンを選んで再試行するか、共通のものがなければエラーを利用者に伝えます。
仕様は、バージョンの世代を次のように呼びます。
- Modern: バージョン、識別、ケイパビリティをリクエストごとの
_metaで運ぶ版 (2026-07-28 以降) - Legacy:
initializeハンドシェイクでセッションを作る版 (2025-11-25 以前) - Dual-era: 両方に対応する実装
組み合わせごとの結果は次のとおりです。
| クライアント | サーバー | 結果 |
|---|---|---|
| Modern | Modern | 動く。server/discover は任意 |
| Modern | Legacy | 失敗する。サーバーが独自のエラーを返す、沈黙する、世代があいまいなメソッドを Legacy として処理することもある。stdio では server/discover を先に送り、確実に失敗させることが推奨される |
| Dual-era | Modern | 動く。クライアントは Modern のまま |
| Dual-era | Legacy | 動く。Modern のリクエストが認識できる Modern のエラーなしで失敗したら、initialize に切り替える |
| Legacy | Modern | 失敗する。Legacy のクライアントには、新しい版へ切り替える手段がない |
| Legacy | Dual-era | 動く。サーバーは initialize に応答し、合意した旧版で動く |
Dual-era のサーバーは、_meta を持つリクエストは Modern として、initialize で始まるものは Legacy として扱います。同じエンドポイントや同じプロセスで、両方を同時に提供できます。
世代の判定は、リクエストごとではなく、サーバーの性質です。クライアントは結果を、サーバープロセス (stdio) またはオリジン (HTTP) の存続期間はキャッシュすることが推奨され、仮定が外れたときに再度プローブします。
世代の見分け方は、トランスポートごとに異なります。
- stdio: 他のリクエストより先に
server/discoverを送る。DiscoverResultが返れば Modern。UnsupportedProtocolVersionErrorのような認識できる Modern のエラーなら、Modern でバージョンが合わないだけなので、対応バージョンで再試行する。それ以外のエラーや、タイムアウトなら Legacy としてinitializeに切り替える。切り替えを特定のエラーコードに結びつけてはいけない - Streamable HTTP: Modern のリクエストをまず送り、
400 Bad Requestならボディを調べる。認識できる Modern の JSON-RPC エラーがあれば Modern。空のボディや、それ以外ならinitializeに切り替える
Modern だけに対応するサーバーは、initialize へのエラーに、対応するバージョンを含めることが推奨されます。Legacy のクライアントが利用者に見せられる診断が、そのメッセージだけのこともあるためです。
廃止されたユーティリティ
2026-07-28 では、次のユーティリティが削除されました。
| 削除されたもの | 代わりに |
|---|---|
ping | 後継のメソッドは定義されていない |
logging/setLevel | リクエストごとに _meta の io.modelcontextprotocol/logLevel で指定する。サーバーは、このフィールドのないリクエストに notifications/message を出してはいけない |
notifications/roots/list_changed | Roots の入力は MRTR で必要なときに受け取る |
これらは Roots / Sampling / Logging の非推奨とは別の話です。非推奨の機能は、移行期間のあいだは動作しますが、ここで挙げたメソッドは仕様から取り除かれています。非推奨の一覧は MCP最新仕様 2026-07-28 の変更点と移行 を参照してください。
2. 2025-11-25 と 2026-07-28 の基盤の違い
| 観点 | 2025-11-25 まで | 2026-07-28 |
|---|---|---|
| 接続開始 | initialize / initialized のハンドシェイク | ハンドシェイクなし。server/discover は任意 |
| セッション | Mcp-Session-Id による状態保持 | プロトコルセッションなし。状態は明示的なハンドルで管理 |
| バージョン・ケイパビリティ | 初期化時に一度だけ交渉 | 毎リクエストの _meta に含める |
| list 系の結果 | 接続ごとに変化しうる | 接続ごとに変化しない。ttlMs と cacheScope を返す |
| サーバー起点のリクエスト | サーバーからクライアントへ直接送信 | Multi Round-Trip Requests で代替 |
3. 開発者がつまずきやすいポイント
古い資料のハンドシェイクを実装してしまう
2025 年版の資料や SDK のサンプルは、initialize を最初に呼ぶ前提で書かれています。2026-07-28 のサーバーに initialize を送ると、未知のメソッドとして拒否されます。かといって、initialize を処理するコードを書く必要もありません。Modern のサーバーは、すべてのリクエストを _meta だけで処理します。Legacy のクライアントも受けたいなら、Dual-era のサーバーとして、両方を実装します。
_meta の必須フィールドを忘れる
素の HTTP クライアントや curl でデバッグするとき、protocolVersion と clientCapabilities を _meta に入れ忘れると、-32602 と 400 Bad Request で拒否されます。ケイパビリティを使わないリクエストでも、空のオブジェクト {} は必要です。HTTP では、本文の値と MCP-Protocol-Version ヘッダーの値の食い違いも、HeaderMismatch (-32020) で拒否されます。
clientInfo で挙動や認可を分ける
clientInfo と serverInfo は、自己申告で、検証されません。クライアント名で認可を切り替えたり、機能を出し分けたりすると、名前を偽るだけで回避されます。認可は MCPの認可 - OAuth 2.1とメタデータ探索 のトークンで行います。
resultType を無視して result を決め打ちで読む
tools/call などの結果を complete と決め打ちにすると、input_required が返ったときに、content がなく空の結果のように見えます。結果を読む前に resultType を確認し、input_required なら MRTR の手順に進みます。反対に、旧版のサーバーは resultType を返さないため、欠けている場合は complete と見なします。
リソース未検出のエラーコード
リソースが見つからないときのコードは、-32002 から -32602 に変わりました。クライアントは、新旧どちらも「未検出」として扱う必要があります。サーバーは -32002 を返してはいけません。
接続やプロセスを会話の境界にする
stdio のプロセスを、会話ごとに起動し直したり、「このプロセスは 1 つの会話に属する」と仮定してメモリ上にコンテキストを持ったりする設計は、仕様と合いません。1 つのプロセスに、無関係なリクエストが混在し得ます。状態は、明示的なハンドルで運びます。
ping に頼った生存確認
ping は削除されました。生存確認用の後継メソッドはありません。実際のリクエストのタイムアウトや、ストリームの切断で異常を検出する設計にします。ストリームの接続が切れたら、リクエストを新しいリクエスト ID で再発行します (トランスポート を参照)。
独自のエラーコードを -32000 台に割り当てる
-32000〜-32019 は新規の割り当てが禁止で、-32020〜-32099 は仕様が予約しています。アプリケーション固有のコードは、-32768〜-32000 の外に割り当てます。
4. コード例
最小のリクエストとレスポンス
tools/call の例です。必須の _meta と、結果の resultType と serverInfo を確認します。
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_weather",
"arguments": { "location": "Tokyo" },
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientInfo": { "name": "ExampleClient", "version": "1.0.0" },
"io.modelcontextprotocol/clientCapabilities": {}
}
}
}
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"resultType": "complete",
"content": [{ "type": "text", "text": "晴れ、22 度" }],
"_meta": {
"io.modelcontextprotocol/serverInfo": { "name": "ExampleServer", "version": "1.0.0" }
}
}
}
server/discover
{
"jsonrpc": "2.0",
"id": "discover-1",
"method": "server/discover",
"params": {
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientInfo": { "name": "ExampleClient", "version": "1.0.0" },
"io.modelcontextprotocol/clientCapabilities": {}
}
}
}
{
"jsonrpc": "2.0",
"id": "discover-1",
"result": {
"resultType": "complete",
"supportedVersions": ["2026-07-28"],
"capabilities": { "tools": {}, "resources": {} },
"_meta": {
"io.modelcontextprotocol/serverInfo": { "name": "ExampleServer", "version": "1.0.0" }
},
"instructions": "This server provides weather and resource utilities.",
"ttlMs": 3600000,
"cacheScope": "public"
}
}
バージョン不一致のエラー
supported から、クライアントが対応するバージョンを選んで再試行します。HTTP では 400 Bad Request です。
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32022,
"message": "Unsupported protocol version",
"data": {
"supported": ["2026-07-28", "2025-11-25"],
"requested": "1900-01-01"
}
}
}
ケイパビリティの不足
必要なケイパビリティがリクエストに含まれていない場合のエラーの形です (data.requiredCapabilities に不足分を列挙する)。
{
"jsonrpc": "2.0",
"id": 2,
"error": {
"code": -32021,
"message": "Missing required client capability",
"data": { "requiredCapabilities": { "elicitation": {} } }
}
}
data の厳密なスキーマは、Schema Reference の MissingRequiredClientCapabilityError で確認してください。
結果の読み取り (TypeScript)
resultType を確認してから結果を扱う、クライアント側の骨格です。
type Result = { resultType?: string; [key: string]: unknown };
function readResult(result: Result): Result {
// 旧版のサーバーは resultType を返さない。欠けていれば complete と見なす
const type = result.resultType ?? "complete";
switch (type) {
case "complete":
return result;
case "input_required":
// inputRequests を処理し、inputResponses を付けて元のリクエストを再試行する
throw new Error("MRTR: input required");
default:
// 知らない値は不正として扱う
throw new Error(`Invalid resultType: ${type}`);
}
}
5. まとめ
- MCP のメッセージは JSON-RPC 2.0。クライアントのリクエストにサーバーが応答し、サーバーは独自のリクエストを送らない
- 2026-07-28 はステートレス。
initializeとセッションを廃止し、protocolVersionとclientCapabilitiesを毎回の_metaに載せる server/discoverはサーバーが必須で実装し、クライアントは任意で呼ぶ。stdio では後方互換のプローブになる- すべての結果が
resultTypeを持つ。欠けていればcomplete、input_requiredなら MRTR に進む - エラーコードは、
-32020〜-32099が仕様の予約。リソース未検出は-32602 - バージョンの不一致は
UnsupportedProtocolVersionErrorで検出し、supportedから選んで再試行する ping、logging/setLevel、notifications/roots/list_changedは削除された
関連ドキュメント
- MCP入門 - Model Context Protocolとは
- MCP最新仕様 2026-07-28 の変更点と移行
- トランスポート - stdioとStreamable HTTP
- ElicitationとMulti Round-Trip Requests