Description
google/adk/tools/mcp_tool/session_context.py::_read_timeout documents a version branch that its body never implements.
def _read_timeout(seconds: Optional[float]) -> Optional[timedelta]:
"""Converts a timeout in seconds to the type ``ClientSession`` expects.
ADK carries every timeout as float seconds. MCP SDK 1.x wants a
``timedelta`` here, while 2.x wants the float. Converting in one place keeps
that difference to a single function.
"""
if seconds is None:
return None
return timedelta(seconds=seconds)
The docstring states 2.x wants the float, but the function returns a timedelta unconditionally. On mcp 2.x that value reaches anyio.fail_after, which adds it to current_time():
File "mcp/shared/jsonrpc_dispatcher.py", line 401, in send_raw_request
with anyio.fail_after(opts.get("timeout")):
File "anyio/_core/_tasks.py", line 120, in fail_after
deadline = (current_time() + delay) if delay is not None else math.inf
TypeError: unsupported operand type(s) for +: 'float' and 'datetime.timedelta'
Every session creation fails at initialize().
Reproduction
ADK 2.8.0, mcp 2.1.1. Patching just this one function makes the timedelta error disappear:
import google.adk.tools.mcp_tool.session_context as sc
sc._read_timeout = lambda seconds: seconds
Suggested fix
The docstring already specifies the behaviour:
from importlib.metadata import version
_MCP_MAJOR = int(version("mcp").split(".")[0])
def _read_timeout(seconds: Optional[float]) -> Optional[timedelta | float]:
if seconds is None:
return None
return seconds if _MCP_MAJOR >= 2 else timedelta(seconds=seconds)
Context: what else blocks mcp 2.x
I hit this while getting McpToolset working against mcp 2.1.1, and this was not the only blocker — recording the rest in case it is useful for the wider 2.x migration:
-
Renames, all resolvable by aliasing:
mcp.shared.session.ProgressFnT → mcp.client.session.ProgressFnT (used only as a type annotation)
mcp.shared.exceptions.McpError → MCPError, same module
mcp.server.fastmcp.FastMCP → mcp.server.mcpserver.MCPServer — needed only by _agent_to_mcp, but mcp_tool/__init__.py imports it eagerly, so the whole toolset surface disappears without it
-
This bug.
-
Two HTTP stacks. ADK types its client factory as httpx.*; mcp 2.x runs on httpx2, a separate distribution (httpx.Timeout is not httpx2.Timeout). ADK's Timeout object reaches httpcore2 and fails the same way. This one is already solvable from outside via httpx_client_factory on the connection params — a public, documented seam. It worked well: with the aliases, the _read_timeout patch, and a factory returning an httpx2.AsyncClient, McpToolset connects to a real streamable-HTTP server and returns fully-typed tool declarations.
So on 2.8.0 the only change that needs to happen inside ADK is this one function; the rest can be handled by a caller. Happy to open a PR if that would help.
Note on the silent failure
mcp_tool/__init__.py catches the ImportError and logs at DEBUG, so on mcp 2.x McpToolset, McpTool and require_confirmation are simply absent from the package rather than raising. from google.adk.tools.mcp_tool import McpToolset then fails with a bare "cannot import name", which does not point at the dependency conflict. A warning naming the mcp version would have saved a fair amount of digging.
Description
google/adk/tools/mcp_tool/session_context.py::_read_timeoutdocuments a version branch that its body never implements.The docstring states 2.x wants the float, but the function returns a
timedeltaunconditionally. On mcp 2.x that value reachesanyio.fail_after, which adds it tocurrent_time():Every session creation fails at
initialize().Reproduction
ADK 2.8.0, mcp 2.1.1. Patching just this one function makes the
timedeltaerror disappear:Suggested fix
The docstring already specifies the behaviour:
Context: what else blocks mcp 2.x
I hit this while getting
McpToolsetworking against mcp 2.1.1, and this was not the only blocker — recording the rest in case it is useful for the wider 2.x migration:Renames, all resolvable by aliasing:
mcp.shared.session.ProgressFnT→mcp.client.session.ProgressFnT(used only as a type annotation)mcp.shared.exceptions.McpError→MCPError, same modulemcp.server.fastmcp.FastMCP→mcp.server.mcpserver.MCPServer— needed only by_agent_to_mcp, butmcp_tool/__init__.pyimports it eagerly, so the whole toolset surface disappears without itThis bug.
Two HTTP stacks. ADK types its client factory as
httpx.*; mcp 2.x runs onhttpx2, a separate distribution (httpx.Timeout is not httpx2.Timeout). ADK's Timeout object reaches httpcore2 and fails the same way. This one is already solvable from outside viahttpx_client_factoryon the connection params — a public, documented seam. It worked well: with the aliases, the_read_timeoutpatch, and a factory returning anhttpx2.AsyncClient,McpToolsetconnects to a real streamable-HTTP server and returns fully-typed tool declarations.So on 2.8.0 the only change that needs to happen inside ADK is this one function; the rest can be handled by a caller. Happy to open a PR if that would help.
Note on the silent failure
mcp_tool/__init__.pycatches theImportErrorand logs atDEBUG, so on mcp 2.xMcpToolset,McpToolandrequire_confirmationare simply absent from the package rather than raising.from google.adk.tools.mcp_tool import McpToolsetthen fails with a bare "cannot import name", which does not point at the dependency conflict. A warning naming the mcp version would have saved a fair amount of digging.