Overview

MCP defines two standard transports, stdio and Streamable HTTP; custom transports are allowed if they keep the JSON-RPC format, the message patterns, and per-request metadata. The HTTP+SSE transport from 2024-11-05 is deprecated. Semantics are identical on both transports: a transport only frames messages and signals cancellation. Auth rules are in mcp-security.

Use stdio for local servers

The client launches the server as a subprocess. Messages are newline-delimited JSON-RPC on stdin and stdout, and must not contain embedded newlines.

  • The server must write nothing to stdout except valid MCP messages. Log to stderr; clients may capture or ignore it and should not treat it as an error.
  • Credentials come from the environment the client passes, not from the OAuth flow.
  • Cancel with notifications/cancelled. Shut down by closing stdin; servers should exit on EOF, and clients escalate to SIGTERM then SIGKILL.
  • If the process dies, the client restarts it and retries lost requests, since there is no session to rebuild; subscriptions/listen streams must be reopened.

Use stdio for filesystem, local database, and dev-tool servers used by one person on one machine.

Use Streamable HTTP for remote and shared servers

The server exposes one MCP endpoint (conventionally /mcp). Every JSON-RPC message is its own POST with Accept: application/json, text/event-stream. The server replies with one JSON object or an SSE stream scoped to that request carrying progress notifications and then the final response.

  • Required headers: MCP-Protocol-Version (must equal the _meta value), Mcp-Method, and Mcp-Name for tools/call, resources/read, and prompts/get. They let gateways route and rate-limit without parsing the body. A mismatch with the body must return 400 with HeaderMismatch (-32020).
  • x-mcp-header on a primitive tool parameter mirrors it into an Mcp-Param-{Name} header. Never annotate secrets; intermediaries see headers.
  • Servers do not send requests on the stream. Server-initiated input rides on InputRequiredResult (see mcp-elicitation), and change notifications arrive on a long-lived response stream opened with subscriptions/listen. Emit periodic SSE comment lines as keep-alive and send X-Accel-Buffering: no so nginx does not buffer.
  • Closing the response stream cancels the request.

Design for lost streams, not resumable ones

The 2026-07-28 revision removed Mcp-Session-Id, the standalone GET stream, and Last-Event-ID resumption. A broken stream loses the in-flight request, and the client re-issues it as a new request with a new id. Retry with exponential backoff, and make tool calls idempotent or use a handle plus the io.modelcontextprotocol/tasks extension for long operations.

Secure the HTTP endpoint

Validate the Origin header on every connection and return 403 when it is present and invalid; this blocks DNS rebinding. Bind local servers to 127.0.0.1, not 0.0.0.0. Require authentication; see mcp-security.

Run remote servers as stateless services

Any request can reach any instance, so keep per-caller state (rate-limit counters, handles, audit entries) in a shared store keyed by authenticated identity, never in process memory. Derive the isolation key from the token subject or mTLS identity, not a client-supplied field, and scope third-party credentials per tenant at the request level. Provide a /health endpoint that returns 200 when ready, drain in-flight requests on shutdown, and log version, transports, and declared capabilities at startup. Put the server behind a CDN or reverse proxy only with response buffering off for SSE; see cloudflare.

Interoperate with legacy peers deliberately

A client that must reach older servers sends a modern request first. On 400 with a body that is not a recognized modern JSON-RPC error, it falls back to initialize (2025-11-25 and earlier). The same non-modern body on 400, 404, or 405 also signals a possible legacy HTTP+SSE server: open a GET to the URL and look for an endpoint event. On stdio the probe is server/discover: a result or a recognized modern error means a modern server, any other error or a timeout means legacy. A server supporting only this revision answers GET and DELETE with 405 and ignores Mcp-Session-Id and Last-Event-ID. New servers implement Streamable HTTP only.