🧭

MCP の remote server を作り始めると、最初に迷うのは transport 全体ではなく session です。MCP-Session-Id は必須なのか、404 が返ったら何をやり直すのか、SSE を使わない JSON 応答中心の server でも session を持つべきなのか。この判断を曖昧にしたまま実装すると、ローカルでは動いても multi-node や再接続で破綻します。

この記事は、MCP の session lifecycle だけを切り出して整理したい人向けです。2026年5月10日時点での最新 MCP 仕様は 2025-11-25 ですが、session の基本ルール自体は 2025-03-26 から大きくは変わっていません。一方で、周辺の transport 仕様は polling SSE や MCP-Protocol-Version header まで整理が進んでおり、古い理解のまま session を実装するとズレます。transport 全体の移行論は MCP Streamable HTTP 移行ガイド、認証と配布統制は MCP エンタープライズ運用ガイド、未信頼入力と read/write 分離は MCP prompt injection 対策ガイド、Claude Code 側の接続判断は Claude Code MCP ガイド を先に読んでください。

先に結論: session は「使える」ではなく「必要なときだけ使う」

私の結論はかなり単純です。

  1. デフォルトは stateless でよい
  2. server から client へ能動的に通知したいときだけ stateful を検討する
  3. stateful を選ぶなら、404 再初期化と DELETE 終了まで最初から設計する
  4. multi-node で in-memory session を雑に持たない

MCP 仕様では、server は初期化応答で MCP-Session-Id を返してもよいし、返さなくてもよいです。つまり session は必須機能ではありません。実際、MCP C# SDK の公式 docs でも「多くの server は stateless を推奨」と明記されています。TypeScript SDK も stateless mode を「simple API-style servers に向く」と整理しています。

ここを誤解すると、「Streamable HTTP だから session は必須」と思い込んで、不要な状態管理を抱え込みます。まず先に考えるべきなのは、その server が per-client state を本当に持つ必要があるかです。

まず押さえる: spec 上の session lifecycle は 5 手順だけ

最新の MCP transports 仕様で、session 管理のルールはかなり明快です。

  1. server は initialize 応答で MCP-Session-Id を返してよい
  2. 返した場合、client は以後の HTTP request に同じ header を必ず付ける
  3. server は session を終了させた後、その session ID に対して 404 Not Found を返す
  4. client は MCP-Session-Id 付き request に対する 404 を受けたら、session ID なしで InitializeRequest をやり直す
  5. client は不要になった session を DELETE で明示終了してよい

重要なのは、404 が「session を捨てて再初期化しろ」という合図だと spec で決まっている点です。ここを 400 や 401 で実装すると、client 側の再接続戦略と噛み合いません。TypeScript SDK の Streamable HTTP transport も、stateful mode では invalid session ID を 404 Not Found、非初期化 request の session ID 欠落を 400 Bad Request と分けています。

この 404 / 400 の意味づけを崩すと、client は「古い session だから作り直せばよい」のか、「単なる不正 request だから直せばよい」のか判断できなくなります。session lifecycle の設計は、status code の設計でもあります。

session はいつ不要か

stateless で問題ない server はかなり多いです。私なら、次の条件なら session を持ちません。

  • 1 request ごとに完結する
  • server から client への unsolicited notification が不要
  • 再接続時に replay したい event backlog がない
  • client ごとの log level、購読状態、進行中ジョブを保持しない
  • load balancer 配下でどの node が受けても困らない

典型例は、read-only の docs 検索、metadata 参照、軽い tool 実行、JSON 応答中心の API 風 server です。こういう server に session を足すと、得られるものより失うものの方が大きいです。

  • session store が必要になる
  • 404 再初期化を client と揃える必要が出る
  • sticky session や共有状態の議論が増える
  • session hijacking や cleanup まで考える必要が出る

MCP の C# SDK docs が stateless を強く勧めているのは、このコストがあるからです。session がないと困る理由を言えないなら、それは session を入れない理由です。

stateful が必要になるのは 3 パターン

一方で、stateful を避けられない場面もあります。

1. server から client へ通知したい

たとえば job 完了通知、progress、subscription 更新、client への request 発火などです。session ごとの接続や識別子がないと、どの client へ何を返すか曖昧になります。

2. per-client state を保持したい

client ごとの購読状態、log level、途中経過、認証済みコンテキスト、stream resumability の cursor などを持つなら、session が自然です。

3. SSE の resumability を本気で使う

最新 spec では polling SSE と Last-Event-ID による再開が整理されました。ここで event をどの stream / session に属するものとして replay するかを持つなら、stateful 設計の意味が出ます。

ただし、ここでも雑に「SSE を使うから session 必須」とは言えません。server-to-client 通知を使わず、単一 request の stream response だけで足りるなら、stateless のまま設計できる場合もあります。判断基準は transport の種類ではなく、session をまたいで保持したい状態の有無です。

実装で一番重要なのは「404 のあとに何を捨てるか」

session 管理の事故は、ID 発行より再初期化で起きます。

client が 404 を受けたら、新しい InitializeRequest を session ID なしで送り直す。これは spec 上かなり明確です。ですが実装では、その前に何を捨てるかを決めないと壊れます。

私なら次を session 単位の揮発状態として扱います。

  • SSE 接続ハンドル
  • request と response の相関状態
  • progress / task の一時的な購読状態
  • replay 用 event cursor
  • session ごとの log level や subscription state

逆に、session を超えて残したいものは別 storage に切ります。

  • durable task の実体
  • 認証主体そのもの
  • 監査ログ
  • ユーザー設定
  • 永続的なジョブ実行履歴

ここを分けないと、404 再初期化時に「session を消したのに durable task まで飛ぶ」「古い SSE stream が残って二重配信になる」といった事故が起きます。session は永続化の単位ではなく、会話的な接続状態の単位として扱う方が安定します。

DELETE はおまけではない

MCP 仕様では、client は不要になった session を DELETE で明示終了してよいとされています。server は 405 Method Not Allowed を返しても構いませんが、stateful server を作るなら、私は基本的に DELETE を受ける前提で設計します。

理由は単純です。

  • ブラウザや desktop client を閉じた後の cleanup がしやすい
  • session 数の無限増殖を防ぎやすい
  • SSE や subscription を明示的に閉じられる
  • 監査上も「切断された」のではなく「終了した」と分かる

放置された session を TTL だけで回収すると、短命 client が多い環境で無駄な state が溜まりやすいです。DELETE を受けるなら、session 終了を明示イベントとして扱えます。

ただし、DELETE を入れてもそれだけで十分ではありません。client が異常終了することは普通にあるので、次も必要です。

  • session TTL
  • idle timeout
  • stale stream cleanup
  • 404 再初期化との整合

DELETE は cleanup の唯一手段ではなく、cleanup を早める手段として置くのが現実的です。

multi-node で困るのは session そのものより配置戦略

ローカル実装では動くのに本番で壊れるのは、だいたいここです。

TypeScript SDK docs でも multi-node pattern は 3 つに整理されています。

  1. stateless mode
  2. shared storage を使う persistent state mode
  3. local state + message routing

私なら、判断はこう置きます。

stateless を保てるなら最優先

任意 node が任意 request を処理できるので、一番運用が軽いです。autoscaling と相性がよく、sticky session も不要です。

stateful だが durable にしたいなら shared storage

session metadata、event cursor、subscription state を DB や共有 store に寄せます。session を in-memory だけで持つより安全ですが、read/write latency と整合性の設計が増えます。

local state を使うなら routing まで含めて設計

各 node が local memory を持つなら、同じ session に属する traffic を同じ node へ送る仕組みが要ります。load balancer の sticky session で雑に済ませると、node 再起動や scale out/in で壊れやすいです。

ここで大事なのは、session を使うかどうかの判断は、そのまま deployment 設計の判断でもあるという点です。single-process のサンプルコードをそのまま本番に持ち込まない方がいいです。

session 管理でも security 境界は残る

最新 spec では Origin 検証、localhost bind、認証が引き続き重要です。加えて session を持つなら、session ID 自体の扱いも security 対象になります。

私なら最低限、次を守ります。

  1. session ID は推測困難な値にする
  2. session ID を log や URL query に雑に出さない
  3. Origin 不一致は 403 で落とす
  4. token と session ID を同一視しない
  5. 未信頼コンテンツを読む session と write 系 session を分ける

特に 5 は見落とされやすいです。MCP prompt injection 対策でも書いた通り、危険なのは server 単体ではなく 同じ session に何が同席しているか です。未信頼な remote resource を読む session に write tool や shell 実行が見えているなら、session 設計の時点で危ないです。

2026年5月時点で私が採る実装順

今から remote MCP server を作るなら、この順が一番壊れにくいです。

  1. まず stateless で実装する
  2. POST JSON 応答だけで成立するか確認する
  3. 本当に必要なら SSE を追加する
  4. それでも足りないなら session を導入する
  5. session 導入と同時に 404 再初期化、DELETE、TTL、multi-node 方針を入れる

TypeScript SDK なら、stateful は sessionIdGenerator を与える、stateless は undefined にする、という切り替えがかなり明確です。つまり技術的には簡単に有効化できます。だからこそ、簡単にオンにできるものほど、先に設計理由を言えるようにしておくべきです。

最後に使う判断表

迷ったら、私は次の 5 問だけで決めます。

質問Yes なら
server から client へ自発通知したいかstateful を検討
per-client の購読状態や log level を保持したいかstateful を検討
任意 node が任意 request を処理してほしいかstateless を優先
404 で再初期化しても困らない揮発状態だけかstateless で十分な可能性が高い
DELETE / TTL / shared storage まで面倒を見られるかできないなら stateful を広げない

MCP の session 管理は、transport の細部ではありません。状態をどこまで持つか、どこで捨てるか、どの単位で再開させるか という設計そのものです。

最初から全部入りにすると、接続はできても運用できません。逆に、stateless をデフォルトにし、必要な理由が出た時だけ stateful へ進めば、MCP-Session-Id は厄介な足かせではなく、扱いやすい lifecycle 境界になります。

WRITTEN BY nidoneko

Full-stack engineer with 8+ years of experience in TypeScript, React, Node.js, and cloud-native development across healthcare, finance, HR, and IoT domains.

View Profile →