# Streamable HTTP

Streamable HTTP is MCP's remote transport: a single endpoint that accepts POST, carries one JSON-RPC message per request, and may answer either with a JSON body or with an event stream on that same response. Since revision 2026-07-28 there is no session and no separate GET stream.

- **Status:** Reference
- **Audience:** developer
- **Last verified:** 2026-09-10
- **Canonical:** https://connectbyjbrh.com/docs/protocols/streamable-http/

## One endpoint, one method

The whole transport is a URL that accepts POST. A request carries `Content-Type: application/json` and exactly one JSON-RPC message; the response carries the reply. Anything a client needs to say about the conversation travels with the message rather than being remembered between them, which is what makes the endpoint horizontally scalable — any process can answer any request.

The name says 'streamable' rather than 'streaming' for a reason. A server that needs to send progress notifications before its result may answer a POST with `text/event-stream` and write several messages on that one response. A server whose work completes immediately answers with a JSON body. Both are the same transport; the client must be prepared for either and should look at the response's content type rather than assuming.

| Method | Answer |
|---|---|
| POST | The JSON-RPC exchange — this is the transport |
| GET | `405` with `Allow: POST`; the standalone stream was removed |
| DELETE | `405`; there is no session to delete |

## The three headers, and why they must match the body

**`MCP-Protocol-Version`** — The revision this message is written to. Mirrors `params._meta["io.modelcontextprotocol/protocolVersion"]`.
**`Mcp-Method`** — The JSON-RPC method name, copied out of the body so a proxy can route on it without parsing.
**`Mcp-Name`** — The tool, resource or prompt being addressed. Non-ASCII values are wrapped in a `=?base64?…?=` sentinel, because a header field is not a good place for arbitrary text.

If a header is present and disagrees with the body, the server answers `-32020` with HTTP `400`. This is not fussiness. The headers exist so that middleboxes — load balancers, gateways, rate limiters, audit collectors — can act on a request without reading its body. The moment the header and the body can differ, the component that decided where a request went and the component that executed it saw two different requests, and every log downstream is describing something that did not happen.

A client that sends no headers at all is accepted: the body is authoritative. A client that sends them must send them accurately.

## Cancellation, retries and idempotency

Without sessions, cancellation is the transport's own: close the connection. There is no cancel handshake to complete and no session to tidy up afterwards. A `notifications/cancelled` message is accepted and answered with `202` and no body, like any notification.

Retrying is the client's decision and depends on what the call does. Every tool on Connect's public endpoint is read-only and idempotent, so a retry is safe and cheap. That is a property of these particular tools, not of the transport — a transport cannot make a write idempotent, and a client should not assume one is.

- `429` — back off. The window is a rolling minute; retrying immediately just consumes the next slot.
- `413` — do not retry. Send less.
- `400` with `-32020` — fix the header, then retry.
- `-32602` — do not retry. The arguments will be equally wrong.
- A dropped connection mid-response — retry is safe here because these tools read files; for a writing endpoint you would need an idempotency key.

## Does Connect use Streamable HTTP?

Yes — it is the only transport Connect's MCP server offers, at `POST /mcp`. There is no stdio build and no other endpoint.

In practice every response is a JSON body rather than an event stream. The public tools read a generated documentation corpus and complete in one step, so there is no progress to report and nothing to stream. A client that handles both response shapes is still correct; it will simply never see the second one here.

Everything the revision removed is genuinely absent: no session is minted or required, `Mcp-Session-Id` and `Last-Event-ID` are ignored, and `GET` and `DELETE` answer `405` with an explanation rather than silence. Batching is refused, so one message per request is not a convention here but a rule.

```http
POST /mcp HTTP/1.1
Host: connectbyjbrh.com
Content-Type: application/json
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/list

{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}

HTTP/1.1 200 OK
Content-Type: application/json

{"jsonrpc":"2.0","id":1,"result":{"tools":[ ... ]}}
```

## Questions

### Do I need to handle server-sent events to use Connect's MCP endpoint?

Not in practice — every response here is a JSON body. Handling both is still the correct way to write a general MCP client, because another server may answer a POST with a stream when it has progress to report.

### How do I cancel a call in flight?

Close the connection. There are no sessions in this revision, so there is nothing to release afterwards. `notifications/cancelled` is accepted and answered with `202`, but the connection closing is what actually stops the work.

### Why does my proxy see 400 with code -32020?

One of `MCP-Protocol-Version`, `Mcp-Method` or `Mcp-Name` does not match the body. Usually something rewrote a header, or a client set `Mcp-Method` once and reused the connection for a different method. Send the headers that match each message, or send none.

## Related

- [MCP transports](https://connectbyjbrh.com/docs/protocols/mcp-transports/)
- [Model Context Protocol](https://connectbyjbrh.com/docs/protocols/mcp/)
- [JSON-RPC 2.0](https://connectbyjbrh.com/docs/protocols/json-rpc/)
- [Using Connect from any MCP client](https://connectbyjbrh.com/developers/mcp-client-generic/)
- [Designing a resilient client](https://connectbyjbrh.com/developers/errors-and-retries/)

## What this page is based on

- https://modelcontextprotocol.io/specification/2026-07-28
- `backend/app/mcp_server.py` — the POST route, header checks and _decode_header
- `docs-source/facts.py` — PROTOCOLS['mcp'].headers and .transport
