MCPのresultTypeフィールド — complete/input_requiredの見分け方
MCP 2026-07-28版は、すべての成功応答にresultTypeフィールドを必須化しました。complete/input_requiredの2値と、後方互換の欠落時扱いを実装例とあわせて解説します。
MCPの成功応答は、2026-07-28版からresultオブジェクトにresultTypeフィールドを含むことが必須になりました。resultTypeは結果の種類を示す文字列で、クライアントはこの値を見てからresultオブジェクトの残りをどう解釈するか決めます。コア仕様が定義する値は"complete"と"input_required"の2つだけです。この2値の使い分けと、値が欠けている応答の扱いを見ていきます。
resultTypeは何のために必須化されたか
resultTypeが導入される前、MCPの結果応答は基本的に単一の形しか想定していませんでした。リクエストが成功すればresultにその内容がそのまま入るという単純な構造です。しかし事情が変わりました。サーバーが処理を完了する前にクライアント側からの追加入力(elicitation・sampling・roots)を必要とするケースを、MCPはマルチラウンドトリップリクエスト(MRTR)としてプロトコルに正式に組み込みました。1つのリクエストに対して「完了した結果」と「まだ追加入力が要る」という、構造がまったく異なる2種類の応答があり得るようになったのです。
resultTypeは、この多態性(polymorphic result)をクライアントが型安全に処理するための識別子です。仕様の言葉を借りれば、resultは任意のJSONオブジェクト構造を取ってよいが、その中にresultTypeだけは必ず含まれ、クライアントはこれを見てパース方法を切り替えます。
MRTR自体、2026-07-28版で新規に導入された仕組みです。それ以前にサーバーがクライアントへroots/listやsampling/createMessage、elicitation/createを送っていたやり方を置き換える、破壊的変更にあたります。仕様は「旧来のサーバー起点リクエストのパターンはもうサポートしない」と明記しています。旧方式は、サーバーが任意のタイミングでクライアントに問い合わせを差し込める代わりに、どのクライアント接続がどのリクエストへの応答を待っているかをサーバー側が覚えておく必要がありました。複数のサーバーインスタンスで同じ処理を引き継ぐには、インスタンス間で状態を共有するストレージ層か、特定のインスタンスに固定してルーティングするステートフルなロードバランシングのどちらかが要ります。MRTRはこの前提を外し、サーバーが必要な状態をrequestStateという1つの不透明な文字列に押し込め、次に届くリクエストがそれを運んでくることだけを頼りに処理を再開する設計に変えました。
complete/input_requiredの2つの値
| 値 | 意味 |
|---|---|
"complete" | 意味リクエストが成功し、resultに最終的な内容が入っている |
"input_required" | 意味リクエストが未完了で、処理を続けるには追加情報が必要 |
"input_required"のとき、resultはInputRequiredResultという専用の構造を持ちます。次の例は、サーバーがツール呼び出しの途中でGitHubのユーザー名(elicitation)と、別の情報をLLMに問い合わせる結果(sampling)の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"
}
}仕様は拡張機能が独自のresultType値を追加することも認めています。ただしクライアントが対応する拡張をcapabilitiesで広告していない限り、コアプロトコルで定義された値の集合以外は「未認識」として扱われ、未認識のresultTypeはすべて無効と見なされます。
混同しやすい点として、resultTypeが必須になるのはあくまで成功応答(resultフィールドを持つ応答)だけです。リクエストが失敗した場合の応答は別の構造のerrorフィールドを使い、resultTypeは登場しません。実装するときは「応答にresultとerrorのどちらが入っているか」を先に見て、resultのときだけresultTypeを参照するという順序を守ります。
resultTypeが欠けている応答をどう扱うか
ここが実装で最も間違えやすい点です。resultTypeは2026-07-28版で必須になったフィールドですが、それより前の版のサーバーはこのフィールドを送ってきません。仕様はこの後方互換のために明確な規則を置いています。クライアントは、resultTypeが欠落した結果を"complete"として扱わなければなりません。つまり旧サーバーからの応答は「フィールドが無いから不正」ではなく「暗黙に完了扱い」というのが正しい実装です。この規則を知らずにresultTypeの有無で判定を分岐させると、旧サーバーとの通信で完了した処理を「未完了」と誤判定するバグを作り込みます。
InputRequiredResultが返ってくる3つのリクエスト
InputRequiredResultを返せるのは、仕様が明示したprompts/get・resources/read・tools/callの3種類のクライアントリクエストに限られます。それ以外のリクエストに対してinput_requiredを返すことは仕様違反です。いずれもサーバーが処理の途中でユーザーやLLM側の情報(elicitation・sampling・roots)を必要とし得るリクエストという共通点があり、逆にinitializeやpingのような単発で完結するリクエストは対象に含まれません。
InputRequiredResultはinputRequestsとrequestStateという2つの任意フィールドを持ちますが、このどちらか一方は必ず含まれていなければなりません。inputRequestsはサーバーが割り当てたキーごとに、elicitation/create・sampling/createMessage・roots/listのいずれかのリクエストを並べたマップです。クライアントはこれらを満たしてから、新しいJSON-RPC IDで元のリクエストをリトライします。requestStateはサーバーだけが意味を知る不透明な文字列で、クライアントはリトライ時にこれをそのまま送り返す以外の解釈をしてはいけません。
この往復の流れをツール呼び出しで説明すると次のようになります。クライアントがtools/callを送る。サーバーがelicitationを必要とし、InputRequiredResult(IDは1、requestState付き)を返す。この時点で最初のリクエストは完了扱いとして終わります。クライアントはユーザーに入力を促し、得られた回答とrequestStateを添えて、新しいID(たとえば2)で同じtools/callを再送します。サーバーはrequestStateから文脈を復元し、今度こそresultType: "complete"を返します。
再送するリクエストのparamsには、inputRequestsのキーに対応するInputResponsesオブジェクトを含めます。値はキーごとの応答結果で、elicitationならElicitResult、samplingならCreateMessageResult、rootsならListRootsResultです。
{
"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"
}
}JSON-RPCのIDは初回リクエストとリトライで必ず変えることが仕様上の要件です。両者は独立した別のリクエストとして扱われます。またinputRequestsとrequestStateは、あくまで元のリクエストのリトライにだけ作用するもので、クライアントが並行して送っている他のリクエストには一切影響しません。サーバー側のエラー処理にも規則があります。InputResponsesの形式が不正なら通常のJSON-RPCエラーを返すべきですが、クライアントが要求した情報の一部だけを送ってきた場合は、エラーではなく新しいInputRequiredResultを返して不足分を再度求めるべきだとされています。逆に、サーバーが必要としない余分な情報が含まれていた場合は、それを無視してよいことになっています。
requestStateはクライアントを信用しない前提で設計する
requestStateは仕組み上、クライアントを経由して往復します。仕様はこれを攻撃者が制御し得る入力として扱うことをサーバー側に義務付けています。requestStateが認可判断やリソースアクセス、業務ロジックに影響するなら、HMACやAEADのような改ざん検知を必ず付け、検証に失敗した状態は拒否しなければなりません。改ざんの結果がリクエスト失敗以上の被害を生まない場合に限り、この保護を省略できます。
再送攻撃(リプレイ)を防ぐため、仕様はrequestStateの中に認証済みプリンシパル・有効期限(TTL)・元リクエストの識別子(メソッド名とパラメータのダイジェストなど)を含め、受信のたびに検証することを推奨しています。ただしこれらの対策はリプレイの窓を狭め、他ユーザー・他リクエストでの使い回しを防ぐだけです。一度きりの利用を保証したい場合(ワンタイムの権限付与など)は、サーバー側で別途その不変条件を強制する必要があります。
resultTypeが変えたのは「新しい機能」ではなく「型付けの有無」
ここまで見てきた仕組みは、elicitation自体を新しく生み出したわけではありません。サーバーがクライアントに追加入力を求めるやり取り自体は以前から存在していました。resultTypeとInputRequiredResultが変えたのは、この「途中で入力を要求する」というやり取りをJSON-RPCの応答形式として明示的に型付けし、コアの成功応答と構造レベルで区別した点です。以前の仕様ではサーバーが任意のタイミングでクライアントへ問い合わせを差し込めた分、クライアント側は「今どの接続がどのリクエストの続きを待っているか」を実行時に判定する必要がありました。resultTypeはその判定を、応答に載った1つの文字列を読むだけの作業に変えています。
クライアント・サーバーの実装をゼロから作る場合の設計はMCPサーバー自作ガイド、Agent SDKからMCPサーバーへ接続する構成はAgent SDK MCP接続ガイドにまとめています。
まとめ
resultTypeはMCPの成功応答を"complete"と"input_required"に分ける必須フィールドです。旧版のサーバーが送ってこない場合は"complete"として扱うのが正しい後方互換の実装で、逆にクライアントが認識しない値はすべて無効と見なします。input_requiredが返せるのはprompts/get・resources/read・tools/callの3種類だけで、InputRequiredResultのrequestStateは攻撃者が制御し得る入力として整合性を検証しなければなりません。認証まわりの実装はリモートMCPのOAuth認証、Claude Code側からの接続設定はClaude Code MCP設定ガイドで扱っています。