MCP is transport-agnostic by design. The same server logic can work over different communication channels. Understanding these transports helps you choose the right one for your use case and deploy servers effectively.
1. Transport Overview
MCP supports three transport mechanisms, each suited for different scenarios:
- stdio: Communication via standard input/output streams
- SSE: Server-Sent Events over HTTP for real-time communication
- HTTP: Stateless HTTP requests for serverless environments
All transports use JSON-RPC 2.0 as the message format, so the actual MCP protocol remains the same regardless of transport.
2. Standard I/O (stdio)
The most common transport for local servers. The client spawns the server as a subprocess and communicates via stdin/stdout.
When to Use stdio
- Local servers running on your machine
- Servers that need access to local files
- Simple setup without network configuration
- Maximum security (no network exposure)
- Development and testing
How stdio Works
- Client spawns the server as a child process
- Client writes JSON-RPC messages to server's stdin
- Server writes responses to stdout
- Server can use stderr for logging (won't interfere with protocol)
Configuration Example
{
"mcpServers": {
"my-server": {
"command": "node",
"args": ["path/to/server.js"]
}
}
}TypeScript Implementation
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
const server = new Server(
{ name: "my-server", version: "1.0.0" },
{ capabilities: { tools: {} } }
);
// Set up handlers...
const transport = new StdioServerTransport();
await server.connect(transport);Python Implementation
from mcp.server import Server
from mcp.server.stdio import stdio_server
app = Server("my-server")
# Define tools...
async def main():
async with stdio_server() as (read_stream, write_stream):
await app.run(read_stream, write_stream, app.create_initialization_options())
asyncio.run(main())3. Server-Sent Events (SSE)
SSE is an HTTP-based transport for real-time server-to-client communication. Useful for remote servers that need to push updates.
When to Use SSE
- Remote servers hosted on the cloud
- Sharing a server across multiple users
- When you need streaming responses
- Long-running operations with progress updates
- Web-based MCP clients
How SSE Works
SSE uses two HTTP connections:
- GET /sse: Long-lived connection for server → client messages
- POST /messages: Client → server requests
Server Implementation
import express from "express";
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
const app = express();
const server = new Server(
{ name: "sse-server", version: "1.0.0" },
{ capabilities: { tools: {} } }
);
// Store transports for each client
const transports = new Map();
app.get("/sse", async (req, res) => {
const transport = new SSEServerTransport("/messages", res);
const clientId = req.query.clientId;
transports.set(clientId, transport);
await server.connect(transport);
});
app.post("/messages", async (req, res) => {
const clientId = req.query.clientId;
const transport = transports.get(clientId);
if (transport) {
await transport.handlePostMessage(req, res);
} else {
res.status(404).send("Client not found");
}
});
app.listen(3000);Client Configuration
{
"mcpServers": {
"remote-server": {
"url": "https://my-server.example.com/sse",
"transport": "sse"
}
}
}4. HTTP (Stateless)
Pure HTTP transport for simple request/response patterns. Each request is independent, making it ideal for serverless environments.
When to Use HTTP
- Serverless functions (AWS Lambda, Vercel, Cloudflare Workers)
- Stateless API wrappers
- Load-balanced deployments
- Simple tools that don't need streaming
- Cost-sensitive deployments (pay per request)
Serverless Example (AWS Lambda)
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
const server = new Server(
{ name: "lambda-server", version: "1.0.0" },
{ capabilities: { tools: {} } }
);
// Define tools...
export async function handler(event) {
const request = JSON.parse(event.body);
const response = await server.handleRequest(request);
return {
statusCode: 200,
headers: { "Content-Type": "application/json" },
body: JSON.stringify(response)
};
}5. Transport Comparison
| Feature | stdio | SSE | HTTP |
|---|---|---|---|
| Deployment | Local only | Local or remote | Remote only |
| Streaming | Yes | Yes | No |
| State | Per-process | Per-connection | Stateless |
| Latency | Lowest | Low | Higher (cold starts) |
| Scalability | Single user | Multi-user | Unlimited |
| Security | Highest (local) | Requires auth | Requires auth |
6. Choosing the Right Transport
Use stdio when:
- Building local development tools
- Accessing local filesystem or databases
- Security is paramount (no network exposure)
- You want the simplest setup
Use SSE when:
- Deploying to a remote server
- Multiple users need to share the server
- You need streaming responses or progress updates
- Building a web-based MCP client
Use HTTP when:
- Deploying to serverless platforms
- Building simple, stateless tools
- Cost optimization is important
- You need automatic scaling
7. Security Considerations
Remote transports (SSE and HTTP) require additional security measures:
Security Checklist for Remote Transports
- ✓ Always use HTTPS for remote connections
- ✓ Implement authentication (API keys, OAuth, JWT)
- ✓ Validate all inputs on the server side
- ✓ Rate limit requests to prevent abuse
- ✓ Log access for auditing
- ✓ Use CORS headers appropriately
- ✓ Consider IP allowlisting for sensitive servers
Authentication Example
// Express middleware for API key auth
function authenticate(req, res, next) {
const apiKey = req.headers["x-api-key"];
if (!apiKey || !isValidApiKey(apiKey)) {
return res.status(401).json({ error: "Unauthorized" });
}
next();
}
app.use("/sse", authenticate);
app.use("/messages", authenticate);8. Implementation Examples
Hybrid Server (stdio + SSE)
You can support multiple transports in the same server:
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
const server = new Server(
{ name: "hybrid-server", version: "1.0.0" },
{ capabilities: { tools: {} } }
);
// Define tools once, use with any transport
const mode = process.env.TRANSPORT || "stdio";
if (mode === "stdio") {
const transport = new StdioServerTransport();
await server.connect(transport);
} else if (mode === "sse") {
// Start Express server with SSE endpoints
startSSEServer(server);
}Conclusion
Choosing the right transport depends on your deployment scenario. Start with stdio for local development—it's the simplest and most secure. Move to SSE or HTTP when you need remote access or serverless deployment. The beauty of MCP is that your server logic stays the same regardless of transport.
Outdated Content Warning
This guide was last updated on January 11, 2025 (12 months ago).
The information presented here may be significantly outdated. Technologies, APIs, and best practices may have changed since this content was written.
We strive to keep our content current, but with rapidly evolving technologies, some details may no longer be accurate.
Last updated
January 11, 2025
375 days ago
This content may be outdated
This content may contain outdated information. Please verify details before use.