MCP session pool can grow unbounded with high-cardinality dynamic headers
- Lingua principale
- Python
- Stelle
- 21.5k
- Fork
- 4k
- Merge medio
- 1g 14h
- PR unite (30g)
- 37
Descrizione
### 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:
```python
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",
),
)
```
3. **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={...})
```
4. **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.
Guida per i contributori
Apri la guida per i contributori
Valutazione
Questa issue non è ancora stata valutata.