Skip to content

dicussion: ship shared MCP tool proxy support for subagents #1545

@threepointone

Description

@threepointone

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:

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:

class Chat extends AIChatAgent<Env> {
  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:

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:

interface RemoteMcpToolProvider {
  listMcpToolDescriptors(options?: { timeout?: number }): Promise<McpToolDescriptor[]>;
  callMcpTool(serverId: string, name: string, args: Record<string, unknown>): Promise<McpCallToolResult>;
}

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestmcpIssues related to MCP functionality

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions