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 は「使える」ではなく「必要なときだけ使う」
私の結論はかなり単純です。
- デフォルトは stateless でよい
- server から client へ能動的に通知したいときだけ stateful を検討する
- stateful を選ぶなら、404 再初期化と DELETE 終了まで最初から設計する
- 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 管理のルールはかなり明快です。
- server は
initialize応答でMCP-Session-Idを返してよい - 返した場合、client は以後の HTTP request に同じ header を必ず付ける
- server は session を終了させた後、その session ID に対して
404 Not Foundを返す - client は
MCP-Session-Id付き request に対する404を受けたら、session ID なしでInitializeRequestをやり直す - 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 つに整理されています。
- stateless mode
- shared storage を使う persistent state mode
- 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 対象になります。
私なら最低限、次を守ります。
- session ID は推測困難な値にする
- session ID を log や URL query に雑に出さない
Origin不一致は 403 で落とす- token と session ID を同一視しない
- 未信頼コンテンツを読む session と write 系 session を分ける
特に 5 は見落とされやすいです。MCP prompt injection 対策でも書いた通り、危険なのは server 単体ではなく 同じ session に何が同席しているか です。未信頼な remote resource を読む session に write tool や shell 実行が見えているなら、session 設計の時点で危ないです。
2026年5月時点で私が採る実装順
今から remote MCP server を作るなら、この順が一番壊れにくいです。
- まず stateless で実装する
POSTJSON 応答だけで成立するか確認する- 本当に必要なら SSE を追加する
- それでも足りないなら session を導入する
- 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 境界になります。