Guide · Transport

MCP server transport comparison

Every MCP server must choose a transport — or support several. The three options differ in where the server runs, who can connect to it, how it scales, and whether it can be monitored externally. Choosing the wrong transport early means a rewrite later: a stdio-only server can't be shared across a team; an SSE server can't run on Lambda. This guide helps you pick the right transport from the start and understand the migration paths when your requirements change.

Quick decision rule

Personal tool for one developer? Use stdio. Shared API for a team or public? Use Streamable HTTP. Supporting clients that haven't updated to Streamable HTTP? Add SSE alongside it. The McpServer core is transport-agnostic — you can support all three from one server binary by connecting to different transport instances at startup based on a command-line flag or environment variable.

The three transports at a glance

PropertystdioSSE (HTTP+SSE)Streamable HTTP
Where server runsLocal machine (spawned by host)Remote server or local HTTPRemote server, serverless, or local HTTP
Network requiredNo — OS pipeYes — HTTPYes — HTTP
Endpoint countNone (stdin/stdout)Two: GET /sse + POST /messagesOne: POST /mcp
Simultaneous clientsOne (the host process)Many (one transport instance per client)Many (one transport per session or stateless)
AuthenticationNone (implicit trust of spawning host)Any HTTP auth (OAuth, API key, JWT)Any HTTP auth (OAuth, API key, JWT)
Serverless compatibleNoNo (persistent connection required)Yes (stateless mode)
Horizontally scalableNoOnly with sticky sessionsYes in stateless mode; sticky sessions in stateful
External monitoringNo — local process onlyYes — HTTP endpointYes — HTTP endpoint
Spec statusCurrentLegacy (pre-2025-03-26)Current (2025-03-26+)
SDK version requiredAnyAny1.1.0+

Use-case decision table

Use caseRecommended transportReason
Personal productivity tool (calendar, notes, local scripts)stdioNo server to operate; distribute via npm; no auth surface to secure
Local filesystem tool (read/write user's files)stdioFile access is naturally scoped to the user's machine; no network needed
Distribute via npm for Claude Desktop / Cursorstdionpx your-server is the full install; no separate deployment
Shared team API (internal knowledge base, company data)Streamable HTTPMultiple clients, needs auth, should be monitored for uptime
Public MCP API (SaaS product, open ecosystem tool)Streamable HTTPMulti-tenant, auth required, can be registered with MCP registries
Serverless deployment (Lambda, Cloudflare Workers, Vercel)Streamable HTTP (stateless mode)No persistent connections; stateless mode fits request/response model
Support legacy clients (older SDK versions)SSE + Streamable HTTPMount both; modern clients use Streamable HTTP; older ones fall back to SSE
Browser extension or web app clientSSEEventSource is natively available in browsers; simpler than fetch-based streaming
LLM agent framework integration (LangChain, CrewAI)Streamable HTTP or stdioDepends on the framework's MCP adapter; check adapter docs for transport support
Development-time tool (build, test, lint, git)stdioRuns locally; project path available from environment; no infra overhead

stdio in depth: strengths and limits

Strengths

Hard limits

See the full guide: MCP server stdio transport.

SSE transport in depth: strengths and limits

Strengths

Hard limits

See the full guide: MCP server SSE transport.

Streamable HTTP in depth: strengths and limits

Strengths

Limits

See the full guide: MCP server Streamable HTTP transport.

Supporting multiple transports from one server

The McpServer core is transport-agnostic. You can connect it to different transport types at startup based on a flag, environment variable, or a CLI argument — or mount all three simultaneously in an Express app to serve different client populations:

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import express from 'express';

function createServer() {
  const server = new McpServer({ name: 'my-server', version: '1.0.0' });
  // Register all tools once here — same tools regardless of transport
  return server;
}

const mode = process.env.MCP_TRANSPORT ?? 'stdio';

if (mode === 'stdio') {
  // Local development and Claude Desktop / Cursor integration
  const server = createServer();
  const transport = new StdioServerTransport();
  await server.connect(transport);

} else {
  // Shared deployment — HTTP with both SSE and Streamable HTTP
  const app = express();
  app.use(express.json());

  const sseSessions = new Map();

  // SSE transport — for legacy and browser clients
  app.get('/sse', async (req, res) => {
    const transport = new SSEServerTransport('/messages', res);
    sseSessions.set(transport.sessionId, transport);
    transport.onclose = () => sseSessions.delete(transport.sessionId);
    await createServer().connect(transport);
  });
  app.post('/messages', async (req, res) => {
    const t = sseSessions.get(req.query.sessionId);
    t ? await t.handlePostMessage(req, res) : res.status(404).end();
  });

  // Streamable HTTP — for modern clients
  const httpSessions = new Map();
  app.post('/mcp', async (req, res) => {
    const sid = req.headers['mcp-session-id'];
    if (sid) {
      const t = httpSessions.get(sid);
      t ? await t.handleRequest(req, res, req.body) : res.status(404).end();
      return;
    }
    const transport = new StreamableHTTPServerTransport({
      sessionIdGenerator: () => crypto.randomUUID(),
      onsessioninitialized: (id) => httpSessions.set(id, transport),
    });
    transport.onclose = () => httpSessions.delete(transport.sessionId);
    await createServer().connect(transport);
    await transport.handleRequest(req, res, req.body);
  });

  app.listen(3000);
}

The tool registration code runs once in createServer() and is identical across all transports. Changing or adding tools requires no transport-specific changes.

Migration path: SSE to Streamable HTTP

If you have a production SSE server and want to move to Streamable HTTP:

  1. Update SDK — bump @modelcontextprotocol/sdk to 1.1.0+ in package.json and run npm install.
  2. Add the POST /mcp handler — mount it alongside the existing GET /sse + POST /messages handlers. Do not remove the SSE handlers yet.
  3. Test with a modern client — connect using an updated MCP client SDK. It will automatically prefer POST /mcp over GET /sse.
  4. Announce the migration to clients — notify client teams to update their SDK versions. Give them a transition window (typically 4-8 weeks).
  5. Remove SSE handlers — after the transition window, remove GET /sse + POST /messages and clean up the SSE session map.

Run both transports simultaneously during step 2–4. There's no flag-day cutover — clients migrate at their own pace.

Transport and external monitoring

Only HTTP-based transports (SSE and Streamable HTTP) can be monitored by external probes. Stdio servers are local child processes — no URL, no port, no network reachability.

TransportAliveMCP monitoringWhat is probed
stdioNot possibleLocal process — no network endpoint
SSE (HTTP+SSE)YesGET /sse → endpoint event → POST /messages → initialize response
Streamable HTTPYesPOST /mcp → initialize → initialized → tools/list response

If you deploy a server that started as a stdio-only tool and now needs uptime visibility — for team use, for inclusion in a registry, or for SLA tracking — migrating to Streamable HTTP is the prerequisite for external monitoring. The tool logic doesn't change; only the transport layer is added.

AliveMCP registers and monitors every server in the public MCP registries (MCP.so, Glama, PulseMCP, Smithery). If your HTTP-based server is listed there, it is already being probed. Claim your listing to set custom alert webhooks and see your server's uptime history.

Related questions

Can I start with stdio and add HTTP later without rewriting my tools?

Yes — this is the designed migration path. Register all your tools on a McpServer instance, connect it to StdioServerTransport for local use, and later add an Express app that connects a new McpServer instance (created by the same factory function) to StreamableHTTPServerTransport. The tool handlers are identical. Only the server entry point and the transport layer change.

Does the transport affect tool handler performance?

Minimally. The transport adds serialization/deserialization overhead (JSON stringify/parse) and network RTT for HTTP transports. For stdio, messages travel through OS pipes, which are fast. For HTTP transports, TLS handshake and TCP RTT add latency compared to stdio's pipe. For CPU-intensive tools, transport overhead is negligible relative to handler execution time. For extremely fast tools (sub-millisecond handlers), transport overhead becomes measurable — see MCP server benchmarking for measurement patterns.

Which transport do MCP registries (MCP.so, Glama, Smithery) expect?

Public registries list endpoints — they expect HTTP-based transports (SSE or Streamable HTTP). A stdio server has no URL to register. If you want your server discoverable in the ecosystem, deploy it with an HTTP transport and register the URL. AliveMCP probes registry-listed endpoints and shows their health status on a public dashboard.

What happens if the client and server support different protocol versions?

During the initialize handshake, both parties declare their protocol version. If the versions are incompatible, the server returns a -32600 error. The MCP SDK handles version negotiation automatically — it selects the highest version both sides support. If you target an older version in your server (e.g., protocolVersion: "2024-11-05"), newer clients will negotiate down to that version.

Further reading