Skip to content

Commit f625075

Browse files
authored
Centralize account-ID resolution; upgrade agents/MCP SDK/zod/ai; migrate to registerTool (#384)
* feat: centralize account-id resolution; remove account-management tools 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. * chore: upgrade agents 0.13, MCP SDK 1.29, zod 4, ai 6 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. * refactor: migrate tool registration to registerTool (MCP SDK 1.29) 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. * refactor: remove @ts-ignore from tool/registerTool override forwarding 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. * chore: point packages/tools zod pin at stable 4.4.3 (syncpack) * chore: upgrade dev toolchain for agents 0.13 local dev 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. * chore: migrate to vitest 4 + @cloudflare/vitest-pool-workers 0.16 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. * chore(evals): route AI Gateway via BYOK unified provider; connect over /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. * refactor: remove dormant UserDetails durable object 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). * fix(types): make worker types current; drop --include-env=false 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()). * refactor: remove dead activeAccountId agent state 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.
1 parent 55087fe commit f625075

172 files changed

Lines changed: 245744 additions & 66859 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
'cloudflare-ai-gateway-mcp-server': minor
3+
'cloudflare-autorag-mcp-server': minor
4+
'cloudflare-browser-mcp-server': minor
5+
'cloudflare-casb-mcp-server': minor
6+
'cloudflare-radar-mcp-server': minor
7+
'graphql-mcp-server': minor
8+
'workers-observability': minor
9+
'workers-bindings': minor
10+
'workers-builds': minor
11+
'dex-analysis': minor
12+
'dns-analytics': minor
13+
'auditlogs': minor
14+
'logpush': minor
15+
---
16+
17+
Centralize Cloudflare account resolution and remove the account-management tools.
18+
19+
The `accounts_list` and `set_active_account` tools are removed. Account scoping is now
20+
resolved automatically by an `AccountManager` (via the new `server.accountTool()`
21+
registration), in priority order:
22+
23+
1. **Auth-pinned account** — an account-scoped API token's account, or an OAuth token with a
24+
single account, is used automatically (no `account_id` parameter is exposed).
25+
2. **`cf-account-id` request header** — for tokens that can access multiple accounts, set this
26+
header in your MCP client config to pick an account.
27+
3. **`account_id` tool argument** — for multi-account tokens, account-scoped tools expose an
28+
optional `account_id` parameter; when omitted (and no header is set) the tool returns an
29+
error listing the accounts you can use. Multi-account credentials also list their accounts
30+
in the server's `initialize` instructions.
31+
32+
All tool error responses now set `isError: true` so clients can distinguish failures.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
'cloudflare-ai-gateway-mcp-server': patch
3+
'cloudflare-autorag-mcp-server': patch
4+
'cloudflare-browser-mcp-server': patch
5+
'cloudflare-casb-mcp-server': patch
6+
'cloudflare-radar-mcp-server': patch
7+
'graphql-mcp-server': patch
8+
'workers-observability': patch
9+
'workers-bindings': patch
10+
'workers-builds': patch
11+
'containers-mcp': patch
12+
'dex-analysis': patch
13+
'dns-analytics': patch
14+
'docs-ai-search': patch
15+
'docs-autorag': patch
16+
'docs-vectorize': patch
17+
'auditlogs': patch
18+
'logpush': patch
19+
'demo-day': patch
20+
---
21+
22+
Upgrade core dependencies: `agents` 0.2.19 → 0.13.3, `@modelcontextprotocol/sdk` 1.20.2 →
23+
1.29.0, `zod` 3 → 4, and `ai` 4 → 6.
24+
25+
No user-facing tool or behavior changes. Internal adjustments for the new versions:
26+
- `zod` 4: `z.record(...)` now takes an explicit key schema; `z.string().ip()` replaced with
27+
`z.ipv4()`/`z.ipv6()` validation; dropped the removed `objectOutputType` helper.
28+
- `agents` 0.13: `McpAgent` env generic is constrained to `Cloudflare.Env`.
29+
- MCP SDK 1.29: tool `annotations` hints must be flat (`{ title, readOnlyHint, ... }`) — fixes a
30+
latent bug where nested hints were silently ignored.
31+
- `ai` 6: eval tooling updated (`LanguageModel`, `inputSchema`, `stopWhen`/`stepCountIs`, tool-call `input`).

.syncpackrc.cjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const config = {
2424
{
2525
label: 'pin vitest compatible with @cloudflare/vitest-pool-workers',
2626
dependencies: ['vitest', '@vitest/ui'],
27-
pinVersion: '3.0.9',
27+
pinVersion: '4.1.8',
2828
},
2929
{
3030
label: 'pin typescript for eslint',
@@ -48,7 +48,7 @@ const config = {
4848
{
4949
label: 'use zod v4 in packages/tools',
5050
dependencies: ['zod'],
51-
pinVersion: '4.0.0-beta.20250505T195954',
51+
pinVersion: '4.4.3',
5252
packages: ['@repo/tools'],
5353
},
5454
],

apps/ai-gateway/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ Currently available tools:
1818
| `get_log_request_body` | Retrieves the request body associated with a specific log in a gateway. |
1919
| `get_log_response_body` | Retrieves the response body associated with a specific log in a gateway. |
2020

21-
**Note:** To use these tools, ensure you have an active account set. If not, use `accounts_list` to list your accounts and `set_active_account` to set one as active.
21+
**Note:** These tools are account-scoped. Single-account credentials (and account-scoped API tokens) are detected automatically. If your credentials can access multiple accounts, pass `account_id` to the tool, or set a `cf-account-id` request header in your MCP client config.
2222

2323
This MCP server is still a work in progress, and we plan to add more tools in the future.
2424

apps/ai-gateway/package.json

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,27 @@
88
"deploy": "run-wrangler-deploy",
99
"dev": "wrangler dev",
1010
"start": "wrangler dev",
11-
"types": "wrangler types --include-env=false",
11+
"types": "wrangler types",
1212
"test": "vitest run"
1313
},
1414
"dependencies": {
1515
"@cloudflare/workers-oauth-provider": "0.4.0",
1616
"@hono/zod-validator": "0.4.3",
17-
"@modelcontextprotocol/sdk": "1.20.2",
17+
"@modelcontextprotocol/sdk": "1.29.0",
1818
"@repo/mcp-common": "workspace:*",
1919
"@repo/mcp-observability": "workspace:*",
20-
"agents": "0.2.19",
20+
"agents": "0.13.3",
2121
"cloudflare": "4.2.0",
2222
"hono": "4.7.6",
23-
"zod": "3.24.2"
23+
"zod": "4.4.3"
2424
},
2525
"devDependencies": {
26-
"@cloudflare/vitest-pool-workers": "0.8.14",
27-
"@types/node": "22.14.1",
26+
"@cloudflare/vitest-pool-workers": "0.16.11",
27+
"@types/node": "22.15.17",
2828
"prettier": "3.5.3",
2929
"typescript": "5.5.4",
30-
"vitest": "3.0.9",
31-
"wrangler": "4.10.0"
32-
}
30+
"vitest": "4.1.8",
31+
"wrangler": "4.96.0"
32+
},
33+
"type": "module"
3334
}

apps/ai-gateway/src/ai-gateway.app.ts

Lines changed: 5 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
import OAuthProvider from '@cloudflare/workers-oauth-provider'
22
import { McpAgent } from 'agents/mcp'
33

4+
import { AccountManager } from '@repo/mcp-common/src/account-manager'
45
import { handleApiTokenMode, isApiTokenRequest } from '@repo/mcp-common/src/api-token-mode'
56
import {
67
createAuthHandlers,
78
handleTokenExchangeCallback,
89
} from '@repo/mcp-common/src/cloudflare-oauth-handler'
9-
import { getUserDetails, UserDetails } from '@repo/mcp-common/src/durable-objects/user_details.do'
1010
import { getEnv } from '@repo/mcp-common/src/env'
1111
import { getProps } from '@repo/mcp-common/src/get-props'
1212
import { RequiredScopes } from '@repo/mcp-common/src/scopes'
1313
import { CloudflareMCPServer } from '@repo/mcp-common/src/server'
14-
import { registerAccountTools } from '@repo/mcp-common/src/tools/account.tools'
1514

1615
import { MetricsTracker } from '../../../packages/mcp-observability/src'
1716
import { registerAIGatewayTools } from './tools/ai-gateway.tools'
@@ -21,8 +20,6 @@ import type { Env } from './ai-gateway.context'
2120

2221
const env = getEnv<Env>()
2322

24-
export { UserDetails }
25-
2623
const metrics = new MetricsTracker(env.MCP_METRICS, {
2724
name: env.MCP_SERVER_NAME,
2825
version: env.MCP_SERVER_VERSION,
@@ -31,7 +28,7 @@ const metrics = new MetricsTracker(env.MCP_METRICS, {
3128
// Context from the auth process, encrypted & stored in the auth token
3229
// and provided to the DurableMCP as this.props
3330
type Props = AuthProps
34-
type State = { activeAccountId: string | null }
31+
type State = Record<string, never>
3532

3633
export class AIGatewayMCP extends McpAgent<Env, State, Props> {
3734
_server: CloudflareMCPServer | undefined
@@ -54,6 +51,7 @@ export class AIGatewayMCP extends McpAgent<Env, State, Props> {
5451
// TODO: Probably we'll want to track account tokens usage through an account identifier at some point
5552
const props = getProps(this)
5653
const userId = props.type === 'user_token' ? props.user.id : undefined
54+
const accountManager = new AccountManager(props)
5755

5856
this.server = new CloudflareMCPServer({
5957
userId,
@@ -62,44 +60,13 @@ export class AIGatewayMCP extends McpAgent<Env, State, Props> {
6260
name: this.env.MCP_SERVER_NAME,
6361
version: this.env.MCP_SERVER_VERSION,
6462
},
63+
accountManager,
64+
options: { instructions: accountManager.instructionsSuffix() },
6565
})
6666

67-
registerAccountTools(this)
68-
6967
// Register Cloudflare Log Push tools
7068
registerAIGatewayTools(this)
7169
}
72-
73-
async getActiveAccountId() {
74-
try {
75-
const props = getProps(this)
76-
// account tokens are scoped to one account
77-
if (props.type === 'account_token') {
78-
return props.account.id
79-
}
80-
// Get UserDetails Durable Object based off the userId and retrieve the activeAccountId from it
81-
// we do this so we can persist activeAccountId across sessions
82-
const userDetails = getUserDetails(env, props.user.id)
83-
return await userDetails.getActiveAccountId()
84-
} catch (e) {
85-
this.server.recordError(e)
86-
return null
87-
}
88-
}
89-
90-
async setActiveAccountId(accountId: string) {
91-
try {
92-
const props = getProps(this)
93-
// account tokens are scoped to one account
94-
if (props.type === 'account_token') {
95-
return
96-
}
97-
const userDetails = getUserDetails(env, props.user.id)
98-
await userDetails.setActiveAccountId(accountId)
99-
} catch (e) {
100-
this.server.recordError(e)
101-
}
102-
}
10370
}
10471

10572
const AIGatewayScopes = {

apps/ai-gateway/src/ai-gateway.context.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type { UserDetails } from '@repo/mcp-common/src/durable-objects/user_details.do'
21
import type { AIGatewayMCP } from './ai-gateway.app'
32

43
export interface Env {
@@ -10,7 +9,6 @@ export interface Env {
109
CLOUDFLARE_CLIENT_ID: string
1110
CLOUDFLARE_CLIENT_SECRET: string
1211
MCP_OBJECT: DurableObjectNamespace<AIGatewayMCP>
13-
USER_DETAILS: DurableObjectNamespace<UserDetails>
1412
MCP_METRICS: AnalyticsEngineDataset
1513
DEV_DISABLE_OAUTH: string
1614
DEV_CLOUDFLARE_API_TOKEN: string

0 commit comments

Comments
 (0)