2026年7月23日、GitHub の公式 MCP サーバが、7月28日に確定する新仕様へ先回りで対応した。変更の中身は「セッションを持つのをやめた」というもので、実装では Redis によるセッション保持がまるごと消えている。自作の MCP サーバを持っている人にとっては、他人の移行結果を先に見られる珍しい機会になった。
目次
7月28日の仕様改訂で消えるもの
MCP の 2026-07-28 改訂は、プロトコル層からセッションの概念を取り除く。接続を確立するための initialize ハンドシェイクも無くなり、クライアントのメタデータ・機能・プロトコルバージョンは、毎回のリクエストの _meta フィールドに載って運ばれる。
| 項目 | これまで | 2026-07-28 以降 |
|---|---|---|
| 接続開始 | initialize で握手 |
握手なし。最初のリクエストがそのまま本番 |
| セッションID | Mcp-Session-Id ヘッダで維持 |
プロトコル層から削除 |
| クライアント情報 | 初期化時に1度だけ送る | 毎リクエストの _meta に同梱 |
| サーバの形おすすめ | 状態を持つインスタンス+共有ストア | ステートレスなインスタンスをLBの後ろに並べるだけ |
| ロードバランサ | スティッキーセッションが必要 | 設定を外せる |
GitHub 側の実装変更は、この仕様変更をそのまま反映している。initialize 時のデータベース書き込みが無くなり、呼び出しごとの読み取りも消えた。リクエストの中身を深く覗いて判断していた処理をやめ、HTTP ヘッダから必要な値を読む形に変えている。tier 1 の SDK はいずれも後方互換を保っているため、利用側の改修は不要というのが GitHub の説明だ。
「利用側は無改修」を鵜呑みにしない
筆者は外部APIの仕様変更を、まず疑ってかかることにしている。過去に、廃止予定の機能を全経路に組み込んだまま運用して、ある日いっせいに動かなくなった経験があるからだ。あのときは代替経路を用意していなかったので、復旧まで手が止まった。
今回の「無改修で動く」は、後方互換を維持している SDK を使っている場合の話になる。自前で HTTP を組み立てているクライアントや、ゲートウェイでヘッダを付け替えている構成は、その外側にいる。Mcp-Session-Id をルーティングのキーに使っていたら、そこは自分で外す必要がある。
自作サーバの棚卸しから始める
移行作業の実体は、コードの書き換えより先に「セッションに何を置いていたか」の洗い出しだ。置いていたものは、だいたい次の3種類に分かれる。
| セッションに載せていたもの | 移行先 | 注意点 |
|---|---|---|
| 認証情報(トークン・ユーザーID) | 毎リクエストのヘッダ | 失効の確認をリクエストごとに行う設計へ |
| ページングのカーソル位置 | リクエスト引数(カーソルを返して受け取る) | サーバ側で覚えない。カーソルは不透明な文字列にする |
| ユーザーごとの設定・作業中の状態 | アプリ層の永続ストア | プロトコルの都合と業務データを混ぜない |
3つ目が曲者で、「接続中だけ覚えておけばいい」と思って置いた値ほど、後から業務ロジックが依存している。筆者が自分のサーバを見直したときも、一時的なつもりの値が2箇所ほど残っていた。
書き換えの形
セッションから読んでいた箇所を、リクエストから読む形に置き換える。Python で書いた例を挙げる。
# 変更前: 初期化時に作ったセッションから読む
def handle_search(session_id, params):
sess = redis.hgetall(f"mcp:sess:{session_id}") # 毎回のDB読み取り
token = sess["token"]
return search(token, params["q"], cursor=sess.get("cursor"))
# 変更後: リクエストが自己完結している
def handle_search(request, params):
token = request.headers["Authorization"].removeprefix("Bearer ")
cursor = params.get("cursor") # クライアントが持ち回る
result = search(token, params["q"], cursor=cursor)
return {"items": result.items, "next_cursor": result.next_cursor}
カーソルをクライアントに返して持ち回らせる形にすると、サーバ側は次の呼び出しがどのインスタンスに届いても同じ答えを返せる。スケールの話に見えて、実際にはローカル開発でも効く。プロセスを再起動しても会話が途切れない。
旧クライアントと新クライアントの両方から叩いて確かめる
移行が終わったかどうかは、コードを読んでも分からない。両方の形式で実際にリクエストを投げて、同じ答えが返るかを見る。
# 旧仕様: セッションIDを付けて呼ぶ(無視されて正常応答が返るのが正解)
curl -s -X POST http://127.0.0.1:8080/mcp \
-H "Content-Type: application/json" \
-H "Mcp-Session-Id: legacy-12345" \
-H "Authorization: Bearer $TOKEN" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | jq '.result.tools | length'
# 新仕様: 握手なし・_meta 同梱でいきなり本番リクエスト
curl -s -X POST http://127.0.0.1:8080/mcp \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list",
"params":{"_meta":{"protocolVersion":"2026-07-28",
"clientInfo":{"name":"curl-check","version":"1"}}}}' | jq '.result.tools | length'
確認する点は3つある。両方が同じツール数を返すか、旧仕様のリクエストがエラーにならないか、サーバを再起動した直後でも2回目以降の呼び出しが通るか。3つ目は、状態がどこかに残っていないかを見るための確認になる。
移行で見落としやすい箇所
ロードバランサのスティッキー設定は、外し忘れても動いてしまう。動くので気づかない。設定が残っている限り、インスタンスを増やしても偏る。
ゲートウェイのルーティング規則も同じで、Mcp-Session-Id を見る条件が残っていると、ヘッダが来なくなった時点で全部が既定のルートへ流れる。これは負荷が上がるまで表面化しない。
移行の順番を間違えると戻せなくなる
筆者が気をつけているのは、サーバとクライアントを同時に触らないことだ。両方を一度に変えると、動かなくなったときにどちらが原因か分からない。サーバ側を先にステートレス化して、旧クライアントから叩いて通ることを確認してから、クライアント側の改修に入る。
この順番なら、途中で問題が出ても切り戻す先が1つに絞れる。逆順で進めて両方が中途半端になると、旧仕様でも新仕様でも動かない状態ができあがる。
作業を分ける単位も小さくしておきたい。ツールが10個あるサーバなら、副作用のない読み取り系を1つ選んで先に通す。そこで _meta の受け取りとヘッダからの認証情報の取り出しが正しく書けているかが分かるので、残り9個は同じ形を写すだけになる。
移行が終わった後も、しばらくは旧形式のリクエストが届く。ゲートウェイのログで Mcp-Session-Id ヘッダの有無を数えておくと、旧クライアントがどれだけ残っているかが見える。数がゼロに落ちてから、互換のための分岐を消せばいい。先に消すと、まだ移行していない誰かの接続を切ることになる。
Roots / Sampling / Logging の扱い
今回の改訂では、Roots・Sampling・Logging も非推奨になり、12か月の猶予が置かれた。猶予があるうちに代替を決めておくと、期限直前に慌てずに済む。
筆者の方針は単純で、プロトコルに寄せていた機能をアプリ側へ引き取る。作業ディレクトリの範囲(Roots)はツールの引数として明示的に受け取り、ログはサーバ自身の仕組みで出す。プロトコルが面倒を見てくれる領域は縮む方向にあるので、依存を減らしておくほうが後の移行が軽い。
筆者ならこう進める
いきなり本番のサーバを書き換えることはしない。手順としては、まず読み取り専用のツールを1つだけステートレス化して、旧クライアントから叩いて壊れないことを確かめる。そのうえで残りを移す。
この順番にしている理由は、失敗したときに戻せる範囲を小さくしておきたいからだ。Claude Mission Control のように複数のエージェントから同じ MCP サーバを叩く構成だと、1つの変更が全体に波及する。切り戻しの単位を小さくしておくと、原因の特定も早い。
GitHub の実装が先に出たのは、この意味でありがたい。仕様書を読んで想像するより、本番規模で動いている実例が「何を捨てたか」を見るほうが早い。捨てたものが Redis とパケット検査だと分かれば、自分のサーバで同じ役割を担っている箇所を探せばいい。
よくある疑問
7月28日を過ぎたら旧仕様のクライアントは動かなくなるのか。tier 1 の SDK は後方互換を維持しているというのが GitHub の説明で、SDK 経由なら当面は動く見込みになる。自前で HTTP を組んでいる場合は、その保証の外にいる。
セッションが無くなると認証のたびに検証コストが増えないか。増える。増えるぶん、共有ストアへの読み書きが消える。どちらが重いかは認証方式で変わるので、トークン検証をキャッシュする層を自分で持つかどうかを実測で決めればいい。
ローカルで stdio 接続しているだけなら関係ないか。プロトコル層の変更なので、握手が消える点は同じく効く。影響は小さいが、SDK を上げたときに挙動が変わる可能性はある。手元の MCP サーバを1つ叩いて確かめておくと安心できる。
いつ移行するのが安全か。仕様が確定する7月28日を待つ判断もありうる。GitHub の実装は正式リリース前の対応なので、細部が動く可能性は残っている。急ぎでなければ、確定後に条文と実装を見比べてから着手するほうが手戻りが少ない。
まとめ
ステートレス化の作業は、コードの書き換えより棚卸しが本体になる。セッションに置いていた値を認証情報・カーソル・業務データの3つに仕分けて、それぞれヘッダ・リクエスト引数・アプリ層の永続ストアへ割り振る。仕分けが終われば、書き換え自体は各ハンドラ数行で済む。
今日やるなら、自作サーバのコードで session という語を全文検索するところから始めるのがいい。ヒットした箇所が、そのまま移行対象の一覧になる。
一次ソース: GitHub MCP Server supports the next MCP specification(GitHub Changelog)