cloudflare / cloudflare/agents
dicussion: ship shared MCP tool proxy support for subagents
- Dominant language
- TypeScript
- Stars
- 5.6k
- Forks
- 711
- Avg merge
- 1d 20h
- Merged PRs (30d)
- 53
Description
## Summary
The agent starter now demonstrates a common multi-agent MCP pattern: configure MCP servers once on a root/parent agent, then let child chat agents use those MCP tools during AI SDK generation.
Today the starter implements this locally with a small `SharedMCPClient` helper. It works, but the pattern feels general enough that it probably belongs in the Agents SDK rather than being copied into apps and starters.
## Current starter shape
The starter has this hierarchy:
```txt
Inbox
Chat
Researcher
Planner
```
`Inbox` owns app-level state and shared MCP configuration. Each `Chat` is an `AIChatAgent` subagent with independent message history.
The current local bridge does roughly this:
1. `Inbox` exposes methods to wait for MCP connections, list MCP tool descriptors, and call MCP tools.
2. `Chat` resolves its parent with `this.parentAgent(Inbox)`.
3. `SharedMCPClient` asks the parent for tool descriptors.
4. It converts each MCP descriptor into an AI SDK `tool(...)` entry using `z.fromJSONSchema(...)`.
5. Each generated tool calls back into the parent `Inbox` to execute the MCP tool.
This lets MCP servers be configured once in the root inbox while every chat can use the same external tools.
## Why this may belong in the SDK
`MCPClientManager` already has `getAITools()`, which converts locally owned MCP connections into an AI SDK `ToolSet`.
The starter's `SharedMCPClient` is mostly the same operation, but across an agent boundary:
- local `this.mcp.getAITools()` means "tools from MCP servers owned by this agent"
- shared/remote MCP tools means "tools from MCP servers owned by another agent, usually my parent"
Multi-chat apps will likely want this often:
- users configure MCP once at the app/account/workspace level
- each chat/thread/subagent can use those tools
- OAuth/callback/server state stays centralized
- child agents do not each create duplicate MCP connections
Leaving this in app code means users copy/paste fragile details:
- MCP descriptor shape
- AI SDK `ToolSet` conversion
- JSON Schema to Zod conversion
- tool key namespacing
- MCP error normalization
- connection waiting behavior
- parent/child RPC plumbing
If either MCP descriptors or AI SDK tool definitions evolve, copied starter code can drift.
## Possible API direction
A small helper may be enough. For example:
```ts
class Chat extends AIChatAgent {
async onChatMessage(...) {
const inbox = await this.parentAgent(Inbox);
const mcpTools = await getRemoteMcpAITools(inbox, {
waitForConnections: { timeout: 5000 }
});
return streamText({
// ...
tools: {
...mcpTools,
// chat-local tools
}
});
}
}
```
Or, if we want this to hang off the MCP surface:
```ts
const inbox = await this.parentAgent(Inbox);
const mcpTools = await this.mcp.getAIToolsFrom(inbox, {
timeout: 5000
});
```
The helper would build AI SDK tools whose `execute` functions call MCP tools through the remote agent/stub.
## Possible contract
The SDK could expose a tiny remote MCP tool provider contract, implemented by `Agent` or `MCPClientManager`, along these lines:
```ts
interface RemoteMcpToolProvider {
listMcpToolDescriptors(options?: { timeout?: number }): Promise;
callMcpTool(serverId: string, name: string, args: Record): Promise;
}
```
Then `getRemoteMcpAITools(provider, options)` can be independent of whether the provider is a parent agent, a workspace agent, or some other Durable Object stub.
## Open questions
- Should this be a stable public API, or experimental first?
- Should the helper live under `agents/mcp/client`, `agents`, or a new AI-SDK adapter entry point?
- Should the remote provider methods be automatically available on all `Agent` instances that have `this.mcp`, or should apps opt in explicitly?
- How should tool names be namespaced to avoid collisions across servers and child-local tools?
- Should the helper wait for MCP connections by default, or require callers to opt into waiting?
- Should errors preserve the full MCP `CallToolResult`, throw text content like `getAITools()` does today, or offer both modes?
- Should the helper support filters equivalent to `MCPServerFilter`?
## Suggested near-term path
I would not block the starter refactor on this. The starter can keep the local bridge for now as a concrete example.
As a follow-up, we could extract the pattern into the SDK, add tests around remote descriptor conversion and remote execution, then simplify the starter to use the official helper.
Contributor guide
Assessment
This issue has not been assessed yet.