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/listenstreams 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_metavalue),Mcp-Method, andMcp-Namefortools/call,resources/read, andprompts/get. They let gateways route and rate-limit without parsing the body. A mismatch with the body must return 400 withHeaderMismatch(-32020). x-mcp-headeron a primitive tool parameter mirrors it into anMcp-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 withsubscriptions/listen. Emit periodic SSE comment lines as keep-alive and sendX-Accel-Buffering: noso 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.