Chapter 15

For Remote MCP, Design for Streamable HTTP

When MCP leaves a single machine, Streamable HTTP takes over from stdio. It has one MCP endpoint: each request is sent as a separate POST, and the response can be either JSON or a request-scoped SSE stream. It's worth fixing at once what it is not: this is an HTTP transport for JSON-RPC, not an arbitrary REST API where tool names turned into URL paths.

Here is what a remote tools/call looks like at the wire level - with the revision and routing headers.

POST /mcp HTTP/1.1
Host: tools.example.com
Content-Type: application/json
Accept: application/json, text/event-stream
Authorization: Bearer …
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: review_release

{
  "jsonrpc": "2.0",
  "id": "call-42",
  "method": "tools/call",
  "params": { … modern _meta … }
}

And here is the routing logic on the server side - a description of behavior, not a ready-made file.

SQL
POST /mcp
  validate Origin + authentication
  validate version/method/name headers against body
  parse one JSON-RPC request
  authorize capability + arguments
  return application/json
  or request-scoped text/event-stream

POST /mcp method=subscriptions/listen
  long-lived SSE stream for opted-in change notifications

GET /mcp and DELETE /mcp
  405 Method Not Allowed in 2026-07-28

The key shift here is that the modern transport doesn't tie lists, calls, and results to a TCP connection. That means cache and application state must be explicit. A plain JSON response fits a fast result - it's easier to proxy, observe, and retry. A request-scoped SSE is needed where progress and related notifications flow during a specific request. For long work use the task extension or a domain job id, not the hope that the next request lands on the same pod.

A broken stream doesn't resume automatically. The 2026-07-28 revision removed SSE redelivery via Last-Event-ID. A lost in-flight request is repeated with a new request id only if the operation allows a safe retry. Design idempotency up front, not after the first lost connection.

The transport is chosen - but a production client must manage more than the happy path. Let's give it progress, cancellation, notifications, and a cache.

Links