Serve MCP protocol revision 2026-07-28 only, single lane [worker, bridges, deps, docs, tests] - #253
Conversation
…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>
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
github-webhook-mcp | a979741 | Aug 06 2026, 01:44 PM |
liplus-lin-lay
left a comment
There was a problem hiding this comment.
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- 全リクエストの
_metaがprotocolVersion: "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)。
Closes #249
何をしたか
Worker と 2 つのブリッジのクライアント面を、MCP プロトコル版 2026-07-28(ステートレスコア)へ同一 PR で切り替えた。互換レーンは実装していない(決定 1)。
Worker (
worker/src/)WebhookMcpAgent.serve("/mcp")をagents/mcp/serverのcreateMcpHandlerへ置換。legacy: "reject"を指定しており、単レーンという決定がそのままコードの literal になっている。2025 系の request(initializeを含む)は、endpoint が提供する唯一の版を名指しした unsupported-protocol-version エラーで返る。worker/src/agent.ts(McpAgentを継承した DO クラス)を廃し、リクエスト毎に MCP サーバーを組み立てる factorycreateWebhookMcpServer(env)をworker/src/mcp.tsに置いた。server.tool()はserver.registerTool()へ移行(schema はz.object()で包んだ同一定義)。index.tsが従来どおりctx.propsを書き換え、handler がそれをリクエスト毎の auth context として再公開し、tool 側はgetMcpAuthContext()で読む。this.propsに相当するインスタンス状態は無くなった。マルチアカウント fan-out の意味は不変 —accessible_account_idsからstore-{id}を解決して並列クエリ、という経路はそのまま。envごとに WeakMap で memo 化。factory が bindings を要るのにenvはfetchの中にしか無いため、module scope では作れない。WebhookMcpAgentはworker/src/retired-do.tsへ分離した。過去 migration(v2)が名指しするクラスはスクリプト内に存在せねばならないという Cloudflare の制約のため、MCP_OBJECTbinding もろとも残す。撤去にはdeleted_classesmigration が要るので別件。実データを持つWebhookStore/TenantRegistryは無変更。mcp.tsからcloudflare:workersimport を消し、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 で持つ。別パッケージなので共有はしない。authProviderに繋ぎ直しただけ。依存
agents0.19.0 → 0.20.1、@modelcontextprotocol/serverを exact2.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 まで持ち越さない)。@modelcontextprotocol/sdk依存宣言と override を削除した。Worker はもう SDK v1 を一切 import しない。agents の必須 peer として npm が入れるが(1.30.0)、こちら側にレンジが無いので agents の exact pin と食い違いようがない。@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.1→npm ci後のnpm lsでもツリー全体が4.20260404.1。partyserverは依然agents/node_modulesに nest されたまま(0.5.10)で、pin が load-bearing である状態は変わっていない。検証
ローカルで通したもの(CI 相当)
node --check(index.js / remote-client.js)/ mcp-servernpm test(23) / workertsc --noEmit/wrangler deploy --dry-run/ workernpm test(node 65 + workers pool 42)/ local-mcptsc --noEmit。新規:
worker/test/mcp-stateless-contract.test.tsブリッジの実クライアント(
mcp-server/server/remote-client.js)と Worker の実ハンドラ配線を in-process で接続し、次を検証する。mcp-session-idヘッダは一切無いinitializeを送らない(pin された modern endpoint への connect はserver/discoverで始まる)_metaにio.modelcontextprotocol/protocolVersion: "2026-07-28"と clientCapabilities を載せている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-serverのnpm ciを CI に追加した。検証できていないもの
local-mcp/src/index.ts(開発用ブリッジ)は contract test の対象外。利用者が動かすのは公開ブリッジのほうであり、そちらを押さえている。リリース
minor。
rules/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