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 つです。

この記事の全体像を描いた手書き風インフォグラフィック。パッケージ再編・stateless 既定化・移行は 3 手順の 3 つのポイントと、作業量の判断フロー、弊社の結果(diff 純減・認証変更 0 行・再接続 0 回・実働約 1 時間)がまとめられている
この記事の全体像

v2 で何が変わったのか

2026-07-28 の MCP 仕様改訂と同時に SDK のメジャー更新が stable になりました。プロトコル側の目玉は stateless 化(セッション管理を前提にしないプレーンな HTTP でスケールする方向への転換)です。

MCP 公式ブログの SDK ベータ発表記事。最初の段落の the new protocol revision goes stateless という一文にマーカーが引かれている
公式ブログの発表記事 — 改訂の中心は stateless 化(マーカー部)

SDK 側はバージョンバンプではなくパッケージの再編です。モノリシックだった @modelcontextprotocol/sdk が役割別の server / client / core とフレームワークアダプタ群に分割されました。GitHub 上は同じ monorepo(modelcontextprotocol/typescript-sdk)で、npm の名前だけが変わります。

v1 のモノリシックな @modelcontextprotocol/sdk が、v2 で core / server / client とフレームワークアダプタ、codemod に分割されたことを示す構成図
v1 のモノリシックな sdk が、v2 で役割別のパッケージ群に分割された

旧パッケージは v1 系のままメンテが続き、npm の latest は今も 1.x を指し続けています(2.0.0 公開後も 1.30.0 がリリースされています)。

npm の @modelcontextprotocol/sdk ページ。1.30.0 が 2 日前に公開されたと表示され、バージョン行に赤い下線が引かれている npm の @modelcontextprotocol/server ページ。2.0.0 が 1 日前に公開されたと表示され、バージョン行に赤い下線が引かれている
旧 sdk の latest は今も 1.x(上)、v2 は別名の新パッケージ(下)

依存更新ボットの通知を待っていても 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 の書き換えと inputSchemaz.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 つの質問で見積もれます。

v2 移行の作業量を見積もる判断フロー図。サーバからクライアントへの push を使っていれば作業量大(セッション設計の見直し)、token 検証を Request/Response 境界で外出ししていなければ作業量中(認証まわりの改修)、どちらもクリアなら作業量小(codemod + ハンドラ差し替えだけ)
v2 移行の作業量はこの 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 エンドポイントに同居しています。上の判断フローで言えば「作業量: 小」に該当する構成です。

社内システムの全体構成図。社員が AI エージェントに自然言語で依頼し、エージェントが MCP 経由で Cloudflare Workers 上の Hono API・Better Auth・74 個のツールを呼び、データは Cloudflare D1 に保存される
弊社システムの全体構成 — 判断はエージェント側、サーバは read / write だけ

この構成で移行した結果、入れ替わったのは赤 → 緑の 1 層だけでした。Hono・認証・74 個のツール定義は無改修です。

移行前後の構成比較図。Before では Hono と MCP SDK v1 の間に Cloudflare 製の agents パッケージが挟まっているが、After では @modelcontextprotocol/server 2.0.0 に一本化され、認証層とツール群は無改修
弊社の移行の Before / After — 入れ替わったのは赤 → 緑の 1 層だけ

認証が無傷で済んだ理由は構成にあります。MCP の仕様は認証を独自方式にせず素の OAuth 2.0 としてプロトコルの外側に置いているため、役割分担が OAuth の標準どおりに分かれていれば、SDK の入れ替えはリソースサーバ内側の MCP handler にしか触りません。

MCP の OAuth 役割分担図。MCP クライアントが OAuth クライアント、Better Auth が認可サーバ、Cloudflare Workers 上の MCP サーバがリソースサーバに対応し、今回入れ替えたのはリソースサーバ内の MCP handler だけであることを示す
MCP の認証は素の OAuth 2.0 — 入れ替えたのは緑の枠の内側だけ

リソースサーバ

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 時間程度で移行完了です。

まとめ

移行ツールの充実度と後方互換の作り込みを見る限り、リモート MCP サーバを運用しているなら様子見を続ける理由は薄いと感じました。これから移行する方の見積もりの参考になれば幸いです。