Skip to content

Commit 5f4925c

Browse files
Update testing docs for new Client API
1 parent fce8b9e commit 5f4925c

File tree

4 files changed

+39
-26
lines changed

4 files changed

+39
-26
lines changed

docs/testing.md

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
# Testing MCP Servers
22

3-
If you call yourself a developer, you will want to test your MCP server.
4-
The Python SDK offers the `create_connected_server_and_client_session` function to create a session
5-
using an in-memory transport. I know, I know, the name is too long... We are working on improving it.
3+
The Python SDK provides a `Client` class for testing MCP servers with an in-memory transport.
4+
This makes it easy to write tests without network overhead.
65

7-
Anyway, let's assume you have a simple server with a single tool:
6+
## Basic Usage
7+
8+
Let's assume you have a simple server with a single tool:
89

910
```python title="server.py"
1011
from mcp.server import FastMCP
@@ -40,12 +41,9 @@ To run the below test, you'll need to install the following dependencies:
4041
server - you don't need to use it, but we are spreading the word for best practices.
4142

4243
```python title="test_server.py"
43-
from collections.abc import AsyncGenerator
44-
4544
import pytest
4645
from inline_snapshot import snapshot
47-
from mcp.client.session import ClientSession
48-
from mcp.shared.memory import create_connected_server_and_client_session
46+
from mcp import Client
4947
from mcp.types import CallToolResult, TextContent
5048

5149
from server import app
@@ -56,23 +54,41 @@ def anyio_backend(): # (1)!
5654
return "asyncio"
5755

5856

59-
@pytest.fixture
60-
async def client_session() -> AsyncGenerator[ClientSession]:
61-
async with create_connected_server_and_client_session(app, raise_exceptions=True) as _session:
62-
yield _session
63-
64-
6557
@pytest.mark.anyio
66-
async def test_call_add_tool(client_session: ClientSession):
67-
result = await client_session.call_tool("add", {"a": 1, "b": 2})
68-
assert result == snapshot(
69-
CallToolResult(
70-
content=[TextContent(type="text", text="3")],
71-
structuredContent={"result": 3},
58+
async def test_call_add_tool():
59+
async with Client(app, raise_exceptions=True) as client:
60+
result = await client.call_tool("add", {"a": 1, "b": 2})
61+
assert result == snapshot(
62+
CallToolResult(
63+
content=[TextContent(type="text", text="3")],
64+
structuredContent={"result": 3},
65+
)
7266
)
73-
)
7467
```
7568

7669
1. If you are using `trio`, you should set `"trio"` as the `anyio_backend`. Check more information in the [anyio documentation](https://anyio.readthedocs.io/en/stable/testing.html#specifying-the-backends-to-run-on).
7770

7871
There you go! You can now extend your tests to cover more scenarios.
72+
73+
## Advanced: Low-Level Transport Access
74+
75+
For tests that need direct access to the underlying `ClientSession`, use `InMemoryTransport`:
76+
77+
```python
78+
from mcp.client.session import ClientSession
79+
from mcp.client.transports import InMemoryTransport
80+
81+
from server import app
82+
83+
84+
async def test_with_low_level_access():
85+
transport = InMemoryTransport(app, raise_exceptions=True)
86+
async with transport.connect() as (read_stream, write_stream):
87+
async with ClientSession(read_stream, write_stream) as session:
88+
await session.initialize()
89+
# Direct access to the full ClientSession API
90+
result = await session.call_tool("add", {"a": 1, "b": 2})
91+
```
92+
93+
The `Client` class is built on top of `InMemoryTransport` and handles initialization automatically.
94+
Use `InMemoryTransport` directly only when you need fine-grained control over the session lifecycle.

src/mcp/client/client.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,7 @@ async def __aenter__(self) -> Client:
9292
try:
9393
# Create transport and connect
9494
transport = InMemoryTransport(self._server, raise_exceptions=self._raise_exceptions)
95-
read_stream, write_stream = await self._exit_stack.enter_async_context(
96-
transport.connect()
97-
)
95+
read_stream, write_stream = await self._exit_stack.enter_async_context(transport.connect())
9896

9997
# Create session
10098
self._session = await self._exit_stack.enter_async_context(

src/mcp/client/transports/memory.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class InMemoryTransport:
3333
# Use the session...
3434
3535
Or more commonly, use with Client:
36-
async with Client.from_server(server) as client:
36+
async with Client(server) as client:
3737
result = await client.call_tool("my_tool", {...})
3838
"""
3939

tests/issues/test_141_resource_templates.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,6 @@ def get_user_profile(user_id: str) -> str:
7777
return f"Profile for user {user_id}"
7878

7979
async with Client(mcp) as session:
80-
8180
# List available resources
8281
resources = await session.list_resource_templates()
8382
assert isinstance(resources, ListResourceTemplatesResult)

0 commit comments

Comments
 (0)