google / google/adk-python

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

未关闭
#5,959 1 条评论 0 个 reaction 已指派 2 人 已被 @boyangsvl 认领 在 GitHub 查看
mcp tools
主要语言
Python
星标
21.5k
派生
4k
平均合并
1 天 14 小时
30 天内合并 PR
37

描述

### 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.

贡献指南

打开贡献指南

评估

这个 Issue 还没有评估数据。

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。