Testing a server¶
The helpers in aiohttp_tiny_mcp.testing make an application server test small without replacing the MCP protocol with mocks.
Helper |
Use it when |
What runs |
|---|---|---|
connect |
The handler result is the subject |
Real revision encoding, dispatch, and decoding over in-memory streams |
over_http |
Headers, sessions, streams, or origin rules matter |
A real aiohttp endpoint and HTTP client on loopback |
serving |
The test needs the endpoint URL itself |
A real aiohttp endpoint on a temporary loopback port |
every_revision |
One declaration must work across all supported revisions |
connect once per revision |
Copy this body into an async pytest test. It creates a registry, registers a tool, and calls it through the same protocol path a client uses. No documentation fixture or open port is involved.
from pydantic import BaseModel
from aiohttp_tiny_mcp import Registry
from aiohttp_tiny_mcp.testing import connect, every_revision
class Add(BaseModel):
a: int
b: int
class Sum(BaseModel):
total: int
registry = Registry("calculator", "1.0")
@registry.tool
async def add(args: Add) -> Sum:
"""Add two integers."""
return Sum(total=args.a + args.b)
async with connect(registry) as client:
result = await client.call_tool("add", {"a": 2, "b": 3})
assert result.structured_content == {"total": 5}
results = await every_revision(
registry,
lambda client: client.call_tool("add", {"a": 2, "b": 3}),
)
assert {version: result.structured_content for version, result in results.items()} == {
"2026-07-28": {"total": 5},
"2025-11-25": {"total": 5},
"2025-06-18": {"total": 5},
"2025-03-26": {"total": 5},
"2024-11-05": {"total": 5},
}
The helpers are async context managers. They close streams, HTTP clients, and temporary servers when the block exits. Use a production Client and a deployed URL in integration tests that must cover your proxy, TLS, or load balancer.
Test the HTTP boundary¶
Use over_http when the request itself is part of the behaviour. For example, this sends the header through a real aiohttp request:
from pydantic import BaseModel
from aiohttp_tiny_mcp import Exchange, Registry
from aiohttp_tiny_mcp.testing import over_http
class Nothing(BaseModel):
pass
registry = Registry("headers", "1.0")
@registry.tool
async def request_id(args: Nothing, ex: Exchange) -> str:
"""Read the request identifier set by an HTTP caller."""
return ex.request.headers["X-Request-Id"]
async with over_http(registry, headers={"X-Request-Id": "example-42"}) as client:
result = await client.call_tool("request_id", {})
assert result.content[0].text == "example-42"
The in-memory connect helper deliberately has no HTTP request, so use over_http for middleware, authentication, headers, sessions, response streams, and origin checks.