Skip to content

MCPSessionManager pools a server-terminated streamable-HTTP session forever: _is_session_disconnected only checks local stream flags (scope of closed #3321, still broken in v2.7.1) #6822

Description

@anshulcha

** 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._closed

A 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:

  1. 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.
  2. Create an McpToolset with StreamableHTTPConnectionParams, fetch tools, and make one successful run_async call.
  3. Terminate the session server-side (wait past the idle eviction, or restart the server).
  4. Call run_async again — 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

Metadata

Metadata

Assignees

Labels

mcp[Component] This issues is related to MCP supportrequest clarification[Status] The maintainer need clarification or more information from the author

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions