← ブログに戻る

MCP 2026-07-28はなぜステートレスか

MCP(Model Context Protocol)の仕様 2026-07-28 版が、7月28日に確定しました。5月21日にリリース候補が出て、10週間の検証期間を経ての確定です。リードメンテナの David Soria Parra と Den Delimarsky は、これを「公開以来最大の改訂」と書いています。

何が変わったのかを一言でいうと、こうです。

今までは MCP のクライアントとサーバーの間に「セッション」が必要で、サーバーは相手を覚えておかなければなりませんでした。最終仕様でそれがなくなり、1回のリクエストだけで処理が完結する、普通の Web API と同じ形になりました。

ここでいうクライアントは Claude Desktop や Claude Code のような、LLM を動かしているアプリのことです。サーバーは GitHub 連携やファイル操作などのツールを提供する側です。

この記事は、次のような人に向けて書いています。

  • MCP サーバーを作っている・運用している人: 2026-07-28 に対応するとき、自分のサーバーのどこを直せばいいかが分かります(末尾に確認手順を6つ載せました)
  • 変更点の一覧は読んだが、理由が腑に落ちていない人: initialize やセッションが消えた理由を、提案元の SEP の文章から押さえられます。理由が分かっていれば、仕様に書かれていないケースでも設計を判断できます
  • 以前の MCP の記事や本で学んだ人: 手元の知識のどこが古くなったかを、図2枚で確認できます

変更点の一覧(changelog)を開くと、破壊的変更だけで9項目並んでいます。ただ一つずつ読んでいくと、大半は「セッションをなくす」という同じ方向を向いています。なので、自分の整理も含めてまとめていこうと思います。

書いた動機はもう一つあります。10月16日に、インプレス(OnDeck Books)から『MCP実践セキュリティ AIエージェント時代のModel Context Protocol安全運用ガイド』を出します。この本には、それより前の仕様をもとに書いた部分があります。この版で何が変わったのかを、自分で一度確認しておきたかったのです。

MCP実践セキュリティ AIエージェント時代のModel Context Protocol安全運用ガイド 表紙 MCP実践セキュリティ
AIエージェント時代のModel Context Protocol安全運用ガイド(インプレス OnDeck Books、10月16日発売)
Kindle版 / ペーパーバック版

なぜそこまでしてステートレスにしたのか。答えは、変更を提案した SEP(Specification Enhancement Proposal)の「Motivation」節に書いてあります。

何が変わったか(早見表)

2025-11-25 版からの主な変更を、ステートレス化との関係で並べるとこうなります。

変更2025-11-25 まで2026-07-28SEP
接続の開始initialize で能力を1回交換廃止。毎リクエストの _meta に載せるSEP-2575
セッションMcp-Session-Id ヘッダー廃止SEP-2567
能力の確認ハンドシェイクで自動server/discover(サーバーは実装必須、呼ぶかはクライアント次第)SEP-2575
変更通知HTTP GET / resources/subscribesubscriptions/listen に一本化SEP-2575
サーバーからの聞き返しサーバー発のリクエストMRTR(input_required を返して再送させる)SEP-2322
長時間処理コアの実験機能 Tasks公式拡張 io.modelcontextprotocol/tasks へSEP-2663
Roots / Sampling / Loggingコア機能非推奨(最低12か月は動く)SEP-2577

ping と logging/setLevel の削除、SSE の再開機能(Last-Event-ID)の削除もあります。どれも「接続をまたいで何かを覚えておく」仕組みを外す変更です。

理由1: セッションがあるとロードバランサの後ろに置けない

一番大きな動機は、スケールの話です。SEP-2575「Make MCP Stateless」は、動機をこう書いています。

The current MCP specification requires a mandatory initialization handshake that establishes persistent session state. This creates significant challenges for scalability (load balancing requires sticky sessions), resilience (server failure loses session state), and implementation complexity (both client and server must manage session lifecycles).

挙げている問題は3つです。

  • スケーラビリティ: サーバーを増やしても、負荷をうまく分けられない
  • 耐障害性: サーバーが落ちるとセッション状態も消える
  • 実装の複雑さ: クライアントとサーバーの両方がセッションの寿命を管理しなければならない

1つ目が少し分かりにくいので、補足します。

利用者が増えたら、同じ MCP サーバーを何台か並べて、手前のロードバランサにリクエストを振り分けさせます。ロードバランサは本来、空いているサーバーに次々と仕事を回すのが役目です。

ところがセッションがあると、「このクライアントとはこういう約束で話している」という情報が、最初に応対したサーバー A のメモリにしか残っていません。2回目以降のリクエストが B に回ると、B はそのクライアントのことを何も知らないので処理できません。

そのためロードバランサに「このクライアントは必ず A に送る」と固定させる必要があります。この設定を sticky session と呼びます。銀行でいえば、最初に対応した窓口の担当者が、そのお客さんを最後まで受け持つ「担当者制」です。担当者が忙しくても別の窓口には回せませんし、窓口を増やしても、いま来ているお客さんは元の担当者を待ち続けます。サーバーを増やしても、負荷がきれいに分散しないのです。

図にするとこうなります。

セッションを持つMCPと持たないMCPの比較。左は initialize でセッションIDを受け取り、sticky LB で Server A に固定される。右は毎リクエストの _meta にバージョンと能力を載せ、round-robin LB でどのインスタンスにも届く

左が 2025-11-25 までの形です。最初に initialize で能力を交換し、サーバーから Mcp-Session-Id を受け取って、以後のリクエストすべてにその ID を付けます。B でも処理できるようにするには、sticky session で A に固定するか、全サーバーが読める共有セッションストアを置くしかありません。銀行のたとえでいえば、担当者制にするか、全窓口で同じお客さん台帳を引けるようにするかです。どちらも、MCP サーバーを置く側の手間になります。

右が 2026-07-28 です。プロトコルバージョンとクライアントの能力は、毎回 _meta に入れて送ります。

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "search",
    "arguments": { "q": "MCP" },
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientInfo": { "name": "my-client", "version": "1.0.0" },
      "io.modelcontextprotocol/clientCapabilities": { "elicitation": {} }
    }
  }
}

銀行のたとえに戻ると、お客さんが毎回「必要事項を全部書いた申込書」を持ってくる形です。窓口の人はお客さんのことを覚えていなくても、申込書を読めば手続きできます。だから、どの窓口に並んでもかまいません。

銀行の3つの窓口で、申込書を持った3人のお客さんがそれぞれ別の窓口で手続きしているイラスト。どの窓口でも申込書を読めば対応できる

サーバー側も同じで、届いたリクエストだけを見れば処理できます。ロードバランサは、空いているサーバーに順番に回すだけで済みます(これを round-robin と呼びます)。公式ブログも、これまで sticky session や共有セッションストアが必要だったリモート MCP サーバーが、ふつうの round-robin ロードバランサの後ろで動くようになった、と書いています。置き方は、ほかの Web API と変わりません。

では、最初の initialize でやっていた「お互いに何ができるかの確認」はどこへ行ったのか。これも銀行で考えると分かりやすくなります。

場面銀行のたとえMCP 2026-07-28
以前来店したら、まず受付で手続きを済ませないと窓口に行けない最初に必ず initialize
普段申込書を書いて、いきなり窓口に出すリクエストを直接送る(_meta に必要事項)
書式が古いとき窓口が「この書式は使えません。使えるのはこれとこれです」と返すUnsupportedProtocolVersionError で対応バージョンを返す
事前に知りたいとき入口の案内パンフレットを1枚もらうserver/discover を1回呼ぶ

以前は全員が受付を通る必要がありました。今は、いきなり窓口に行ってかまいません。書式が合わなければその場で教えてもらえますし、気になる人だけ先にパンフレットを見ればいい。SEP はこれを「pay as you go」(使った分だけ払う)と呼んでいます。確認の手間は、もう全員が最初に払うものではありません。必要な人が、必要なときだけ払います。

理由2: セッション単位の tools/list が全員のコストになっていた

「ハンドシェイクは消すとして、セッションは任意で残してもいいのでは」という考え方もあります。実際、元の議論(issue #1442)は「デフォルトはステートレス、必要ならステートフルも選べる」という方向でした。

これを「選べる道も要らない」と切ったのが SEP-2567「Sessionless MCP via Explicit State Handles」です。主張の核はここです。

The mere possibility of session-scoped tools/list forces O(subagents × servers) re-fetches on everyone, even when ~zero servers actually use it

セッションごとにツール一覧が変わる可能性があるだけで、クライアントはサブエージェントごと・サーバーごとに tools/list を取り直すしかなくなります。実際にツール一覧を変えるサーバーがほぼゼロでも、その前提で動くしかありません。サブエージェントを何十本も並列で立ち上げる使い方が増えた今、このコストは小さくありません。

普通の Web API に置き換えると、「API仕様書が接続のたびに変わるかもしれないので、呼ぶ前に毎回読み直してください」と言われている状態です。実際に変わることはほとんどなくても、読み直さないわけにはいきません。

そこで 2026-07-28 では、tools/list / resources/list / prompts/list の結果は接続や直前のツール呼び出しで変えてはいけない、と決めました。一覧が変わらないと保証されるので、キャッシュが効きます。仕様書が変わらないと約束されたので、一度読めば手元に置いておけます。同じ版で list 系の結果に ttlMs(キャッシュしてよい時間)と cacheScope(共有キャッシュに載せてよいか)が必須になったのも、この流れです。tools/list を決まった順番で返すよう推奨(SHOULD)が入ったのも同じ理由で、LLM のプロンプトキャッシュに当たりやすくするためだと説明されています。

では、ショッピングカートのように状態が本当に必要なサーバーはどうするのか。答えは明示的なハンドルです。

create_cart()              → { "cart_id": "c_8f2a" }
add_item(cart_id, sku)     → ...
checkout(cart_id)          → ...

サーバーが ID を発行し、クライアントはそれをツールの引数として渡し続けます。プロトコルに専用の仕組みは無く、ツール設計の作法という扱いです。SEP-2567 は、こちらのほうが表現力が高いとも書いています。セッションは1接続に1つしか持てないので、「サブエージェント同士でカートは共有したいが、ブラウザの状態は分けたい」といった混在ができないからです。ハンドルなら、共有したいものだけ同じ ID を渡せば済みます。

理由3: サーバーが聞き返すと、返事が別のサーバーに届く

ハンドシェイクとセッションを消しても、まだ状態が残る場面がありました。サーバーが処理の途中で、ユーザーに何かを聞き返す場面です(仕様では elicitation と呼びます)。たとえば「この操作を実行してよいですか」「GitHub のユーザー名を教えてください」といった確認です。

ここも銀行で考えます。

以前のやり方は、こうでした。

  1. お客さんが窓口 A で手続きを始める
  2. 途中で A の担当者が「印鑑が必要です」と言う。担当者は書きかけの書類を手元に置いたまま、お客さんが戻るのを待つ
  3. お客さんは印鑑を取ってきて、また列に並ぶ。今度は窓口 B に案内される
  4. B の担当者は、A で何をしていたかを知らない。書きかけの書類は A の机の上にある

これを解決するには、全窓口で書類を共有する棚を作るくらいしかありません。サーバーでいえば共有ストレージです。せっかくステートレスにしたのに、状態を置く場所がまた必要になります。

2026-07-28 のやり方(MRTR: Multi Round-Trip Requests、SEP-2322)は、担当者が待つのをやめます。

  1. お客さんが窓口 A で手続きを始める
  2. A の担当者は「印鑑が足りません」というメモと、書きかけの書類をお客さんに渡して、いったん手続きを終える
  3. お客さんは印鑑を押して、書類一式を持ってもう一度並ぶ
  4. どの窓口に案内されても、書類を見れば続きから手続きできる

elicitation の新旧比較。左は Server A が聞き返したあと待ち続け、返事は Server B に届くため共有ストレージが必要。右は A が足りない情報を返していったん完了し、クライアントが答えと預かった状態を付けて同じリクエストを再送、どのサーバーでも完了できる

図の右側が、これを MCP の言葉で書いたものです。対応はこうなります。

銀行MCP 2026-07-28
「印鑑が足りません」のメモresultType: "input_required" と inputRequests
書きかけの書類requestState
印鑑を押してもう一度並ぶ同じリクエストを inputResponses 付きで送り直す

サーバーが何も覚えていなくても、必要なものはすべて、送り直されたリクエストの中に入っています。

ただし、注意点が一つあります。書きかけの書類をお客さんに預けるので、お客さんが書類を書き換えて持ってくるかもしれません。そこで仕様は、requestState を「改ざんされうる入力」として扱うよう求めています。銀行でいえば、書類に割印を押しておき、戻ってきたときに改ざんされていないか確かめるイメージです。実装では HMAC や AEAD といった暗号の仕組みで改ざんを検知します(認可や業務ロジックに関わる場合は MUST)。古い書類の使い回しを防ぐため、誰の書類か・有効期限・どの手続きの書類かも中に入れて確認するよう推奨されています。

状態をサーバーの外に預けた分、その状態を守る仕事はサーバー側に移りました。

理由4: 使われていない割に重い機能を降ろす

ステートレス化と並んで、Roots / Sampling / Logging の3機能が非推奨になりました(SEP-2577)。理由は、導入数に比べて実装が重いことです。

  • Roots: 意味があいまいで、ツールの引数やサーバー設定と役割がかぶる
  • Sampling: human-in-the-loop・モデル選択・セキュリティと実装が複雑な割に、対応クライアントが少ない
  • Logging: stderr や OpenTelemetry と重なる

移行先として、Roots はツール引数・リソース URI・サーバー設定、Sampling は LLM プロバイダの API を直接呼ぶ形、Logging は stderr か OpenTelemetry が案内されています。

非推奨になっても、すぐには消えません。同じ版で正式な非推奨ポリシー(SEP-2596)ができ、非推奨から削除まで最低12か月空けることになりました。Sampling 経由の攻撃経路については MCP Sampling 権限昇格の検証記事 で書きましたが、移行期間のあいだは既存実装が動き続けるので、防御策は引き続き必要です。

ちなみに Tasks(長時間処理)は残っています。置き場所がコアから公式拡張に移りました。SEP-2663 によると、実運用で見つかった課題が多く、作り直すならコアより拡張に置くのが妥当だと判断されたそうです。

サーバー実装者が今やること

既存の MCP サーバーを 2026-07-28 に対応させるとき、確認する順番はこうなります。

  1. initialize 前提の処理を外す: 能力やバージョンを接続時に保存しているなら、毎リクエストの _meta から読むように変える
  2. server/discover を実装する: サーバー側は必須(MUST)です
  3. セッションに置いていた状態を洗い出す: 必要なものは create_*() が返すハンドルに置き換え、ツールの引数で受け渡す
  4. list 系の結果を固定する: 接続ごとに変えない。ttlMs と cacheScope を付け、tools/list は毎回同じ順番で返す
  5. elicitation を MRTR に書き換える: requestState を使うなら HMAC か AEAD で保護し、有効期限と主体を入れて検証する
  6. エラーコードを直す: リソース未検出が -32002 から -32602 に変わっています

後方互換については、クライアントとサーバーは複数のプロトコルバージョンを同時にサポートしてかまいません。STDIO では、server/discover を投げてみて旧版のサーバーかどうかを判定する手順が仕様に載っています。一気に切り替えなくても大丈夫です。

まとめ

2026-07-28 版の変更は数こそ多いものの、筋は1本です。セッションをなくすことです。

セッションは、1つでいくつもの役目を兼ねていました。理由1〜3で見た変更は、その役目を一つずつ別の仕組みに移す作業でした。

セッションが担っていた役目2026-07-28 での代わり
最初に交換した能力やバージョンを覚えておく毎リクエストの _meta に載せる。事前に知りたければ server/discover
サーバーからの聞き返しの返事を、元の処理と結びつけるMRTR(書きかけの書類を預けて、送り直してもらう)
変更通知(ツール一覧が変わった等)を送る相手を覚えておくsubscriptions/listen で、通知がほしいクライアントだけが受け取る口を開く
接続が切れたときに途中から再開する(Last-Event-ID)廃止。切れたら新しいリクエストとして送り直す
カートなど、アプリの状態を持つサーバーが発行するハンドルを、ツールの引数で渡す

なかでも聞き返しは、最後まで残った難所でした。SEP-2575 はハンドシェイクを消す提案ですが、通信レベルでのセッション廃止は SEP-2322(MRTR)に任せると書いています。ハンドシェイクを消すだけではセッションは消せず、聞き返しの仕組みを作り直して、はじめて消せました。

役目を全部移し終えたので、MCP サーバーはどの窓口でも申込書を読めば手続きできる銀行になりました。普通の Web API と同じように、サーバーを並べて置けます。ただし requestState のように、サーバーの外に預けた状態は、サーバー側で守る必要があります。

MCP サーバーのセキュリティ設計は、書籍『MCP実践セキュリティ AIエージェント時代のModel Context Protocol安全運用ガイド』(インプレス OnDeck Books、10月16日発売)にまとめています。Kindle版 / ペーパーバック版


Sources:

LLMを「嘘つき」から「専門家」に変える技術 関連書籍 LLMを「嘘つき」から「専門家」に変える技術 Context Engineering 実践入門 | RAG・MCP・CLAUDE.md・Agentic RAG をベンチマークで体系化 書籍ページを見る →