google / google/adk-python

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

Đang mở
#5,959 1 bình luận 0 reaction 2 người được giao Được @boyangsvl nhận Xem trên GitHub
mcp tools
Ngôn ngữ chính
Python
Star
21.5k
Fork
4k
Merge trung bình
1 ngày 14 giờ
Pull request đã merge (30 ngày)
37

Mô tả

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

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Đánh giá

Issue này chưa được đánh giá.

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.