Skip to content

Commit e76898b

Browse files
wukathcopybara-github
authored andcommitted
test: build the in-memory MCP session from the pieces the SDK promises
`test_agent_to_mcp` reaches the agent server through `mcp.shared.memory.create_connected_server_and_client_session`. That helper is a convenience wrapper the SDK does not promise, and this file is the only place in ADK that uses it. Assemble the session here instead, from the four pieces underneath it that the SDK does promise: `create_client_server_memory_streams`, the low-level server's `run`, its `create_initialization_options`, and `ClientSession`. Same wiring, so no test changes behaviour. `raise_exceptions` now defaults to True. The wrapper defaulted to False, which turns a raising handler into an error result and hides the traceback. No test here relied on that. Reaching the low-level server behind the high-level one is the one step with no public route. `_lowlevel_server` does it, and tries both names the SDK has given that attribute, so the helper does not pin a private name to one spelling. Co-authored-by: Kathy Wu <wukathy@google.com> PiperOrigin-RevId: 970124629
1 parent fb15710 commit e76898b

2 files changed

Lines changed: 93 additions & 3 deletions

File tree

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# Copyright 2026 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""An in-memory client session for testing `to_mcp_server`.
16+
17+
`mcp.shared.memory.create_connected_server_and_client_session` does the same
18+
thing, but it is a convenience wrapper the SDK does not promise. This builds the
19+
session from the four pieces underneath it, each of which the SDK does promise:
20+
the memory stream pair, the low-level server's `run`, its initialization
21+
options, and `ClientSession`.
22+
"""
23+
24+
from __future__ import annotations
25+
26+
from contextlib import asynccontextmanager
27+
from typing import Any
28+
from typing import AsyncGenerator
29+
30+
import anyio
31+
from mcp.client.session import ClientSession
32+
from mcp.shared.memory import create_client_server_memory_streams
33+
34+
35+
def _lowlevel_server(server: Any) -> Any:
36+
"""Returns the low-level server that `server` wraps, or `server` itself."""
37+
# The high-level server holds the low-level one privately and offers no
38+
# accessor. The SDK's own in-memory helper reaches for it the same way.
39+
for name in ("_mcp_server", "_lowlevel_server"):
40+
wrapped = getattr(server, name, None)
41+
if wrapped is not None:
42+
return wrapped
43+
return server
44+
45+
46+
@asynccontextmanager
47+
async def connected_client_session(
48+
server: Any,
49+
*,
50+
raise_exceptions: bool = True,
51+
) -> AsyncGenerator[ClientSession, None]:
52+
"""Yields an initialized `ClientSession` talking to `server` in memory.
53+
54+
Args:
55+
server: The server to connect to, high-level or low-level.
56+
raise_exceptions: Whether a handler that raises should take the server down
57+
rather than return the error to the client. On by default, so a test sees
58+
the traceback instead of an error result.
59+
60+
Yields:
61+
A `ClientSession` that has completed the initialize handshake.
62+
"""
63+
server = _lowlevel_server(server)
64+
65+
async with create_client_server_memory_streams() as (
66+
client_streams,
67+
server_streams,
68+
):
69+
client_read, client_write = client_streams
70+
server_read, server_write = server_streams
71+
72+
async with anyio.create_task_group() as task_group:
73+
task_group.start_soon(
74+
lambda: server.run(
75+
server_read,
76+
server_write,
77+
server.create_initialization_options(),
78+
raise_exceptions=raise_exceptions,
79+
)
80+
)
81+
try:
82+
async with ClientSession(
83+
read_stream=client_read,
84+
write_stream=client_write,
85+
) as session:
86+
await session.initialize()
87+
yield session
88+
finally:
89+
task_group.cancel_scope.cancel()

tests/unittests/tools/mcp_tool/test_agent_to_mcp.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,10 @@
2525
from google.adk.tools.mcp_tool._agent_to_mcp import _run_agent
2626
from google.adk.tools.mcp_tool._agent_to_mcp import to_mcp_server
2727
from google.genai import types
28-
from mcp.shared.memory import create_connected_server_and_client_session
2928
import pytest
3029

30+
from ._in_memory_session import connected_client_session
31+
3132

3233
class _EchoAgent(BaseAgent):
3334
"""Minimal agent that emits a single final text event."""
@@ -153,7 +154,7 @@ async def test_call_tool_runs_agent_end_to_end():
153154
agent = _EchoAgent(name="assistant")
154155
server = to_mcp_server(agent)
155156

156-
async with create_connected_server_and_client_session(server) as client:
157+
async with connected_client_session(server) as client:
157158
result = await client.call_tool("assistant", {"request": "hi"})
158159

159160
assert not result.isError
@@ -275,7 +276,7 @@ async def test_call_tool_reuses_session_across_calls_on_one_connection():
275276
runner = _FakeRunner([_text_event("ok")])
276277
server = to_mcp_server(agent, runner=runner)
277278

278-
async with create_connected_server_and_client_session(server) as client:
279+
async with connected_client_session(server) as client:
279280
await client.call_tool("assistant", {"request": "first"})
280281
await client.call_tool("assistant", {"request": "second"})
281282

0 commit comments

Comments
 (0)