|
| 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 framework, |
| 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. |
0 commit comments