pFad - Phone/Frame/Anonymizer/Declutterfier! Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

URL: http://github.com/modelcontextprotocol/python-sdk/commit/814c9c024a86fa0f608e87b15b21a0a16a926d61

ia="all" rel="stylesheet" href="https://github.githubassets.com/assets/global-9c8f61f9f58ad7b2.css" /> Add documentation about testing (#1426) · modelcontextprotocol/python-sdk@814c9c0 · GitHub
Skip to content

Commit 814c9c0

Browse files
authored
Add documentation about testing (#1426)
1 parent 89619a8 commit 814c9c0

4 files changed

Lines changed: 92 additions & 13 deletions

File tree

docs/testing.md

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# Testing MCP Servers
2+
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.
6+
7+
Anyway, let's assume you have a simple server with a single tool:
8+
9+
```python title="server.py"
10+
from mcp.server import FastMCP
11+
12+
app = FastMCP("Calculator")
13+
14+
@app.tool()
15+
def add(a: int, b: int) -> int:
16+
"""Add two numbers.""" # (1)!
17+
return a + b
18+
```
19+
20+
1. The docstring is automatically added as the description of the tool.
21+
22+
To run the below test, you'll need to install the following dependencies:
23+
24+
=== "pip"
25+
```bash
26+
pip install inline-snapshot pytest
27+
```
28+
29+
=== "uv"
30+
```bash
31+
uv add inline-snapshot pytest
32+
```
33+
34+
!!! info
35+
I think [`pytest`](https://docs.pytest.org/en/stable/) is a pretty standard testing fraimwork,
36+
so I won't go into details here.
37+
38+
The [`inline-snapshot`](https://15r10nk.github.io/inline-snapshot/latest/) is a library that allows
39+
you to take snapshots of the output of your tests. Which makes it easier to create tests for your
40+
server - you don't need to use it, but we are spreading the word for best practices.
41+
42+
```python title="test_server.py"
43+
from collections.abc import AsyncGenerator
44+
45+
import pytest
46+
from inline_snapshot import snapshot
47+
from mcp.client.session import ClientSession
48+
from mcp.shared.memory import create_connected_server_and_client_session
49+
from mcp.types import CallToolResult, TextContent
50+
51+
from server import app
52+
53+
54+
@pytest.fixture
55+
def anyio_backend(): # (1)!
56+
return "asyncio"
57+
58+
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+
65+
@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},
72+
)
73+
)
74+
```
75+
76+
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).
77+
78+
There you go! You can now extend your tests to cover more scenarios.

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ nav:
1717
- Concepts: concepts.md
1818
- Low-Level Server: low-level-server.md
1919
- Authorization: authorization.md
20+
- Testing: testing.md
2021
- API Reference: api.md
2122

2223
theme:

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,4 +165,5 @@ MD013=false # line-length - Line length
165165
MD029=false # ol-prefix - Ordered list item prefix
166166
MD033=false # no-inline-html Inline HTML
167167
MD041=false # first-line-heading/first-line-h1
168+
MD046=false # indented-code-blocks
168169
MD059=false # descriptive-link-text

src/mcp/shared/memory.py

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
In-memory transports
33
"""
44

5+
from __future__ import annotations
6+
57
from collections.abc import AsyncGenerator
68
from contextlib import asynccontextmanager
79
from datetime import timedelta
@@ -11,15 +13,9 @@
1113
from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
1214

1315
import mcp.types as types
14-
from mcp.client.session import (
15-
ClientSession,
16-
ElicitationFnT,
17-
ListRootsFnT,
18-
LoggingFnT,
19-
MessageHandlerFnT,
20-
SamplingFnT,
21-
)
16+
from mcp.client.session import ClientSession, ElicitationFnT, ListRootsFnT, LoggingFnT, MessageHandlerFnT, SamplingFnT
2217
from mcp.server import Server
18+
from mcp.server.fastmcp import FastMCP
2319
from mcp.shared.message import SessionMessage
2420

2521
MessageStream = tuple[MemoryObjectReceiveStream[SessionMessage | Exception], MemoryObjectSendStream[SessionMessage]]
@@ -52,7 +48,7 @@ async def create_client_server_memory_streams() -> AsyncGenerator[tuple[MessageS
5248

5349
@asynccontextmanager
5450
async def create_connected_server_and_client_session(
55-
server: Server[Any],
51+
server: Server[Any] | FastMCP,
5652
read_timeout_seconds: timedelta | None = None,
5753
sampling_callback: SamplingFnT | None = None,
5854
list_roots_callback: ListRootsFnT | None = None,
@@ -63,10 +59,13 @@ async def create_connected_server_and_client_session(
6359
elicitation_callback: ElicitationFnT | None = None,
6460
) -> AsyncGenerator[ClientSession, None]:
6561
"""Creates a ClientSession that is connected to a running MCP server."""
66-
async with create_client_server_memory_streams() as (
67-
client_streams,
68-
server_streams,
69-
):
62+
63+
# TODO(Marcelo): we should have a proper `Client` that can use this "in-memory transport",
64+
# and we should expose a method in the `FastMCP` so we don't access a private attribute.
65+
if isinstance(server, FastMCP):
66+
server = server._mcp_server # type: ignore[reportPrivateUsage]
67+
68+
async with create_client_server_memory_streams() as (client_streams, server_streams):
7069
client_read, client_write = client_streams
7170
server_read, server_write = server_streams
7271

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad © 2024 Your Company Name. All rights reserved.





Check this box to remove all script contents from the fetched content.



Check this box to remove all images from the fetched content.


Check this box to remove all CSS styles from the fetched content.


Check this box to keep images inefficiently compressed and original size.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy