MCP Server Transport Mechanisms: STDIO, SSE, and Streamable HTTP Explained
The Model Context Protocol (MCP) server supports three distinct transport mechanisms—STDIO, Server-Sent Events (SSE), and Streamable HTTP—each implemented via specific SDK transport classes that enable JSON-RPC communication across different deployment scenarios.
The Model Context Protocol (MCP) provides standardized communication between AI clients and servers through pluggable transport layers. In the modelcontextprotocol/servers repository, the reference implementation exposes three MCP server transport mechanisms that handle message serialization differently while sharing identical core logic. These transports range from simple process pipes for local development to sophisticated HTTP-based streaming with automatic reconnection support.
STDIO Transport
The STDIO transport provides the simplest communication mechanism using standard process input and output streams.
Implementation Details
In src/everything/transports/stdio.ts, the server instantiates StdioServerTransport from the MCP SDK to create a bidirectional pipe. This transport reads JSON-RPC messages from process.stdin and writes responses to process.stdout, making it ideal for local development, testing scenarios, or when the MCP server runs as a spawned child process.
When you execute the server without arguments or explicitly specify stdio, the transport initializes immediately and begins parsing newline-delimited JSON-RPC frames from the parent process.
Server-Sent Events (SSE) Transport
The SSE transport enables HTTP-based streaming communication using the Server-Sent Events protocol for unidirectional server-to-client pushes.
Implementation Details
Located in src/everything/transports/sse.ts, this implementation uses Express to expose two critical endpoints:
- GET /sse – Creates a new
SSEServerTransportinstance and registers it in aMap<string, SSEServerTransport>keyed by a generatedsessionId - POST /message – Receives client-initiated JSON-RPC requests, looks up the corresponding transport by
sessionId, and forwards the request viatransport.handlePostMessage
The transport maintains persistent connections with Content-Type: text/event-stream, pushing server-side events to connected clients as they occur. This approach suits scenarios requiring real-time updates but relies on separate HTTP POST requests for client-to-server communication.
Streamable HTTP Transport
The Streamable HTTP transport offers the most robust solution, combining bidirectional HTTP communication with resumable stream capabilities.
Implementation Details
Implemented in src/everything/transports/streamableHttp.ts, this transport uses StreamableHTTPServerTransport from the SDK and introduces several advanced features:
Session Management – A Map<string, StreamableHTTPServerTransport> stores active transports per sessionId, enabling multiple concurrent client connections.
POST /mcp Endpoint – Handles both session initialization (when no mcp-session-id header exists) and subsequent requests (when the header is present). During initialization, the server generates a randomUUID session identifier and attaches an InMemoryEventStore to buffer events for replay.
GET /mcp Endpoint – Provides an SSE-compatible stream for established sessions. The server checks the optional Last-Event-ID header and replays missed events from the in-memory store, enabling seamless client reconnection after network interruptions.
DELETE /mcp Endpoint – Terminates active sessions, explicitly cleaning up the transport instance and associated event store to prevent memory leaks.
The InMemoryEventStore class (defined within the same file) maintains a bounded buffer of server-sent events, allowing clients to resume streams exactly where they left off using standard HTTP headers.
How Transport Selection Works
The entry point at src/everything/index.ts parses a single CLI argument to determine which transport module to load:
node ./index.js [stdio|sse|streamableHttp]
If omitted, the system defaults to stdio. The selected transport module's main() routine creates the appropriate transport instance, builds the core MCP server via createServer(), connects them, and installs graceful shutdown handlers for SIGINT signals.
Practical Code Examples
Starting a STDIO Server
node ./src/everything/index.js stdio
Output:
Starting default (STDIO) server...
Starting an SSE Server
node ./src/everything/index.js sse
Output:
Starting SSE server...
Server is running on port 3001
Client connection example:
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
const transport = new SSEClientTransport({
url: "http://localhost:3001/sse",
});
await transport.connect();
const response = await transport.request({
jsonrpc: "2.0",
method: "ping",
id: 1
});
console.log(response);
Starting a Streamable HTTP Server
node ./src/everything/index.js streamableHttp
Output:
Starting Streamable HTTP server...
Client connection example:
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
const transport = new StreamableHTTPClientTransport({
url: "http://localhost:3001/mcp",
});
await transport.connect(); // Receives mcp-session-id header
const resp = await transport.request({
jsonrpc: "2.0",
method: "status",
id: 42
});
console.log(resp);
Embedding Transport Directly
For embedding the server within existing applications:
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { createServer } from "./src/everything/server/index.js";
async function runEmbedded() {
const transport = new StdioServerTransport();
const { server, cleanup } = createServer();
await server.connect(transport);
process.on("SIGINT", async () => {
await server.close();
cleanup();
process.exit(0);
});
}
runEmbedded();
Summary
- STDIO provides process-based communication through
process.stdinandprocess.stdout, ideal for local development and child process spawning. - SSE enables HTTP streaming via Express routes (
/sseand/message), using session maps to manage multiple client connections. - Streamable HTTP offers production-grade bidirectional communication with automatic session resumption via
InMemoryEventStoreand standard HTTP headers. - All transports share the same core server logic from
src/everything/server/index.ts, differing only in their message serialization layers. - Transport selection occurs at startup through CLI arguments parsed in
src/everything/index.ts, defaulting to STDIO when unspecified.
Frequently Asked Questions
What is the difference between SSE and Streamable HTTP transports?
SSE provides unidirectional server-to-client streaming over HTTP, requiring separate POST requests for client-to-server communication and managing sessions through a Map<string, SSEServerTransport>. Streamable HTTP offers true bidirectional communication through a single endpoint (/mcp) with support for session resumption via the Last-Event-ID header and the InMemoryEventStore, making it more resilient for production environments where network interruptions occur.
When should I use STDIO instead of HTTP-based transports?
Use STDIO when running the MCP server as a local child process or during development, as implemented in src/everything/transports/stdio.ts. This transport eliminates network overhead and simplifies debugging by using standard process pipes. HTTP transports (SSE or Streamable HTTP) become necessary when clients need to connect over networks or when the server must handle multiple concurrent connections across different machines.
How does session resumption work in the Streamable HTTP transport?
The Streamable HTTP transport in src/everything/transports/streamableHttp.ts stores emitted events in an InMemoryEventStore associated with each session ID. When a client reconnects using the Last-Event-ID header on the GET /mcp endpoint, the server replays missed events from the store before streaming new ones. This mechanism ensures no messages are lost during temporary disconnections, though events are currently stored in memory and lost if the server process restarts.
Can I switch transports without modifying the core server logic?
Yes, the transport layer is completely decoupled from the core MCP server implementation. As shown in src/everything/index.ts, all transports use the same createServer() factory function from src/everything/server/index.ts. You can switch between STDIO, SSE, and Streamable HTTP simply by changing the CLI argument or importing a different transport module, without altering any business logic or tool implementations within the server itself.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →