MCPのMRTRとは — サーバー主導リクエストを置き換えた新パターン
MCPのMRTRは、サーバーが独立したリクエストを送る代わりにレスポンスでリトライを促す往復パターンです。仕組みとrequestStateの安全な実装方法を解説します。
MCPのMRTRとは何か
MRTR(Multi Round-Trip Requests)とは、MCPサーバーがクライアントに追加情報を求めるときの往復パターンです。サーバーから独立したリクエストを送る代わりに、レスポンス自体へ「まだ完了していない」という状態を含め、クライアントにリトライさせます。2026-07-28のMCP仕様改訂(SEP-2322)で新設されました。roots/list・sampling/createMessage・elicitation/createという、サーバーがクライアント側の情報を必要とする3つのやり取りは、すべてこの型に一本化されています。
この改訂は破壊的変更です。仕様書は「サーバーはMRTRパターンを使わなければならず、従来のサーバー主導リクエストはもうサポートされない」と明記しています。読み替えの余地はありません。2026-07-28のプロトコルリビジョンで通信するサーバーを書くなら、この記事で説明する往復の型に乗せる以外の選択肢は無くなりました。MCP自体の全体像はMCPとはで扱っているので、初めて触れる場合はそちらを先に見ておくと文脈がつかみやすくなります。
サーバー主導リクエストの何が問題だったのか
旧来の仕組みは、サーバーがツール呼び出しの処理中にクライアントへroots/listやsampling/createMessageのような別のJSON-RPCリクエストを割り込ませる形でした。応答が返るまで、元の処理は保留されます。これ自体は自然な発想ですが、サーバーを複数プロセスで水平スケールさせようとした瞬間に破綻します。
割り込みリクエストへの応答は、割り込みを送った同じサーバープロセスに返ってこなければなりません。ロードバランサーの背後に複数インスタンスを並べる構成を考えます。リクエストの往復のあいだ同じインスタンスへ固定的にルーティングする仕組み(セッションアフィニティ)か、インスタンス間で状態を共有するストレージ層のどちらかが必要になります。どちらも実装コストと運用コストを押し上げます。サーバーレス環境やオートスケール環境とは相性が悪いという問題も残っていました。
MRTRはこの前提そのものを外します。サーバーは処理の続きに必要な文脈をrequestStateという不透明な文字列へ丸ごと詰め込み、クライアントにそのまま持ち帰らせます。クライアントがリトライしたとき、応答を受け取るサーバープロセスは最初にリクエストを処理したプロセスと同一である必要がありません。共有ストレージもステートフルなロードバランシングも要らなくなります。
MRTRの往復フロー — InputRequiredResultとrequestState
基本の流れは4ステップです。①クライアントが処理に必要なパラメータを添えて最初のリクエストを送る。②サーバーが追加情報の不足に気づき、その情報を求める応答を返す。③クライアントがユーザーや他の情報源から求められた情報を集め、それを添えて元のリクエストをもう一度送り直す。④サーバーが十分な情報を得たと判断し、最終結果を返す。
サーバーが②で返す応答はInputRequiredResultという型です。resultTypeフィールドには"input_required"が入ります。中身はinputRequests(サーバーが割り当てたキーと、ElicitRequest・CreateMessageRequest・ListRootsRequestいずれかの組)とrequestState(不透明な文字列)の2つで、少なくとも一方を必ず含みます。
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"resultType": "input_required",
"inputRequests": {
"github_login": {
"method": "elicitation/create",
"params": {
"mode": "form",
"message": "GitHubのユーザー名を入力してください",
"requestedSchema": {
"type": "object",
"properties": { "name": { "type": "string" } },
"required": ["name"]
}
}
}
},
"requestState": "AEAD-protected blob"
}
}クライアントはinputRequestsが示す情報をユーザーや他の情報源から集めたら、inputResponsesとして元のリクエストに添えてもう一度送ります。このときrequestStateが付いていれば、内容を検査・変更せずにそのまま送り返すことが仕様で義務づけられています。JSON-RPCのidは初回とリトライで必ず変える必要があり、両者は完全に独立したリクエストとして扱われます。
InputRequiredResultを返してよいのはprompts/get・resources/read・tools/callの3つのクライアントリクエストに限られます。それ以外のリクエストでこの応答を返すことは禁じられています。サーバーが宣言していないケーパビリティの入力要求を混ぜることも同様に禁止です。たとえばクライアントがelicitationのサポートを表明していないのに、elicitation/createをinputRequestsに含めることはできません。
対象がこの3つに絞られているのは偶然ではありません。tools/listやresources/listのような一覧系のリクエストは、起動時やUI表示のたびに気軽に呼べる軽量な操作として設計されています。そこに「追加入力待ち」という中断が挟まると、使い勝手が損なわれます。一方、tools/callのようにユーザーが明示的にアクションを起こすリクエストであれば、途中で追加確認が挟まっても自然です。MRTRの適用範囲を絞り込んでいるのは、この体験の違いを踏まえた設計判断だと読めます。
requestStateの改ざん対策はどう実装するか
requestStateはクライアントを経由して往復します。悪意のあるクライアントや乗っ取られたクライアントが内容を書き換え、サーバーの認可判定やビジネスロジックを狂わせようとする余地があるため、仕様はここを明確にセキュリティ要件として扱っています。
requestStateが認可・リソースアクセス・業務ロジックに影響するなら、サーバーはHMACやAEADのような方式で整合性を保護しなければなりません。検証に失敗した状態は拒否します。省略が許されるのは、改ざんの結果がリクエスト失敗以上の実害を生まないケースに限られます。つまり「念のため」で省いてよい項目ではなく、影響範囲を見極めたうえでの判断が要求される項目です。
再送(リプレイ)攻撃への対策も定められています。整合性保護されたペイロードには、認証済みの主体(別ユーザーが提示した状態は拒否)・短い有効期限(TTL、期限切れ後の提示は拒否)・元のリクエストを識別する情報(メソッド名とパラメータのダイジェストなど)の3点を含めて検証することが推奨されます。これらはリプレイの窓を狭め、他ユーザーや別リクエストへの流用を防ぎます。ただし単発利用が必須な用途(1回限りの引き換えコードなど)では、それをサーバー側で別途強制する必要がある点に注意が必要です。
サーバー実装でつまずきやすい点
MRTRの実装でつまずくのは、往復の形式そのものより細部のルールです。仕様が明記している要件を落とすと、クライアント側は正常に見えるのにリトライが噛み合わないという分かりにくい不具合になります。
resultTypeの付け忘れ。すべての結果にresultTypeが必須になり、通常の完了は"complete"、追加入力待ちは"input_required"です。旧プロトコルのサーバーが返す、このフィールドを欠いた結果はクライアント側で"complete"扱いになるため、新規実装でうっかり省略すると意図せず「完了」と解釈されます。inputRequestsに未対応のケーパビリティを混ぜる。クライアントがelicitationを宣言していないのにelicitation/createを含めるのは仕様違反で、送ってはいけません。接続時にクライアントのcapabilitiesを確認してからinputRequestsを組み立てる必要があります。requestStateをクライアントに解釈させる設計にする。クライアントはrequestStateの中身を検査・加工してはいけない不透明な文字列として扱う前提なので、そこに人間可読な情報を入れて動作を分岐させる設計は仕様の想定から外れます。InputResponsesの不足を即エラーにする。クライアントがリトライ時に必要な情報を送りきれなかった場合、仕様が推奨するのは新しいInputRequiredResultで不足分をもう一度求めることです。即座にエラーを返す実装は不親切な失敗体験になります。未知の追加パラメータが混ざっていた場合も、サーバーは無視してよいとされています。
Claude CodeのMCPサーバー実装はMRTRにどう対応すべきか
Claude Codeはv2.1.232以降、既定でMCP TypeScript SDK 2.0を使うv2ランタイムに切り替わります。このランタイムは、HTTPサーバーとclaude.aiコネクタサーバーには2026-07-28のプロトコルリビジョンに対応しているかを毎回確認し、対応していればそのリビジョンで通信します。stdioサーバーに対しては、環境変数MCP_PROTOCOL_NEGOTIATIONをautoに設定したときだけ同じ確認を行い、それ以外はv1相当の従来プロトコルで接続します。
つまり、自作のMCPサーバーがHTTP経由でClaude Codeに接続され、かつ2026-07-28への対応を表明した場合、実装の書き直しが必要になります。elicitation/createやsampling/createMessageを使う部分は、旧来の独立したサーバー主導リクエストではなく、この記事で説明したMRTRの往復に沿わせます。具体的には、追加情報が必要になった箇所で新しいJSON-RPCリクエストを送るコードを、InputRequiredResultを返すコードへ置き換える作業です。
自作サーバーの設計・実装手順はMCPサーバー自作ガイドにまとめています。Claude Code側での接続設定はClaude Code MCP設定ガイドを参照してください。プロトコルの往復が正しく動いているかは、MCP Inspectorの使い方で紹介しているツールを使い、ツール呼び出しを直接叩いて確認できます。ただしInputRequiredResultが返ってきた後、リトライまで通しで確認するのが確実です。単発のリクエストが成功するだけでは、往復の2回目でサーバーが状態を正しく復元できているかまでは分かりません。
なお、Claude Codeがv1ランタイムで動いている場合は、2026-07-28への協議自体が発生せず、旧来のプロトコルで接続され続けます。該当するのはAmazon Bedrock・Claude Platform on AWS・Google Cloud's Agent Platform・Microsoft Foundry経由での実行時、またはfeature-flag取得が無効な環境です。同じサーバーでも、どちらのランタイムで接続されるかによってMRTRへの対応が要るかどうかが変わる点は覚えておく価値があります。
まとめ
MRTRは、サーバーが複数インスタンスへ水平スケールしながらもelicitationやsamplingのような対話的なやり取りを続けられるようにする、2026-07-28導入の必須パターンです。影響を受けるのは、これら3つのサーバー主導リクエストのいずれかを実装しているMCPサーバーの開発者です。Claude Codeのv2ランタイムと通信するHTTPサーバーであれば、対応の優先度は今すぐ上がります。stdioサーバーはMCP_PROTOCOL_NEGOTIATIONを明示的にautoにしない限り旧プロトコルのままなので、移行のタイミングを自分でコントロールできます。