Repository navigation
Description
** Please make sure you read the contribution guide and file the issues in the right place. **
Describe the bug
When a streamable-HTTP MCP server terminates a session server-side (session eviction, server restart, or any condition that makes the server answer HTTP 404 "Session not found" for the stored mcp-session-id), MCPSessionManager never detects it. _is_session_disconnected() checks only the local stream flags:
return session._read_stream._closed or session._write_stream._closedA server-side termination arrives as a JSONRPCError ("Session terminated") delivered into the request's read stream — the local streams stay open and the background task stays alive, so the pooled session still looks healthy. retry_on_errors then retries the call through the same dead session, which fails identically. From that point every tool call on the toolset fails forever; the session is never re-created.
With _MCP_GRACEFUL_ERROR_HANDLING (experimental, default on), the effect is worse in practice: McpTool.run_async converts the raised McpError into a returned dict {"error": "MCP tool execution failed: Session terminated"}. Callers get no exception, so application code cannot distinguish a permanent transport failure from an ordinary tool result, and nothing upstream can trigger a reconnect. In our production system this combination caused a silent multi-day outage: every call "succeeded" with an error dict and no signal fired.
Issue #3321 described this exact scenario (server restart) and named _is_session_disconnected; it was closed in Nov 2025, but the promised handling of McpError/"Session terminated" never landed — the method is byte-identical in v2.7.1. Related: the MCP Python SDK does not re-initialize on the 404 either (modelcontextprotocol/python-sdk#1676, PR #1818 pending), so an ADK-side fix is needed regardless.
To Reproduce
Steps to reproduce the behavior:
- Run any MCP server over streamable HTTP with stateful sessions. Two easy ways to kill the session server-side: (a) use a server that evicts idle sessions (e.g. Apollo MCP Server v1.17 evicts after ~5 idle minutes), or (b) simply restart the server container between calls.
- Create an
McpToolsetwithStreamableHTTPConnectionParams, fetch tools, and make one successfulrun_asynccall. - Terminate the session server-side (wait past the idle eviction, or restart the server).
- Call
run_asyncagain — and any number of times after.
import asyncio
from google.adk.tools.mcp_tool.mcp_toolset import McpToolset, StreamableHTTPConnectionParams
async def main():
ts = McpToolset(connection_params=StreamableHTTPConnectionParams(
url="http://localhost:5000/mcp"))
tools = await ts.get_tools()
tool = tools[0]
print(await tool.run_async(args={}, tool_context=None)) # works
input("Now restart the MCP server (or wait past its idle-session eviction), then press Enter...")
print(await tool.run_async(args={}, tool_context=None)) # {'error': 'MCP tool execution failed: Session terminated'}
print(await tool.run_async(args={}, tool_context=None)) # same, forever — never recovers
asyncio.run(main())Expected behavior
The session pool detects the server-side termination (the McpError/404 on a pooled session) and re-creates the session, so the retry_on_errors retry runs against a fresh session and succeeds. At minimum, the error-dict result produced by graceful error handling should be distinguishable as a transport-level failure so callers can force a reconnect themselves.
Observed behavior
The dead session is returned from the pool indefinitely; every retry goes through it; every call returns {"error": "MCP tool execution failed: Session terminated"} (or raises McpError with ADK_DISABLE_MCP_GRACEFUL_ERROR_HANDLING=1). The only recovery is tearing down and re-creating the whole McpToolset from application code.
Environment
- ADK version: reproduced on 2.2.0; code unchanged on v2.7.1 and current main (
mcp_session_manager.py) - mcp (python-sdk): 1.29.0
- Python: 3.12
- OS: Linux (Cloud Run) and macOS, identical behavior
- Transport: streamable HTTP, stateful server sessions
Additional context
- McpToolset does not handle MCP server restart #3321 (closed): same failure mode via server restart; the fix discussed there (invalidate on
McpError: Session terminated) does not appear in the codebase. - McpToolset connection failure (HTTP 401/403/5xx) causes ASGI crash instead of graceful error #4300: the origin of graceful error handling — the swallow is intentional there; this report is about the session pool never recovering, with the swallow as the reason the failure is also invisible to callers.
- MCP client doesn't not initialize new session when getting 404 session not found modelcontextprotocol/python-sdk#1676 / PR Using Gemma 3n e4B or e2B in google ADK #1818: client-side re-init on 404 is also missing at the SDK layer.
Activity
guiyangyuan commentedon Aug 20, 2026
Hi maintainers — I'd like to work on this if the direction is welcome.
My proposed scope is:
McpError/ JSON-RPC error path throughMCPSessionManagerandMcpToolto identify the narrowest invalidation boundary.I'll keep the change focused on ADK session-pool recovery rather than broad MCP SDK behavior. Could you confirm that this is the preferred direction before I open a PR?
llalitkumarrr commentedon Aug 21, 2026
Hello @anshulcha,
Could you please try this patch to
mcp_tool.py(lines 467–504) ingoogle-adk==2.7.1? Please let us know if it resolves the issue and share your feedback:adk-bot commentedon Aug 28, 2026
This issue has been automatically marked as stale because it has not had recent activity for 7 days after a maintainer requested clarification. It will be closed if no further activity occurs within 7 days.
gioboa commentedon Aug 28, 2026
@guiyangyuan Apologies for overlapping.
I saw your claim after finishing a tested fix and opened a PR. Your review there would be very welcome!
llalitkumarrr commentedon Sep 10, 2026
Hello @anshulcha,
Could you please confirm if the shared patch worked or not? Also it would be great if you could share your feedback on linked PR.
adk-bot commentedon Sep 17, 2026
This issue has been automatically marked as stale because it has not had recent activity for 7 days after a maintainer requested clarification. It will be closed if no further activity occurs within 7 days.
gioboa commentedon Sep 17, 2026
It's still valid, I created a PR to solve this.
llalitkumarrr commentedon Sep 28, 2026
Hello @gioboa,
Thank you for your contribution, it seems there are several conflicts. Could you please take a look at them?
gioboa commentedon Sep 28, 2026
I fixed the conflicts 🤞
llalitkumarrr commentedon Sep 28, 2026
Hello @gioboa,
Thank you for your prompt response. We will review this PR and reach out if any additional details are required. In the meantime we request your patience.
willie3838 commentedon Oct 29, 2026
Hey @gioboa thanks for contributing to this! An internal team will be taking over and your work will be credited.
gioboa commentedon Oct 29, 2026
Thanks 🙏☺️