Files
Obsidian/X-Articles/mcp/MCP Server Performance Benchmark v2 15 Implementations, IO-Bound Workloads.md
coco 8b94d76799 a
2026-07-21 13:57:15 +08:00

32 KiB
Raw Permalink Blame History

title, source, author, published, created, description, tags
title source author published created description tags
MCP Server Performance Benchmark v2: 15 Implementations, I/O-Bound Workloads https://www.tmdevlab.com/mcp-server-performance-benchmark-v2.html
Thiago Mendes
2026-02-28 2026-03-07 Expanded MCP server performance benchmark covering 15 implementations across Rust, Java (Spring MVC, WebFlux, Virtual Threads, GraalVM native), Quarkus, Micronaut, Go, Bun, Node.js, and Python. 39.9 million requests across three independent runs with I/O-bound workloads (Redis + HTTP API), 0% error rate.
clippings

Abstract

This experiment presents a comprehensive performance analysis of 15 Model Context Protocol (MCP) server implementations spanning Rust, Java (Spring MVC, WebFlux, Virtual Threads), Quarkus, Micronaut (JVM and GraalVM native images), Go, Bun, Node.js, and Python. Three independent runs totaling 39.9 million requests were executed with I/O-bound workloads (Redis + HTTP API), achieving a 0% error rate across all 15 servers in all 3 runs.

Key Findings: Rust leads throughput at 4,845 RPS with only 10.9 MB RAM and a CV of 0.04%. Quarkus leads latency at 4.04ms average and 8.13ms P95. Go and Java remain competitive at 3,616 and 3,540 RPS respectively. Classic blocking I/O (Spring MVC) outperforms reactive WebFlux at 50 VUs. GraalVM native images uniformly reduce memory (27-81%) while reducing throughput (20-36%), with Quarkus-native as the best native trade-off. Bun delivers 2.2x the RPS of Node.js on identical application code. Python with 4 workers and uvloop reaches 259 RPS. The bottleneck is FastMCP session overhead, not the ASGI server.

Recommendations: For high-load production deployments, Rust offers unmatched throughput and resource efficiency. Quarkus is the optimal choice when latency SLAs are primary. Go provides an excellent balance of performance, memory, and operational simplicity. Java Spring MVC (blocking) remains a strong Tier 1 choice. Native images are justified where memory is constrained and throughput requirements are moderate (below 3,500 RPS). JavaScript and Python runtimes are better suited for low-to-moderate traffic MCP deployments.

Keywords: MCP, Model Context Protocol, Performance Benchmark, Rust, Quarkus, GraalVM Native Image, Virtual Threads, Spring WebFlux, Micronaut, Bun, Go, Java, Node.js, Python, k6, Redis, Load Testing, Streamable HTTP

1. Introduction and Motivation

The MCP ecosystem is growing rapidly. Organizations adopting MCP servers face a widening set of implementation trade-offs: native compilation via GraalVM, reactive vs blocking I/O models within the JVM, alternative JavaScript runtimes, and the impact of real external I/O workloads on framework-level decisions. v2 was designed to surface these trade-offs empirically.

The v1 benchmark drew valid community feedback: no Quarkus, no GraalVM native images, no Virtual Threads, no reactive frameworks, no Micronaut, no Rust, and Python running in a single-worker default configuration. Version 2 is the direct experimental response to those criticisms, expanding from 4 to 15 implementations and replacing synthetic CPU tools with real Redis and HTTP API workloads.

Research Questions:

  • How do JVM-based implementations compare across blocking, reactive, and virtual thread concurrency models?
  • What are the throughput and latency trade-offs between JVM and GraalVM native images under I/O-bound load?
  • Does Rust belong in the MCP server performance conversation?
  • Can Bun's JavaScriptCore runtime change the JavaScript performance story for MCP servers?
  • What is the realistic production ceiling for optimized Python (multi-worker + uvloop)?

2. Experimental Setup

2.1 From v1 to v2: What Changed

Dimension v1 v2
Servers 4 15
Java variants 1 (Spring Boot + Spring AI) 6 (Spring MVC, WebFlux, VT + 3 native images)
New runtimes None Rust, Quarkus, Micronaut, Bun
Tools 4 synthetic (fibonacci, HTTP, JSON, sleep) 3 I/O-bound (Redis + HTTP API)
Total requests ~3.9M ~39.9M
Runs 3 rounds 3 full independent runs
CPU per container 1.0 vCPU 2.0 vCPUs
Memory per container 1 GB 2 GB

Workload Design Rationale: Synthetic tools stress isolated performance dimensions (pure CPU, artificial delay). I/O-bound tools are representative of how MCP servers operate in production: fetching data from external services and reading/writing persistent state. The v2 workload makes framework-level I/O handling decisions visible in the results in a way that synthetic tools cannot.

2.2 Test Environment

Component Specification
Host Microsoft Azure VM, 8 vCPUs, 32 GB RAM
OS Ubuntu 24.04 LTS
Container Runtime Docker with Docker Compose
CPU Limit (per MCP server) 2.0 vCPUs
Memory Limit (per MCP server) 2 GB
Infrastructure Redis 7 Alpine (0.5 vCPU / 512 MB) + Go API service (2 vCPUs / 2 GB)
Network Docker bridge (inter-container, localhost)
Test Runs 3 independent runs (February 27-28, 2026)

2.3 Server Implementations

Server Framework / SDK Runtime
rust rmcp 0.17.0 Rust / Tokio
quarkus Quarkus 3.31.4 MCP Server SDK Java 21 / Vert.x
go mcp-go Go 1.23
java Spring Boot 4 MVC + Spring AI Java 21
java-vt Spring Boot 4 + Project Loom Java 25
java-webflux Spring Boot 4 WebFlux + Spring AI Java 21 / Netty
micronaut Micronaut 4.10.8 / MCP SDK 0.0.19 Java 21
quarkus-native Quarkus native image GraalVM 23 / native
java-native Spring Boot native image GraalVM 25 / native
java-vt-native Spring Boot VT native image GraalVM 25 / native
java-webflux-native Spring Boot WebFlux native GraalVM 25 / native
micronaut-native Micronaut native image GraalVM 23 / native
bun Express + MCP SDK Bun 1 / JavaScriptCore
nodejs Express + MCP SDK Node.js 22 / V8
python FastMCP + Starlette Python 3.11 / CPython

Production-Representative Configurations: Each server uses production-appropriate settings: connection pools tuned, workers configured, json_response enabled where supported. This follows the same "Standard DX" principle as v1, extended to include configurations that would survive real production load rather than default out-of-box settings.

2.4 Benchmark Tools and Workload

Each server implements three identical tools performing I/O-bound operations against a Redis instance and an HTTP API service (100,000 products in-memory):

Tool I/O Operations Performance Dimension
search_products HTTP GET /products/search + Redis ZRANGE (parallel) Parallel async I/O, HTTP client pool
get_user_cart Redis HGETALL, then HTTP GET /products/{id} + Redis LRANGE (parallel) Sequential + parallel I/O, Redis read patterns
checkout HTTP POST /cart/calculate + Redis pipeline INCR+RPUSH+ZADD (parallel) Write throughput, Redis pipeline efficiency
// k6 Load Profile — v2
export const options = {
    stages: [
        { duration: '10s', target: 50 },  // Ramp-up to 50 VUs
        { duration: '5m', target: 50 },   // Sustained load
        { duration: '10s', target: 0 },   // Ramp-down
    ],
    thresholds: {
        'http_req_failed': ['rate<0.05'],
    },
};
// First 60 seconds excluded from metrics (WARMUP_SECONDS=60)

2.5 Test Methodology

Figure 1: Per-server test cycle ensuring isolation and reproducibility

  1. Redis Isolation: Flush and re-seed before each server to ensure identical data state across all 15 servers.
  2. Server Isolation: Only one MCP server running during each test period, eliminating resource contention.
  3. Warmup: 60 seconds of real tool calls excluded from metrics to allow JIT compilation on all code paths (5 init sessions + 9 tool call sessions per server).
  4. Sustained Load: 50 VUs, 5 minutes, all 3 tools called in rotation. VU N uses user-(N%1000), providing 50 distinct users.
  5. Parallel Stats Collection: Docker stats sampled alongside k6 metrics for CPU and memory.

Run Timestamps:

  • First run: February 27, 2026 at 21:08:47 UTC
  • Second run: February 27, 2026 at 23:34:47 UTC
  • Third run: February 28, 2026 at 00:00:47 UTC

3. Implementation Details

3.1 Rust (rmcp SDK)

SDK Bug Discovered During Benchmarking: SSE Hardcoded in rmcp v0.16

During our benchmarking, we identified an anomalous fixed latency on every tool that performed HTTP calls. In an isolated run with rmcp v0.16 defaults, the search_products tool (pure Redis path) ran at 1.11ms avg, while get_user_cart and checkout were stuck at 40.84ms regardless of actual I/O time. We traced the cause to rmcp v0.16, which hardcodes text/event-stream for all responses, even stateless request-response exchanges. Every response carries chunked transfer-encoding, SSE framing, and keep-alive pings: approximately 40ms of pure transport overhead per request on any tool that returns an HTTP-originated payload. The MCP spec explicitly permits application/json for stateless responses. rmcp was simply not implementing that path.

This issue is fixed in rmcp v0.17.0, released on February 27, 2026, which ships the json_response option as an official feature. The benchmark was run with the equivalent patch applied to v0.16. The server implementation has since been updated to use rmcp 0.17.0 from crates.io directly, with no local patch required.

Configuration RPS Avg Latency Tool Breakdown
Without json_response patch (SSE default) 1,283 27.59 ms search_products: 1.11ms / get_user_cart: 40.84ms / checkout: 40.84ms
With json_response patch 4,845 5.09 ms search_products: 6.12ms / get_user_cart: 5.63ms / checkout: 3.51ms

Fix Merged Upstream: PR #683 shipped in rmcp v0.17.0

We submitted the fix to the official modelcontextprotocol/rust-sdk repository as PR #683. The patch adds json_response: bool to StreamableHttpServerConfig, backwards-compatible by default (false preserves the original SSE path unchanged). It was refined during code review (maintainers suggested tokio::select! with cancellation safety and tracing::info! logging), merged, and shipped as part of rmcp v0.17.0 on February 27, 2026.

Key Characteristics:

  • rmcp 0.17.0 (official release) with json_response: true in StreamableHttpServerConfig
  • Tokio async runtime with deadpool-redis connection pool (pool size 100)
  • Parallel tool handlers via tokio::spawn
  • Redis pipeline combines 3 write operations in checkout into a single RTT
  • 10.9 MB average RAM (lowest of all 15 servers)
  • CV 0.04% (most stable across all 3 runs)

3.2 Quarkus (Vert.x / Mutiny)

Default Pool Limits Caused Catastrophic Failure (0 RPS): Quarkus REST Client Reactive ships with a conservative connection pool (default approximately 50 connections per host). Under 50 VUs with parallel I/O, the pool exhausted instantly and all requests queued until the 30-second timeout, yielding less than 1 effective RPS. Other frameworks passed the same test at 3,000+ RPS.

quarkus.rest-client.api-service.connection-pool-size=1000
quarkus.rest-client.api-service.keep-alive-enabled=true
quarkus.redis.max-pool-size=100
quarkus.redis.max-pool-waiting=1000

Key Characteristics:

  • Reactive Vert.x/Mutiny event loop model, non-blocking I/O throughout
  • Lowest CPU usage among all Java frameworks (161.7% avg, vs 200%+ for others)
  • 194.5 MB RAM (lowest JVM footprint among JVM Java variants)
  • Best latency of all 15 servers: 4.04ms avg, 8.13ms P95, 11.16ms P99

3.3 Go (mcp-go)

var httpClient = &http.Client{
    Transport: &http.Transport{
        MaxIdleConns:        100,
        MaxIdleConnsPerHost: 100,
        IdleConnTimeout:     90 * time.Second,
    },
    Timeout: 10 * time.Second,
}

Why Pool Tuning Mattered:http.DefaultClient uses MaxIdleConnsPerHost=2. Under 50 VUs, goroutines contended for two idle keep-alive connections, causing TCP connection churn and P95 latency spikes of 61ms. Replacing it with a tuned transport reduced P95 to 17.62ms.

Key Characteristics:

  • Goroutine-per-request concurrency model with net/http standard library
  • 23.9 MB average RAM (second lowest after Rust)
  • Static binary, no runtime dependency
  • CV 0.19% (third most stable overall)

3.4 Java (Spring MVC, Blocking I/O)

Blocking Outperforms Reactive at 50 VUs: With 50 VUs and fast I/O (approximately 6ms avg), the Tomcat thread pool never exhausts. Sequential blocking code eliminates reactive scheduling overhead. Java MVC (3,540 RPS) outperforms Java WebFlux (3,032 RPS) in this workload. The blocking model is appropriate when the concurrency level is lower than the thread pool capacity.

Key Characteristics:

  • Spring Boot 4 MVC with Tomcat thread pool executor
  • Sequential blocking I/O via RestClient and StringRedisTemplate per request
  • No CompletableFuture or reactive primitives
  • 368.1 MB average RAM (standard Spring Boot JVM footprint)

3.5 Java Virtual Threads (Project Loom)

java-vt-native: Worst Native Image Configuration: The java-vt-native image is the worst-performing native image (19.06ms avg latency, 2,447 RPS). GraalVM's AOT-compiled VT continuation mechanism adds overhead that the JIT would otherwise optimize at runtime. Combining VT scheduling overhead with AOT compilation is the least efficient native image configuration in this benchmark.

Key Characteristics:

  • spring.threads.virtual.enabled=true (canonical Spring Boot 4 configuration)
  • Java 25 selected to include JEP 491 (delivered in Java 24): Virtual Threads can now acquire and release synchronized monitors without pinning the carrier thread. Before this fix, any synchronized block inside the call stack (including Spring internals) would pin the Virtual Thread to its OS carrier thread for the duration, eliminating the concurrency benefit for I/O-bound code.
  • Same RestClient and StringRedisTemplate as Java MVC
  • 349.7 MB RAM (slightly higher than MVC due to Virtual Thread scheduler metadata)
  • Competitive throughput at 3,482 RPS

3.6 Java WebFlux (Reactor / Netty)

Netty Off-Heap Memory: WebFlux uses Netty as its HTTP server instead of Tomcat. Netty allocates I/O buffers outside the JVM heap (off-heap / direct memory) via PooledByteBufAllocator. These buffers are invisible to -Xmx and persist in GraalVM native images. This explains the high memory peak (663 MB max) and why java-webflux-native also has elevated memory (351 MB avg) despite native compilation.

Key Characteristics:

  • Reactor event loop model, non-blocking I/O throughout
  • Competitive average latency (8.89ms avg) but high P99 tail in checkout (47ms)
  • 484.6 MB average RAM (highest among JVM variants, due to Netty off-heap buffers)

3.7 Micronaut

GraalVM 23 Required for micronaut-native: The Dockerfile uses graalvm/native-image-community:23. The previous GraalVM version could not fully inline Micronaut's annotation-driven dispatch at compile time, causing a -49% RPS regression vs JVM in earlier testing (worst of any stack). GraalVM 23 improves closed-world analysis for annotation metadata, reducing the regression significantly.

micronaut-native CPU Throttling: CPU consistently exceeds 200% (avg 233.2%), above the 2-vCPU container limit. Docker CFS burst behavior allows short-window overages before throttling. The reported throughput (2,161 RPS) may be suppressed by CFS quota enforcement. On dedicated hardware, performance could differ.

Key Characteristics:

  • Micronaut MCP Server SDK 0.0.19 (pre-release)
  • Compile-time dependency injection and annotation processing
  • Netty server (same off-heap memory pattern as WebFlux)
  • JVM variant competitive at 3,382 RPS

3.8 GraalVM Native Images: Cross-Stack Analysis

Five server stacks were benchmarked in both JVM and GraalVM native image configurations, revealing consistent trade-off patterns across all stacks.

Stack JVM RPS Native RPS RPS Regression JVM RAM avg Native RAM avg RAM Saving
Quarkus 4,739 3,449 -27% 194 MB 36 MB -81%
Java MVC 3,540 2,316 -35% 368 MB 178 MB -52%
Java VT 3,482 2,447 -30% 350 MB 194 MB -44%
WebFlux 3,032 2,413 -20% 485 MB 351 MB -28%
Micronaut 3,382 2,161 -36% 216 MB 63 MB -71%

The Trade-off Pattern: In I/O-bound workloads under sustained load, JIT-compiled code consistently outperforms AOT. The JIT observes actual runtime call patterns and optimizes hot paths adaptively at runtime. AOT compiles conservatively at build time without that observability. The throughput regression from native images (20-36%) becomes measurable only when the server is under significant and sustained load. At low or intermittent request rates, the difference is negligible and the memory savings dominate. The benefits of native images (fast startup, predictable memory footprint) do not translate to throughput advantages in sustained high-load I/O scenarios. Quarkus-native is the strongest exception: it maintains competitive P95 latency (15.92ms, 4th best overall) despite the RPS regression.

3.9 Node.js and Bun

SDK Per-Request Instantiation: An Intentional Design Constraint

Creating a new McpServer and StreamableHTTPServerTransport per HTTP request is the intentional design for stateless MCP servers. The SDK throws "Stateless transport cannot be reused across requests" on any reuse attempt. This sets a fixed 5-10ms overhead floor per request that no framework swap can eliminate.

Experiments attempted and reverted: undici pool with connections: 100 regressed RPS by 28%. Hono + WebStandardStreamableHTTPServerTransport cost 23% RPS due to IncomingMessage-to-Request adaptation overhead on Node.js. Express with StreamableHTTPServerTransport is retained.

// Cluster entry (WEB_CONCURRENCY=4 worker processes)
import cluster from 'cluster';
const WORKERS = parseInt(process.env.WEB_CONCURRENCY || '1');
if (cluster.isPrimary) {
    for (let i = 0; i < WORKERS; i++) cluster.fork();
} else {
    // Per-request McpServer (SDK design constraint — cannot be reused)
    app.post('/mcp', async (req, res) => {
        const server = createMcpServer();
        const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
        await server.connect(transport);
        await transport.handleRequest(req, res, req.body);
    });
}

Bun vs Node.js: Same Code, 2.2x Throughput: Both containers run the exact same index.js with WEB_CONCURRENCY=4. Bun's JavaScriptCore JIT and native fetch() deliver 876 RPS vs Node.js 423 RPS (2.2x ratio). Memory cost: Bun uses 540.8 MB vs Node.js 389.2 MB (+154 MB). Both runtimes saturate 2 vCPUs at approximately 200% with 4 workers.

3.10 Python (FastMCP + uvloop)

v1 Criticism Addressed: v1 ran Python with a single-worker uvicorn. v2 runs 4 workers with uvloop, directly responding to community feedback. The json_response=True option was already present in v1.

Granian Experiment: ASGI Server is Not the Bottleneck: A Rust-based ASGI server (Granian with Tokio I/O) was tested as a replacement for uvicorn. Result: -12% RPS. The bottleneck is not the ASGI server layer. It is FastMCP's per-request MCP session processing in CPython. The ASGI server choice is irrelevant to Python MCP performance.

Key Characteristics:

  • GIL limits true parallelism within each worker process
  • 4 workers x approximately 65 RPS per worker = 259 RPS total
  • 258.6 MB average RAM
  • The bottleneck is FastMCP session overhead in CPython, not uvicorn or network I/O

4. Results and Analysis

4.1 Overall Performance Metrics

Perfect Reliability: 39,958,616 total requests across 15 servers and 3 independent runs. Error rate: 0% for every server in every run. All 15 implementations demonstrated robust MCP protocol compliance under sustained load.

Server RPS (avg) ▼ Avg Latency P95 Latency Requests Served (3 runs)
rust 4,845 5.09 ms 10.99 ms 4,724,624
quarkus 4,739 4.04 ms 8.13 ms 4,620,520
go 3,616 6.87 ms 17.62 ms 3,525,424
java 3,540 6.13 ms 13.71 ms 3,452,064
java-vt 3,482 9.03 ms 18.43 ms 3,395,384
quarkus-native 3,449 10.36 ms 15.92 ms 3,362,784
micronaut 3,382 9.75 ms 17.00 ms 3,297,208
java-webflux 3,032 8.89 ms 27.48 ms 2,956,424
java-vt-native 2,447 19.06 ms 36.82 ms 2,385,880
java-webflux-native 2,413 14.43 ms 44.17 ms 2,353,056
java-native 2,316 16.20 ms 42.44 ms 2,258,592
micronaut-native 2,161 20.75 ms 36.94 ms 2,107,080
bun 876 48.46 ms 98.50 ms 853,736
nodejs 423 123.50 ms 200.07 ms 412,888
python 259 251.62 ms 342.41 ms 252,952

Reading CPU Percentages: Docker stats reports CPU usage as a percentage of a single logical CPU core. Each MCP server container was allocated 2.0 vCPUs, so 200% represents full utilization of both allocated cores. Values above 200% (most notably micronaut-native at 233.2%) occur because Docker CFS (Completely Fair Scheduler) allows short burst windows that temporarily exceed the configured quota before throttling is applied. These bursts are real CPU consumption, not a measurement artifact.

Server CPU avg (%) RAM avg (MB) Error Rate
rust 117.9% 10.9 MB 0%
quarkus 161.7% 194.5 MB 0%
java-vt 184.9% 349.7 MB 0%
micronaut 190.5% 216.3 MB 0%
go 209.9% 23.9 MB 0%
java 206.0% 368.1 MB 0%
quarkus-native 204.6% 36.1 MB 0%
java-webflux 207.2% 484.6 MB 0%
java-vt-native 211.7% 193.8 MB 0%
java-webflux-native 204.4% 351.2 MB 0%
java-native 202.8% 178.2 MB 0%
micronaut-native 233.2% 63.0 MB 0%
bun 205.8% 540.8 MB 0%
nodejs 202.2% 389.2 MB 0%
python 206.6% 258.6 MB 0%

4.2 Latency Analysis

Average latency measurements reveal four distinct performance tiers. The top tier (Rust, Quarkus, Go, Java) operates in the 4-7ms range with I/O-bound workloads. This contrasts sharply with the v1 sub-millisecond averages, which reflected synthetic CPU-bound tools. The v2 numbers represent realistic production latency with actual Redis and HTTP network round-trips.

Average Latency — All 15 Servers (ms, sorted ascending within tier)

Tier 1 (≤7ms)

Tier 2 (7–13ms)

Tier 3 (13–25ms)

Tier 4 (25ms+)

quarkus

4.04 ms

rust

5.09 ms

java

6.13 ms

go

6.87 ms


java-webflux

8.89 ms

java-vt

9.03 ms

micronaut

9.75 ms

quarkus-native

10.36 ms


java-webflux-native

14.43 ms

java-native

16.20 ms

java-vt-native

19.06 ms

micronaut-native

20.75 ms


bun

48.46 ms

nodejs

123.50 ms

python

251.62 ms

Latency Context: The v1 top performers (Java, Go) reached sub-millisecond averages on CPU-bound synthetic tools. In v2, the same servers operate at 5-7ms average because tools now perform I/O-bound network operations (2 Redis operations + 1 HTTP call per request). This is intentional: the v2 numbers reflect realistic production latency, not synthetic minimums.

4.3 Throughput Comparison

Throughput measurements reveal three clear clusters: Rust and Quarkus at 4,700-4,850 RPS, the Java/Go cluster at 3,000-3,620 RPS, and the JS/Python group at 250-880 RPS. The gap between Tier 1 and Tier 4 is approximately 19x (Rust vs Python). Within the Java ecosystem, the 10-way spread from micronaut-native (2,161 RPS) to quarkus (4,739 RPS) illustrates the significant impact of framework, concurrency model, and compilation strategy.

Throughput Stability: Rust achieved a CV (Coefficient of Variation) of 0.04% (most consistent across runs). Node.js achieved 1.61% (least consistent, but still excellent). All 15 servers achieved CV below 2% across 3 independent runs, confirming that the rankings are reliable for technology selection decisions.

4.4 Resource Efficiency

Server RPS / CPU% RPS / MB RAM CPU Efficiency Rank
rust 41.1 444.5 1st
quarkus 29.3 24.4 2nd
java-vt 18.8 10.0 3rd
micronaut 17.7 15.6 4th
go 17.2 151.3 5th
java 17.2 9.6 5th
quarkus-native 16.9 95.5 7th
java-webflux 14.6 6.3 8th
java-webflux-native 11.8 6.9 9th
java-vt-native 11.6 12.6 10th
java-native 11.4 13.0 11th
micronaut-native 9.3 34.3 12th
bun 4.3 1.6 13th
nodejs 2.1 1.1 14th
python 1.3 1.0 15th

Rust's Resource Advantage: At 4,845 RPS on 117.9% CPU and 10.9 MB RAM, Rust uses approximately 44x less memory than Java WebFlux (484.6 MB) while delivering 60% more throughput. In a 100-instance deployment, this translates to roughly 47 GB less RAM consumed. Go is the second-best option for memory-constrained deployments at 23.9 MB and 151.3 RPS per MB.

4.5 Tool-Specific Performance

Table 4 breaks down average latency (ms, consolidated across 3 runs) per tool, with each panel sorted independently to show which server leads for that specific operation.

search_products

HTTP GET + Redis ZRANGE (parallel)

Server Avg (ms)
quarkus 4.37
rust 6.12
java 6.41
go 8.41
java-vt 9.00
quarkus-native 9.71
java-webflux 9.76
micronaut 9.82
java-native 13.97
java-webflux-native 15.12
java-vt-native 15.86
micronaut-native 20.87
bun 41.47
nodejs 119.79
python 244.50

get_user_cart

Redis HGETALL + HTTP + LRANGE (parallel)

Server Avg (ms)
quarkus 4.35
rust 5.63
java 5.63
go 6.65
java-vt 8.24
java-webflux 8.56
micronaut 10.36
quarkus-native 12.45
java-native 12.74
java-webflux-native 14.63
java-vt-native 18.14
micronaut-native 22.03
bun 59.31
nodejs 141.88
python 260.39

checkout

HTTP POST + Redis pipeline (parallel)

Server Avg (ms)
quarkus 3.38
rust 3.51
go 5.57
java 6.35
java-webflux 8.34
quarkus-native 8.93
micronaut 9.06
java-vt 9.83
java-webflux-native 13.54
micronaut-native 19.34
java-native 21.88
java-vt-native 23.18
bun 44.61
nodejs 108.84
python 249.97

Key Tool Observations:

  • checkout is consistently the fastest tool for top performers (Quarkus 3.38ms, Rust 3.51ms). Redis pipeline combines 3 write operations into 1 RTT, eliminating the per-operation network overhead.
  • search_products is the slowest tool for most servers. It requires a parallel HTTP GET + Redis ZRANGE, and the HTTP call to the API service dominates the latency.
  • Java MVC's sequential I/O is visible in get_user_cart, where the server must wait for HGETALL to complete before firing the HTTP call, unlike reactive implementations that parallelize immediately.

4.6 Stability and Reproducibility

Server CV ▲ Mean RPS Std Dev Stability
rust 0.04% 4,845 2 Excellent
java-webflux-native 0.10% 2,413 2 Excellent
go 0.19% 3,616 7 Excellent
java-vt-native 0.36% 2,447 9 Excellent
java-native 0.44% 2,316 10 Excellent
quarkus 0.50% 4,739 24 Excellent
bun 0.52% 876 5 Excellent
micronaut-native 0.57% 2,161 12 Excellent
java-webflux 0.62% 3,032 19 Excellent
java 0.64% 3,540 23 Excellent
micronaut 0.70% 3,382 24 Excellent
java-vt 0.92% 3,482 32 Excellent
quarkus-native 1.13% 3,449 39 Excellent
python 1.58% 259 4 Excellent
nodejs 1.61% 423 7 Excellent

All 15 servers achieved a CV (Coefficient of Variation: the ratio of standard deviation to mean, expressed as a percentage) below 2% across the 3 independent runs. A CV below 5% is generally considered excellent for load tests on shared infrastructure. The Redis flush-and-reseed methodology eliminates state drift between servers. The 60-second warmup exclusion eliminates JIT cold-start noise. The result is stable, reproducible rankings suitable for technology selection decisions. P95 latency showed equivalent stability: 13 of 15 servers had a stable P95 trend across runs. Only quarkus-native (+1.06ms) and bun (+2.86ms) showed slight increases. Python was the sole variable entry (range of approximately 30ms across runs), consistent with CPython GIL scheduling variability.

7. Conclusion

This experimental analysis expanded the MCP server benchmark from 4 to 15 implementations, replacing synthetic CPU tools with real Redis and HTTP API workloads. The expansion revealed performance characteristics that were invisible in v1: the critical impact of connection pool configuration (Quarkus 0 RPS without tuning), the JVM vs native image throughput-memory trade-off under I/O load, the significance of runtime choice within the JavaScript ecosystem (Bun 2.2x Node.js), and the realistic production ceiling of optimized Python (259 RPS with 4 workers and uvloop).

The 39.9 million requests processed with 0% errors across all 15 servers validate the methodology's reproducibility. The CV below 2% for every server confirms that the rankings are stable. The data provides a reliable empirical basis for MCP server technology selection decisions.

Key Finding: In I/O-bound workloads representative of production MCP deployments, Rust and Quarkus lead the field at 4,845 and 4,739 RPS respectively, with Quarkus holding the best latency at 4.04ms average and 8.13ms P95. Go remains the optimal choice for teams prioritizing operational simplicity and resource efficiency. The study confirms that GraalVM native images reduce memory at the cost of throughput in sustained I/O workloads, with Quarkus-native as the best-positioned exception.

Summary of Findings:

  • Performance tiers are clearly separated: Rust/Quarkus at 4,700-4,850 RPS, Go/Java cluster at 3,000-3,620 RPS, JS/Python at 250-880 RPS.
  • Native images consistently reduce memory (27-81%) at a 20-36% throughput cost under sustained high load. At low request rates, this throughput regression is not observable. Quarkus-native offers the best trade-off at high load.
  • Classic blocking I/O (Spring MVC) outperforms reactive (WebFlux) at 50 VUs in this I/O-bound workload.
  • Bun delivers 2.2x the throughput of Node.js on identical code, making it the clear choice when the JavaScript ecosystem is required.
  • All 15 servers achieved 0% errors and CV below 2% across 39.9 million requests, validating the methodology's reproducibility.

Recommendations Summary:

  • Production choice (throughput): Rust at 4,845 RPS with 10.9 MB RAM
  • Production choice (latency): Quarkus at 4.04ms avg, 8.13ms P95
  • Resource and operational choice: Go at 23.9 MB RAM and 3,616 RPS
  • Java ecosystem: Spring MVC (blocking) at 3,540 RPS for strong throughput with operational simplicity. Java-VT for future-proofing at higher concurrency levels.
  • JavaScript ecosystem: Bun over Node.js (2.2x throughput advantage)
  • Python: Appropriate for low-traffic deployments and Python-native teams. The ceiling is FastMCP session overhead in CPython, not the ASGI server.

Future Work: Higher concurrency levels (100-200 VUs) to identify saturation points. Persistent session benchmarks. Multi-instance Kubernetes deployments with session affinity. Rust with rmcp v0.17.0 under higher concurrency profiles.