2026-07-29
MCP SDK v1 → v2 移行をやってみて分かったこと
2026 年 7 月 28 日に MCP TypeScript SDK v2(@modelcontextprotocol/server 2.0.0)が stable になったので、翌日に社内の MCP サーバを v1 から移行しました。結果は 12 ファイル +187 / −541 行の純減・実働 1 時間程度。認証まわりの変更もクライアントの再認証・再接続もゼロで、v1 → v2 は見かけほど大きな移行ではありませんでした。
この記事で分かることは 3 つです。
- v2 で何が変わったのか — パッケージ再編と stateless 既定化
- 移行の実手順 — 実質 3 つ(依存入れ替え・codemod・ハンドラ差し替え)
- 構成タイプ別の作業量 — 判断フローつき。重いのは SDK ではなくセッションと認証の設計
v2 で何が変わったのか
2026-07-28 の MCP 仕様改訂と同時に SDK のメジャー更新が stable になりました。プロトコル側の目玉は stateless 化(セッション管理を前提にしないプレーンな HTTP でスケールする方向への転換)です。
SDK 側はバージョンバンプではなくパッケージの再編です。モノリシックだった @modelcontextprotocol/sdk が役割別の server / client / core とフレームワークアダプタ群に分割されました。GitHub 上は同じ monorepo(modelcontextprotocol/typescript-sdk)で、npm の名前だけが変わります。
旧パッケージは v1 系のままメンテが続き、npm の latest は今も 1.x を指し続けています(2.0.0 公開後も 1.30.0 がリリースされています)。
依存更新ボットの通知を待っていても v2 移行は始まりません。Renovate / Dependabot は旧パッケージの 1.x を「最新」と報告し続けるため、移行のきっかけは公式ブログやリリースノートを自分で読みに行く必要があります。
後方互換も v2 側で解決されています。createMcpHandler は 1 つの factory で 2 世代を同時にサービングし、2025 世代クライアント(現行の Claude Code / Codex / claude.ai コネクタ)は legacy fallback が自動処理します。サーバを先に v2 化してもクライアントは古いままで動きます。唯一の挙動差分は 2025 世代への応答が JSON 単発 → SSE 形式になることですが、streamable HTTP クライアントは SSE 受信必須のため互換性の問題にはなりません。
移行の実手順は 3 つ
1. 依存の入れ替え
bun remove @modelcontextprotocol/sdk agents
bun add @modelcontextprotocol/server@^2.0.0
足したのは 1 つ、消えたのは 2 つ。v2 の transport が Web 標準 fetch native になったため、Workers で必要だった Cloudflare 製ラッパー(agents)ごと消せました(事例の Before / After 参照)。
2. 公式 codemod
bunx @modelcontextprotocol/codemod@latest v1-to-v2 src/routes/mcp
公式の移行ガイドどおり、import の書き換えと inputSchema の z.object() 化を 73 箇所自動変換。手動対応は、codemod が判定できず @mcp-codemod-error でマークされた 1 箇所と、import 挿入位置の軽微な崩れ 1 箇所だけでした。
codemod 直後の生 diff は ±2,400 行ありますが、フォーマッタを通すと実質差分は +187 / −541 行に収束します。生 diff の大きさで作業量を見積もらないのがコツです。
3. ハンドラの差し替え
本質的な変更はここだけです(以下は弊社の diff の抜粋)。
// Before: agents/mcp(v1 SDK を Workers で動かすためのラッパー)
createMcpHandler(createMatchingMcpServer(db, ...), { enableJsonResponse: true })
// 呼び出し側: handler(req, c.env, c.executionCtx)
// After: @modelcontextprotocol/server 2.0.0
createMcpHandler(() => createMatchingMcpServer(db, ...), { responseMode: "json" }).fetch
// 呼び出し側: handler(req)
v2 は server インスタンスではなく factory 関数を渡し、SDK がリクエストごとに fresh な server を作ります(v1 でも stateless 運用ではリクエストごとの構築が推奨でしたが、v2 では API の形として標準化されました)。env / ExecutionContext の受け渡しと型キャストも消えました。あとは typecheck・テスト・wrangler deploy --dry-run を確認して終わりです。
1 点、自信のない箇所があります。v2 には Hono 用アダプタ @modelcontextprotocol/hono も公開されています(MCP 向け既定値つきの Hono app 作成・body パース・Host ヘッダ検証を提供する薄いレイヤー)。弊社は既存の Hono アプリに /mcp を同居させる構成のため、アダプタは使わず core の createMcpHandler(...).fetch を直接マウントする形を選びましたが、これが本来の想定どおりの使い分けなのかは正直よく分かっていません。認識違いがあれば、有識者のご指摘をいただけると助かります。
構成タイプ別の作業量
ここまでは誰の環境でも同じ話です。では自分の環境ではどれだけ作業が要るのか — 次の 2 つの質問で見積もれます。
1 問目が「はい」の場合 — つまり Durable Objects ベースの McpAgent などでサーバ側にセッションを持っている場合は、v2 の「セッションを持たないのが既定」という設計とのすり合わせから考え直すことになります。なお Cloudflare 自身も McpAgent を deprecated にし、createMcpHandler への移行を推奨しています(弊社も v1 時代からセッションなしの構成で、v2 でも createMcpHandler を使っています)。2 問目が「いいえ」の場合 — 認証コードがツール定義や SDK の設定に食い込んでいる場合は、v2 の「token 検証は SDK では行わず、検証済みの authInfo を呼び出し元が渡す」設計に合わせて、まず認証をハンドラの外へ切り出す作業が発生します。両方クリアなら、やることは前章の 3 手順だけです。
実際にやってみた — 弊社の場合
ここからは事例です。弊社はエンジニアと案件のマッチング業務を MCP で回しており、画面はほとんどなく、社員は Claude Code / Codex に自然言語で依頼し、エージェントが MCP 経由で 74 個のツール(決定論的な read / write のみ)を呼びます。スタックは Cloudflare Workers + Hono + Better Auth + D1 で、MCP サーバは Worker の /mcp エンドポイントに同居しています。上の判断フローで言えば「作業量: 小」に該当する構成です。
この構成で移行した結果、入れ替わったのは赤 → 緑の 1 層だけでした。Hono・認証・74 個のツール定義は無改修です。
認証が無傷で済んだ理由は構成にあります。MCP の仕様は認証を独自方式にせず素の OAuth 2.0 としてプロトコルの外側に置いているため、役割分担が OAuth の標準どおりに分かれていれば、SDK の入れ替えはリソースサーバ内側の MCP handler にしか触りません。
リソースサーバ
MCP サーバ(Worker)。Bearer token を検証して通すだけ
認可サーバ
Better Auth の mcp plugin。ログイン・同意・client 登録・token 発行を担当
OAuth クライアント
Claude Code / Codex。Dynamic Client Registration で自己登録して接続
弊社が自分で書いた認証コードは、Better Auth の withMcpAuth で包んで staff 判定(Google Workspace の検証済み @zenshin-inc.co.jp メールだけに権限付与)を足した数十行だけ。これが Request / Response 境界のラッパーで SDK に依存しないため、内側の SDK を総入れ替えしても 1 行も変わりませんでした。発行済み token は D1 にあり、サーバ側にセッションも存在しないため、デプロイ後の再認証・再接続もゼロ。既存接続・新規接続・未認証時の 401 + OAuth 発見情報の 3 面を本番で確認し、実働 1 時間程度で移行完了です。
まとめ
- v1 → v2 の実体は「パッケージ再編 + stateless 既定化」。公式 codemod と移行ガイドが揃っており、作業は見かけより小さい
- 移行のきっかけは自分で取りに行く。パッケージ名が変わるため、依存更新ボットは教えてくれない
- 作業量を決めるのは SDK ではなく、セッションと認証の設計。Request 境界で認証を外出しした stateless 構成なら、ハンドラ 1 層の差し替えで終わる
移行ツールの充実度と後方互換の作り込みを見る限り、リモート MCP サーバを運用しているなら様子見を続ける理由は薄いと感じました。これから移行する方の見積もりの参考になれば幸いです。
参考リンク
- Beta SDKs for the 2026-07-28 MCP Spec Release Candidate Are Here(MCP 公式ブログ)
- v1 → v2 移行ガイド(typescript-sdk リポジトリ)
- 2026-07-28 revision 採用ガイド(stateless サーバは追加対応不要。elicitation 等の新機能を使うときに読む)
- v2 API ドキュメント