Skip to content

Serve MCP protocol revision 2026-07-28 only, single lane [worker, bridges, deps, docs, tests] - #253

Merged
liplus-lin-lay merged 1 commit into
mainfrom
249-spec-mcp-2026-07-28-migration-plan-single-lane-flip-no-dual-lane-fallback
Aug 6, 2026
Merged

Serve MCP protocol revision 2026-07-28 only, single lane [worker, bridges, deps, docs, tests]#253
liplus-lin-lay merged 1 commit into
mainfrom
249-spec-mcp-2026-07-28-migration-plan-single-lane-flip-no-dual-lane-fallback

Conversation

@liplus-lin-lay

Copy link
Copy Markdown
Member

Closes #249

何をしたか

Worker と 2 つのブリッジのクライアント面を、MCP プロトコル版 2026-07-28(ステートレスコア)へ同一 PR で切り替えた。互換レーンは実装していない(決定 1)。

Worker (worker/src/)

  • WebhookMcpAgent.serve("/mcp")agents/mcp/servercreateMcpHandler へ置換。legacy: "reject" を指定しており、単レーンという決定がそのままコードの literal になっている。2025 系の request(initialize を含む)は、endpoint が提供する唯一の版を名指しした unsupported-protocol-version エラーで返る。
  • worker/src/agent.tsMcpAgent を継承した DO クラス)を廃し、リクエスト毎に MCP サーバーを組み立てる factory createWebhookMcpServer(env)worker/src/mcp.ts に置いた。server.tool()server.registerTool() へ移行(schema は z.object() で包んだ同一定義)。
  • テナント同一性はリクエスト毎の props から取る。index.ts が従来どおり ctx.props を書き換え、handler がそれをリクエスト毎の auth context として再公開し、tool 側は getMcpAuthContext() で読む。this.props に相当するインスタンス状態は無くなった。マルチアカウント fan-out の意味は不変accessible_account_ids から store-{id} を解決して並列クエリ、という経路はそのまま。
  • handler は env ごとに WeakMap で memo 化。factory が bindings を要るのに envfetch の中にしか無いため、module scope では作れない。
  • 退役した WebhookMcpAgentworker/src/retired-do.ts へ分離した。過去 migration(v2)が名指しするクラスはスクリプト内に存在せねばならないという Cloudflare の制約のため、MCP_OBJECT binding もろとも残す。撤去には deleted_classes migration が要るので別件。実データを持つ WebhookStore / TenantRegistry は無変更。
    • 分離は同時に mcp.ts から cloudflare:workers import を消し、plain node でのテストを可能にしている。

ブリッジ 2 面(mcp-server/local-mcp/

rag-mcp と違い本 repo は手書きハンドシェイクを 2 箇所持つ。両方とも、getSessionIdWithToken + mcp-session-id ヘッダは移行ではなく削除になった(新仕様では毎リクエストが自己完結するため)。手書きの 401 リトライは transport の onUnauthorized に移り、セッション失効リトライ(-32600 / -32001 分岐)は対象そのものが消えた。

  • mcp-server/(npm 公開ブリッジ): クライアント面の構築とキャッシュを server/remote-client.js に分離した。index.js は import 時に stdio transport を繋いでしまうためテストから触れない。
  • local-mcp/(ローカル開発用ブリッジ): TypeScript の双子として同じ配線を inline で持つ。別パッケージなので共有はしない。
  • Claude Desktop に対する面は両方とも SDK v1 / 2025 系のまま(制約どおり。Desktop ↔ ブリッジ間の移行は本 issue の範囲外)。
  • OAuth の一式(web flow / state polling / token 保存 / refresh rotation / auth-required 応答)は既存のまま。transport の authProvider に繋ぎ直しただけ。

依存

  • agents 0.19.0 → 0.20.1、@modelcontextprotocol/serverexact 2.0.0 で追加。agents が非 optional peer として exact 2.0.0 を要求するため、caret にすると fix(worker): dedupe MCP SDK + fix agent.ts type errors, add CI tsc gate #239 が問題にしていた二重コピーを再現してしまう。exact なら agents の bump が install 時に失敗する(= tsc まで持ち越さない)。
  • worker の @modelcontextprotocol/sdk 依存宣言と override を削除した。Worker はもう SDK v1 を一切 import しない。agents の必須 peer として npm が入れるが(1.30.0)、こちら側にレンジが無いので agents の exact pin と食い違いようがない。
  • 両ブリッジは Desktop 面のために @modelcontextprotocol/sdk を保持したまま、@modelcontextprotocol/client を追加。
  • @cloudflare/workers-types の pin は lockfile の実測で確認したmemo(worker): the workers-types pin now sits below the range partyserver declares #252)。作業前 4.20260404.1 → agents bump 後の npm install 後も 4.20260404.1npm ci 後の npm ls でもツリー全体が 4.20260404.1partyserver は依然 agents/node_modules に nest されたまま(0.5.10)で、pin が load-bearing である状態は変わっていない。

検証

ローカルで通したもの(CI 相当)

node --check(index.js / remote-client.js)/ mcp-server npm test (23) / worker tsc --noEmit / wrangler deploy --dry-run / worker npm test(node 65 + workers pool 42)/ local-mcp tsc --noEmit

新規: worker/test/mcp-stateless-contract.test.ts

ブリッジの実クライアント(mcp-server/server/remote-client.js)と Worker の実ハンドラ配線を in-process で接続し、次を検証する。

  • connect も含め全て POST、mcp-session-id ヘッダは一切無い
  • initialize を送らない(pin された modern endpoint への connect は server/discover で始まる)
  • 全 request が _metaio.modelcontextprotocol/protocolVersion: "2026-07-28" と clientCapabilities を載せている
  • 5 つの tool が modern wire 越しに publish される
  • tools/call まで通る — rag-mcp では Vectorize / Workers AI / D1 の binding に阻まれて検証できなかった層。本 repo の store は DO stub の fetch 境界なので差し替えが効き、get_pending_status を実際に呼んで「リクエストの props → store-{id} 解決 → 複数 store の fan-out → マージ」まで押さえた。本 PR で最も到達距離の長い変更(テナント同一性の出どころが変わった件)がここに乗る
  • 旧ブリッジ相当の initialize は 400 / -32022 / supported: ["2026-07-28"] で拒否される

CI では contract test が bridge の依存を解決する必要があるため、mcp-servernpm ci を CI に追加した。

検証できていないもの

local-mcp/src/index.ts(開発用ブリッジ)は contract test の対象外。利用者が動かすのは公開ブリッジのほうであり、そちらを押さえている。

リリース

minorrules/operations/release-version-rule.md の「構造変更 かつ user/system observable」に該当する(旧ブリッジが実際に壊れる)。README 注意書きは本 PR に含む。

破壊的変更の告知

README.md / mcp-server/README.md に、この版から Worker が新仕様のみを提供すること、旧ブリッジは動作しないこと、MCP クライアントの再起動でブリッジ更新が要ること、版を pin していると再起動しても復旧しないことを明記した。

本 repo 固有の追記として、旧ブリッジでも channel 通知だけは届き続けることを書いた。/events は MCP ではないため無傷で、通知は健康に見えるのに mark_processed を含む全 tool 呼び出しが失敗する — つまり pending キューが黙って溜まり続ける。この気づきにくさは注意書きの本体に値する。

要求仕様(en/ja)には、単レーンの決定とその理由、ブリッジ二面の独立性、破断の非対称の所在を記録した。

範囲外にした判断: Dynamic Client Registration の撤去

issue #249 は「OAuth 周辺を触る本移行に相乗りさせる」と書いていたが、相乗りの前提が成立しないため本 PR では撤去していない。本移行は OAuth の実装に一切触れておらず(ブリッジ側は transport の authProvider に繋ぎ直しただけ)、DCR 撤去は Client ID Metadata Documents への移行という両面の認証設計変更になる。プロトコル切替と同一リリースに載せると、利用者が再起動して降りてくる先で破断面が二つ同時に開く。

最短撤去は 2027-07-28 以降の最初のリビジョンであり、しかも「最短撤去」は撤去が可能になる時点にすぎない。この判断と根拠は要求仕様(F7 節)に記録した。別 issue にするかは親の判断に委ねる。

🤖 Generated with Claude Code

…er, bridges, deps, docs, tests]

Worker と 2 つのブリッジのクライアント面を、MCP プロトコル版 2026-07-28
(ステートレスコア) へ同一 PR で切り替える。互換レーンは実装しない。

Worker side:
- `WebhookMcpAgent.serve("/mcp")` を agents の `createMcpHandler` へ置換し、
  `legacy: "reject"` を指定。2025 系の request (`initialize` を含む) は、この
  endpoint が提供する唯一の版を名指しした unsupported-protocol-version エラーで
  返り、互換レーンは存在しない。
- `worker/src/agent.ts` (McpAgent 継承の DO) を廃し、リクエスト毎に MCP サーバーを
  組み立てる factory `createWebhookMcpServer(env)` を `worker/src/mcp.ts` に置く。
  テナント同一性はインスタンスフィールドではなく、`getMcpAuthContext()` 経由で
  リクエスト毎の props から取る。マルチアカウント fan-out の意味は不変。
- 退役した `WebhookMcpAgent` クラスは `worker/src/retired-do.ts` へ分離。過去
  migration が名指しするクラスはスクリプト内に存在せねばならないため export だけ
  残す。実データを持つ WebhookStore / TenantRegistry は無変更。

Bridge side (mcp-server/ と local-mcp/):
- 手書きハンドシェイク (`getSessionIdWithToken` + `mcp-session-id` ヘッダ) を
  移行ではなく削除し、`@modelcontextprotocol/client` を版 2026-07-28 に pin した
  client へ置換。手書きの 401 リトライは transport の `onUnauthorized` に移る。
- 公開ブリッジのクライアント面は `mcp-server/server/remote-client.js` へ分離した。
  `index.js` は import 時に stdio transport を繋いでしまうためテストから触れない。
- Claude Desktop に対する面は SDK v1 / 2025 系のまま (両ブリッジとも)。

Deps:
- `agents` 0.19.0 -> 0.20.1、`@modelcontextprotocol/server` を exact 2.0.0 で追加
  (agents が非 optional peer として exact 2.0.0 を要求するため)。Worker が SDK v1 を
  import しなくなったので、その依存宣言と override を削除。
- `@cloudflare/workers-types` の pin は lockfile 実測で確認。作業前 4.20260404.1、
  agents bump 後の npm install / npm ci 後も 4.20260404.1 のまま (#252)。

Tests / docs:
- `worker/test/mcp-stateless-contract.test.ts` を追加。ブリッジの実クライアントと
  Worker の実ハンドラ配線を in-process で接続し、セッション不使用・envelope 付与・
  旧ブリッジの拒否 (-32022) を検証する。store は DO stub 境界で差し替えられるため
  `tools/call` も通し、props からのテナント解決まで押さえた。
- CI に mcp-server の npm ci を追加 (contract test が bridge の依存を解決するため)。
- README / mcp-server README / 要求仕様 (en/ja) に破壊的変更の注意書きと単レーン
  決定の記録を追加。旧ブリッジは静かに壊れ、channel 通知だけは届き続けるため
  気づきにくい点も明記した。

Refs #249

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
github-webhook-mcp a979741 Aug 06 2026, 01:44 PM

@liplus-lin-lay liplus-lin-lay left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI self-review(親、execution_mode = auto)

head a979741 に対して実施。担当の報告は根拠に用いず、以下はすべて親が実読して確認した。

受け入れ条件の照合

#249 の受け入れ条件 判定 親の確認方法
Worker が 2026-07-28 のみを提供(legacy: "reject" pass worker/src/index.ts:140 を実読
ブリッジ 2 本が新仕様で疎通、Desktop 面は従来どおり pass mcp-server/ / local-mcp/ 双方が変更ファイルに含まれ、SDK v1 を保持したまま @modelcontextprotocol/client を追加
契約テストが flip 前ブリッジの拒否を固定 pass worker/test/mcp-stateless-contract.test.ts の assert を実読(下記)
README に破壊的変更の注意書き pass README.md / mcp-server/README.md が変更ファイルに含まれる
CI green pass CI / test / Workers Builds すべて pass、mergeStateStatus: CLEAN

契約テストの評価(本 PR の最大の成果)

assert を実読した結果:

  • 全リクエストが POST、セッションヘッダ null
  • initialize を含まない。最初のメソッドが server/discover
  • 全リクエストの _metaprotocolVersion: "2026-07-28"clientCapabilities を運ぶ
  • テナント解決が store-4242 / store-99 へ fan-out し、マージ結果まで検証
  • flip 前ブリッジが 400 / -32022 / supported: ["2026-07-28"] で拒否される

姉妹リポジトリ github-rag-mcp の契約テスト(PR #229)は tools/call の実行経路を本番送りにしていたが、本 PR はそこを in-process で固定している。本 repo のほうが検証の射程が広い。store が DO スタブの fetch 境界であることを利用した設計で、移行の影響が最も大きい経路を落としていない。

wrangler.toml の扱い

退役した WebhookMcpAgent のバインディングを残した判断は正しい。Cloudflare は過去 migration が名指しするクラスの存在を要求するため、deleted_classes migration なしの削除は不可。加えて本 PR はその理由を wrangler.toml 内のコメントとして残している。姉妹リポジトリ github-rag-mcp では同じ状態が無記載で残り、親が別 issue(rag-mcp#230)を立てて補う必要があった。こちらのほうが良い。

scope 逸脱 1 件 — DCR の相乗りを実施せず(判断を支持する)

#249 本文は「OAuth 周辺を触る本移行に相乗りさせる」と記載していたが、担当はこれを実施せず、前提が成立しないことを理由に挙げている。

親が検証した: worker/src/oauth.ts は変更ファイルに含まれていない。本移行はブリッジ側の認証を transport の authProvider へ配線し直しただけで、認可サーバ実装には触れていない。したがって「どうせ触るなら相乗り」という前提は成立しない。issue 本文を書いたのは親(私)であり、書いた時点の想定が実装で否定された形。

加えて、DCR 撤去は Client ID Metadata Documents への移行を伴い、利用者に強いる 1 回の再起動に破壊面を 2 つ載せることになる。最短撤去は 2027-07-28 以降の最初のリビジョンであり、しかもそれは撤去が「可能になる」時点にすぎない。急ぐ理由がない。

判断は妥当。逸脱として記録するが、是正は求めない。DCR 撤去は親が別 issue として分離する。

本 PR が明らかにした運用上の危険(記録)

古いブリッジは channel 通知を受け取り続ける。 /events は MCP 経路ではなく(worker/src/index.ts:260/mcp と別に分岐)、flip の影響を受けない。したがって flip 後の旧ブリッジは、通知が正常に届いているように見えながら、mark_processed を含む全ツール呼び出しがプロトコルエラーをツール出力テキストとして返す。pending キューが黙って溜まり続ける。

落ち方が静かであることは rag-mcp でも同じだったが、本 repo では「通知は生きている」という誤った健全性シグナルが伴う点が悪質。担当がこれを文書化した判断は適切。

検証範囲の限界(記録)

  • デプロイ済み Worker に対する検証はゼロ。 担当が pre-flip ブリッジ経由で本番を叩き、旧プロトコルで正常応答することを確認している = 本番はまだ flip していない。flip は merge 後の main デプロイで起きる
  • local-mcp/src/index.ts はテスト無し(開発用ブリッジ、typecheck のみ)
  • ブリッジ 2 本が動作中クライアントへ届くのは npm リリース + クライアント完全再起動の後(#247

次の段階

auto モードにつき human gate なし。self-review pass -> merge。リリース版種は minor(構造変更かつ user/system observable)。

@liplus-lin-lay
liplus-lin-lay merged commit 9244a28 into main Aug 6, 2026
3 checks passed
@liplus-lin-lay
liplus-lin-lay deleted the 249-spec-mcp-2026-07-28-migration-plan-single-lane-flip-no-dual-lane-fallback branch August 6, 2026 13:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

spec: MCP 2026-07-28 migration plan - single-lane flip, no dual-lane fallback

1 participant