Choose MCP stdio when one host launches and owns a local server process. Choose Streamable HTTP when the server is an independently deployed service for multiple clients, needs standard HTTP authorization, or must support managed streaming and resumability. Do not choose from a synthetic “messages per second” number alone; the transports have different lifecycle and trust boundaries.
This article gives a reproducible benchmark plan but does not publish fabricated results. Run it against your exact SDK, operating system, network path, TLS termination, payloads, and session model before making a performance claim.
Compare the protocol-defined behavior
MCP 2025-11-25 defines two standard transports (transport specification):
| Property | stdio | Streamable HTTP |
|---|---|---|
| Server lifecycle | Client launches subprocess | Independent service |
| Client-to-server messages | Newline-delimited JSON-RPC on stdin | HTTP POST |
| Server-to-client messages | stdout stream | POST response SSE or optional GET SSE |
| Logging | stderr | Normal service telemetry |
| Network exposure | None required | HTTP endpoint |
| Authorization model | Environment or launcher-specific | MCP OAuth model when protected |
| Multiple clients | Usually one child per host | Designed for many connections |
| Resumability | Application-specific | Optional SSE event replay |
For stdio, stdout is protocol-only: every line must be a valid MCP message, and messages must not contain embedded newlines. Logs go to stderr. A stray console.log can corrupt the connection.
For Streamable HTTP, every client message uses a new POST. The client advertises both application/json and text/event-stream, and a server response may be a single JSON object or an SSE stream. An optional GET opens a server-to-client SSE channel. A disconnect is not cancellation; cancellation requires an MCP cancellation notification.
Make the architecture decision before benchmarking
Use stdio when:
- the tool should run on the user's machine;
- the host controls installation, launch, environment, and shutdown;
- local files or applications are the capability boundary; and
- one process per host is acceptable.
Use Streamable HTTP when:
- several clients share a managed capability;
- authentication, tenant authorization, quotas, and central auditing are required;
- the server must scale independently; or
- streaming, reconnect, and optional session management belong at a service boundary.
Stdio is not a sandbox. The child process inherits whatever files, network, environment variables, and OS privileges the launcher grants it. Streamable HTTP is not automatically scalable: stateful sessions, long-lived streams, and in-memory Task data can still pin work to one replica.
Build one transport-neutral test server
Benchmark the same application handler behind both transports. Include three tools:
echo_bytes(size) deterministic response of bounded size
cpu_work(iterations) deterministic CPU work with no I/O
delayed_result(ms) bounded asynchronous wait
The names are illustrative. Set strict upper bounds and keep implementation identical after decoding. Do not compare a debug stdio process with an optimized remote service or add a database to only one path.
Capture these phases separately:
- process or connection setup;
- MCP initialization and capability negotiation;
- warm steady-state calls;
- concurrent calls;
- streaming or progress traffic;
- disconnect and recovery; and
- graceful shutdown.
Report cold and warm behavior separately. A stdio subprocess start belongs in cold latency; reusing an already initialized child belongs in warm latency. For HTTP, measure DNS, TCP, TLS, proxy, and connection reuse explicitly rather than hiding them inside one number.
Define the measurement matrix before running
Use a checked-in manifest such as:
protocol: "2025-11-25"
payload_bytes: [0, 1024, 65536]
concurrency: [1, 8, 32]
calls_per_case: 1000
warmup_calls: 100
deadline_ms: 10000
http:
tls: true
connection_reuse: true
session_mode: stateless
stdio:
process_reuse: true
These values are an example experiment definition, not a universal recommendation. Choose counts large enough for stable distributions and publish the raw observations.
For each case, record:
- successful, failed, and timed-out calls;
- latency distribution, including median and tail percentiles;
- process CPU time and wall time;
- peak resident memory;
- bytes sent and received;
- server starts, connections, and open streams;
- cancellation completion time; and
- recovery time after a forced disconnect or process exit.
Never report only the arithmetic mean. Tail latency and failure rate usually matter more for tool-using agents that compose several calls.
Control the benchmark environment
Record the date, commit, exact dependency lockfile, runtime, operating system, CPU, memory, and network topology. Pin CPU frequency policy where possible and prevent unrelated workloads from contaminating results.
For a local HTTP comparison, say whether TLS and a reverse proxy were present. Loopback HTTP without TLS does not represent a deployed remote server. For a remote test, report client-server region and measure network baseline separately.
Randomize case order or run several interleaved rounds to reduce thermal and temporal bias. Keep server logging at the same level. Validate response content so an optimization cannot “win” by skipping work.
Run each configuration from a clean process for cold trials and a stabilized initialized process for warm trials. Publish scripts and raw data, not just a chart.
Test concurrency semantics, not only throughput
One stdio connection can multiplex JSON-RPC requests, but the server implementation may serialize handlers. HTTP can receive concurrent POSTs, but session state or application locks may still serialize them. Measure actual overlapping execution using trace spans or controlled barriers.
Test independent requests and requests that share session state separately. A high-throughput stateless echo says little about a stateful resource subscription.
Bound client concurrency. An unlimited load generator measures collapse behavior, not sustainable capacity. Increase offered load in steps, observe where latency and errors rise, then report the operational knee with the method used to identify it.
Exercise streaming and recovery
For Streamable HTTP, test both JSON responses and SSE responses. If the server supports resumability, attach unique event IDs, force a connection drop, reconnect with Last-Event-ID, and verify exactly which events are replayed. The specification permits resumability but does not require it.
Test multiple simultaneous SSE streams and verify the server sends each JSON-RPC message on only one connected stream rather than broadcasting duplicates.
For stdio, terminate the child after a request begins. Verify that the client reports an interrupted request, reinitializes a fresh process only under an explicit retry policy, and does not repeat a non-idempotent effect blindly.
In both transports, a lost response creates ambiguity for writes. Use operation-level idempotency keys; transport retries cannot prove whether the server committed before disconnection.
Include security and operations in the result
The fastest transport may be the wrong system boundary.
For stdio, evaluate:
- executable provenance and update integrity;
- inherited environment and credentials;
- filesystem and network permissions;
- child cleanup and orphan processes; and
- stdout corruption by logs.
For Streamable HTTP, evaluate:
- TLS and the MCP authorization specification;
Originvalidation and DNS-rebinding defenses;- request, connection, stream, and body limits;
- tenant isolation and rate limits;
- proxy buffering and timeout behavior; and
- session storage and draining during deploys.
The transport specification requires Origin validation for incoming Streamable HTTP connections, recommends binding local servers to loopback, and recommends authentication. Include negative tests for all three.
Interpret results without overclaiming
Your conclusion should look like this:
Under environment E and workload W, implementation A had lower warm
latency, while implementation B supported the required shared-service
authorization and recovery model. Raw data and scripts: …
It should not say “stdio is X times faster” without scope. SDK buffering, process reuse, HTTP connection reuse, TLS, proxies, payload sizes, and handler concurrency can all reverse an isolated result.
Decision checklist
- Is the server local and owned by one host? Prefer stdio.
- Is it a shared independently deployed capability? Prefer Streamable HTTP.
- Does it require standard remote OAuth and tenant policy? Use Streamable HTTP.
- Does it need access to local user resources? Prefer a tightly sandboxed stdio process.
- Can the system tolerate stateful-session routing and SSE operations?
- Are write operations idempotent across disconnect ambiguity?
- Has the exact deployment been benchmarked with raw data and failures included?
For implementation basics, see the safe server tutorial. For a remote deployment, add OAuth and audience validation.