Skip to content

Latest commit

 

History

7 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

hello-mcp

A minimal MCP 2026-07-28 server/client pair, implemented twice — once in Rust with turul-mcp-framework, once in Python with FastMCP — used to verify cross-language interop and compare framework overhead on identical add/del tools.

MCP 2026-07-28 is stateless: no initialize handshake, no Mcp-Session-Id. Every request carries MCP-Protocol-Version / Mcp-Method headers and a params._meta block; named calls also carry Mcp-Name.

Layout

hello-mcp/
  Cargo.toml           # Rust workspace root
  server/               # turul-mcp-framework server (add/del tools)
  client/               # turul-mcp-client functional test client
  client-perf/          # turul-mcp-client latency/throughput probe
  python/
    server/             # FastMCP server (same add/del tools)
    client/              # FastMCP functional test client
    client-perf/         # FastMCP latency/throughput probe
  scripts/
    build.sh             # cargo build --release + uv sync x3
    test-functional.sh   # full 2x2 client x server correctness matrix
    test-perf.sh          # full 2x2 client x server perf matrix
    test-all.sh            # build + functional + perf, in order

Prerequisites

  • Rust (stable, edition 2024 toolchain)
  • uv — manages Python 3.14 automatically, no separate install needed
  • jq/curl are only used for manual wire-level probing, not by the scripts

All scripts set UV_MANAGED_PYTHON=1, forcing uv's own downloaded Python 3.14 build rather than a distro-packaged one that happens to satisfy the version constraint (e.g. apt's python3.14 on newer Ubuntu). We hit a real case of this: Ubuntu's system Python 3.14.0 segfaults inside CPython's own _asyncio C extension during asyncio.run() shutdown, under this project's client code specifically — uv's python-build-standalone 3.14.0 does not. If you run uv/Python commands manually outside the scripts, set UV_MANAGED_PYTHON=1 yourself (or rm -rf .venv && UV_MANAGED_PYTHON=1 uv sync to fix an existing venv built against the wrong interpreter).

Quick start

git clone https://github.com/aussierobots/hello-mcp.git
cd hello-mcp
./scripts/build.sh            # cargo build --release; uv sync x3 (Python 3.14 pinned)
./scripts/test-functional.sh  # correctness: both clients x both servers
./scripts/test-perf.sh        # perf: both clients x both servers, 1000 calls x 3 runs

Or all three in one go:

./scripts/test-all.sh [ITERATIONS] [RUNS]   # defaults: 1000 3

Servers bind to 127.0.0.1:8000 (Rust) and 127.0.0.1:8100 (Python); the scripts start and stop both automatically and clean up on exit.

Binaries land in ./target/release/ unless CARGO_TARGET_DIR (or a .cargo/config.toml build.target-dir) points elsewhere — scripts/*.sh resolve the real path via cargo metadata automatically, so they work either way; the manual commands below assume the default location.

Running a server manually

# Rust
./target/release/hello-mcp                     # http://127.0.0.1:8000/mcp

# Python (uvloop + httptools + JIT enabled)
cd python/server && PYTHON_JIT=1 uv run python server.py   # http://127.0.0.1:8100/mcp

Running a client manually

./target/release/hello-mcp-client <url>                        # Rust
(cd python/client && uv run python client.py <url>)            # Python

# perf probes: <url> [iterations=1000] [runs=3]
./target/release/hello-mcp-client-perf <url> 1000 3
(cd python/client-perf && uv run python perf.py <url> 1000 3)

Performance

Rust binaries are built with LTO + codegen-units=1 + strip (see [profile.release] in the workspace Cargo.toml). The Python server enables uvloop + httptools (in place of the default asyncio loop / h11 parser), disables uvicorn's per-request access log, and runs with Python 3.14's experimental JIT (PYTHON_JIT=1).

Latest benchmark, ./scripts/test-perf.sh 1000 3 — 24,000 total calls, 0 errors, localhost, single persistent connection per pairing:

Client → Server Mean latency p95 Throughput
Rust → Rust (turul) 0.032ms 0.04ms ~31,570 calls/sec
Rust → Python (FastMCP) 0.64ms 0.67ms ~1,560 calls/sec
Python → Rust (turul) 0.97ms 0.99ms ~1,030 calls/sec
Python → Python (FastMCP) 1.20ms 1.41ms ~833 calls/sec

(p99 over only 3×1000-sample runs is noisy — single-request outliers show up in some runs — so p95 is the more representative tail figure here; see the raw test-perf.sh output for full percentiles per run.)

Framework overhead, isolated: holding the client fixed (Rust, release build) and varying only the server cancels out client-language cost — turul is ~20x faster than FastMCP server-side (0.032ms vs 0.64ms mean). With a Python client instead, the end-to-end gap shrinks to ~1.2x, since asyncio/Starlette client-side overhead dominates and masks most of the server-side difference. The 20x number is the real framework delta; the ~1.2x number is what you'd feel from a Python-based agent talking to either server.

Un-optimized (debug Rust, no uvloop/httptools/JIT) the gap was only ~3.3x — release build + LTO is what unlocks turul's full advantage over FastMCP; the uvloop/httptools/access-log/JIT changes on the Python side each closed a further ~10-15% of FastMCP's own latency, without changing the underlying ratio much (the gap is dominated by Starlette routing + Pydantic validation

  • JSON-RPC dispatch layers, not the event loop or HTTP parser).

Known upstream gaps

  • turul-mcp-server (Rust): McpServerBuilder didn't expose .description()/.website_url() until 0.4.2, even though the underlying Implementation protocol type always supported both — fixed upstream, Cargo.toml here just pins "0.4".
  • fastmcp (Python, 4.0.0b3): FastMCP.__init__ accepts website_url and instructions but has no description parameter at all, despite the wire-level Implementation type (and the underlying mcp SDK's low-level Server) both supporting it — a one-line gap in FastMCP's own constructor forwarding, unresolved as of this beta.

About

Minimal MCP 2026-07-28 server/client in Rust (turul-mcp-framework) and Python (FastMCP) — interop + perf comparison

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages