Claude Media
MCPのMRTRとは — サーバー主導リクエストを置き換えた新パターン

MCPのMRTRとは — サーバー主導リクエストを置き換えた新パターン

MCPのMRTRは、サーバーが独立したリクエストを送る代わりにレスポンスでリトライを促す往復パターンです。仕組みとrequestStateの安全な実装方法を解説します。

MCPのMRTRとは何か

MRTR(Multi Round-Trip Requests)とは、MCPサーバーがクライアントに追加情報を求めるときの往復パターンです。サーバーから独立したリクエストを送る代わりに、レスポンス自体へ「まだ完了していない」という状態を含め、クライアントにリトライさせます。2026-07-28のMCP仕様改訂(SEP-2322)で新設されました。設計原則の背景はMCPの設計原則8箇条にまとめています。roots/list・sampling/createMessage・elicitation/createは、いずれもサーバーがクライアント側の情報を必要とするやり取りです。この3つは、すべてこの型に一本化されています。

仕様書は、サーバーがこれらをMRTRで送らなければならず、従来のサーバー主導リクエストはもうサポートされないと明記しています。これは破壊的変更です。MCP自体の全体像はMCPとはで扱っているので、初めて触れる場合はそちらを先に見ておくと文脈がつかみやすくなります。

サーバー主導リクエストの何が問題だったのか

旧来の仕組みは、サーバーがツール呼び出しの処理中にクライアントへroots/listやsampling/createMessageのような別のJSON-RPCリクエストを割り込ませる形でした。応答が返るまで、元の処理は保留されます。これ自体は自然な発想ですが、サーバーを複数プロセスで水平スケールさせようとした瞬間に破綻します。

割り込みリクエストへの応答は、割り込みを送った同じサーバープロセスに返ってこなければなりません。ロードバランサーの背後に複数インスタンスを並べる構成を考えます。リクエストの往復のあいだ同じインスタンスへ固定的にルーティングする仕組み(セッションアフィニティ)か、インスタンス間で状態を共有するストレージ層のどちらかが必要になります。MRTRの仕様は、共有ストレージ層もステートフルなロードバランシングも要らない形を目標に置いています。

MRTRはこの前提そのものを外します。サーバーは処理の続きに必要な文脈をrequestStateという不透明な文字列へ詰め込み、クライアントにそのまま持ち帰らせます。リトライを受け取るサーバープロセスは、最初のリクエストを処理したプロセスと同一である必要がありません。仕様の言葉では、リトライ側のサーバーは「リトライのリクエストに直接含まれている情報」以外を必要としません。

MRTRの往復フロー — InputRequiredResultとrequestState

手順

MRTRの4ステップ

  1. 1

    最初のリクエストを送る

    クライアントが、処理に必要なパラメータを添えてリクエストを送ります(例ではid: 1のtools/call)。

  2. 2

    サーバーが追加入力を求める

    追加情報が不足しているとサーバーが判断し、InputRequiredResultを返します。この時点で最初のリクエストは終了します。

  3. 3

    クライアントが入力を集めてリトライする

    ユーザーや他の情報源から入力を集め、inputResponsesとrequestStateを添えて元のリクエストをid: 2で送り直します。

  4. 4

    サーバーが最終結果を返す

    requestStateから状態を復元し、処理を完了して結果を返します。

ステップ2でサーバーが返す応答はInputRequiredResultという型で、resultTypeフィールドには"input_required"が入ります。中身はinputRequestsとrequestStateの2つで、少なくとも一方を必ず含みます。inputRequestsは、サーバーが割り当てたキーと、ElicitRequest・CreateMessageRequest・ListRootsRequestのいずれかの組です。

{
  "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"
  }
}

1回の応答に複数の入力要求を載せることもできます。仕様の例では、github_loginというキーのelicitationと、capital_of_franceというキーのsamplingが同じinputRequestsに並んでいます。クライアントはキーごとに結果を返し、リトライでは同じキーのinputResponsesとして戻します。

{
  "github_login": {
    "action": "accept",
    "content": { "name": "octocat" }
  },
  "capital_of_france": {
    "role": "assistant",
    "content": { "type": "text", "text": "The capital of France is Paris." },
    "model": "claude-3-sonnet-20240307",
    "stopReason": "endTurn"
  }
}

elicitationの結果はactionとcontent、samplingの結果はrole・content・model・stopReasonという、従来と同じ形のまま入ります。変わったのは運ばれ方だけです。

InputRequiredResultを返してよいのはprompts/get・resources/read・tools/callの3つのクライアントリクエストに限られます。それ以外のリクエストでこの応答を返すことは禁じられています。tools/listなどの一覧系は対象外です。

義務はサーバーとクライアントでどう分かれるか

くらべる

往復の2回目を支える義務

書く側

サーバー

inputRequestsのキーはリクエスト内で一意にします。宣言されていないケーパビリティの入力要求は混ぜません。inputRequestsとrequestStateの少なくとも一方を必ず含めます。クライアントが再試行する保証はなく、同じリクエストに何度でもInputRequiredResultを返して入力を促すこともできます。

接続する側

クライアント

inputRequestsがあれば、入力を組み立ててからリトライします。requestStateがあれば値をそのまま返し、無ければリトライに含めません。JSON-RPCのidは初回と変えます。inputRequestsとrequestStateが影響するのは、その元リクエストのリトライだけです。並行して送る別のリクエストには使えません。

inputRequestsが無くrequestStateだけの応答では、クライアントは入力を集めずにすぐリトライしてかまいません。つまりサーバーは、入力を求めずに状態だけを持ち帰らせる使い方もできます。

requestStateの改ざん対策はどう実装するか

requestStateはクライアントを経由して往復します。仕様は、サーバーがこれを攻撃者の制御下にある入力として扱うことを求めています。悪意のあるクライアントや乗っ取られたクライアントが内容を書き換え、サーバーの認可判定やビジネスロジックを狂わせようとする余地があるためです。

requestStateが認可・リソースアクセス・業務ロジックに影響するなら、サーバーはHMACやAEADのような方式で整合性を保護しなければなりません。検証に失敗した状態は拒否します。省略が許されるのは、改ざんの結果がリクエスト失敗以上の実害を生まないケースに限られます。

再送(リプレイ)攻撃への対策として、仕様は整合性保護されたペイロードに次の3点を含め、受け取るたびに検証することを推奨しています。

推奨

リプレイ対策に入れる3点

  • 認証済みの主体

    別のユーザーが提示した状態は拒否します。

  • 短い有効期限(TTL)

    期限が切れた後に提示された状態は拒否します。

  • 元リクエストの識別子

    メソッド名と主要パラメータのダイジェストなどで、別のリクエストに流用された状態を拒否します。

仕様はこれらを、リプレイの窓を狭めて他ユーザーや別リクエストへの流用を防ぐ手段と位置づけています。ただし単発利用までは保証しません。1回限りの引き換えコードのように一度しか受け付けてはいけない用途では、その不変条件をサーバー側で別途強制する必要があります。

サーバー実装でつまずきやすい点

MRTRの実装でつまずくのは、往復の形式そのものより細部のルールです。仕様が明記している要件を落とすと、クライアント側は正常に見えるのにリトライが噛み合わないという分かりにくい不具合になります。

  • resultTypeの付け忘れ。2026-07-28ではすべての結果にresultTypeが必須で、通常の完了は"complete"、追加入力待ちは"input_required"です。旧プロトコルのサーバーが返す、このフィールドを欠いた結果はクライアント側で"complete"扱いになるため、新規実装でうっかり省略すると意図せず「完了」と解釈されます。
  • inputRequestsに未対応のケーパビリティを混ぜる。クライアントがelicitationを宣言していないのにelicitation/createを含めるのは仕様違反です。接続時にクライアントのcapabilitiesを確認してからinputRequestsを組み立てます。
  • requestStateの中身をクライアントに解釈させる。クライアントは中身を検査・解析・変更してはならず、内容について何も仮定しない前提です。人間可読な情報を入れて動作を分岐させる設計は仕様の想定から外れます。形式は自由で、base64のJSONや暗号化したJWT、シリアライズしたバイナリなどが例に挙がっています。
  • InputResponsesの不足を即エラーにする。リトライで必要な情報が揃っていなければ、仕様が推奨するのは新しいInputRequiredResultで不足分をもう一度求めることです。ただし不正なJSONやスキーマ違反、サーバー内部のエラーといったプロトコルエラーは、従来どおりJSON-RPCのエラー応答で返します。
  • 未知の追加パラメータで失敗させる。InputResponsesに想定外の項目が混ざっていても、サーバーは認識できない情報や不要な情報を無視することが推奨されています。

Claude CodeのMCPサーバー実装はMRTRにどう対応すべきか

Claude CodeのMCPクライアントには、MCP TypeScript SDK 1.xベースのv1ランタイムと、SDK 2.0ベースのv2ランタイムがあります。2026-07-28のプロトコルリビジョンに対応するのはv2です。ランタイムは起動のたびに選ばれ、終了するまで変わりません。

v2が既定になる条件は2系統あります。フィーチャーフラグを取得するセッションではv2.1.232以降です。取得しないセッションでは、v2.1.274以降で次のいずれかに当たるときにv2が既定になります。

  • Amazon Bedrock・Claude Platform on AWS・Google Cloud's Agent Platform・Microsoft Foundryのセッション。ただし、組み込み側がCLAUDE_CODE_PROVIDER_MANAGED_BY_HOSTを設定した場合は除きます
  • Claude appsゲートウェイ経由でサインインしたセッション
  • DISABLE_TELEMETRYなどでテレメトリーやフィーチャーフラグの取得を止めたセッション

環境変数MCP_SDK_GENERATION(Claude Code v2.1.218以降)にv1かv2を入れれば、ランタイムを自分で固定できます。

v2ランタイムが新しいリビジョンを問い合わせる範囲は、接続先の種類で変わります。

接続先既定の挙動MCP_PROTOCOL_NEGOTIATION=auto
HTTPサーバー既定の挙動2026-07-28に対応するか問い合わせるMCP_PROTOCOL_NEGOTIATION=auto同じ
claude.aiコネクタ既定の挙動フラグを取得するセッションだけ問い合わせるMCP_PROTOCOL_NEGOTIATION=auto全セッションで問い合わせる
stdioサーバー既定の挙動問い合わせず、v1と同じ従来の接続MCP_PROTOCOL_NEGOTIATION=auto問い合わせる

MCP_PROTOCOL_NEGOTIATION(Claude Code v2.1.221以降)をlegacyにすると、すべてのサーバーで問い合わせを省きます。SSEとWebSocketのサーバーは、常に従来のプロトコルで接続されます。問い合わせに応答しないサーバーも、従来のプロトコルで接続されます。サーバーが対応していても、Claude Codeが取得するフィーチャーフラグで、Anthropicが特定のサーバーを従来のプロトコルのままにする場合があります。

自作のMCPサーバーがHTTP経由でClaude Codeに接続し、2026-07-28への対応を表明したとします。その場合、elicitation/createやsampling/createMessageを使う部分はMRTRの往復に沿わせる必要があります。具体的には、追加情報が必要になった箇所で新しいJSON-RPCリクエストを送るコードを、InputRequiredResultを返すコードへ置き換える作業です。

2026-07-28で接続しているとき、Claude Codeはクライアントのケーパビリティにelicitation: {form: {}, url: {}}を宣言します。つまりformとurlのどちらのモードもinputRequestsに入れられます。urlモードはv2.1.281以降の接続で使えます。ユーザーにダイアログを出さずに自動で応答させたい場合は、Elicitationフックを使います。

自作サーバーの設計・実装手順はMCPサーバー自作ガイドにまとめています。Claude Code側での接続設定はClaude Code MCP設定ガイドを参照してください。プロトコルの往復が正しく動いているかは、MCP Inspectorの使い方で紹介しているツールでツール呼び出しを直接叩いて確認できます。ただしInputRequiredResultが返ってきた後、リトライまで通しで確認するのが確実です。単発のリクエストが成功するだけでは、往復の2回目でサーバーが状態を正しく復元できているかまでは分かりません。

チャンネルサーバーには別の制約があります。MCP_PROTOCOL_NEGOTIATIONをautoにしたv2ランタイムでは、チャンネルサーバーも2026-07-28で接続することがあります。このリビジョンはチャンネルメッセージを運べないため、Claude Codeはそのサーバーをチャンネルとして登録しません。変数を未設定のままかlegacyにしておけば、stdioサーバーは従来のハンドシェイクのままです。

まとめ

MRTRへ移行する時期は、HTTPサーバーが2026-07-28への対応を表明するかどうかで決まります。表明しなければ従来のプロトコルで接続され続け、stdioサーバーはMCP_PROTOCOL_NEGOTIATIONをautoにするまで影響を受けません。

この記事を共有:XはてブLinkedIn
MCP をもっと見る →