What it costs¶
This page is the motivation for the library, stated in numbers rather than in
adjectives. It compares this package with the official Python SDK on one
tools/call, per protocol revision, for the server and for the client
separately, over HTTP and over a pipe.
The harness lives in benchmarks/ in the source tree and is not installed
with the package. Reproduce any row with:
uv run python -m benchmarks.run --suite server
uv run python -m benchmarks.run --suite client
uv run python -m benchmarks.run --suite stdio
uv run python -m benchmarks.run --concurrency 32 --calls 3000
How to read it¶
Both libraries serve the same tool – add two integers, return a model. What differs is the protocol machinery between the socket and that function.
The official SDK row is its current high-level server (MCPServer in
mcp==2.0.0; older SDK releases called the comparable API FastMCP). This is
an implementation comparison, not a claim that the two projects expose the
same public class names. The benchmark uses the SDK version pinned in the
conditions below.
Over HTTP they do not stand on the same web framework: this package is aiohttp, the official SDK is Starlette on uvicorn. So every HTTP table ends with two rows that speak no protocol at all – one hand-written handler per stack, parsing the request and answering a fixed JSON body. Compare a row with the floor of its own stack, not with the other library’s. Without those rows a reader compares two web servers and believes they compared two MCP libraries.
The stdio tables have no such problem, and are the cleanest comparison here: one subprocess, one pipe, no framework on either side.
The server rows have no client library in them. The bytes of one real request
are recorded, then replayed with nothing but aiohttp in the way. The client
rows hold the server constant and vary the client, including the cross
pairings: this package’s client against the SDK’s servers, and the SDK’s
client against this one.
Conditions¶
Apple M4, 10 cores, macOS 26.2. Python 3.10.11, aiohttp 3.14.3, pydantic
2.13.4, mcp 2.0.0, uvicorn 0.51.0. Loopback, no TLS. One call at a time,
--calls 1200 --warmup 200 --repeats 3; every row is measured three times in
rotation and keeps its best round.
Every logger is set to ERROR, on both sides. This matters: starting the
SDK’s server sets the root logger to INFO and installs a rich handler for
the whole process, after which its stateless server writes a line per request
and httpx writes another. Left on, that costs whoever talks to it 17-25% of
its rate. It is a real cost of a deployment that logs at INFO, and it is not
what these tables measure, so it is turned off for both alike.
Server over HTTP, per revision¶
Calls a second.
Server |
2024-11-05 |
2025-03-26 |
2025-06-18 |
2025-11-25 |
2026-07-28 |
|---|---|---|---|---|---|
aiohttp-tiny-mcp |
3687 |
3562 |
3629 |
3607 |
3624 |
official SDK, with session |
1078 |
1031 |
1033 |
1030 |
1393 |
official SDK, stateless |
889 |
896 |
904 |
868 |
1462 |
aiohttp, no protocol |
4779 |
||||
uvicorn, no protocol |
3731 |
The same tool returning a plain string instead of a model:
Server |
2024-11-05 |
2025-03-26 |
2025-06-18 |
2025-11-25 |
2026-07-28 |
|---|---|---|---|---|---|
aiohttp-tiny-mcp |
3659 |
3736 |
3730 |
3781 |
3656 |
official SDK, with session |
1044 |
1025 |
1019 |
1023 |
1656 |
official SDK, stateless |
878 |
888 |
892 |
885 |
1668 |
aiohttp, no protocol |
4686 |
||||
uvicorn, no protocol |
3705 |
The revision costs this package nothing measurable. Five revisions, and
the spread between them is smaller than the spread between two runs of the
same row. Serving 2024-11-05 and 2026-07-28 from one set of handlers is
not paid for at call time.
What the SDK spends is largely the session. Same server, same handler,
same stack; only the revision differs. The four revisions that carry a session
sit near 1000 calls a second. 2026-07-28, the one with no session at all,
reaches 1400-1670.
The SDK’s stateless mode is the slower of its two. stateless_http=True
does not remove the session – it builds and destroys one per request – and
on the legacy revisions it costs about 15% against keeping one.
At 32 calls in flight the gap widens rather than closes: this package reaches 5510-5913 against a floor of 8135, while the SDK’s session revisions reach 1051-1159 against a floor of 5715, with a 99th percentile an order of magnitude apart.
Client over HTTP, per revision¶
The server is held constant down each block, so what varies is the cost of building a request and reading a reply.
Client |
Against tiny-mcp |
Against SDK stateless |
Against SDK session |
|---|---|---|---|
raw aiohttp, no client library |
3692 |
866 |
1027 |
aiohttp-tiny-mcp, 2024-11-05 |
3353 |
975 |
1188 |
aiohttp-tiny-mcp, 2025-03-26 |
3395 |
994 |
1179 |
aiohttp-tiny-mcp, 2025-06-18 |
3491 |
983 |
1183 |
aiohttp-tiny-mcp, 2025-11-25 |
3480 |
1009 |
1164 |
aiohttp-tiny-mcp, 2026-07-28 |
3345 |
1418 |
1404 |
official SDK, 2025-11-25 |
783 |
384 |
399 |
Against the same server the two clients run at 3480 and 783 calls a second:
about a millisecond of client-side work per call. This package’s client stays
within 5-9% of a hand-written aiohttp loop.
The official client appears once rather than once per revision. It offers only
LATEST_PROTOCOL_VERSION and negotiates down, and nothing in its API asks for
another; its row is labelled with what the handshake actually settled on.
Over stdio¶
One subprocess, one pipe, no web framework on either side. Calls a second.
Pairing |
|
|
|---|---|---|
raw pipe, no protocol |
14338 |
14072 |
tiny-mcp 2024-11-05 client and server |
6797 |
7163 |
tiny-mcp 2025-03-26 client and server |
6845 |
7121 |
tiny-mcp 2025-06-18 client and server |
7031 |
7165 |
tiny-mcp 2025-11-25 client and server |
6773 |
7265 |
tiny-mcp 2026-07-28 client and server |
5813 |
6415 |
official SDK client -> tiny-mcp server |
3350 |
3567 |
tiny-mcp 2025-11-25 client -> SDK server |
1870 |
1860 |
official SDK client and server |
1468 |
1453 |
This is the comparison with the fewest things in it, and the ratios are the largest: against the same server the two clients are 6773 and 3350; against the same client the two servers are 6773 and 1870.
What these numbers are not¶
Not a claim about your machine. One run, one laptop, loopback, no TLS, no proxy. Compare rows within a table, never a table with someone else’s.
Not a claim about your handler. Both tools here are trivial on purpose. Real work would swamp every difference on this page, which is the honest thing to say about it: if a tool call spends 50 ms in a database, none of this matters.
Not a feature comparison. The SDK has sampling, roots, middleware and an extension mechanism; this package has bearer-token verification for an OAuth protected resource. See how this compares with the official SDK for what each one does and does not do.
Not a distributed-system benchmark. Every server and driver runs on one machine over loopback. The benchmark does not exercise a shared
SessionStore, a sharedHub, a load balancer, a second worker, a remote database, or a question that resumes on another request. Those are the conditions this project was designed for, but measuring them requires a deployment benchmark with real backend implementations and network topology. The numbers here describe the per-request protocol overhead before application work and distributed coordination are added.Not a many-client benchmark. The HTTP rows reuse one initialized client session per server while calls are concurrent. This measures contention in a hot session; it does not model a fleet of independent clients opening and refreshing sessions at the same time.
Best-round results are optimistic. Each row is measured in several rotated rounds and the fastest round is kept. This reduces the effect of a busy laptop, but it also favors unusually quiet rounds. Treat the values as within-run comparisons, not confidence intervals or capacity guarantees.
Not a latency promise for remote-web-mcp. TLS, proxy buffering, network RTT, backend contention, open-stream lifetimes, and failover dominate a remote deployment. Use these rows to compare the two implementations under identical local conditions, then measure your actual deployment separately.
Not free of loose ends. Over HTTP the raw driver, which should be the ceiling, runs 10-15% below both client libraries against servers that frame answers as event streams, and the cause is not isolated.
benchmarks/README.mdrecords that rather than hiding it.