Skip to main content
Back to Guides

MCP Transport Protocols

Understanding stdio, SSE, and HTTP transports for connecting MCP servers to clients.

Jordan Kim
Updated January 14, 2025
10 min read

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

  1. Client spawns the server as a child process
  2. Client writes JSON-RPC messages to server's stdin
  3. Server writes responses to stdout
  4. 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

FeaturestdioSSEHTTP
DeploymentLocal onlyLocal or remoteRemote only
StreamingYesYesNo
StatePer-processPer-connectionStateless
LatencyLowestLowHigher (cold starts)
ScalabilitySingle userMulti-userUnlimited
SecurityHighest (local)Requires authRequires 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.

Needs Review

Last updated

January 11, 2025

375 days ago

This content may be outdated

This content may contain outdated information. Please verify details before use.