- Published on
MCPがセッションを取り払う — 2026-07-28リビジョンのステートレスコアを読む
- Authors

- Name
- Youngju Kim
- @fjvbn20031
- はじめに — この記事の賞味期限を先に断っておきます
- 何が問題だったのか — セッションがロードバランサーと戦う
- ステートレスコア — ハンドシェイクとセッションが消える
- MRTR — サーバーが聞き返す方式がひっくり返った
- 運用者にとって実際に変わること — ヘッダー、キャッシュ、トレース
- 認証 — OAuth/OIDCへさらに寄っていく
- 消えていくもの — Roots、Sampling、Logging
- 拡張 — Tasksがコアから出ていく
- 今何をすべきか — そして何を急がなくていいか
- おわりに
- 参考資料
はじめに — この記事の賞味期限を先に断っておきます
今日は2026年7月16日です。そしてMCPの現在(Current)の仕様はいまだに2025-11-25です — 公式のバージョニング文書がそう述べています。
これから話す2026-07-28リビジョンはまだドラフトで、リリース候補(RC)としてロックされた状態です。RC公表によれば、RCは2026年5月21日にロックされ、最終仕様は2026年7月28日に公開される予定です。つまりこの記事を書いている時点から12日後です。
だからこの記事は「新仕様の解説」ではなく「RCを先読みする記事」です。細部はまだ変わりうるし、実際に変われば本稿の一部は誤りになります。その前提で読んでください。MCPがそもそも何で、ツール・リソース・プロンプトがどんな形かはMCPリファレンスの回ですでに整理したので、ここでは何が変わるのかだけに焦点を当てます。
何が問題だったのか — セッションがロードバランサーと戦う
Streamable HTTPがリモートデプロイを開いたあと、規模の面で問題が露呈しました。2026年ロードマップはこれをかなり率直に書いています — 状態を持つセッションはロードバランサーと戦い、水平スケーリングには回避策が要り、レジストリやクローラーがサーバーに接続せずにそのサーバーが何をするか知る標準的な方法がない、と。
具体的にはこういう意味です。既存の設計では、クライアントはまずinitializeハンドシェイクを行い、サーバーはMcp-Session-Idを発行し、以後すべてのリクエストはそのセッションに紐づきます。すると運用側にはこういうものが付きまといます。
- スティッキーセッション。 同じセッションのリクエストは同じインスタンスに行かねばならず、ラウンドロビンが使えません。
- 共有セッションストア。 インスタンスを複数立てるには、セッション状態をどこかで共有する必要があります。
- ゲートウェイでの本文検査。 このリクエストが
tools/callなのかtools/listなのかを知るにはJSON-RPCの本文を開いて見る必要があります。HTTP層ではすべて同じPOSTだからです。
RC公表はこの変化の結果を一文でまとめています — 以前はスティッキーセッションと共有セッションストア、ゲートウェイでの深いパケット検査が必要だったリモートMCPサーバーが、今では普通のラウンドロビン型ロードバランサーの背後で動けるようになった、と。
ロードマップには率直な注釈も付いています — 今回のサイクルでは公式のトランスポート(transport)を新たに追加するのではなく、既存のトランスポートを進化させるだけだ、と。新アーキテクチャを乗せる話ではなく、HTTPトランスポートに手を入れる作業です。
ステートレスコア — ハンドシェイクとセッションが消える
公式changelogが挙げる主要変更(major changes)の冒頭はすべてこの話です。
SEP-2575: initializeとnotifications/initializedのハンドシェイクを撤廃します。代わりにすべてのリクエストが_metaに自身のプロトコルバージョンとクライアント能力を載せます — io.modelcontextprotocol/protocolVersion、io.modelcontextprotocol/clientCapabilities。クライアントは毎リクエストで自分を名乗ること(io.modelcontextprotocol/clientInfo)が推奨され、サーバーも各結果の_metaで自分を名乗ります。バージョンが合わなければUnsupportedProtocolVersionErrorが返ります。
SEP-2567: Mcp-Session-Idヘッダーとプロトコル層のセッション自体をなくします。そのため一覧系エンドポイント(tools/list、resources/list、prompts/list)は接続ごとに変わらなくなります。呼び出し間で状態が必要なサーバーはどうするかというと — サーバーが発行した明示的なハンドルをふつうのツール引数としてやり取りします。これが核心の設計思想です。状態を禁じるのではなく、状態をプロトコルの暗黙の層からアプリケーションの明示的データへ引き下ろすということです。
server/discoverの追加: サーバーはこのRPCを必ず実装しなければなりません(MUST)。対応するプロトコルバージョン、能力、身元を広告する用途です。クライアントは他のリクエストに先立ってこれを呼び、バージョンをあらかじめ選べますし、stdioでは下位互換の探り(プローブ)として使うこともできます。ロードマップが言っていた「接続せずにサーバーが何をするか知る方法」の答えがここにあります。
なくなるもの: ping、logging/setLevel、notifications/roots/list_changedが消えます。ログレベルはこれからリクエストごとに_metaのio.modelcontextprotocol/logLevelで指定し、このフィールドがないリクエストに対してサーバーはnotifications/messageを出してはいけません(MUST NOT)。
購読方式の置き換え: HTTP GETエンドポイントとresources/subscribe / resources/unsubscribeがsubscriptions/listenひとつに置き換わります — サーバーからクライアントへの変更通知のための単一の長生きするPOSTレスポンスストリームです。クライアントは欲しい種類(toolsListChanged、promptsListChanged、resourcesListChanged、resourceSubscriptions)を選んでオプトインします。注意すべき区別がひとつあります — notifications/progressやnotifications/messageのようなリクエストスコープの通知は、依然としてそのリクエスト自身のレスポンスストリームに流れ、subscriptions/listenストリームには来ません。
そして、ここが本当のコストです
同じSEP-2575がStreamable HTTPからSSEストリームの再開(resumability)とメッセージ再送を撤廃します — Last-Event-IDヘッダーとSSEイベントIDがなくなります。changelogの文はあっさりしています。レスポンスストリームが切れると進行中だったリクエストは失われ、クライアントは新しいリクエストIDで新しいリクエストを送り直さなければなりません(MUST)。
これは純粋な利得ではなくトレードオフです。再開機能はもともと「長い時間かかるツール呼び出し中にネットワークが一度切れても続きを受け取る」ための仕掛けでした。それがなくなったということは、時間のかかる作業の信頼性は今やプロトコルコアではなく、別の場所 — 後述のTasks拡張 — から調達しなければならないということです。
MRTR — サーバーが聞き返す方式がひっくり返った
個人的には、今回のリビジョンでもっとも深い変化だと見ている部分です。
これまでサーバーは処理の途中でクライアントに聞き返すことができました — roots/listでディレクトリを尋ねたり、sampling/createMessageでLLMを借りたり、elicitation/createでユーザー入力を受け取ったり。これはサーバーがリクエストを抱えたまま生きている双方向チャネルを前提とします。つまり本質的に状態を持ちます。
MRTR(Multi Round-Trip Requests)パターン文書はこれを置き換えます。そしてかなりはっきり釘を刺しています — サーバーはサーバーからクライアントへのリクエストを必ずMRTRパターンで送らねばならず(MUST)、以前のサーバー起点リクエストパターンはもはやサポートされず、これは破壊的変更(breaking change)だと。
新しい流れはこうです。
1. クライアント -> サーバー : tools/call (id: 1, パラメータ)
2. サーバー -> クライアント : InputRequiredResult (id: 1)
resultType: "input_required"
inputRequests: { "github_login": ElicitRequest, ... }
requestState: "<サーバーだけが解読できる不透明なブロブ>"
-> ここで最初のリクエストは終了します。サーバーは何も抱えません。
3. クライアント: ユーザーに尋ねて入力を集める
4. クライアント -> サーバー : tools/call (id: 2, 元のパラメータ
+ inputResponses + requestState)
-> 必ず新しいJSON-RPC idでなければなりません。独立したリクエストだからです。
5. サーバー -> クライアント : Result (id: 2, resultType: "complete")
サーバーが聞き返すときに返すInputRequiredResultは、おおよそこんな形です。
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"resultType": "input_required",
"inputRequests": {
"github_login": {
"method": "elicitation/create",
"params": {
"mode": "form",
"message": "Please provide your GitHub username",
"requestedSchema": {
"type": "object",
"properties": { "name": { "type": "string" } },
"required": ["name"]
}
}
}
},
"requestState": "AEAD-protected blob"
}
}
文書が強調している一文がこの設計の要点です — 各段階のリクエストは完全に独立しており、再試行を処理するサーバーはその再試行リクエストに直接載っている情報以外は何も必要としません。だからインスタンス間の共有ストレージも、状態ベースのロードバランシングも要らなくなります。
いくつかの細かい規則です。
- すべての結果に
resultTypeフィールドが必須になります — 通常の結果は"complete"、MRTRの中間結果は"input_required"です。従来プロトコルのサーバーがこのフィールドを欠いた場合、クライアントは"complete"として扱わねばなりません(MUST)。 - サーバーが
InputRequiredResultを送れるリクエストはprompts/get、resources/read、tools/callの3つだけです。それ以外に送ってはいけません(MUST NOT)。 - クライアントが能力として宣言していない種類の
inputRequestsをサーバーは送ってはいけません。クライアントがelicitationに対応すると言っていなければ、elicitation/createを差し込むことはできません。 - サーバーはクライアントが再試行してくれると仮定してはいけません(MUST NOT)。単に戻ってこないこともあります。
状態は消えたのではなく、引っ越しました
ここが正直になるべき部分です。MRTRはサーバーの状態をなくしたわけではなく、requestStateという不透明なブロブに詰めて、信頼できないクライアントを通過させて戻す方式です。そして仕様はその結果を隠しません。
クライアントのリクエストにrequestStateが含まれている場合、サーバーはそれを攻撃者が制御する入力として扱わねばなりません(MUST)。この値が認可・リソースアクセス・ビジネスロジックに影響するなら、サーバーは完全性を保護しなければならず(HMACやAEADなど)、検証に失敗した状態は拒否しなければなりません。完全性保護を省略してよいのは、改ざんがリクエスト失敗以外の害を及ぼしえないときだけです。
リプレイ防止のため、サーバーは完全性保護されたペイロードの中に、認証済みの主体(principal)、短い有効期限(TTL)、元のリクエスト識別子(メソッド名と主要パラメータのダイジェストなど)を入れ、毎回検証することが推奨されます(SHOULD)。そして文書に付いた警告をそのまま移すと — これらの措置はリプレイの窓を狭め、ユーザー間・リクエスト間の再利用を防ぎますが、それ自体で一回限りの使用(single-use)を保証するわけではありません。一度きりの償還のように厳密に一度だけ消費されねばならない場合は、サーバー自身がその不変条件を強制しなければなりません(MUST)。
まとめると、サーバー作成者にとってこれは新しい宿題です。以前はセッションオブジェクトをメモリに持っておけばよかったことが、今や暗号処理をきちんとやることになりました。署名なしにrequestStateにユーザーIDを入れた瞬間、それは認可バイパスです。ステートレスはタダではなく、そのコストのかなりの部分がここにあります。
もうひとつ — 再試行は文字どおり元のリクエストの再実行です。サーバーが聞き返す前に行った作業が高価だったなら、その作業は再試行でもう一度発生する(コスト)か、あるいはrequestStateに載せなければなりません(サイズと複雑さ)。
運用者にとって実際に変わること — ヘッダー、キャッシュ、トレース
ステートレスコアほど華やかではありませんが、ゲートウェイを運用する人にとってはこちらのほうが体感しやすいかもしれません。
標準ヘッダー(SEP-2243)。Streamable HTTPのPOSTリクエストにMcp-MethodとMcp-Nameヘッダーが必須になります。ロードバランサー・ゲートウェイ・レートリミッターが本文を開かずにHTTP層でルーティングと制限をかけられるようにする狙いです。ツールパラメータでカスタムヘッダーを渡すx-mcp-header対応も一緒に入ります。
キャッシュ(SEP-2549)。tools/list、prompts/list、resources/list、resources/read、resources/templates/listの結果にttlMsとcacheScopeが新しい必須のCacheableResultインターフェースとして加わります。ttlMsは鮮度のヒント(ミリ秒)でクライアントがキャッシュしてポーリングを減らせるようにし、cacheScopeは"public"または"private"で、共有の中間者がキャッシュしてよいかを制御します。HTTPのCache-Controlを模したもので、既存のlistChanged通知を置き換えるのではなく補完します。
セッションが消えて一覧が接続ごとに変わらなくなったことと、このキャッシュフィールドが一組になっている点は注目に値します。一覧が安定しているからキャッシュが意味を持つのです。同じ文脈でtools/listは決定的な順序でツールを返すことが推奨されますが(SHOULD)、理由がおもしろいところです — クライアント側のキャッシュだけでなく、LLMプロンプトのキャッシュヒット率を上げるためです。プロトコル設計がLLM推論コストまで意識し始めた地点です。
トレーシング(SEP-414)。_metaにおけるOpenTelemetryトレースコンテキスト伝搬の慣例が文書化されます — traceparent、tracestate、baggageというキー名が固定され、SDKとゲートウェイをまたいで分散トレースが相関します。
エラーコード。 リソース不在エラーがMCP固有の-32002からJSON-RPC標準の-32602(Invalid Params)に変わります。文字どおりの-32002でマッチさせているクライアントコードがあれば直す必要があります。さらにエラーコード割り当てポリシーができ、-32000〜-32019は実装定義のまま(既存SDKの使用はそのまま認められます)、-32020〜-32099を仕様が予約します。このドラフトで導入されたコードもそれに合わせて番号が振り直されます — HeaderMismatchが-32001から-32020へ、MissingRequiredClientCapabilityが-32003から-32021へ、UnsupportedProtocolVersionが-32004から-32022へ。
認証 — OAuth/OIDCへさらに寄っていく
認証は複数のSEPが少しずつ締めています。
- SEP-2468: 認可サーバーはRFC 9207に従い認可レスポンスに
issパラメータを含めることが推奨され(SHOULD)、MCPクライアントはissがあれば認可コードを償還する前に記録済みの発行者と照合して検証しなければなりません(MUST)。認可サーバーのミックスアップ攻撃の緩和です。 - SEP-837: クライアントは動的クライアント登録(DCR)の際に適切な
application_typeを指定しなければなりません — OpenID Connectのリダイレクトurl衝突を避けるためです。 - SEP-2352: クライアント資格情報は、それを発行した認可サーバーに紐づきます。クライアントは保存した資格情報を発行者識別子でキー付けしなければならず(MUST)、別の認可サーバーに再利用してはならず(MUST NOT)、認可サーバーが変われば再登録しなければなりません(MUST)。
- そしてDCR(RFC 7591)自体が非推奨入りします — Client ID Metadata Documentsが推奨される登録メカニズムになります。ただし、これに対応していない認可サーバーとの下位互換のため、DCRは引き続き使用できます。
消えていくもの — Roots、Sampling、Logging
SEP-2577がRoots、Sampling、Loggingの3機能を非推奨として印付けます。提案された移行経路はこうです。
| 機能 | 代替 |
|---|---|
| Roots | ツールパラメータ、リソースURI、またはサーバー設定でディレクトリ・ファイルを渡す |
| Sampling | LLMプロバイダーAPIと直接統合する |
| Logging | stdioではstderr、構造化された観測はOpenTelemetryで |
重要なのは今すぐ壊れるわけではないという点です。RC公表の表現どおり、該当するメソッド・型・能力フラグは今回のリリースでも、そして今回のリリースから1年以内に発行されるすべての仕様バージョンでも動き続けます。今回は注釈が付くだけです。
そしてこれが可能になる理由が、静かではあるがもしかすると今回のリビジョンより重要な変化です — SEP-2596が機能ライフサイクル・非推奨ポリシーを導入します。機能はActive / Deprecated / Removedの3状態のいずれかにあり、非推奨から削除までのあいだには最低12か月の猶予期間が保証されます。この期間はSEPがFinalになった日からではなく、その機能がDeprecatedと印付けられた仕様リビジョンがリリースされた時点から数えます。例外はひとつだけ — 公表済みのセキュリティ勧告や、実際の悪用が文書化されていて代替の緩和策がない、進行中のセキュリティリスクである場合に限り期間を短縮できますが、そのときでも最低90日は与えなければなりません。
これに、非推奨になった機能を一箇所に集めたレジストリが付きます。複数のリビジョンのchangelogに散らばった項目を漁って「何がいつなくなるのか」を再構成しなくて済むようにする狙いです。Tier 1のSDKは該当するAPI表面を各言語らしいやり方(@Deprecated、[Obsolete]、@deprecated JSDocなど)で示す義務を負います。
同じ方針のもと、HTTP+SSEトランスポート(2025-03-26から非推奨状態だった)とincludeContextの"thisServer" / "allServers"値も、正式にDeprecatedへ再分類されます。
正直に言うと、このガバナンスの変化のほうがステートレスコアよりもエコシステムに長く残るだろうと私は見ています。「このプロトコルの上に製品を乗せてよいのか」という問いへの答えが、ここで初めて日付を持ったからです。
拡張 — Tasksがコアから出ていく
SEP-2133が拡張(extension)を第一級の概念にします。逆引きDNS識別子を持ち、能力のextensionsマップで交渉され、仕様とは独立にバージョニングされ、自前のリポジトリと委任された管理者を持ちます。
この枠組みの上に、2つの公式拡張が登場します。
- MCP Apps(SEP-1865): サンドボックス化されたiframeの中でサーバーがレンダリングするHTMLインターフェース。ツールがUIテンプレートを宣言し、UIから発生した動作は直接呼び出しと同じJSON-RPC経路を通ります。
- Tasks(SEP-2663): 時間のかかる作業のためのものです。もともと
2025-11-25で実験的にコアに入っていた機能ですが、RC公表の表現をそのまま移すと — 本番利用が十分な再設計を呼び込み、仕様よりも拡張のほうがこれの正しい住処だという結論になりました。新しい設計はブロッキング方式のtasks/resultをtasks/getポーリングに置き換え、クライアントからサーバーへの入力用にtasks/updateを追加し、tasks/listを削除し、サーバーがリクエストごとのオプトインなしにタスクハンドルを先に返せるようにします。
先にSSE再開がなくなったと述べた箇所がここでつながります。長い作業の信頼性は、もはやストリーム再開ではなくタスクハンドル+ポーリングで解くのが正攻法です。そして警告がひとつ — 2025-11-25の実験的Tasks APIに合わせてすでにリリースした人は、新しいライフサイクルへ移行しなければなりません。
ツールスキーマもゆるくなります。SEP-2106がinputSchemaとoutputSchemaにJSON Schema 2020-12のキーワードを全部許可し(oneOf、anyOf、allOf、条件分岐、$ref、$defs)、structuredContentが任意のJSON値を受け取れるようにします。ただし実装は外部の$refのURIを自動で逆参照してはならず、深さと検証時間に上限を設けなければなりません — スキーマが表現力を得た分だけ新しい攻撃面が開くからです。
今何をすべきか — そして何を急がなくていいか
2026年6月29日にベータSDKが出ました。4つのTier 1 SDKすべてです。
Python mcp==2.0.0b1 (v2、正確なピン留めが必要 — しないと1.xに留まる)
TypeScript @modelcontextprotocol/server@beta (v2、サーバー/クライアントパッケージ分離
@modelcontextprotocol/client@beta ESM専用、Node.js 20+ / Bun / Deno)
Go v1.7.0-pre.1 (モジュールパスはstableと同じ)
C# 2.0.0-preview.1 (dotnet add package ... --prerelease)
そして公表自身が付けた警告をそのまま移します — 重要なワークロードにはstable SDKリリースが依然として推奨バージョンであり、公開APIはベータとstableのあいだでまだ変わりえます。テスト中は正確なバージョンをピン留めしろということです。
下位互換については明確です — 既存のサーバーは動き続け、公表の表現では、今日も何も壊れず、7月28日にも何も壊れません。新しいクライアントは旧型のサーバーに出会うとレガシーハンドシェイクへ自動でフォールバックします。
今試す価値のあること
- コードベースで文字どおりの
-32002マッチを探しておく。静かに壊れうる数少ない箇所です。 2025-11-25の実験的Tasks APIに賭けていたなら、移行計画を立てる。これは確定した宿題です。- リモートサーバーを運用しているなら、スティッキーセッション・共有セッションストアが今どれだけコストになっているか計算してみる。それが今回のリビジョンの価値を測る物差しです。
- ベータSDKでテスト環境で突いてみる。本番ではなく。
急がなくてもいいこと
ここが一番重要です。stdioでローカルサーバーしか動かしていないなら、今回のリビジョンの大部分はあなたの話ではありません。 ステートレスコアは最初から最後までリモート・水平スケーリングの物語です。プロセス1つにクライアント1つが付くstdioサーバーにとって、スティッキーセッションはそもそも問題ではありませんでしたし、ラウンドロビン型のロードバランサーもありません。そういうサーバーに返ってくるのは利得ではなく移行コストだけです — 非推奨になったRoots・Sampling・Loggingをいつか取り除かねばならず、MRTRで聞き返しを書き直さねばならず、その見返りはあまりありません。
12か月の非推奨猶予期間は、まさにこういう人のためにあります。急ぐ理由がないなら、急がなくて構いません。
おわりに
まとめるとこうです。2026-07-28はMCPがプロトコル層で状態を手放すリビジョンです。ハンドシェイクとMcp-Session-Idが消え、すべてのリクエストが_metaで自らを説明し、server/discoverが接続なしの発見を開き、標準ヘッダーとキャッシュヒントがゲートウェイにHTTPらしい取っ手を与えます。目標はひとつ — どのインスタンスでもどのリクエストでも処理できるようにすること。
代償もはっきりしています。サーバーが聞き返す作業はMRTRでひっくり返されて破壊的変更になり、状態は消えたのではなくrequestStateブロブに詰められて信頼できないクライアントを往復し、そのブロブを安全にする暗号処理は今やサーバー作成者の責任になりました。SSE再開が抜けた穴はTasks拡張が埋めなければなりません。Roots・Sampling・Loggingは期限付きになりました。
そして今日時点でこれは全部まだRCです。現行の仕様は2025-11-25で、最終版は12日後に出ます。仕様が確定したら読み直すべき文書はブログ記事ではなく公式changelogです — 本稿の目的は、その文書を読みに行くとき何に目を配ればいいかを伝えることだけです。
参考資料
- Key Changes — 2026-07-28ドラフト公式changelog
- The 2026-07-28 MCP Specification Release Candidate (2026-05-21)
- Beta SDKs for the 2026-07-28 MCP Spec Release Candidate Are Here (2026-06-29)
- The 2026 MCP Roadmap
- Multi Round-Trip Requests (MRTR) パターン仕様
- Feature Lifecycle and Deprecation Policy (SEP-2596)
- Versioning — Draft/Current/Finalと現行リビジョン
- RFC 9207 — OAuth 2.0 Authorization Server Issuer Identification
- MCPリファレンス — 構造・プリミティブ・トランスポート(関連記事)