# MCP Streamable HTTP uses one POST endpoint, and Mcp-Session-Id sessions end with 2026-07-28

> Streamable HTTP sends every client message as a POST to one MCP endpoint and answers with JSON or a request-scoped SSE stream; the optional Mcp-Session-Id sessions and GET stream of 2025-03-26 to 2025-11-25 are removed in 2026-07-28.

## Answer

A Streamable HTTP server exposes **one MCP endpoint**. The client sends every JSON-RPC message as its own `POST` with `Accept: application/json, text/event-stream`. The server answers a request with either `Content-Type: application/json` or an SSE stream (`text/event-stream`), and answers an accepted notification with `202 Accepted` and no body. In MCP **2025-03-26 to 2025-11-25**, a server could also issue an `Mcp-Session-Id` and offer a GET stream. The latest revision, **2026-07-28**, removes both: every request is self-contained. This page covers both eras and marks which is which.

## Details

### Rules in every revision

- One endpoint path, for example `https://example.com/mcp`.
- The client MUST send an `Accept` header that lists both `application/json` and `text/event-stream`. The client MUST handle either response type.
- When a server replies with SSE, it may send notifications about that request (such as progress) before the final response. The response SHOULD end the stream.
- Since 2025-06-18, clients send an `MCP-Protocol-Version` request header. An unsupported value gets `400 Bad Request`.
- Servers MUST validate `Origin` and MUST answer an invalid `Origin` with `403 Forbidden`. Local servers SHOULD bind to 127.0.0.1, and all servers SHOULD authenticate. These rules defend against DNS rebinding.

### Sessions in 2025-11-25 (legacy)

1. The server MAY return `MCP-Session-Id` on the HTTP response that carries the `InitializeResult`. The value must be visible ASCII (0x21–0x7E) and SHOULD be cryptographically secure.
2. The client MUST then send that header on every later request. A server that requires it SHOULD answer requests without it with `400`.
3. Once a session ends, the server MUST answer that ID with `404 Not Found`. The client MUST then start over with a new `initialize` and no session ID.
4. A client that is finished SHOULD send `DELETE` with the header. The server MAY answer `405`.

The optional **GET** opened a standalone SSE stream for messages the server starts itself. A server that does not offer that stream MUST answer `405 Method Not Allowed`. Streams could resume with `Last-Event-ID`.

### Stateless vs stateful (legacy era)

The TypeScript SDK v1 makes this a single option. `sessionIdGenerator: () => crypto.randomUUID()` gives a stateful server that issues IDs and returns 404 for unknown ones. `sessionIdGenerator: undefined` gives a stateless server with no session header. The v1 source warns that a stateless transport **cannot be reused across requests**. Create a new one for each request, or two clients' message IDs can collide.

### What 2026-07-28 changed

| Legacy (up to 2025-11-25) | Modern (2026-07-28) |
|---|---|
| Optional `Mcp-Session-Id`, ended with DELETE | No protocol sessions. State goes in server-issued handles passed as tool arguments |
| GET opens a server-to-client stream | GET is removed. Long-lived notifications use a `subscriptions/listen` POST |
| Server may send JSON-RPC requests on SSE | Server returns `InputRequiredResult`, and the client sends the request again with `inputResponses` |
| Resumable via `Last-Event-ID` | Not resumable. The client sends a broken request again with a new ID |
| Only `MCP-Protocol-Version` | Also `Mcp-Method` (every request) and `Mcp-Name` (`tools/call`, `resources/read`, `prompts/get`), which must match the body. A mismatch gets `400` with error `-32020` |
| Cancel with `notifications/cancelled` | Close the request's response stream |

A server that speaks only 2026-07-28 SHOULD answer `GET` and `DELETE` with `405`. It SHOULD ignore `Mcp-Session-Id` and `Last-Event-ID`. An unknown method gets `404` with JSON-RPC error `-32601`. That body tells it apart from a 404 sent by an old HTTP+SSE server.

### Example modern request

```http
POST /mcp HTTP/1.1
Content-Type: application/json
Accept: application/json, text/event-stream
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/list

{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{"_meta":{
  "io.modelcontextprotocol/protocolVersion":"2026-07-28",
  "io.modelcontextprotocol/clientCapabilities":{}}}}
```

### Practical takeaway

If you design a new server today, make it stateless. That is the only shape 2026-07-28 allows, and it scales horizontally without sticky sessions. Keep session handling only where you must still serve 2025-era clients.

## See also

- [[mcp-transports-stdio-vs-http]]
- [[mcp-initialize-handshake]]
- [[mcp-connection-errors]]
- [[mcp-json-rpc-format]]

## Sources

- [MCP Specification 2026-07-28 — Streamable HTTP](https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http)
- [MCP Specification 2025-11-25 — Transports](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports)
- [MCP Specification 2026-07-28 — Key changes](https://modelcontextprotocol.io/specification/2026-07-28/changelog)
- [MCP TypeScript SDK v1.x — Streamable HTTP server transport](https://github.com/modelcontextprotocol/typescript-sdk/blob/v1.x/src/server/webStandardStreamableHttp.ts)

## Sources

- [MCP Specification 2026-07-28 — Streamable HTTP](https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http)
- [MCP Specification 2025-11-25 — Transports](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports)
- [MCP Specification 2026-07-28 — Key changes](https://modelcontextprotocol.io/specification/2026-07-28/changelog)
- [MCP TypeScript SDK v1.x — Streamable HTTP server transport](https://github.com/modelcontextprotocol/typescript-sdk/blob/v1.x/src/server/webStandardStreamableHttp.ts)