MCPのバージョニングと後方互換性はどう決まるか
MCP 2026-07-28版はハンドシェイクを廃止し、全リクエストにバージョンを載せる方式に変わりました。Modern/Legacy/Dual-eraの3分類と、Claude Codeが実際にどちらで動いているかを解説します。
MCPの2026-07-28版は、バージョンの決め方そのものを作り直しました。それまでの版はinitializeという最初のリクエストでクライアントとサーバーがハンドシェイクを行い、そこで確定した版をセッション全体で使い続ける方式でした。現行版にハンドシェイクはありません。すべてのリクエストが、自分がどの版のプロトコルで書かれているかを毎回申告します。サーバーはリクエストごとに、その版をサポートするかを独立に判断します。この変更が生む新旧混在の状態を、仕様は3つの用語と組み合わせごとの動作表で整理しています。
なぜハンドシェイクが無くなったのか
MCPは元々ステートレスなプロトコルを志向しています。サーバーは接続やセッションをまたいだ状態を仮定せず、リクエストごとに独立して処理すべきだという原則です。initializeハンドシェイクは、この原則と相性が悪い仕組みでした。一度確立した版をセッションの生存期間だけ覚えておく必要があり、その分だけ状態を持つからです。
現行版は版の情報をリクエストの_metaフィールドに載せ、HTTPではMCP-Protocol-Versionヘッダーにも重複させます。サーバーが対応していない版を要求されたらUnsupportedProtocolVersionError(コード-32022)を返し、対応する版の一覧をdata.supportedに含めます。クライアントはその一覧から使える版を選び直してリトライします。エラーコードの割り当て全体はMCPのエラーコードは-32000番台をどう割り当てるかで扱っています。
サーバーはserver/discoverという専用メソッドの実装が必須です。クライアントは他のリクエストを送る前にこれを呼んで対応版を先に知ることができますが、必須ではありません。対応版が分かっている前提でいきなり本題のリクエストを送り、UnsupportedProtocolVersionErrorが返ってきたときだけ対処する実装も仕様上は正当です。
Modern・Legacy・Dual-eraという3分類
仕様は版の新旧を扱うために3つの用語を定義しています。
| 用語 | 定義 |
|---|---|
| Modern | 定義版・識別情報・機能をリクエストごとのメタデータとして運ぶ(2026-07-28以降) |
| Legacy | 定義initializeハンドシェイクでセッションを確立する(2025-11-25以前) |
| Dual-era | 定義ModernとLegacyの両方に対応する実装 |
Dual-eraのサーバーは、クライアントの接続方法から自分がどちらの流儀で応じるかを決めます。Modern形式の_metaを伴うリクエストが来ればステートレスに現行版で処理し、initializeリクエストが来ればLegacyの意味論に切り替えます。同じエンドポイントやプロセスで両方の流儀を同時に扱うことも認められています。
era判定はどうやって行うか
Dual-eraのクライアントが「相手のサーバーがどちらのeraか」を見分ける方法は、トランスポートごとに異なります。stdioではserver/discoverを送り、認識できるModernのエラー以外が返るかタイムアウトすればLegacyへフォールバックします。Streamable HTTPでは、まずModern形式のリクエストを試し、400 Bad Requestのボディを調べてから判断します。認識できるModernのエラー(UnsupportedProtocolVersionErrorなど)が返ってきた場合はサーバーがModernだと確定し、クライアントはバージョンを選び直してリトライします。それ以外の応答はすべてLegacyサーバーの兆候として扱われます。
この判定はサーバープロセスまたはHTTPのオリジンごとの性質であり、個々のリクエストの性質ではありません。クライアントはサーバープロセスの生存期間(stdio)またはオリジン(HTTP)の間、判定結果をキャッシュしておくべきだとされています。同じサーバー構成であれば再起動をまたいで結果を保持してもよく、キャッシュした前提が後で外れたら再判定します。
クライアント×サーバーの組み合わせでどう動くか
era判定を踏まえると、クライアントとサーバーの組み合わせは次の7パターンに分類できます。
| クライアント | サーバー | 結果 |
|---|---|---|
| Modern | サーバーModern | 結果成立。版が食い違えばUnsupportedProtocolVersionErrorでリトライ |
| Modern | サーバーLegacy | 結果失敗。stdioではserver/discoverを先に送り決定的に失敗させるべき |
| Dual-era | サーバーModern | 結果成立。probeがModernの結果を返し、クライアントはModernのまま進む |
| Dual-era | サーバーLegacy | 結果成立。probeが失敗しLegacyへフォールバック |
| Legacy | サーバーModern | 結果失敗。Legacyクライアントにフォールバック手段が無い |
| Legacy | サーバーDual-era | 結果成立。initializeに応じてLegacy版で処理される |
| Legacy | サーバーLegacy | 結果成立。従来のLegacy版どおりに動作する |
Legacyクライアントが根本的に詰む唯一のケースは、相手がModern専用サーバーの場合です。stdioではinitialize自体が未知のメソッドかつ必須の_metaを欠いているためJSON-RPCエラーで拒否され、HTTPでは必須ヘッダーが欠けているため400 Bad Requestになります。Legacyクライアントにはこのエラーから回復してModernへ乗り換える手段がありません。この非対称性を踏まえ、仕様はModern専用サーバーに対して「initializeリクエストへのエラー応答に、自分が対応する版を名指しで含めるべき」と求めています。Legacyクライアントがそのエラーから読み取れる唯一の手がかりになるためです。
拡張機能もこの仕組みでネゴシエーションする
コアプロトコルを超える追加機能は拡張(extensions)という別枠で扱われ、フィールドに識別子とその設定オブジェクトのマップとして広告します。たとえばMCP Appsという拡張をio.modelcontextprotocol/uiという識別子で宣言する場合は次の形になります。
{
"capabilities": {
"roots": {},
"extensions": {
"io.modelcontextprotocol/ui": {
"mimeTypes": ["text/html;profile=mcp-app"]
}
}
}
}一方が拡張に対応し他方が対応していない場合、対応している側はコアプロトコルの挙動へ戻すか、適切なエラーで拒否しなければなりません。この扱いは拡張ごとにドキュメント化することが推奨されています。
Claude CodeはどちらのMCPクライアントランタイムを使っているか
ここまではMCP仕様一般の話です。では実際にClaude Codeは、Modern(2026-07-28)とLegacyのどちらでMCPサーバーに接続しているのか。公式ドキュメントは、Claude Codeが2つのクライアントランタイムを使い分けていることを明かしています。v1ランタイムはMCP TypeScript SDK 1.x上に構築され、v2ランタイムは同じコードをMCP TypeScript SDK 2.0上で動かしたもので、2026-07-28のプロトコル改訂に対応しています。
Claude Code v2.1.232以降は既定でv2ランタイムを使いますが、次の条件下ではv1に留まります。
- Amazon Bedrock・Claude Platform on AWS・Google Cloud's Agent Platform・Microsoft Foundryで実行している(ホスト側が
CLAUDE_CODE_PROVIDER_MANAGED_BY_HOSTを設定していない限り) - Claude apps gateway経由でサインインしている
- フィーチャーフラグの取得が無効になっている
さらに見落としやすいのは、v2ランタイムを使っていても、全サーバーが自動的にModern版を試すわけではないという点です。Claude CodeはHTTPサーバーとclaude.aiコネクタサーバーには常に新しい版への対応を尋ねますが、stdioサーバーに対してはMCP_PROTOCOL_NEGOTIATIONという環境変数をautoに設定した場合しか尋ねません。設定しなければstdioサーバーはv1と同じLegacy扱いのままです。SSEとWebSocketのサーバーはそもそもprobeの対象外で、常にLegacyのまま接続します。
| 環境変数 | 役割 |
|---|---|
MCP_SDK_GENERATION | 役割使うランタイムをv1またはv2に固定する |
MCP_PROTOCOL_NEGOTIATION | 役割autoでHTTP・コネクタ・stdioの全サーバーにModern対応を尋ねる。legacyで全サーバーへの問い合わせを止める |
つまり既定状態のClaude Code v2ランタイムは、接続の種類によって前提とするeraが違う非対称なDual-eraクライアントです。HTTP接続とコネクタはModernへ積極的に乗り換えを試み、stdioはオプトインしない限りLegacyのまま安定動作を優先する。この設計は、Claude CodeがサーバーとのChannels連携にも影響します。Modern版のstdioサーバーはChannelsメッセージを配送できません。そのためMCP_PROTOCOL_NEGOTIATION=autoでModernに接続したstdioのChannelsサーバーは、Claude Codeにチャンネルとして登録されません。Channelsを使うサーバーをstdioで自作している場合、この変数をlegacyのままにしておくか未設定にしておく必要があります。
この設計は何を優先しているか
ハンドシェイクの廃止は、MCPをよりステートレスなプロトコルに寄せる決定です。しかしModern版への移行そのものは、後方互換を壊さない形で慎重に進められています。仕様がera判定のキャッシュやserver/discoverの任意化を細かく規定しているのは、既存のLegacy実装を置き去りにしないための配慮です。Claude Codeの実装がstdioとHTTPで異なる既定値を選んでいるのも、同じ配慮の表れと見ることができます。HTTP・コネクタは新しい版のメリット(通知ストリームの保持など)を先取りしやすい一方、無数に存在する自作のstdioサーバーは、明示的な選択なしに挙動を変えるとエコシステム全体で予測しにくい互換性問題を生みかねません。ロールアウトの速度に差をつけることは、破壊的な移行を避ける現実的な選択です。
まとめ
MCPの2026-07-28版はハンドシェイクを廃止し、全リクエストにバージョンを載せる方式へ変わりました。Modern・Legacy・Dual-eraの3分類と7通りの組み合わせのうち、根本的に詰むのはLegacyクライアントがModern専用サーバーに接続する場合だけです。Claude CodeはClaude Code v2.1.232以降で既定にv2ランタイムを使いますが、stdioトランスポートを使うサーバーへのModern対応はMCP_PROTOCOL_NEGOTIATION=autoを設定しない限り有効になりません。自作サーバーの接続設定はClaude Code MCP設定ガイド、Agent SDKで独自ツールを組み込む方法はAgent SDKカスタムツールの作り方で扱っています。