MCP Transport: stdio vs Streamable HTTP vs SSE Comparison Guide
TL;DR — MCP transports: stdio for local, Streamable HTTP for remote. MCP Spec: "Two standard transports: stdio (standard I/O) and Streamable HTTP. JSON-RPC MUST be UTF-8 encoded. Clients SHOULD support stdio whenever possible." TrueFoundry: "Streamable HTTP introduced in 2025-03-26, retained November 2025. Replaces HTTP+SSE with single-endpoint design. Server exposes one URL (/mcp) accepting POST and GET. POST JSON-RPC; respond with JSON or SSE stream." MCP Blog: "2026-07-28 RC: MCP now stateless at protocol layer. Six SEPs eliminate session management complexity. In 2025-11-25, calling a tool over Streamable HTTP means establishing and maintaining a session. 2026-07-28 eliminates this." AWS Builder: "MCP transport mechanisms: STDIO vs Streamable HTTP. Choose based on deployment model." WebFuse: "MCP cheat sheet: architecture, primitives, transports, SDK quick starts, security best practices." Learn more with MCP guide, build MCP server, build MCP client, and Claude Desktop MCP.
MCP Spec defines the transports: "MCP uses JSON-RPC to encode messages. JSON-RPC messages MUST be UTF-8 encoded. The protocol currently defines two standard transport mechanisms for client-server communication: stdio, communication over standard in and standard out, and Streamable HTTP. Clients SHOULD support stdio whenever possible."
TrueFoundry explains the evolution: "Streamable HTTP, introduced in MCP spec 2025-03-26 and retained in the November 2025 revision, replaces the older HTTP+SSE transport with a single-endpoint design. The server exposes one URL (e.g., /mcp) that accepts both POST and GET. Clients POST JSON-RPC messages; servers respond with either a single JSON object or SSE stream."
MCP Transport Architecture
spawns server subprocess"] StdServer["MCP Server
runs as child process"] Stdin["stdin
client → server
JSON-RPC messages"] Stdout["stdout
server → client
JSON-RPC responses"] Stderr["stderr
server → logs
NEVER stdout"] StdEnv["Environment
credentials from env
NOT HTTP auth"] end subgraph HTTP["Streamable HTTP Transport (Remote)"] Endpoint["Single Endpoint
e.g., https://example.com/mcp
accepts POST and GET"] Post["HTTP POST
client → server
JSON-RPC messages"] Response["Response
application/json OR
text/event-stream (SSE)"] Get["HTTP GET
optional SSE stream
server → client notifications"] OAuth["OAuth Authentication
bearer tokens, API keys
custom headers"] Session["Session Management
2025-11-25: stateful
2026-07-28: stateless"] end subgraph Features["Streamable HTTP Features"] Resumability["Resumability
reconnect after disconnect"] Redelivery["Redelivery
replay missed messages"] MultiConn["Multiple Connections
many clients per server"] BackCompat["Backwards Compatibility
with HTTP+SSE (2024-11-05)"] end subgraph Custom["Custom Transports"] CustomImpl["Custom Transport
any bidirectional channel
MUST preserve JSON-RPC format"] CustomDoc["Document Patterns
connection establishment
message exchange"] end StdClient --> StdServer StdClient -->|stdin| Stdin StdServer -->|stdout| Stdout StdServer -->|stderr| Stderr StdServer --> StdEnv Endpoint --> Post Post --> Response Endpoint --> Get Endpoint --> OAuth Endpoint --> Session Response --> Resumability Response --> Redelivery Endpoint --> MultiConn Endpoint --> BackCompat CustomImpl --> CustomDoc
Transport Comparison
| Feature | stdio | Streamable HTTP |
|---|---|---|
| Connection | Standard I/O streams | HTTP POST + optional SSE |
| Location | Local (same machine) | Remote (network) |
| Clients | Single client | Many clients |
| Auth | Environment credentials | OAuth, bearer tokens, API keys |
| Performance | Optimal, no network overhead | Network-dependent |
| Streaming | Not supported | Optional SSE streaming |
| Session | Process lifecycle | 2025-11-25: stateful, 2026-07-28: stateless |
| Resumability | N/A | Supported |
| Redelivery | N/A | Supported |
| Use Case | Claude Desktop, Cursor, local dev | Production, cloud, shared servers |
Streamable HTTP Request/Response
| Request | Method | Headers | Response |
|---|---|---|---|
| Send message | POST | Accept: application/json, text/event-stream |
JSON object or SSE stream |
| Listen for server | GET | Accept: text/event-stream |
SSE stream or 405 Method Not Allowed |
| Session init | POST | MCP-Session-Id header |
Session ID in response |
Implementation
from dataclasses import dataclass
from typing import Optional
from enum import Enum
class TransportType(Enum):
STDIO = "stdio"
STREAMABLE_HTTP = "streamable_http"
CUSTOM = "custom"
@dataclass
class MCPTransportGuide:
"""MCP transport mechanisms guide."""
def get_stdio_overview(self) -> dict:
"""stdio transport overview."""
return {
"description": (
"Standard input/output streams for "
"direct process communication between "
"local processes on the same machine"
),
"how_it_works": [
"Client spawns server as subprocess",
"Client sends JSON-RPC via stdin",
"Server responds via stdout",
"Logging via stderr — never stdout",
"Server process lifecycle = session",
],
"characteristics": {
"performance": "Optimal, no network overhead",
"clients": "Single client per server",
"auth": "Retrieve credentials from "
"environment, not HTTP auth",
"encoding": "UTF-8 JSON-RPC messages",
},
"best_for": [
"Local development",
"Claude Desktop integration",
"Cursor IDE integration",
"Local tools (filesystem, local DB)",
"Single-user scenarios",
],
"logging_rule": (
"On stdio transports, log to stderr. "
"Never print() to stdout or you "
"break JSON-RPC messages. stdout is "
"reserved for protocol messages only."
),
}
def get_streamable_http_overview(self) -> dict:
"""Streamable HTTP transport overview."""
return {
"description": (
"HTTP POST for client-to-server messages "
"with optional Server-Sent Events (SSE) "
"for streaming. Single-endpoint design."
),
"endpoint": (
"Single HTTP endpoint (e.g., "
"https://example.com/mcp) that "
"supports both POST and GET methods"
),
"how_it_works": [
"Client sends JSON-RPC via HTTP POST",
"Client MUST include Accept header: "
"application/json and text/event-stream",
"Server responds with either: "
"Content-Type application/json (single "
"JSON object) OR text/event-stream "
"(SSE stream for multiple messages)",
"Client MAY issue HTTP GET to open "
"SSE stream for server-to-client "
"notifications",
"Server MAY return 405 if no SSE stream",
],
"characteristics": {
"performance": "Network-dependent, "
"supports streaming",
"clients": "Many clients per server",
"auth": "OAuth recommended, bearer "
"tokens, API keys, custom headers",
"encoding": "UTF-8 JSON-RPC messages",
},
"features": [
"Session management (2025-11-25: stateful, "
"2026-07-28: stateless)",
"Resumability — reconnect after disconnect",
"Redelivery — replay missed messages",
"Multiple connections — many clients",
"Backwards compatibility with HTTP+SSE "
"(protocol version 2024-11-05)",
],
"best_for": [
"Remote/production servers",
"Cloud-hosted MCP servers",
"Shared team servers",
"Multi-client scenarios",
"Horizontal scaling",
],
"replaces": (
"HTTP+SSE transport from protocol "
"version 2024-11-05. Introduced in "
"MCP spec 2025-03-26, retained in "
"November 2025 revision."
),
}
def get_stateless_2026(self) -> dict:
"""2026-07-28 stateless protocol change."""
return {
"headline": (
"MCP is now stateless at the protocol "
"layer as of 2026-07-28 release candidate"
),
"what_changed": (
"In 2025-11-25, calling a tool over "
"Streamable HTTP means establishing a "
"session, maintaining session state, "
"and tearing it down. The 2026-07-28 RC "
"eliminates this complexity."
),
"mechanism": (
"Six Specification Enhancement Proposals "
"(SEPs) work together to achieve "
"stateless design, completing the plan "
"laid out in The Future of MCP "
"Transports in December."
),
"impact": [
"No session management needed",
"Each request is self-contained",
"Simpler server implementation",
"Better horizontal scalability",
"Easier load balancing",
"Reduced state management overhead",
],
}
def get_custom_transports(self) -> dict:
"""Custom transport support."""
return {
"supported": (
"Clients and servers MAY implement "
"additional custom transport mechanisms "
"to suit their specific needs. Protocol "
"is transport-agnostic."
),
"requirements": [
"MUST preserve JSON-RPC message format",
"MUST preserve lifecycle requirements",
"MUST support bidirectional message exchange",
"SHOULD document connection establishment",
"SHOULD document message exchange patterns",
],
"use_cases": [
"WebSocket transport",
"gRPC transport",
"Message queue transport",
"Custom protocol adapters",
],
}
def get_decision_matrix(self) -> dict:
"""Transport selection decision matrix."""
return {
"choose_stdio": {
"when": [
"Server runs locally on same machine",
"Single client (Claude Desktop, Cursor)",
"No network needed",
"Optimal performance required",
"Local tools (filesystem, local DB)",
"Development and testing",
],
"advantages": [
"No network overhead",
"Optimal performance",
"Simple setup",
"No auth complexity",
"Process lifecycle = session",
],
},
"choose_streamable_http": {
"when": [
"Server runs remotely",
"Many clients need access",
"OAuth authentication needed",
"Production deployment",
"Cloud-hosted server",
"Team-shared server",
"Horizontal scaling needed",
"Streaming responses needed",
],
"advantages": [
"Remote access",
"Many clients",
"OAuth authentication",
"SSE streaming",
"Resumability and redelivery",
"Load balancer friendly (2026-07-28)",
],
},
}
def get_server_run_code(self) -> dict:
"""Code for running server with each transport."""
return {
"stdio": (
"# Run with stdio transport (default)\n"
"from mcp.server.fastmcp import FastMCP\n"
"mcp = FastMCP('my-server')\n"
"\n"
"if __name__ == '__main__':\n"
" mcp.run(transport='stdio')"
),
"streamable_http": (
"# Run with Streamable HTTP transport\n"
"from mcp.server.fastmcp import FastMCP\n"
"mcp = FastMCP('my-server')\n"
"\n"
"if __name__ == '__main__':\n"
" mcp.run(\n"
" transport='streamable-http',\n"
" host='0.0.0.0',\n"
" port=8000,\n"
" )\n"
"# Endpoint: http://localhost:8000/mcp"
),
"client_stdio": (
"# Client with stdio transport\n"
"from mcp import ClientSession,\n"
" StdioServerParameters\n"
"from mcp.client.stdio import stdio_client\n"
"\n"
"params = StdioServerParameters(\n"
" command='python',\n"
" args=['server.py'],\n"
")\n"
"# Use with AsyncExitStack"
),
"client_http": (
"# Client with Streamable HTTP\n"
"from mcp.client.streamable_http import\n"
" streamablehttp_client\n"
"\n"
"# Connect to remote server\n"
"async with streamablehttp_client(\n"
" 'https://example.com/mcp'\n"
") as (read, write, _):\n"
" session = ClientSession(read, write)\n"
" await session.initialize()"
),
}
MCP Transport Checklist
- [ ] MCP defines two standard transports: stdio and Streamable HTTP
- [ ] JSON-RPC messages MUST be UTF-8 encoded for both transports
- [ ] Clients SHOULD support stdio whenever possible
- [ ] stdio: standard input/output streams for local process communication
- [ ] stdio: client spawns server as subprocess, communicates via stdin/stdout
- [ ] stdio: logging via stderr — NEVER stdout (breaks JSON-RPC)
- [ ] stdio: optimal performance, no network overhead
- [ ] stdio: single client per server
- [ ] stdio: auth via environment credentials, NOT HTTP auth
- [ ] stdio: best for local development, Claude Desktop, Cursor, local tools
- [ ] Streamable HTTP: HTTP POST for client-to-server, optional SSE for streaming
- [ ] Streamable HTTP: single endpoint (e.g., /mcp) accepts POST and GET
- [ ] Streamable HTTP: introduced in MCP spec 2025-03-26, retained November 2025
- [ ] Streamable HTTP: replaces HTTP+SSE transport from protocol version 2024-11-05
- [ ] Client MUST use HTTP POST to send JSON-RPC messages to MCP endpoint
- [ ] Client MUST include Accept header: application/json and text/event-stream
- [ ] Server MUST return either Content-Type application/json or text/event-stream
- [ ] Client MAY issue HTTP GET to open SSE stream for server-to-client notifications
- [ ] Server MAY return 405 Method Not Allowed if no SSE stream offered
- [ ] Streamable HTTP: many clients per server
- [ ] Streamable HTTP: OAuth recommended for auth tokens, bearer tokens, API keys, custom headers
- [ ] Streamable HTTP: session management (2025-11-25: stateful, 2026-07-28: stateless)
- [ ] Streamable HTTP: resumability — reconnect after disconnect
- [ ] Streamable HTTP: redelivery — replay missed messages
- [ ] Streamable HTTP: multiple connections supported
- [ ] Streamable HTTP: backwards compatibility with HTTP+SSE
- [ ] Streamable HTTP: best for remote, production, cloud, shared team servers
- [ ] 2026-07-28 RC: MCP now stateless at protocol layer
- [ ] 2026-07-28: six SEPs eliminate session management complexity
- [ ] 2026-07-28: each request is self-contained, no session state needed
- [ ] 2026-07-28: better horizontal scalability, easier load balancing
- [ ] Custom transports: MAY implement additional transport mechanisms
- [ ] Custom transports: MUST preserve JSON-RPC message format
- [ ] Custom transports: MUST preserve lifecycle requirements
- [ ] Custom transports: MUST support bidirectional message exchange
- [ ] Custom transports: SHOULD document connection and message patterns
- [ ] Choose stdio for: local, single client, no network, optimal performance, local tools
- [ ] Choose Streamable HTTP for: remote, many clients, OAuth, production, cloud, scaling
- [ ] Server run stdio: mcp.run(transport='stdio')
- [ ] Server run HTTP: mcp.run(transport='streamable-http', host='0.0.0.0', port=8000)
- [ ] Client stdio: StdioServerParameters + stdio_client + AsyncExitStack
- [ ] Client HTTP: streamablehttp_client('https://example.com/mcp')
- [ ] stdio logging: use logging module with stream=sys.stderr
- [ ] HTTP auth: use OAuth for obtaining authentication tokens
- [ ] HTTP+SSE transport deprecated — use Streamable HTTP for new work
- [ ] Read MCP developer guide for protocol
- [ ] Read build MCP server for server creation
- [ ] Read build MCP client for client implementation
- [ ] Read Claude Desktop MCP for desktop integration
- [ ] Read MCP tools for tool development
- [ ] Test: stdio server communicates via stdin/stdout correctly
- [ ] Test: Streamable HTTP server accepts POST and returns JSON or SSE
- [ ] Test: HTTP GET opens SSE stream for server notifications
- [ ] Test: stdio logging goes to stderr, not stdout
- [ ] Test: HTTP auth with OAuth bearer tokens
- [ ] Test: multiple clients connect to Streamable HTTP server
- [ ] Document transport choice, auth method, endpoint URL, and deployment model
FAQ
What are the MCP transport mechanisms?
MCP defines two standard transports: stdio for local process communication and Streamable HTTP for remote server communication. MCP Spec: "MCP uses JSON-RPC to encode messages. JSON-RPC messages MUST be UTF-8 encoded. The protocol currently defines two standard transport mechanisms: stdio, communication over standard in and standard out, and Streamable HTTP. Clients SHOULD support stdio whenever possible." TrueFoundry: "Streamable HTTP, introduced in MCP spec 2025-03-26 and retained in November 2025 revision, replaces the older HTTP+SSE transport with a single-endpoint design. Server exposes one URL (e.g., /mcp) that accepts both POST and GET. Clients POST JSON-RPC messages; servers respond with either a single JSON object or SSE stream." stdio: standard I/O streams, local, single client, no network overhead. Streamable HTTP: HTTP POST + optional SSE, remote, many clients, OAuth auth.
How does Streamable HTTP transport work in MCP?
Streamable HTTP uses a single endpoint (e.g., /mcp) that accepts POST for client-to-server messages and optional SSE for streaming. MCP Spec: "In Streamable HTTP transport, server operates as independent process handling multiple client connections. Uses HTTP POST and GET requests. Server can optionally use Server-Sent Events (SSE) to stream multiple server messages. Client MUST use HTTP POST to send JSON-RPC messages. Client MUST include Accept header listing both application/json and text/event-stream. Server MUST either return Content-Type text/event-stream to initiate SSE stream, or application/json to return one JSON object." TrueFoundry: "Single-endpoint design. Server exposes one URL that accepts both POST and GET. Clients POST JSON-RPC messages; servers respond with either a single JSON object or SSE stream." Features: session management, resumability and redelivery, multiple connections, backwards compatibility with HTTP+SSE.
How does stdio transport work in MCP?
stdio transport uses standard input/output streams for direct process communication between local processes on the same machine. MCP Spec: "stdio, communication over standard in and standard out. Clients SHOULD support stdio whenever possible." MCP Architecture: "Stdio transport uses standard input/output streams for direct process communication between local processes on the same machine, providing optimal performance with no network overhead. Local MCP servers that use STDIO transport typically serve a single MCP client." stdio: (1) Client spawns server as subprocess. (2) Client sends JSON-RPC via stdin. (3) Server responds via stdout. (4) Logging via stderr — never stdout (breaks JSON-RPC). (5) No network overhead — optimal performance. (6) Single client per server. (7) Auth: retrieve credentials from environment, not HTTP auth. (8) Best for local tools (filesystem, local database, local APIs).
What is the 2026-07-28 MCP specification stateless transport change?
The 2026-07-28 MCP specification release candidate makes MCP stateless at the protocol layer, eliminating session management complexity for Streamable HTTP. MCP Blog: "The headline change is that MCP is now stateless at the protocol layer. Six Specification Enhancement Proposals (SEPs) work together to get there, completing the plan we laid out in The Future of MCP Transports in December. In 2025-11-25, calling a tool over Streamable HTTP means establishing a session, maintaining session state, and tearing it down. The 2026-07-28 RC eliminates this complexity." Impact: (1) No session management needed for Streamable HTTP. (2) Each request is self-contained. (3) Simpler server implementation. (4) Better horizontal scalability. (5) Easier load balancing. (6) Reduced state management overhead. (7) Six SEPs work together for stateless design.
How do you choose between stdio and Streamable HTTP for MCP?
Use stdio for local servers (Claude Desktop, Cursor, local development) and Streamable HTTP for remote servers (production, shared team servers, cloud-hosted). TrueFoundry: "Streamable HTTP for enterprise: single-endpoint design, session management, resumability, OAuth auth. stdio for local: optimal performance, no network overhead." AWS Builder: "MCP transport mechanisms: STDIO vs Streamable HTTP. Choose based on deployment model." Choose stdio when: (1) Server runs locally on same machine. (2) Single client (Claude Desktop, Cursor). (3) No network needed. (4) Optimal performance required. (5) Local tools (filesystem, local DB). Choose Streamable HTTP when: (1) Server runs remotely. (2) Many clients need access. (3) OAuth authentication needed. (4) Production deployment. (5) Cloud-hosted server. (6) Team-shared server. (7) Horizontal scaling needed.
Want a self-hosted AI company brain that does all of this out of the box?
Book a demo →