You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@cloudflare/think's beforeTurn + TurnConfig.activeTools (#1278) and MCPClientManager.getAITools({ serverId }) (#1271) together almost enable the consolidated MCP pattern popularized by Cloudflare's own Code Mode: give agents an entire API in 1,000 tokens — replace a full tool catalog with a search + execute pair and save 99%+ of tool-definition tokens.
One gap remains: activeTools limits what the model can call, but the auto-merge of MCP tools into streamText's tool list happens before beforeTurn runs, so the raw schemas still ship in every turn's prompt. We pay the full context cost of N MCP tool definitions and then tell the model it's only allowed to call two of them. No net token savings.
Use case
We build a multi-tenant platform where customers attach MCP servers to their agents. A single enterprise MCP (Atlassian, GitHub Enterprise, Salesforce) brings 30–80 tools; attaching two or three pushes the callable surface past what most models reason over cleanly. We want to:
Register our own mcp_search(query) + mcp_execute(server, tool, args) builtins backed by this.mcp.callTool.
Hide the raw per-tool schemas from the prompt.
Let the model discover MCP tools by keyword only when needed.
Steps 1 and 3 work with the current SDK. Step 2 needs SDK help.
Minimal repro
classMyAgentextendsThink<Env>{getTools(){return{mcp_search: tool({/* this.mcp.listTools() keyword ranked */}),mcp_execute: tool({/* this.mcp.callTool() */}),};}beforeTurn(): TurnConfig{// activeTools gates calls, but SDK-auto-merged MCP schemas// still appear in the streamText tool_definitions payload.return{activeTools: ["mcp_search","mcp_execute"]};}}
Expected: prompt carries 2 tool definitions. Actual: 2 + N (where N = sum of attached MCP tool counts). For our workload, N ≈ 80–200, roughly 20k–50k tokens of schema per turn.
Three shapes that would close this (preference order)
addMcpServer(name, url, { autoRegister: false }) — opt-in suppression of the auto-merge. Zero default-behavior change. Consumers who want raw tools call this.mcp.getAITools() explicitly and merge themselves. Smallest API surface.
Let beforeTurn replace tools instead of only appending — e.g. TurnConfig { tools, replaceTools?: boolean } or a separate setTools field. More flexible, slightly larger API.
Default getAITools() to { state: "ready" } filter AND respect a per-connection register flag — matches @threepointone's comment on Add optional server filter to getAITools() #1073 ("I wonder if getAITools should only return tools for active servers by default"). Most automatic, hardest to reason about from outside.
Happy to send a PR against option (1). Smallest change: optional third arg on addMcpServer, store the flag on the connection, check it where the Think harness pulls mcp.getAITools() into the streamText call.
Summary
@cloudflare/think'sbeforeTurn+TurnConfig.activeTools(#1278) andMCPClientManager.getAITools({ serverId })(#1271) together almost enable the consolidated MCP pattern popularized by Cloudflare's own Code Mode: give agents an entire API in 1,000 tokens — replace a full tool catalog with asearch+executepair and save 99%+ of tool-definition tokens.One gap remains:
activeToolslimits what the model can call, but the auto-merge of MCP tools intostreamText's tool list happens beforebeforeTurnruns, so the raw schemas still ship in every turn's prompt. We pay the full context cost of N MCP tool definitions and then tell the model it's only allowed to call two of them. No net token savings.Use case
We build a multi-tenant platform where customers attach MCP servers to their agents. A single enterprise MCP (Atlassian, GitHub Enterprise, Salesforce) brings 30–80 tools; attaching two or three pushes the callable surface past what most models reason over cleanly. We want to:
mcp_search(query)+mcp_execute(server, tool, args)builtins backed bythis.mcp.callTool.Steps 1 and 3 work with the current SDK. Step 2 needs SDK help.
Minimal repro
Expected: prompt carries 2 tool definitions. Actual:
2 + N(whereN= sum of attached MCP tool counts). For our workload,N ≈ 80–200, roughly 20k–50k tokens of schema per turn.Three shapes that would close this (preference order)
addMcpServer(name, url, { autoRegister: false })— opt-in suppression of the auto-merge. Zero default-behavior change. Consumers who want raw tools callthis.mcp.getAITools()explicitly and merge themselves. Smallest API surface.beforeTurnreplacetoolsinstead of only appending — e.g.TurnConfig { tools, replaceTools?: boolean }or a separatesetToolsfield. More flexible, slightly larger API.getAITools()to{ state: "ready" }filter AND respect a per-connectionregisterflag — matches @threepointone's comment on Add optional server filter to getAITools() #1073 ("I wonder ifgetAIToolsshould only return tools for active servers by default"). Most automatic, hardest to reason about from outside.Why existing APIs don't solve this
TurnConfig.activeTools(feat(think): lifecycle hooks, dynamic context, extension manifest #1278) — gates calls, not prompt payload.MCPServerFilterongetAITools/listTools(feat: add MCPServerFilter to scope getAITools and list methods #1271) — lets us read a filtered catalog, doesn't affect what the framework auto-merges.beforeTurn.tools— additive, not replacement.Offer
Happy to send a PR against option (1). Smallest change: optional third arg on
addMcpServer, store the flag on the connection, check it where the Think harness pullsmcp.getAITools()into thestreamTextcall.