Skip to content

Migrate the mcp/ worker to the v2 MCP SDK (stateless dual-era) - #3760

Open
chelojimenez wants to merge 5 commits into
mainfrom
claude/mcp-worker-v2-migration-81myc3
Open

Migrate the mcp/ worker to the v2 MCP SDK (stateless dual-era)#3760
chelojimenez wants to merge 5 commits into
mainfrom
claude/mcp-worker-v2-migration-81myc3

Conversation

@chelojimenez

@chelojimenez chelojimenez commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

The @mcpjam/mcp Cloudflare Worker was the last v1 holdout in the monorepo — it extended McpAgent from agents@0.5 and imported @modelcontextprotocol/sdk@1.26.0. McpAgent is deprecated and feature-frozen in agents@0.20; the endorsed replacement is a stateless per-request server built from a factory.

Serving is now createMcpHandler from @modelcontextprotocol/server@2.0.0 directly (not the agents/mcp/server wrapper — we already own routing, auth, landing and well-known in index.ts, and the raw handler skips the wrapper's localhost-only Origin allowlist that would 403 every browser client on a custom domain). One factory serves both eras: the modern 2026-07-28 revision, and 2025-era Streamable HTTP clients through the default legacy: 'stateless' posture.

What this buys:

  • The inspector's v2 client now negotiates the modern era against this server instead of silently falling back to legacy — with zero inspector-side changes.
  • The Durable Object is gone, which dissolves two open security-audit findings for free: no sessions to hijack, and no bearer tokens persisted at rest in DO storage. DO-churn cost largely evaporates too.
  • The ~228-line two-mode sessionToolRegistrar and the cf-mcp-message header-sniffing onConnect hack both disappear — they existed only because the DO built its tool list once, before it knew the client's capabilities.

Notable decisions

MCP Apps UI _meta is always advertised now. Statelessly a legacy tools/list arrives with no memory of the initialize capabilities, so per-request gating is impossible. Always-advertise is a SHOULD deviation from SEP-1865; the MUST (a meaningful content array) still holds, and _meta.ui is inert for non-UI hosts. This also fixes the resources/read widget path, which used to break whenever UI wasn't negotiated because the ui:// resource got disabled outright. No visibility: ["app"] is emitted (we don't today, and it would let hosts hide tools from the model).

Guest-token cache moved from DO instance state to an isolate-global Map keyed by client IP, mirroring the existing jwksCache in auth.ts. Best-effort by design — a cold isolate re-mints — because the inspector's mint route is the authoritative rate limiter (10/min/IP), and its own comment notes worker-side limiting is unreliable. Two anonymous users behind one NAT share a guest identity; acceptable for throwaway anon use. Added a 1000-entry bound with oldest-first eviction so the map can't grow without limit in a long-lived isolate.

index.ts now owns the /mcp CORS contract, including the OPTIONS preflight. The v2 handler is deliberately validation-free and emits no CORS headers, where McpAgent.serve() used to do both. Header set is pinned: allow-origin: *, allow-methods: GET, POST, DELETE, OPTIONS, allow-headers: authorization, content-type, mcp-session-id, mcp-protocol-version, expose-headers: mcp-session-id, WWW-Authenticate. The 401 challenge responses get the same headers.

Handler is built once at module scope, with the factory reading a module-scoped currentEnv set at the top of fetch — safe in a single-threaded isolate, and it avoids re-registering the 44-tool catalog plus inlined widget HTML on every request. handler.close() is never called, which is safe only under the no-SSE invariant (no subscriptions, no mid-call notifications, so responseMode: 'auto' never upgrades to a stream and no keepalive interval is ever created). That invariant is stated explicitly in a code comment.

Wrangler migrations are append-only, so staging keeps its applied v1 entry and adds v2 with deleted_classes — a lone v2 would be rejected against the deployed worker's last-applied tag. dev, preview and production drop migrations entirely: dev is local-only, preview workers are created fresh per PR so they never create the DO, and production has never been deployed.

@modelcontextprotocol/sdk stays a dependency, for the browser widget bundle only (src/ui, via ext-apps' peer, which imports it at runtime). The worker runtime no longer imports it, and the root check:mcp-v1-runtime-imports guard now covers mcp/src outside src/ui to keep it that way. The child overrides block is dropped (npm ignored it anyway — only root overrides apply, so it never actually pinned the workspace).

Changes

File Change
mcp/src/server.ts McpAgent subclass → createMcpHandler factory; module-scope guest cache + getBearerToken free function
mcp/src/index.ts /mcp branch delegates to the v2 handler with pass-through authInfo; owns CORS + preflight; DO export removed
mcp/src/tools/sessionToolRegistrar.ts Collapsed to a thin v2 registerTool/registerResource helper; all setUiEnabled/enable/disable mutation gone; 4-arg call shape kept
mcp/src/tools/platformTools.ts, showServers.ts agent: McpJamMcpServercontext: PlatformToolContext ({ getBearerToken, runtimeEnv })
mcp/src/shared/platform-widgets.ts Local RESOURCE_MIME_TYPE / RESOURCE_URI_META_KEY (byte-identical to ext-apps'), so the worker drops @modelcontextprotocol/ext-apps/server
mcp/wrangler.jsonc, worker-configuration.d.ts DO bindings removed across all 4 envs; staging carries the v1 + v2 history
mcp/README.md, .github/workflows/pr-mcp-preview.yml Comment/doc corrections — both described DO-backed architecture

mcp/src/auth.ts and its 15 tests are untouched, as are platformWidgets.test.ts and format.test.ts. platformTools.test.ts needed fixture retyping only — every assertion survives.

Verification

npm run build -w @mcpjam/sdk, npm run typecheck -w @mcpjam/mcp, npm run test:fast -w @mcpjam/mcp (36/36), check:mcp-v1-runtime-imports (verified it actually catches a planted v1 import in mcp/src), check:bundled-runtime-paths, and npm ci --dry-run for lockfile consistency — all green.

Driven against a local wrangler dev --env dev, with a stub inspector standing in for the guest JWKS / mint / platform API:

  • Modern era — the SDK's MCPClientManager (the inspector's own client path, era mode left at its auto default) negotiates 2026-07-28; tools/list returns all 44 tools; show_servers carries both the nested _meta.ui.resourceUri and the flat ui/resourceUri; all 7 ui:// resources list, and resources/read returns text/html;profile=mcp-app.
  • Legacy eras — clients pinned to 2025-06-18, 2025-03-26 and 2025-11-25 each negotiate their pin and get the same 44-tool catalog statelessly, with no session id; raw GET and DELETE /mcp return a clean 405 Method not allowed. rather than a crash.
  • Anonymous path — connect + tools/list mints zero guest tokens; the first tool execution mints exactly one; three consecutive calls reuse it (one mint, three API hits all bearing the same token). With the mint route unreachable, runPlatformOperation degrades to a tool error (No bearer token on the request.), not a crash.
  • Authed path — a verified guest bearer is passed through verbatim to the Platform API and mints nothing.
  • Routing/CORSOPTIONS /mcp → 204 with the full pinned header set; an invalid bearer → 401 with WWW-Authenticate and CORS; /mcp/ subpath still reaches the handler; every /.well-known/* route and the / landing page (which the CI smoke test greps) are unchanged.

Preview-worker deploy is the remaining gate — the deleted_classes migration wants a real Cloudflare run before staging picks it up on merge. Prod stays workflow_dispatch behind its green-staging gate.


Generated by Claude Code


Note

High Risk
This is a core auth/MCP serving rewrite plus a staging DO teardown migration; wrong deploy order or CORS/auth regressions would break all remote MCP clients until rolled back.

Overview
Replaces the mcp/ Cloudflare Worker’s McpAgent + Durable Object stack with createMcpHandler from @modelcontextprotocol/server@2.0.0, serving modern 2026-07-28 and legacy Streamable HTTP clients via legacy: "stateless" (no sessions, no tokens at rest).

index.ts now handles /mcp CORS (including OPTIONS) and passes verified JWTs as authInfo into handleMcpRequest; server.ts builds a fresh McpServer per request, moves guest-token mint/cache to an isolate-global IP-keyed Map (lazy on first tool call), and drops the DO class. Tool wiring uses PlatformToolContext instead of McpJamMcpServer.

sessionToolRegistrar is simplified: widget tools always register MCP Apps _meta and ui:// resources (no per-session UI gating). MCP Apps constants are inlined in platform-widgets.ts so the worker runtime drops @modelcontextprotocol/ext-apps/server.

wrangler.jsonc removes DO bindings from all envs; staging adds migration v2 with deleted_classes: ["McpJamMcpServer"]. agents is removed; root check:mcp-v1-runtime-imports now covers mcp/src (excluding UI). Docs/CI comments updated for a stateless preview worker.

Reviewed by Cursor Bugbot for commit c225799. Bugbot is set up for automated code reviews on this repo. Configure here.


Summary by cubic

Migrated the @mcpjam/mcp Worker to the v2 MCP server with a stateless per-request handler. Drops the Durable Object, serves both modern (2026-07-28) and 2025-era clients, and stores no sessions or tokens at rest. Also stabilizes a flaky inspector test by pinning the clock.

  • Refactors

    • Switched to createMcpHandler from @modelcontextprotocol/server@2.0.0 with legacy: "stateless"; removed agents and the Durable Object.
    • Built the handler per request; index.ts owns /mcp routing and CORS (OPTIONS preflight; GET/DELETE → 405; allow mcp-method, mcp-name, accept, last-event-id).
    • Always advertise MCP Apps _meta and register ui:// resources; tools use a PlatformToolContext; inlined MCP Apps wire constants.
    • Moved guest-token cache to an isolate-global IP-keyed Map (1000 cap) with lazy mint on first tool run; pass through verified bearers and set AuthInfo.expiresAt.
    • Added requirePlatformApiUrl fail-fast; kept @modelcontextprotocol/sdk only for browser widgets and extended the root guard to block v1 imports in worker code; removed DO bindings and added a staging v2 migration with deleted_classes.
  • Bug Fixes

    • Pinned timers in the HostConfigComparisonMatrix test to prevent date-based flakiness and unblock CI.

Written for commit 63db780. Summary will update on new commits.

Review in cubic

The `@mcpjam/mcp` Cloudflare Worker was the last v1 holdout in the
monorepo: it extended `McpAgent` from `agents@0.5` and imported
`@modelcontextprotocol/sdk@1.26.0`. `McpAgent` is deprecated and
feature-frozen in `agents@0.20`; the endorsed replacement is a stateless
per-request server built from a factory.

Serving is now `createMcpHandler` from `@modelcontextprotocol/server`
directly (not the `agents/mcp/server` wrapper — we already own routing,
auth, landing and well-known in `index.ts`, and the raw handler skips the
wrapper's localhost-only Origin allowlist that would 403 every browser
client on a custom domain). One factory serves both eras: the modern
2026-07-28 revision, and 2025-era Streamable HTTP clients through the
default `legacy: "stateless"` posture.

Consequences:

- The MCPJam inspector's v2 client now negotiates the modern era against
  this server instead of silently falling back to legacy, with zero
  inspector-side changes.
- The Durable Object is gone, which dissolves two open security-audit
  findings for free: no sessions to hijack, and no bearer tokens
  persisted at rest in DO storage.
- The ~228-line two-mode `sessionToolRegistrar` and the `cf-mcp-message`
  header-sniffing `onConnect` hack both disappear — they existed only
  because the DO built its tool list once, before it knew the client's
  capabilities.

Notable decisions:

- MCP Apps UI `_meta` is now always advertised. Statelessly a legacy
  `tools/list` arrives with no memory of the `initialize` capabilities,
  so per-request gating is impossible. Always-advertise is a SHOULD
  deviation from SEP-1865; the MUST (a meaningful `content` array) still
  holds and `_meta.ui` is inert for non-UI hosts. This also fixes the
  `resources/read` widget path, which used to break whenever UI was not
  negotiated because the `ui://` resource got disabled outright.
- The guest-token cache moves from DO instance state to an isolate-global
  Map keyed by client IP, mirroring the existing `jwksCache`. Best-effort
  by design — the inspector's mint route is the authoritative rate
  limiter.
- `index.ts` now owns the `/mcp` CORS contract, including the OPTIONS
  preflight: the v2 handler is deliberately validation-free and emits no
  CORS headers, where `McpAgent.serve()` used to do both.
- Wrangler migrations are append-only, so `staging` keeps its applied
  `v1` entry and adds `v2` with `deleted_classes`. `dev`, `preview` and
  `production` drop migrations entirely — no DO was ever created there.

`@modelcontextprotocol/sdk` stays a dependency for the browser widget
bundle only (`src/ui`, via ext-apps' peer); the worker runtime no longer
imports it, and the root `check:mcp-v1-runtime-imports` guard now covers
`mcp/src` outside `src/ui` to keep it that way.

Verified against a local `wrangler dev --env dev`: the SDK client
manager negotiates 2026-07-28 and lists all 44 tools; clients pinned to
2025-06-18 / 2025-03-26 / 2025-11-25 get the same catalog statelessly
with no session id and a spec-compliant 405 on GET/DELETE; the `ui://`
resources read back as `text/html;profile=mcp-app`; an anonymous client
mints exactly one guest token lazily on first tool execution (none at
connect or `tools/list`) and reuses it; a verified bearer is passed
through verbatim with no mint; and an unreachable mint route degrades to
a clean tool error rather than a crash.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ARLzF6yHBkZzZyc1gMTSv
@dosubot dosubot Bot added size:XL This PR changes 500-999 lines, ignoring generated files. enhancement New feature or request labels Aug 7, 2026
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@cursor

cursor Bot commented Aug 7, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_751e7ffe-bea0-4269-886e-c4793162750d)

@chelojimenez

chelojimenez commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

MCP worker preview

Preview URL: https://mcpjam-mcp-pr-3760.marcelo-1cb.workers.dev
MCP endpoint: https://mcpjam-mcp-pr-3760.marcelo-1cb.workers.dev/mcp
Built from 63db780. Each push overwrites the mcpjam-mcp-pr-3760 worker, so the URL is stable for the life of the PR.
The live mcpjam-mcp-staging worker only changes on merge to main. This preview worker is deleted when the PR is closed.

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Internal preview

Preview URL: https://mcp-inspector-pr-3760.up.railway.app
Deployed commit: 7293d52
PR head commit: 63db780
Backend target: staging fallback.
Health: ✅ Convex reachable
Access is employee-only in non-production environments.

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3423ee14-0b19-41fd-8c41-3912c227b44f

📥 Commits

Reviewing files that changed from the base of the PR and between e3e82e3 and c225799.

📒 Files selected for processing (1)
  • mcpjam-inspector/client/src/components/hosts/comparison/__tests__/host-config-comparison-matrix.test.tsx

Walkthrough

The MCP worker now uses the v2 server package and creates stateless MCP servers per request. Durable Object bindings, sessions, and migrations are removed from runtime configuration. The entrypoint adds MCP CORS handling and converts verified authentication data into AuthInfo. Guest-token caching supports anonymous requests. Tool registration now uses PlatformToolContext, standard JSON schemas, and always-on MCP Apps resources. Documentation, deployment workflows, and platform tool tests reflect the new architecture.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
mcp/wrangler.jsonc (1)

51-61: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Confirm per-PR preview workers before merging

The open preview workers share one mcpjam-mcp-pr-* name for the life of each PR. An earlier preview push can have already applied the v1 migration to that worker; the preview env does not declare any migrations or McpJamMcpServer class, so the next preview deploy will migrate that worker to empty while leaving the migration history behind. Delete stale per-PR workers or be prepared to fix the first failed preview deploy after this change.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mcp/wrangler.jsonc` around lines 51 - 61, Before merging, verify and clean up
existing per-PR preview workers using the mcpjam-mcp-pr-* naming pattern. Remove
stale workers that may retain prior v1 migration history, or ensure the first
preview deployment after this change handles the resulting migration failure; do
not treat the preview environment as declaring migrations or the McpJamMcpServer
class.
🧹 Nitpick comments (1)
mcp/src/index.ts (1)

63-80: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Carry the token exp through as AuthInfo.expiresAt.

MCP TypeScript SDK 2.0.0 defines AuthInfo.expiresAt as seconds since the Unix epoch, and bearer authentication rejects tokens without an expiry. Use verified.payload.exp instead of relying on downstream reads later.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mcp/src/index.ts` around lines 63 - 80, The toAuthInfo function currently
omits the verified token expiry, causing bearer authentication to reject the
resulting AuthInfo. Extract the exp claim from verified.payload and assign it to
AuthInfo.expiresAt as Unix-epoch seconds, preserving the existing token,
clientId, scopes, and claims mapping.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@mcp/README.md`:
- Around line 19-22: Update the Status paragraph in mcp/README.md to document
both authentication paths: authenticated requests use the caller’s AuthKit JWT,
while anonymous requests use the lazily minted guest token described later near
line 224. Replace the “every call” wording so it does not imply anonymous access
is impossible.

In `@mcp/src/server.ts`:
- Around line 144-147: Add a requirePlatformApiUrl helper in server.ts that
validates Env.PLATFORM_API_URL and throws a clear configuration error when
missing, then use its returned value when constructing toolContext.runtimeEnv
instead of the unsafe as Required<Env> cast.
- Around line 156-197: The module-scoped currentEnv binding in handleMcpRequest
is unsafe because mcpHandler.fetch() invokes the factory asynchronously after
request classification. Remove this shared state and pass each request’s Env
through request-level options or create a per-request handler closure, ensuring
the factory used by buildServer receives the Env belonging to that request.

---

Outside diff comments:
In `@mcp/wrangler.jsonc`:
- Around line 51-61: Before merging, verify and clean up existing per-PR preview
workers using the mcpjam-mcp-pr-* naming pattern. Remove stale workers that may
retain prior v1 migration history, or ensure the first preview deployment after
this change handles the resulting migration failure; do not treat the preview
environment as declaring migrations or the McpJamMcpServer class.

---

Nitpick comments:
In `@mcp/src/index.ts`:
- Around line 63-80: The toAuthInfo function currently omits the verified token
expiry, causing bearer authentication to reject the resulting AuthInfo. Extract
the exp claim from verified.payload and assign it to AuthInfo.expiresAt as
Unix-epoch seconds, preserving the existing token, clientId, scopes, and claims
mapping.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b5c5ce15-555f-4ff2-900e-f0c866910fd7

📥 Commits

Reviewing files that changed from the base of the PR and between e3bc6ff and 024834b.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (13)
  • .github/workflows/pr-mcp-preview.yml
  • mcp/README.md
  • mcp/package.json
  • mcp/src/index.ts
  • mcp/src/server.ts
  • mcp/src/shared/platform-widgets.ts
  • mcp/src/tools/platformTools.ts
  • mcp/src/tools/sessionToolRegistrar.ts
  • mcp/src/tools/showServers.ts
  • mcp/tests/platformTools.test.ts
  • mcp/worker-configuration.d.ts
  • mcp/wrangler.jsonc
  • package.json

Comment thread mcp/README.md Outdated
Comment thread mcp/src/server.ts
Comment thread mcp/src/server.ts

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

All reported issues were addressed across 14 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread mcp/src/index.ts Outdated
Comment thread mcp/src/server.ts Outdated
Comment thread mcp/README.md Outdated
Three valid findings from the automated reviewers on #3760.

`mcp-method` / `mcp-name` were missing from the `/mcp` CORS allow-list
(cubic, P1). This is a real browser-client breakage, not a nit: the v2
client derives both headers from the message body on *every* modern-era
request, so a browser preflight would have been rejected before the
worker saw the request. My earlier verification ran over Node, which
enforces no CORS, so it could not surface this. The allow-list is now
every non-safelisted header the official client sends, `last-event-id`
and `accept` included, and is verified by simulating the preflight with
`Access-Control-Request-Headers` and asserting each one comes back
granted.

The module-scoped `currentEnv` binding was unsound (cubic P2,
CodeRabbit). `createMcpHandler.fetch()` awaits request classification —
body read plus era routing — before it invokes the factory, so a second
request could reassign the binding in between and the first would build
its server against the wrong bindings. My comment claiming the factory
read it "synchronously on the same turn" was simply wrong. The handler
is now built per request, closing over that request's `env`. The reason
I had avoided this — per-request tool re-registration — does not apply:
the factory is invoked once per request under this SDK either way, so
all a per-request handler adds is the handler object and its event bus.
The isolate-global guest-token cache is unaffected and still shows one
mint across three calls.

`env as Required<Env>` asserted away a real doubt (CodeRabbit).
`PLATFORM_API_URL` is optional on `Env`, and if it were ever unset every
tool call would fetch `undefined/projects` — an opaque network error
rather than a legible configuration one. Replaced with a
`requirePlatformApiUrl` check, mirroring the `AUTHKIT_DOMAIN` /
`WORKOS_CLIENT_ID` fail-fast checks already in `index.ts`.

Also carried the token's `exp` through as `AuthInfo.expiresAt` so the
pass-through stays faithful, and corrected the README Status paragraph,
which described the AuthKit JWT as the only bearer and so read as though
anonymous access were unsupported.

Re-verified against a local `wrangler dev`: modern era still negotiates
2026-07-28 with all 44 tools and 7 `ui://` resources; the three pinned
2025 eras still serve statelessly with 405 on GET/DELETE; anonymous
still mints exactly one guest token lazily and reuses it; a verified
bearer still passes through verbatim with no mint; and the simulated
browser preflight now grants every client header.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ARLzF6yHBkZzZyc1gMTSv
@cursor

cursor Bot commented Aug 7, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_b68f06d8-ea22-472e-97e7-ea98fb62bcb2)

Copy link
Copy Markdown
Contributor Author

Pushed e3e82e3 addressing the review. Three findings were valid, one I'm skipping with a reason.

Fixed — mcp-method / mcp-name missing from the CORS allow-list (cubic P1). Verified real, and it's a genuine browser-client breakage rather than a nit: the v2 client's _applyBodyDerivedHeaders sets both on every modern-era request (derived from the message body — mcp-name carries the tool name, or the URI on resources/read). A browser preflight would have been rejected before the worker ever saw the request. My original verification ran over Node, which enforces no CORS, so it couldn't surface this. The allow-list is now every non-safelisted header the official client sends — accept and last-event-id included — and I added a check that simulates the preflight with Access-Control-Request-Headers and asserts each one comes back granted.

Fixed — the module-scoped currentEnv binding (cubic P2, CodeRabbit). Correct, and CodeRabbit's diagnosis is exactly right: createMcpHandler.fetch() awaits request classification before invoking the factory, so a second request can reassign the binding in between. My comment claiming the factory read it "synchronously on the same turn" was wrong. The handler is now built per request, closing over that request's env. Worth noting for anyone reading the original design note: the cost I was avoiding — per-request tool re-registration — doesn't actually apply. The factory is invoked once per request under this SDK either way, so a per-request handler only adds the handler object and its event bus. The isolate-global guest-token cache is untouched and still shows one mint across three calls.

Fixed — env as Required<Env> (CodeRabbit). Agreed, the cast asserted away a real doubt. Replaced with a requirePlatformApiUrl check that mirrors the AUTHKIT_DOMAIN / WORKOS_CLIENT_ID fail-fast checks already in index.ts. Also took the AuthInfo.expiresAt nitpick (carries the token's own exp) and the README Status wording, which did read as though anonymous access were unsupported.

Skipping — stale per-PR preview workers with v1 already applied (CodeRabbit, outside-diff). Real but narrow, and already mitigated. This PR's own upsert-mcp-preview job deployed cleanly to a fresh mcpjam-mcp-pr-3760 worker, which is the common case — a new preview worker never creates the DO, so there's nothing to unwind. The scenario that remains is an older open PR whose preview worker already has v1 applied getting rebased onto this change. Giving preview the same two-entry v1+v2 history as staging would cover that, but it trades a narrow problem for a broader one: every fresh preview would then have to create-then-delete the class in a single deploy, which is the top risk this migration was designed to avoid. The destroy-mcp-preview job already wrangler deletes preview workers on PR close, so the fix for an affected PR is to close/reopen it or delete the worker once.

Re-verified against a local wrangler dev after the changes: modern era negotiates 2026-07-28 with all 44 tools and 7 ui:// resources; the three pinned 2025 eras serve statelessly with 405 on GET/DELETE; anonymous mints exactly one guest token lazily and reuses it; a verified bearer passes through verbatim with no mint; simulated browser preflight grants every client header.


Generated by Claude Code

@cursor

cursor Bot commented Aug 7, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_377c9b73-500f-4c35-9bcb-49588169962e)

Copy link
Copy Markdown
Contributor Author

Build and Test went red on e3e82e3, but the failure was on the base branch, not this PR:

client/src/components/UserTestingTab.tsx(1,21): error TS6133: 'useRef' is declared but its value is never read.
client/src/components/UserTestingTab.tsx(1,29): error TS6133: 'useState' is declared but its value is never read.

That file isn't in this diff. Timeline (UTC):

That also explains why the same check passed on my first commit: it merged against ecc2b8d, where those imports were still used.

main has already recovered, so rather than wait I've merged it in (ac6670e, clean, no conflicts). npm run typecheck:client -w @mcpjam/inspector — the exact command that failed — now passes locally against the merged base, and the mcp worker's typecheck and 36 tests are still green.


Generated by Claude Code

Copy link
Copy Markdown
Contributor Author

Run Tests is red on ac6670e, but not because of this PR — main is red for everyone as of today, and this one will not self-recover.

1 failure out of 13,059 tests, in client/src/components/hosts/comparison/__tests__/host-config-comparison-matrix.test.tsx:

✕ HostConfigComparisonMatrix > shows per-host verified dates only in public caniuse mode
  Unable to find an element with the text: 2026-07-08
  (rendered instead: "Last checked over 30 days ago")

Root cause — a time bomb that detonated today. The test builds its fixture with Date.UTC(2026, 6, 8) (2026-07-08) and asserts the literal date string renders, but it never pins the clock. The component swaps the date for the staleness warning once the fixture is more than 30 days old. Today is 2026-08-07 — exactly 30 days on. It has passed every day since it was written and fails from today onward, on every branch.

The giveaway is the very next test in the same file: flags caniuse hosts checked over 30 days ago uses the same Date.UTC(2026, 6, 8) fixture but wraps it in vi.useFakeTimers() + vi.setSystemTime(...), and the file's afterEach already calls vi.useRealTimers(). So the failing test is the one that simply forgot to freeze the clock.

Evidence it isn't mine:

  • git diff origin/main HEAD -- mcpjam-inspector/client/src/components/hosts/ is empty — that code is byte-identical to main
  • this PR touches only mcp/, .github/workflows/pr-mcp-preview.yml, and the root manifests
  • it reproduces locally on the identical code

Fix (for whoever owns this surface — mirrors the pattern already used one test below):

   it("shows per-host verified dates only in public caniuse mode", () => {
     const subject = makeSubject(
       "preset:claude", "Claude", { hostStyle: "claude" }, "claude",
       Date.UTC(2026, 6, 8)
     );

+    vi.useFakeTimers();
+    vi.setSystemTime(new Date("2026-07-10T00:00:00.000Z"));
+
     const { rerender } = render(<HostConfigComparisonMatrix subjects={[subject]} />);

I've left it alone since it's outside this PR's scope, but flagging that it blocks main and every open PR until someone lands it. Happy to include the one-line fix here if that's preferred over a separate PR.


Generated by Claude Code

Unrelated to this PR's MCP worker migration, but it blocks main and every
open PR, so it rides along here.

`shows per-host verified dates only in public caniuse mode` builds its
fixture with `Date.UTC(2026, 6, 8)` and asserts the literal date string
renders, without pinning the clock. The matrix swaps that date for a
"Last checked over 30 days ago" warning once the fixture ages past 30
days, so the assertion passed every day since it was written and began
failing on 2026-08-07 — exactly 30 days on — on every branch at once.

The fix is the pattern the next test in the same file already uses:
`vi.useFakeTimers()` plus `vi.setSystemTime()`, pinned to the same
2026-07-10 it picks. The file's `afterEach` already restores real timers,
so no other cleanup is needed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ARLzF6yHBkZzZyc1gMTSv

Copy link
Copy Markdown
Contributor Author

Pushed the fix as c225799 rather than leaving main red — since this one can't self-recover, waiting it out would have blocked this PR and every other open one indefinitely.

It's the two-line change from my previous comment: vi.useFakeTimers() + vi.setSystemTime("2026-07-10") on the test that was missing them, matching the test directly below it in the same file. The file's afterEach already calls vi.useRealTimers(), so nothing else was needed. That file now passes 21/21 locally.

Flagging the scope explicitly: mcpjam-inspector/client/src/components/hosts/comparison/__tests__/host-config-comparison-matrix.test.tsx is unrelated to the MCP worker migration and is the one non-mcp/ source file in this diff. It's isolated in its own commit, so drop it with a revert of c225799 if you'd rather it land separately — but note main stays red until an equivalent fix merges somewhere.


Generated by Claude Code

@cursor

cursor Bot commented Aug 7, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_a2554668-d553-4f07-b595-8e44435e3ba4)

…migration-81myc3

# Conflicts:
#	mcpjam-inspector/client/src/components/hosts/comparison/__tests__/host-config-comparison-matrix.test.tsx
@cursor

cursor Bot commented Aug 7, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_913bd1be-2d98-4afe-a58a-ce62aa1735ed)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants