Title: @temporalio/ai-sdk: reuse MCP client connections across tool calls (idle-timeout pooling, matching strands-agents)
Is your feature request related to a problem? Please describe.
In contrib/ai-sdk/src/activities.ts, activitiesForName(name, mcpClientFactory) creates a brand new MCP client on every single listToolsActivity/callToolActivity invocation:
const mcpClient = await mcpClientFactory(args.clientArgs)
try {
// ... call the tool ...
} finally {
await mcpClient.close()
}
For a multi-turn agent loop — the normal usage pattern, where an LLM calls the same MCP server's tools repeatedly across steps — this means the underlying MCP transport (a stdio subprocess spawn, an SSE/HTTP handshake, an OAuth token exchange) is torn down and rebuilt on every single tool call and every listTools refresh. For stdio-based or OAuth-secured MCP servers in particular, this adds real, avoidable latency and resource churn to every agent step.
Notably, this repo has already solved this exact problem twice, just not in this package:
@temporalio/strands-agents (contrib/strands/src/plugin.ts, contrib/strands/src/temporal-mcp-client.ts) already ships StrandsPluginOptions.mcpConnectionIdleTimeout (default 5 minutes), keeping one lazily-opened MCP connection alive across activity calls and resetting the idle timer on reuse.
@temporalio/openai-agents (contrib/openai-agents/src/worker/stateful-mcp-provider.ts) goes further with a StatefulMCPServerProvider that keeps one persistent MCP session alive for an entire workflow run.
@temporalio/ai-sdk is the newest, most actively-developed of the three contrib packages (it just had its ai@7 provider-spec upgrade merged in #2192), and it's the one left without either pattern.
Describe the solution you'd like
Port the lighter, already-proven strands-agents idle-timeout pattern into @temporalio/ai-sdk:
- In
contrib/ai-sdk/src/activities.ts, change activitiesForName to hold a Map<string, { client: MCPClient; timer: NodeJS.Timeout }> keyed by MCP server name, lazily creating the client via mcpClientFactory on first use and reusing it across subsequent listTools/callTool calls within the same worker process, instead of closing it in every finally.
- Add an
mcpConnectionIdleTimeout option to createActivities(provider, mcpClientFactories, options?), mirroring StrandsPluginOptions.mcpConnectionIdleTimeout (a Duration, default ~5 minutes); reset the idle timer on each reuse; evict/close the connection on idle expiry or worker shutdown.
- Keep today's stateless behavior available as an opt-out (e.g.
mcpConnectionIdleTimeout: 0) for transports/servers that don't tolerate a reused/concurrent session.
- Add tests mirroring the existing
test-ai-sdk.ts MCP tests, verifying the client factory is called once and reused across repeated tools() calls, and that the connection is evicted after the idle window.
This is scoped to a lifecycle/caching change inside one existing file plus one new option — not a rewrite of the MCP integration, and not an attempt to unify all three contrib packages' MCP handling into a shared library (a larger, separate ask).
Describe alternatives you've considered
- Leaving connection lifecycle as-is and relying on users to configure long-lived MCP servers themselves — doesn't help stdio-subprocess-based servers, where "long-lived" isn't an option if the SDK itself closes the client after every call.
- Adopting the heavier
openai-agents-style dedicated-Worker-per-run session model instead of the strands-agents idle-timeout model — possible, but a bigger design/risk surface for a first pass; happy to expand scope if maintainers prefer that direction.
Additional context
Not aware of any existing issue or PR covering this — searched for connection pooling/reuse, MCP session reuse, and reconnect-overhead related terms with no hits; the only PRs touching contrib/ai-sdk/src/activities.ts are the ai@7 upgrade (#2192) and an unrelated schema fix. Happy to contribute a PR for this if the team is open to the approach (or to the heavier session-model alternative, if preferred).
Title:
@temporalio/ai-sdk: reuse MCP client connections across tool calls (idle-timeout pooling, matching strands-agents)Is your feature request related to a problem? Please describe.
In
contrib/ai-sdk/src/activities.ts,activitiesForName(name, mcpClientFactory)creates a brand new MCP client on every singlelistToolsActivity/callToolActivityinvocation:For a multi-turn agent loop — the normal usage pattern, where an LLM calls the same MCP server's tools repeatedly across steps — this means the underlying MCP transport (a stdio subprocess spawn, an SSE/HTTP handshake, an OAuth token exchange) is torn down and rebuilt on every single tool call and every
listToolsrefresh. For stdio-based or OAuth-secured MCP servers in particular, this adds real, avoidable latency and resource churn to every agent step.Notably, this repo has already solved this exact problem twice, just not in this package:
@temporalio/strands-agents(contrib/strands/src/plugin.ts,contrib/strands/src/temporal-mcp-client.ts) already shipsStrandsPluginOptions.mcpConnectionIdleTimeout(default 5 minutes), keeping one lazily-opened MCP connection alive across activity calls and resetting the idle timer on reuse.@temporalio/openai-agents(contrib/openai-agents/src/worker/stateful-mcp-provider.ts) goes further with aStatefulMCPServerProviderthat keeps one persistent MCP session alive for an entire workflow run.@temporalio/ai-sdkis the newest, most actively-developed of the three contrib packages (it just had itsai@7provider-spec upgrade merged in #2192), and it's the one left without either pattern.Describe the solution you'd like
Port the lighter, already-proven
strands-agentsidle-timeout pattern into@temporalio/ai-sdk:contrib/ai-sdk/src/activities.ts, changeactivitiesForNameto hold aMap<string, { client: MCPClient; timer: NodeJS.Timeout }>keyed by MCP server name, lazily creating the client viamcpClientFactoryon first use and reusing it across subsequentlistTools/callToolcalls within the same worker process, instead of closing it in everyfinally.mcpConnectionIdleTimeoutoption tocreateActivities(provider, mcpClientFactories, options?), mirroringStrandsPluginOptions.mcpConnectionIdleTimeout(aDuration, default ~5 minutes); reset the idle timer on each reuse; evict/close the connection on idle expiry or worker shutdown.mcpConnectionIdleTimeout: 0) for transports/servers that don't tolerate a reused/concurrent session.test-ai-sdk.tsMCP tests, verifying the client factory is called once and reused across repeatedtools()calls, and that the connection is evicted after the idle window.This is scoped to a lifecycle/caching change inside one existing file plus one new option — not a rewrite of the MCP integration, and not an attempt to unify all three contrib packages' MCP handling into a shared library (a larger, separate ask).
Describe alternatives you've considered
openai-agents-style dedicated-Worker-per-run session model instead of thestrands-agentsidle-timeout model — possible, but a bigger design/risk surface for a first pass; happy to expand scope if maintainers prefer that direction.Additional context
Not aware of any existing issue or PR covering this — searched for connection pooling/reuse, MCP session reuse, and reconnect-overhead related terms with no hits; the only PRs touching
contrib/ai-sdk/src/activities.tsare theai@7upgrade (#2192) and an unrelated schema fix. Happy to contribute a PR for this if the team is open to the approach (or to the heavier session-model alternative, if preferred).