cloudflare / cloudflare/agents

addMcpServer({ autoRegister: false }) — let consumers suppress MCP tool auto-merge to enable compact / search+execute patterns

Open
#1,336 0 comments 0 reactions 1 assignee Claimed by @mattzcarey View on GitHub
enhancement mcp
Dominant language
TypeScript
Stars
5.6k
Forks
711
Avg merge
1d 20h
Merged PRs (30d)
53

Description

### Summary

`@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](https://blog.cloudflare.com/code-mode-mcp/) — 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:

1. Register our own `mcp_search(query)` + `mcp_execute(server, tool, args)` builtins backed by `this.mcp.callTool`.
2. Hide the raw per-tool schemas from the prompt.
3. 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

```ts
class MyAgent extends Think {
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)

1. **`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.
2. **Let `beforeTurn` replace `tools` instead of only appending** — e.g. `TurnConfig { tools, replaceTools?: boolean }` or a separate `setTools` field. More flexible, slightly larger API.
3. **Default `getAITools()` to `{ state: "ready" }` filter AND respect a per-connection `register` flag** — matches @threepointone's comment on #1073 ("I wonder if `getAITools` should 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` (#1278) — gates *calls*, not *prompt payload*.
- `MCPServerFilter` on `getAITools`/`listTools` (#1271) — lets us *read* a filtered catalog, doesn't affect what the framework *auto-merges*.
- `beforeTurn.tools` — additive, not replacement.
- #1126, #1121, #1268 — sibling codemode-side problems, not the Think/streamText auto-merge.

### 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 pulls `mcp.getAITools()` into the `streamText` call.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.