Centralize account-ID resolution; upgrade agents/MCP SDK/zod/ai; migrate to registerTool#384
Merged
Merged
Conversation
Replace the tool-based account selection (accounts_list + set_active_account,
per-app getActiveAccountId/setActiveAccountId, UserDetails activeAccountId) with a
centralized AccountManager + a CloudflareMCPServer.accountTool() wrapper.
Resolution precedence (per call):
1. Auth-pinned account — account-scoped token's account, or a single-account OAuth
token (no account_id param exposed in this case).
2. cf-account-id request header (user-configured) — multi-account tokens only.
3. account_id tool argument — auto-appended only for multi-account tokens.
Multi-account credentials get their account list injected into the server initialize
instructions for discovery. All tool error responses now set isError: true.
- New: packages/mcp-common/src/account-manager.ts (3-layer resolver) + account-tool.ts
(buildAccountTool wrapper core, kept ajv-free for testing) + specs (19 tests).
- CloudflareMCPServer gains accountTool(); collapse dead CloudflareMcpAgentNoAccount layer.
- Migrate all account-scoped shared + per-app tools to accountTool.
- Remove account.tools.ts, account.api.ts, constants.ts, accounts.eval.ts; update READMEs
and implementation-guides/tools.md.
Supersedes #316.
Bumps across all packages: agents 0.2.19→0.13.3, @modelcontextprotocol/sdk
1.20.2→1.29.0, zod 3.24.2→4.4.3, ai 4.3.10→6.0.193 (+ @ai-sdk/* v3 providers,
ai-gateway-provider 3.1.3).
Migration fixes:
- zod 4: z.record(key, value) explicit key; z.string().ip() -> z.ipv4()/z.ipv6();
drop removed objectOutputType (use z.infer<z.ZodObject<Shape>>).
- agents 0.13: McpAgent env generic constrained to Cloudflare.Env; api-handler /
api-token-mode infer the env generic (no `any`).
- MCP SDK 1.29: flatten tool annotations to { title, readOnlyHint, ... } (fixes a
latent bug where nested annotations were ignored).
- ai 6: eval tooling (LanguageModel, inputSchema, stopWhen/stepCountIs, tool-call input);
MCPClientManager now takes a storage option.
Typecheck + lint green across all 19 packages. Changeset added for all servers.
The legacy `.tool()` API is deprecated in favour of `.registerTool(name, config, cb)`.
- CloudflareMCPServer now wraps BOTH `tool()` and `registerTool()` for metrics via a shared
`trackCb` helper, so every registration path is tracked identically.
- accountTool() registers via `registerTool({ description, inputSchema, annotations }, cb)`;
buildAccountTool's callback is typed as the SDK `ToolCallback<ZodRawShape>`, so the call is
fully type-checked (removed the `@ts-ignore`).
- Converted all 78 remaining `.tool(...)` call sites across apps + shared tools to
`.registerTool(...)` with the config-object form (61 in radar, 17 across 8 other files).
Typecheck + lint green across all 19 packages; account-manager + accountTool specs pass.
The two overrides spread `unknown[]` into the bound original methods (`_tool(name, ...rest)`), which TS rejects with TS2556 (spread into an overloaded signature without a rest param) — the forwarding is correct at runtime. Type the bound originals as variadic `(...args: unknown[]) => ReturnType<McpServer['tool' | 'registerTool']>` so the spread is legal. No suppression, no `any`. Typecheck + lint green.
agents 0.13 needs a newer local workerd than the pinned wrangler provided (`cloudflare:workers` `exports`), so bump the dev/test toolchain: - wrangler 4.10.0 → 4.96.0 (recent workerd; fixes `wrangler dev`) - esbuild override 0.25.1 → 0.27.3 (required by wrangler 4.96) - @cloudflare/vitest-pool-workers 0.8.14 → 0.12.0 (newest on esbuild 0.27 that keeps the vitest-3 `/config` API; avoids the vitest-4 config-rewrite migration) - vitest 3.0.9 → 3.2.6, @vitest/ui → 3.2.6 All dev dependencies — no change to deployed runtime. Verified `wrangler dev` boots the workers-observability server and tools resolve end-to-end; tests 116/116, types, lint, deps, format all green.
Completes the test-toolchain upgrade (vitest 3.2 → 4.1.8, pool-workers 0.12 → 0.16.11),
using pool-workers' official `vitest-v3-to-v4` codemod + recipes.
- Ran the codemod across all vitest configs: `defineWorkersConfig`/`defineWorkersProject`
→ `defineConfig` from 'vitest/config' with the `cloudflareTest({...})` Vite plugin (the
`poolOptions.workers` block moves into the plugin). Removed dropped options
(`singleWorker`, `isolatedStorage`).
- Migrated the workspace: `vitest.workspace.ts` → root `vitest.config.ts` with `test.projects`.
- Added `"type": "module"` to the worker packages (pool-workers 0.16 main entry is ESM-only,
so `.ts` configs must load as ESM).
- pool-workers 0.16 removed `fetchMock` from `cloudflare:test`: migrated the 4 specs that
mocked api.cloudflare.com to MSW (per the official request-mocking recipe) — shared
`src/test/msw-server.ts` + `msw-setup.ts`, `server.use(http.<m>(url, () => HttpResponse...))`.
- tsconfig `types`: `@cloudflare/vitest-pool-workers` → `/types` (cloudflare:test module moved).
- eval-tools: import `env` from `cloudflare:workers` (cloudflare:test `env` is deprecated);
typed eval vars via a local `EvalEnv`; added missing credentials guard in getAnthropicModel.
- Deduped `@types/node` to 22.15.17 (override + declarations) to collapse a duplicate `vite`
that made plugin types "unrelated".
All gates green: check:types + check:lint (37/37), tests (116), check:deps, check:format.
…r /mcp - test-models: use ai-gateway-provider createUnified() with the gateway's stored (BYOK) keys instead of an empty per-provider apiKey, fixing the 'Incorrect API key' failures. Judge: gpt-5.4-nano; subjects: gpt-5.4-mini, gpt-4.1, and workers-ai/@cf/moonshotai/kimi-k2.6. - drop now-unused providers (@ai-sdk/openai, @ai-sdk/anthropic, @ai-sdk/google, workers-ai-provider) from eval-tools. - eval clients connect to /mcp (token-mode servers only serve /mcp, not /sse). - bump ai 6.0.193 -> 6.0.194.
The UserDetails DO existed only to persist activeAccountId across sessions (added in #85) for the old set_active_account / accounts_list flow. Account resolution is now derived per-request (auth-pinned -> cf-account-id header -> account_id arg), so nothing reads or writes it — getUserDetails has no callers. - delete user_details.do.ts and remove the USER_DETAILS binding, export, and Env type from all 13 account-scoped apps (dev + staging + production). - add a deleted_classes DO migration (tag v2) to the two owning workers (workers-observability, workers-builds) so the namespace is torn down. - strip the dead commented-out tool blocks in r2_bucket.tools.ts and hyperdrive.tools.ts (they referenced the removed getActiveAccountId API). - regenerate worker-configuration.d.ts across apps (drops USER_DETAILS).
CI typecheck broke after regenerating worker-configuration.d.ts to the wrangler 4.96 / workerd 1.20260529 runtime types. Fixes: - remove '--include-env=false' from every app's 'types' script (and the run-wrangler-types helper). Under the new wrangler that flag also strips the global 'interface Env' the app/test code relies on (getEnv<Env>, McpAgent<Env>, vitest TestEnv extends Env); without it the global Env is emitted again. - regenerate packages/mcp-common/worker-configuration.d.ts (was stuck at workerd 1.20250409, predating embeddinggemma in AiModels) so the docs-vectorize embeddings tool typechecks; drop the now-unnecessary @ts-expect-error directives. - docs-* and sandbox-container: drop 'public' from the 'ctx: DurableObjectState' constructor params — the new DurableObjectState<unknown> default conflicts with the McpAgent/DurableObject base's DurableObjectState<{}>; inheriting it avoids the redeclaration (matches every other app). - api-token-mode: ExecutionContext.props is now readonly; assign auth props via a typed mutable view (runtime sets props before serve()).
Vestigial leftover from the account-id centralization: 'activeAccountId' was still declared in each agent's State (and one initialState) but never read or written — account resolution is now derived per-request via AccountManager. - drop the field from all 13 account-scoped apps' State (and workers-bindings' initialState); workers-builds keeps its still-used activeBuildUUID/activeWorkerId. - mcp-common CloudflareMCPAgentState is now an empty base (Record<string, unknown>) so servers with their own state (workers-builds) stay assignable to CloudflareMcpAgent.
mattzcarey
added a commit
that referenced
this pull request
Jun 2, 2026
#384 removed the UserDetails Durable Object from every server but only added the delete-class migration to the two servers that own the class (workers-observability, workers-builds). graphql's deployed worker still depends on UserDetails, so `wrangler deploy` rejected the new version with code 10064 and aborted the staging and production deploys; it also blocked workers-observability from applying its own deleted_classes migration (code 10061). Adding the delete-class migration lets graphql deploy and releases the binding. Validated on staging: graphql + observability deploy cleanly and the staging UserDetails namespace is removed.
Merged
This was referenced Jun 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A sequence of related changes on this branch (each its own commit):
UserDetailsDurable Object (+ DO deletion migration).agents0.2.19 → 0.13.3,@modelcontextprotocol/sdk1.20.2 → 1.29.0,zod3 → 4,ai4 → 6..tool()API to.registerTool().@cloudflare/vitest-pool-workers0.16.--include-env=false).Supersedes #316 (per-tool
account_id) — this is a superset, so #316 can be closed.1. Account-ID resolution
Removes
accounts_listandset_active_account(and the per-appgetActiveAccountId/setActiveAccountId). Account scoping is now resolved by a singleAccountManager(packages/mcp-common/src/account-manager.ts) via a newserver.accountTool()wrapper, in priority order:account_idparameter is exposed in this case).cf-account-idrequest header — set by the user in their MCP client config (multi-account tokens).account_idtool argument — auto-appended to account-scoped tools only for multi-account tokens; when omitted (and no header) the tool returns an error listing the available accounts. Multi-account tokens also get their account list injected into the serverinitializeinstructions for discovery.All tool error responses now set
isError: true.2. Remove the
UserDetailsDurable ObjectUserDetailsexisted only to persistactiveAccountIdacross sessions for the oldset_active_accountflow (added in #85). Account resolution is now derived per-request, so nothing reads or writes it. This PR:user_details.do.tsand removes theUSER_DETAILSbinding,export, andEnvtype from all 13 account-scoped apps (dev + staging + production).deleted_classesDO migration (tagv2) to the two owning workers —workers-observabilityandworkers-builds— so the namespace is torn down.UserDetailsviascript_namein staging/prod); their bindings are removed.r2_bucket.tools.ts/hyperdrive.tools.tsthat referenced the removedgetActiveAccountId.3. Dependency upgrade
z.record(key, value)explicit key;z.string().ip()→z.ipv4()/z.ipv6(); dropped removedobjectOutputType(→z.infer<z.ZodObject<Shape>>).McpAgentenv generic constrained toCloudflare.Env;createApiHandler/handleApiTokenModeinfer the env generic (noany);MCPClientManagertakes astorageoption (eval clients).annotationsmust be flat ({ title, readOnlyHint, ... }) — fixes a latent bug where nestedannotations: {...}hints were silently ignored.LanguageModel,inputSchema,stopWhen/stepCountIs, tool-callinput).4. registerTool migration
CloudflareMCPServerwraps bothtool()andregisterTool()for metrics via one shared helper.accountTool()and all remaining.tool()call sites use.registerTool(name, { description, inputSchema, annotations }, cb). No@ts-ignore/anyintroduced.5. Vitest 4 + pool-workers 0.16
defineConfig+ thecloudflareTest({...})Vite plugin (replacesdefineWorkersConfig/poolOptions.workers); rootvitest.config.tswithtest.projects(replacesvitest.workspace.ts)."type": "module"(pool-workers 0.16 main entry is ESM-only).fetchMockwas removed fromcloudflare:test→ migrated those specs to MSW (http/HttpResponse/setupServer).6. Evals
ai-gateway-provider'screateUnified()— no per-provider key in the repo.openai/gpt-5.4-mini,openai/gpt-4.1,workers-ai/@cf/moonshotai/kimi-k2.6; judge:openai/gpt-5.4-nano./mcp(token-mode servers don't serve/sse).7. Worker types currency
--include-env=falsefrom every app'stypesscript (andrun-wrangler-types). Under wrangler 4.96 that flag also strips the globalinterface Envthe app/test code relies on (getEnv<Env>,McpAgent<Env>, vitestTestEnv extends Env).worker-configuration.d.tsacross the repo to the wrangler 4.96 / workerd 1.20260529 runtime types (this also brought@cf/google/embeddinggemma-300mintoAiModels, so the docs-vectorize embeddings tool typechecks without@ts-expect-error).publicfrom thectx: DurableObjectStateconstructor params in docs-* / sandbox-container (newDurableObjectState<unknown>default conflicted with theMcpAgent/DurableObjectbase's<{}>); assign auth props through a typed mutable view sinceExecutionContext.propsis nowreadonly.Breaking / behavior changes
accounts_listandset_active_accountare removed.readOnlyHint/destructiveHint) now actually apply (were silently dropped before).account_idor set acf-account-idheader.Deployment
This must be deployed manually, in order, because the
UserDetailsdeletion interacts with cross-script bindings:workers-buildsfirst — theirUserDetailsbindings are now removed.workers-observabilitylast — it owns the sharedUserDetailsDO that the cross-binders referenced viascript_name, and itsdeleted_classesmigration can only run once nothing else binds the class.Deploying
workers-observabilitybefore the cross-binders are updated will fail the migration (or break a lagging cross-binder's deploy). ExistingUserDetailsinstances and their storedactiveAccountIddata are dropped by the migration; nothing reads that data anymore.Verification
check:types,check:lint,check:format,check:deps(syncpack) all green across the workspace; tests pass.eval:ciruns end-to-end against a local server through the AI Gateway (see §6).react/aipeer-dep warnings from agents 0.13 are harmless (this repo doesn't use react).Changesets
centralize-account-id-resolution(minor, 13 account servers).upgrade-agents-mcp-sdk-zod-ai(patch, all 18 apps).