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

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、resultresult に 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/subscriptionIdsubscriptions/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 つの場面です。

  1. サーバー情報の提示: tools/list、prompts/list、resources/list を別々に呼ばずに、サーバーの対応範囲を 1 回で表示したい
  2. 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〜-32099MCP 仕様が予約。仕様が定義したコードだけを、定義された意味で使う

2026-07-28 が定義するコードは 3 つです。

コード名前用途
-32020HeaderMismatchHTTP ヘッダーが本文と一致しない、または必須ヘッダーが欠けている
-32021MissingRequiredClientCapability処理に必要なクライアントのケイパビリティが宣言されていない
-32022UnsupportedProtocolVersion要求されたバージョンにサーバーが対応していない

過去の版のコードのうち、次の 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: 両方に対応する実装

組み合わせごとの結果は次のとおりです。

クライアントサーバー結果
ModernModern動く。server/discover は任意
ModernLegacy失敗する。サーバーが独自のエラーを返す、沈黙する、世代があいまいなメソッドを Legacy として処理することもある。stdio では server/discover を先に送り、確実に失敗させることが推奨される
Dual-eraModern動く。クライアントは Modern のまま
Dual-eraLegacy動く。Modern のリクエストが認識できる Modern のエラーなしで失敗したら、initialize に切り替える
LegacyModern失敗する。Legacy のクライアントには、新しい版へ切り替える手段がない
LegacyDual-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_changedRoots の入力は 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 は削除された

関連ドキュメント​

参考資料​