Claude Media
MCPのセッションID(Mcp-Session-Id)はなぜ廃止されたのか

MCPのセッションID(Mcp-Session-Id)はなぜ廃止されたのか

MCPのMcp-Session-Idヘッダーは2026-07-28の仕様改訂で削除されました。狙いはステートレス化によるサーバーの水平スケール対応です。

MCPのセッションID(Mcp-Session-Id)はなぜ廃止されたのか

2026-07-28のMCP仕様改訂(SEP-2567)で、Streamable HTTPトランスポートのプロトコルレベルのセッションとMcp-Session-Idヘッダーが削除されました。狙いは、MCPの設計原則8箇条が重視するステートレスなプロトコルへの作り直しです。サーバーが接続ごとに状態を抱える前提を外し、どのプロセスがリクエストを処理しても結果が変わらない設計に寄せています。

Mcp-Session-Idは、サーバーが接続に割り当てるセッション識別子を運ぶHTTPヘッダーです。2025-03-26から2025-11-25までのStreamable HTTP仕様で使われていました。2026-07-28ではヘッダーだけでなく、セッションという仕組みが丸ごと無くなっています。

何が消えて、何に置き換わったのか

旧仕様のセッションは、サーバーがMcp-Session-Idで割り当て、HTTP DELETEで終了させるものでした。クライアントはHTTP GETで独立したSSEストリームを開き、サーバー主導のメッセージを受け取れました。サーバーはそのストリームでJSON-RPCリクエストも送れ、Last-Event-IDで再開もできました。tools/listなどのリスト系の応答が接続ごとに変わり得る前提も、この上に乗っていました。

2026-07-28では、これらがそれぞれ別の仕組みに置き換わっています。

対応表

消えた仕組みと置き換え先

  • セッションID

    サーバー発行のハンドルを、普通のツール引数として受け渡します。リスト系の応答は接続によって変わりません。

  • HTTP GETの常設ストリーム

    subscriptions/listenのPOSTを送り、その応答のSSEストリームでlist_changedなどの通知を受けます。

  • サーバー発行のリクエスト

    サーバーはリクエストを送らず、結果に入力要求を載せて返します。クライアントが再試行で応答するMRTRです。

  • initializeハンドシェイク

    プロトコルバージョンとクライアントのケーパビリティを、全リクエストの_metaに載せます。

もう一つ見落としやすいのが、SSEの再開機能の廃止です。Last-Event-IDによる再開も、イベントIDによる再配信も仕様から外れました。応答ストリームが途中で切れると、処理中だったリクエストは失われます。クライアントは新しいリクエストIDで同じ要求を出し直す必要があります。

なぜステートレスにする必要があったのか

セッションが接続に紐づくと、そのセッションのやり取りは常に同じサーバープロセスへ届かなければなりません。ロードバランサーの背後に複数台を並べる構成では、セッションIDで振り分けるセッションアフィニティか、インスタンス間で状態を共有するストレージのどちらかが要ります。

Kubernetesのように、インスタンスが入れ替わる前提の環境では、一般にこの固定が運用上の足かせになります。プロセスが入れ替われば、そのプロセスが持っていたセッションの状態は引き継げないからです。仕様は、ロードバランサーやゲートウェイがボディを読まずにルーティングできるよう、必要な情報をヘッダーへ写す設計にしています。changelogではセッションの削除とinitializeの削除が同じ改訂に並んでいて、接続単位の状態を前提にする部分が一度に無くなっています。

状態を持ち回りたいサーバーは、あるツール呼び出しの結果にトークンや識別子を含め、次の呼び出しでクライアントがそれを引数に載せる設計です。状態はプロトコルの外のアプリケーションデータになるので、ロードバランサーはハンドルの中身を気にする必要がありません。どのインスタンスでも、ハンドルさえあれば続きを処理できます。

同じ発想は、同じ改訂でコア仕様から拡張機能に切り出されたMCP Tasksにも見えます。結果を待ち続けるブロッキングのtasks/resultをやめ、tasks/getでポーリングする設計になりました。

セッションが無いと、ロードバランサーは何で振り分けるのか

仕様の答えは、JSON-RPCボディの一部をHTTPヘッダーに写して見せることです。すべてのPOSTにMCP-Protocol-VersionとMcp-Methodが必須です。tools/call・resources/read・prompts/getでは、対象を示すMcp-Nameも付きます。tools/callの実際のリクエストは、仕様の例だと次の形です。

POST /mcp HTTP/1.1
Content-Type: application/json
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: get_weather

ボディのparams._metaには、同じプロトコルバージョンとclientCapabilitiesが入ります。ヘッダーとボディの値が食い違うと、サーバーは400 Bad RequestとHeaderMismatchエラーで拒否します。

ゲートウェイは、ボディをパースせずにMcp-Nameでツール単位のルーティングやレート制限ができます。ただし仕様は、MCP-Protocol-Versionが古い、またはヘッダーが無いリクエストを、ヘッダーを信用せず拒否することを中間者に勧めています。検証されていないヘッダーの値を信用しないためです。

長寿命のSSEストリームを扱うサーバーには、別の運用上の注意があります。リバースプロキシのバッファリングを避けるためX-Accel-Buffering: noヘッダーを含めること、接続維持のためSSEのコメント行を定期的に流すことが、仕様で勧められています。

古いクライアントが来たとき、サーバーはどう返すのか

2026-07-28だけに対応するサーバーが、2025-03-26〜2025-11-25世代のクライアントから受ける通信への応答が決まっています。

  • MCPエンドポイントへのHTTP GETとDELETEには、405 Method Not Allowedを返す
  • リクエストのMcp-Session-Idは無視し、セッションIDを発行もエコーもしない
  • Last-Event-IDも無視し、ストリームは再開しない

旧世代とも話す必要があるサーバーは、旧リビジョンの挙動を別途実装します。ヘッダーを無視するだけでは済みません。

クライアントの側にも、世代の見分け方が定められています。

手順

新旧どちらのサーバーかを見分ける手順

  1. 1

    まず新形式のリクエストを送る

    _metaにプロトコルバージョンを載せたリクエストを、最初から送ります。

  2. 2

    400が返ったらボディを読む

    新世代のサーバーも、バージョン不一致やヘッダー不一致で400を返します。ステータスだけでは旧世代と決められません。

  3. 3

    ボディで分岐する

    既知のモダンなJSON-RPCエラーなら、新世代として再試行します。ボディが空か未知のエラーなら、initializeにフォールバックします。

Claude Codeはいつ新しいリビジョンで話すのか

Claude CodeのMCPクライアントには、MCP TypeScript SDK 1.xのv1ランタイムと、SDK 2.0のv2ランタイムがあります。2026-07-28を話せるのはv2だけです。v2が選ばれる条件は、条件は2段階です。

条件v2が既定になるバージョン
フィーチャーフラグを取得するセッションv2が既定になるバージョンv2.1.232以降
取得しないセッション(Bedrock・Google CloudのAgent Platform・Microsoft Foundry等のクラウド経由、Claude apps gateway経由、テレメトリー無効化など)v2が既定になるバージョンv2.1.274以降

クラウド経由のうち、CLAUDE_CODE_PROVIDER_MANAGED_BY_HOSTを設定するホスト組み込みは、この取得しないセッションに含まれません。

v2でも、全サーバーが新リビジョンで話すわけではありません。Claude CodeはHTTPサーバーに新リビジョンへの対応を問い合わせ、対応するものだけで使います。問い合わせはclaude.aiコネクタのサーバーにも及びますが、対象はフィーチャーフラグを取得するセッションです。stdioサーバーと、すべてのセッションのコネクタサーバーは、環境変数MCP_PROTOCOL_NEGOTIATIONをautoにしたときだけ問い合わせの対象です。ランタイムを固定したいときはMCP_SDK_GENERATIONにv1かv2を指定します。

v2では、新リビジョンのサーバーからのlist_changed通知を、Claude Codeが開いたままにしたストリームで受けます。ストリームが閉じると再び開きます。開いて10秒以内にまた閉じる場合は、再オープンを3回試したところでその接続では止めます。10秒より長く開いてから閉じる場合(サーバーレスのホストでよく起きます)は、1時間に5回再オープンすると、次まで約6時間待ちます。再び開くまでは、最後に取得したツール・プロンプト・リソースが使われます。変更を早く反映させたいときは、/mcpからサーバーを再接続します。

手元のClaude Code(v2.1.287で確認)でclaude mcp add --helpを見ると、トランスポートの選択肢はstdio・sse・httpの3つです。セッションに関するオプションはありません。

-t, --transport <transport>  Transport type (stdio, sse, http). Defaults to
                             stdio if not specified.

チャネル(--channels)を使うサーバーには、例外が1つあります。2026-07-28ではチャネルのメッセージを運べないため、MCP_PROTOCOL_NEGOTIATIONをautoにしてそのリビジョンで接続したチャネルサーバーは、チャネルとして登録されません。変数を未設定かlegacyにしておけば、stdioサーバーは従来のハンドシェイクのままです。

自前のサーバーをセルフホスト環境に配る構成はClaude Codeセルフホスト環境にMCPサーバーを届ける3つの経路で、サーバーの作り方はMCPサーバー自作ガイドで扱っています。接続設定はClaude Code MCP設定ガイドにあります。

既存サーバーを寄せるときにつまずきやすい点

  • リスト系のキャッシュを接続単位で持ち続ける。tools/listなどがセッションごとに変わり得る前提のキャッシュは、接続に依存しない前提へ作り直さないと、古い一覧を返し続けます。2026-07-28では、リスト系の結果にttlMsとcacheScopeが必須になりました。対象はtools/list・prompts/list・resources/list・resources/read・resources/templates/listです。cacheScopeは"public"か"private"で、共有の中間者が応答をキャッシュしてよいかを示します。tools/listは決まった順序で返すことも、サーバーに勧められています。

まとめ

自作MCPサーバーが複数のツール呼び出しにまたがる状態を持つなら、先にどこで接続単位の状態に依存しているかを洗い出すのが、移行の最短経路です。2026-07-28だけに対応するサーバーでは、ロードバランサー配下でもセッションアフィニティの設定そのものが不要になります。

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