Skip to content

MCP session pool can grow unbounded with high-cardinality dynamic headers #5959

@symaras

Description

@symaras

Is your feature request related to a specific problem?

Yes. We are running ADK Python with MCP toolsets in long-running service processes, and we need a supported way to bound or evict entries from the MCP client-session pool.

Today, MCPSessionManager pools MCP client sessions by a key derived from the merged MCP connection headers. For SSE / Streamable HTTP connections, those merged headers can include:

  • static connection headers,
  • headers returned by McpToolset.header_provider,
  • auth headers such as Authorization.

This makes sense for isolation, because different auth/tenant headers may represent different access contexts. However, in production systems these headers can be high-cardinality:

  • per-user OAuth bearer tokens,
  • short-lived / rotated access tokens,
  • service-account impersonation tokens,
  • tenant or company headers,
  • dynamic auth scopes,
  • request/session/correlation headers accidentally or intentionally propagated through header_provider.

Because healthy MCP sessions appear to remain in the pool until they are detected as disconnected/different-loop or until the whole McpToolset / MCPSessionManager is closed, a long-running process can accumulate many MCP client sessions over time.

In our case, this became acute when we forwarded a conversation/session id to an MCP server as a dynamic header. Since it participated in the ADK MCP session key, each new request created a distinct pooled MCP session. The pool grew under sustained traffic and contributed to production OOMs.

We recognize that propagating high-cardinality correlation metadata as a connection/session header is not ideal. However, the broader issue remains even for legitimate auth headers like Authorization, because token values can also be high-cardinality or short-lived in real deployments.

Currently, the only targeted workaround we found requires using private ADK internals such as:

  • _mcp_session_manager
  • _merge_headers
  • _generate_session_key
  • _sessions
  • _session_lock
  • _cleanup_session

That is fragile across ADK releases.

Describe the Solution You'd Like

We would like ADK to expose a public, supported MCP session-pool lifecycle API or policy so long-running services can prevent unbounded MCP client-session growth.

Any of the following would help:

  1. Idle TTL eviction

Allow MCP client sessions to be evicted after they have not been used for some configured duration.

Example concept:

McpToolset(
    connection_params=StreamableHTTPConnectionParams(...),
    session_pool_policy=McpSessionPoolPolicy(
        idle_ttl_seconds=900,
    ),
)
2. **Max pool size / LRU eviction**
Allow a maximum number of pooled MCP sessions per MCPSessionManager, with least-recently-used or similar eviction.
```McpToolset(
    connection_params=...,
    session_pool_policy=McpSessionPoolPolicy(
        max_sessions=100,
        eviction_policy="lru",
    ),
)
  1. Public per-session close / eviction
    Expose a supported API to close the MCP session corresponding to a given effective header set.
await mcp_toolset.close_session(headers={...})

or

await mcp_session_manager.close_session(headers={...})
  1. Per-tool-call metadata provider
    ADK already propagates trace context through MCP tool-call _meta rather than through session headers. It would be useful to expose a supported hook for application metadata that should be sent per tool call but should not participate in MCP session-pool keying.

Impact on your work

This impacts production stability for long-running ADK-based services using MCP toolsets.

Our services use ADK agents with MCP servers for operational workflows. We need MCP calls to carry auth/tenant context and sometimes correlation context, but we cannot allow MCP client sessions to accumulate indefinitely in memory.

Without a supported eviction or pool policy, we have to choose between:

  • risking unbounded memory growth in long-running services or relying on private ADK internals to evict sessions.
    We have already seen this pattern contribute to OOMs in production when a high-cardinality dynamic header was included in MCP session headers. We have a local workaround, but it is intentionally defensive and fragile because it depends on private implementation details.

A supported ADK API would let us implement this safely and reduce operational risk.

Timeline-wise, this is important for production hardening. We can keep our workaround temporarily, but we would prefer to move to an upstream-supported API as soon as practical.

Willingness to contribute

Yes, but some guidance would be greatly appreciated. We would especially appreciate guidance on whether the preferred direction is:

  • TTL / max-size pool policy,
  • public per-session eviction,
  • pool_sessions=False,
  • session-key header policy,
  • per-call metadata provider,
  • or some combination of the above.

Describe Alternatives You've Considered

Local sidecar eviction using private internals
We implemented a local workaround that:

  • records MCP session keys when our header provider runs,
  • tracks last-used timestamps,
  • captures sessions created by requests,
  • evicts captured ephemeral sessions on stream completion,
  • runs an idle TTL sweeper as a backstop.
    However, this requires private ADK internals which makes it fragile.

Metadata

Metadata

Labels

mcp[Component] Issues about MCP supporttools[Component] This issue is related to tools
No fields configured for Feature.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions