# MCP SSE и Streamable HTTP: переход со старого транспорта

> Чем старый транспорт HTTP+SSE отличается от Streamable HTTP двух редакций, как клиент распознаёт старый сервер, как перевести сервер и что не должен делать обратный прокси

Страница: https://mcpdoc.ru/deployment/http-sse/
Указатель сайта: https://mcpdoc.ru/llms.txt

Удалённый сервер MCP сегодня работает по Streamable HTTP: один адрес, который принимает POST. Старый транспорт HTTP+SSE с двумя адресами устарел ещё в редакции `2025-03-26`, но из спецификации не удалён.

SSE при этом никуда не делся. В Streamable HTTP это способ отдать ответ потоком, а не отдельный транспорт. Путать эти две вещи - самая частая ошибка.

описана редакция 2026-07-28, сверено со спецификацией 4 октября 2026

Совместимость Dual-era страница описывает обе эпохи

- **Страница описывает редакцию:** 2026-07-28

- **Затрагивает устаревшее:** транспорт HTTP+SSE

- **Затрагивает удалённое в редакции 2026-07-28:** сессии

- **Проверено:** 2026-10-04

- **Источник:** [modelcontextprotocol.io](https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http)

## Три вещи, которые путают

Под словами «сервер на SSE» скрываются три разных устройства.

**1. HTTP+SSE из редакции `2024-11-05`.** У сервера два адреса. По первому клиент открывает поток SSE и получает сообщения сервера. По второму клиент отправляет свои сообщения запросами POST. Адрес для POST сервер сообщает первым событием потока, оно называется `endpoint`. Этот транспорт устарел.

**2. Streamable HTTP редакций `2025-03-26` - `2025-11-25`.** Адрес один, он принимает POST и GET. Сервер может выдать номер сессии, клиент может открыть отдельный поток запросом GET, оборванный поток можно возобновить. Это Legacy.

**3. Streamable HTTP редакции `2026-07-28`.** Адрес один, он принимает только POST. Сессий, потока по GET, запроса DELETE и возобновления потока нет. Это Modern.

В редакции `2026-07-28` SSE остался в двух местах: как один из двух видов ответа на POST и как вид ответа на запрос `subscriptions/listen`. Отдельного адреса для потока нет. Называть Streamable HTTP транспортом SSE неверно.

Полное описание действующего транспорта - на странице [«Транспорты»](https://mcpdoc.ru/protocol/transports/). Здесь только то, что нужно для перехода и развёртывания.

## Сравнение

| Свойство | HTTP+SSE, `2024-11-05` | Streamable HTTP, `2025-11-25` | Streamable HTTP, `2026-07-28` |
|---|---|---|---|
| адреса | два: поток SSE и адрес для POST | один | один |
| методы HTTP | GET для потока, POST для сообщений | POST и GET | только POST |
| начало работы | GET, затем событие `endpoint` | POST с `initialize` | любой запрос, рукопожатия нет |
| сессии | в описании транспорта заголовка сессии нет | необязательны, заголовок `MCP-Session-Id` | удалены |
| отдельный поток от сервера | весь обмен идёт по одному потоку | клиент может открыть его запросом GET | нет, вместо него `subscriptions/listen` |
| собственные запросы сервера в потоке | - | разрешены | запрещены |
| завершение сессии | - | клиенту следует отправить DELETE | нет, на DELETE сервер отвечает `405` |
| возобновление потока | - | необязательно, через GET и `Last-Event-ID` | нет |
| обрыв потока ответа | - | не считается отменой запроса | считается отменой запроса |
| статус в спецификации | Deprecated | Legacy | действует |

Прочерк означает, что в описании транспорта `2024-11-05` этого вопроса нет.

Источники строк: [транспорты `2024-11-05`](https://modelcontextprotocol.io/specification/2024-11-05/basic/transports), [транспорты `2025-11-25`](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports), [Streamable HTTP `2026-07-28`](https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http) и [перечень изменений `2026-07-28`](https://modelcontextprotocol.io/specification/2026-07-28/changelog).

### Что изменилось между двумя формами Streamable HTTP

- Удалены сессии на уровне протокола и заголовок `Mcp-Session-Id`.
- Удалён поток по GET. Долгий поток уведомлений клиент получает запросом `subscriptions/listen`: ответ на этот POST и есть поток, который остаётся открытым.
- Удалено возобновление потока. Оборванный поток означает потерянный запрос. Клиент обязан отправить его заново с новым номером запроса.
- Сервер не должен отправлять в потоке собственные запросы. В редакциях `2025-03-26` - `2025-11-25` это было разрешено.
- Закрытие потока ответа сервер обязан считать отменой запроса. В редакции `2025-11-25` было наоборот: обрыв не следовало считать отменой.
- В теле POST теперь может быть только запрос или уведомление. Ответы JSON-RPC клиент не отправляет.

## Заголовки и правило 400

В редакции `2026-07-28` клиент обязан указывать:

| Заголовок | Когда |
|---|---|
| `Accept` с обоими типами, `application/json` и `text/event-stream` | в каждом POST |
| `MCP-Protocol-Version` | в каждом POST |
| `Mcp-Method` | в каждом запросе |
| `Mcp-Name` | в запросах `tools/call`, `resources/read`, `prompts/get` |

Значение `MCP-Protocol-Version` обязано совпадать с версией в поле `_meta` тела запроса.

Правило проверки одно. Если обязательного заголовка нет, если он не совпал с телом или содержит недопустимые символы, сервер обязан ответить статусом `400` и ошибкой JSON-RPC `-32020` (`HeaderMismatch`).

Ещё три ответа, которые полезно различать:

- версия не поддерживается - `400` и ошибка `-32022` со списком поддерживаемых версий;
- метод серверу неизвестен - `404` и ошибка `-32601`;
- принято уведомление - `202` без тела.

Для обратного прокси из этого следует практический вывод: перечисленные заголовки нужно пропускать без изменений, иначе сервер отклонит запрос. Прямое требование к посредникам спецификация записывает только для заголовков `Mcp-Param-*`: незнакомый заголовок посредник обязан передать дальше. Посреднику, который принимает решения по этим заголовкам, следует отклонять запрос, если заголовка версии нет или версия старше.

## Как клиент распознаёт старый сервер HTTP+SSE

Спецификация `2026-07-28` описывает порядок для клиента, который хочет работать и со старыми серверами. Это описание, а не обязательное требование.

1. Клиент принимает от пользователя адрес сервера. Адрес может вести и на старый, и на новый транспорт.
2. Клиент отправляет на этот адрес POST с заголовком `Accept`.
3. Если запрос удался, перед ним сервер на Streamable HTTP.
4. Если пришёл статус `400`, `404` или `405` и в теле нет распознанной ошибки Modern, клиент отправляет GET на тот же адрес.
5. Если в ответ открылся поток и первым пришло событие `endpoint`, перед ним сервер на HTTP+SSE. Дальше клиент работает с ним по старому транспорту.

Условие про тело ответа появилось в редакции `2026-07-28`. Сервер Modern тоже отвечает `400` и `404`, но с ошибкой JSON-RPC в теле: неподдерживаемая версия, неизвестный метод, ошибка заголовков. Такой ответ означает сервер Modern, и переходить на старый транспорт клиенту не нужно. В редакции `2025-11-25` этого условия не было, а первым запросом был `initialize`.

Выполняет ли этот порядок конкретная программа-клиент, зависит от неё самой. Спецификация этого не гарантирует.

## Статус HTTP+SSE

HTTP+SSE устарел с редакции `2025-03-26`. В `2026-07-28` он отнесён к состоянию Deprecated по правилам жизненного цикла ([SEP-2596](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2596)). Новым реализациям использовать его не следует, существующим следует перейти на Streamable HTTP.

В [реестре устаревшего](https://modelcontextprotocol.io/specification/2026-07-28/deprecated) самый ранний срок удаления записан словами: «через три месяца после того, как SEP-2596 получит статус Final». Календарной даты в реестре нет. Раздел «Removed» того же реестра пуст.

Этот срок - момент, начиная с которого транспорт можно удалить из спецификации. Это не дата, когда перестанут работать ваши серверы и клиенты: они работают, пока их поддерживают программы на обеих сторонах. Подробнее - на странице [«Что устарело в MCP»](https://mcpdoc.ru/protocol/deprecations/).

## Как перевести сервер с HTTP+SSE

Шаги 1 и 2 следуют спецификации. Остальные - практика, а не правило MCP.

1. **Добавьте адрес Streamable HTTP рядом со старыми.** Спецификация описывает именно такой путь: сервер, которому нужны старые клиенты, продолжает держать оба адреса HTTP+SSE и добавляет новый адрес MCP. Совместить старый адрес для POST с новым адресом можно, но спецификация предупреждает о лишней сложности.
2. **Решите, какие редакции обслуживает новый адрес.** Только `2026-07-28` или ещё и Legacy с `initialize`. Второй вариант описан на странице [«Сервер для двух эпох»](https://mcpdoc.ru/deployment/dual-era/).
3. **Возьмите транспорт из выпущенной версии SDK.** Что умеет каждый SDK, записано на его странице в разделе [«SDK»](https://mcpdoc.ru/sdk/). Не переносите старый код с двумя адресами в новый обработчик.
4. **Уберите зависимость от сессий, если идёте в Modern.** В редакции `2026-07-28` запрос может прийти на любой экземпляр сервера. Состояние между вызовами передаётся явно - см. [«Ядро без состояния»](https://mcpdoc.ru/protocol/stateless/).
5. **Проверьте оба адреса.** Новый - запросом curl с заголовками Modern, старый - прежним клиентом.
6. **Смотрите, кто ещё ходит на старые адреса.** В журнале сервера это запросы GET на адрес потока.
7. **Отключите старые адреса, когда решите сами.** Спецификация срока не назначает. Это решение владельца сервера.

## Что не должен делать обратный прокси

Этот раздел - практика, а не правило MCP. Сведения о поведении nginx взяты из [документации модуля `ngx_http_proxy_module`](https://nginx.org/en/docs/http/ngx_http_proxy_module.html).

**Не накапливать поток ответа.** В nginx директива `proxy_buffering` по умолчанию включена. При выключенной буферизации ответ передаётся клиенту сразу, по мере получения. Иначе события потока придут клиенту пачкой или с задержкой.

Здесь спецификация и nginx сходятся. Спецификация говорит, что серверу следует добавлять к потоку SSE заголовок `X-Accel-Buffering: no`. Документация nginx подтверждает: значение `no` в этом заголовке ответа отключает буферизацию. Но это работает, только пока заголовок не отключён директивой `proxy_ignore_headers`.

**Не обрывать тихий поток раньше времени.** Директива `proxy_read_timeout` по умолчанию равна 60 секундам. Это время между двумя чтениями, а не на весь ответ: если сервер молчит дольше, nginx закроет соединение. Спецификация советует серверу время от времени отправлять в долгий поток строку-комментарий SSE. Какой промежуток выбрать и нужно ли менять срок ожидания, решаете вы.

**Не забывать, что обрыв теперь означает отмену.** В редакции `2026-07-28` сервер обязан считать закрытие потока отменой запроса. Если соединение закрыл прокси, запрос для клиента потерян, и отправлять его придётся заново с новым номером.

**Не менять и не удалять заголовки MCP.** Без `MCP-Protocol-Version` и `Mcp-Method` сервер Modern обязан ответить `400`.

```nginx title="Схема: проксирование адреса MCP"
location /mcp {
    proxy_pass http://127.0.0.1:3000;
    proxy_buffering off;
}
```

Пример схематичный, составлен по документации nginx и здесь не запускался. Адрес и порт условные.

Отдельно от прокси действуют требования спецификации к самому серверу: он обязан проверять заголовок `Origin`, при локальном запуске ему следует слушать только `127.0.0.1`, и ему следует проверять подлинность соединений. Об авторизации - раздел [«Модель авторизации»](https://mcpdoc.ru/auth/).

Удалённому серверу нужна машина с постоянным адресом и сертификатом. Виртуальный сервер можно арендовать у [FirstVDS](https://mcpdoc.ru/go/firstvds/) или [Aéza](https://mcpdoc.ru/go/aeza/).

## Что читать дальше

- [Транспорты](https://mcpdoc.ru/protocol/transports/) - полное описание stdio и Streamable HTTP редакции `2026-07-28`.
- [Сервер для двух эпох](https://mcpdoc.ru/deployment/dual-era/) - как одним адресом отвечать клиентам Legacy и Modern.
- [Что устарело в MCP](https://mcpdoc.ru/protocol/deprecations/) - реестр устаревшего и сроки.
- [Ядро без состояния](https://mcpdoc.ru/protocol/stateless/) - как работать без сессий.
