Сервер для двух эпох
Как один сервер MCP отвечает клиентам Legacy и Modern - что разрешает спецификация, как различить запросы на одном адресе, какие выпущенные SDK это умеют и как проверить
описана редакция 2026-07-28, сверено со спецификацией и выпусками SDK 4 октября 2026
Что получится при каждом сочетании
Заголовок раздела «Что получится при каждом сочетании»Таблица пересказывает матрицу совместимости из спецификации 2026-07-28.
| Клиент | Сервер | Итог | Что происходит |
|---|---|---|---|
| Modern | Modern | работает | несовпадение версий приходит ошибкой -32022, клиент повторяет запрос с общей версией |
| Modern | Legacy | не работает | сервер может вернуть свою ошибку, промолчать или обработать запрос по старым правилам |
| Dual-era | Modern | работает | первый запрос Modern удаётся или возвращает ошибку Modern, клиент остаётся в Modern |
| Dual-era | Legacy | работает | клиент получает ответ 4xx без распознанной ошибки Modern и переходит к initialize |
| Legacy | Modern | не работает | по HTTP запрос без обязательных заголовков отклоняется со статусом 400 |
| Legacy | Dual-era | работает | сервер отвечает на initialize и обслуживает клиента по согласованной редакции Legacy |
| Legacy | Legacy | работает | по правилам редакции Legacy |
Две строки «не работает» различаются по сути. Клиент Modern перед сервером Legacy может получить понятную ошибку, если начнёт с server/discover: на stdio спецификация говорит, что клиенту следует так делать. У клиента Legacy перед сервером Modern выхода нет. Поэтому совместимость со старыми клиентами - забота сервера.
Что спецификация разрешает и что требует
Заголовок раздела «Что спецификация разрешает и что требует»Может. Сервер, который хочет обслуживать и клиентов Legacy, и клиентов Modern, может реализовать оба поведения. Сервер Dual-era может обслуживать обе эпохи одновременно на одном адресе или в одном процессе.
Обязан. Каждый сервер Modern обязан реализовать server/discover. На запрос с неподдерживаемой версией он обязан ответить ошибкой -32022 со списком версий, которые поддерживает.
Следует. Серверу, который знает только Modern, следует называть поддерживаемые версии в любой ошибке, которую он возвращает на initialize. Это может быть единственная подсказка, которую клиент Legacy покажет человеку.
Сторону Legacy сервер Dual-era реализует по тексту соответствующей старой редакции, например 2025-11-25. То есть для таких клиентов остаются сессии, поток по GET и всё остальное, что было в той редакции. Чем эти формы различаются, показано на странице «Streamable HTTP и переход с HTTP+SSE».
Как различить эпоху запроса на одном адресе
Заголовок раздела «Как различить эпоху запроса на одном адресе»Спецификация задаёт правило так: сервер Dual-era выбирает поведение по тому, как клиент начинает разговор.
- Запрос с полями Modern в
_metaобслуживается без состояния, по редакции2026-07-28. - Запрос
initializeвключает правила Legacy. Они действуют в пределах процесса на stdio и в пределах сессии на HTTP.
На HTTP у запроса Modern есть и внешние признаки: заголовок MCP-Protocol-Version: 2026-07-28 и заголовок Mcp-Method. Значение заголовка обязано совпадать с версией в теле.
Запрос initialize редакции 2025-11-25 может прийти без заголовка версии: та редакция требует заголовок в запросах после рукопожатия. Признак здесь - сам метод initialize в теле.
Как именно разбирать запросы, спецификация не предписывает. Один пример из выпущенного SDK: по документации Python SDK 2.3.0 он направляет каждый запрос по заголовку MCP-Protocol-Version.
Запрос без MCP-Protocol-Version
Заголовок раздела «Запрос без MCP-Protocol-Version»Правило в редакции 2026-07-28 состоит из двух частей.
- Сервер, который поддерживает клиентов редакций старше
2025-06-18, может считать запрос без заголовка запросом редакции2025-03-26. В тех редакциях этого заголовка ещё не было. - Сервер, который таких клиентов не поддерживает, обязан отклонить запрос без заголовка: статус
400и ошибка-32020.
В редакции 2025-11-25 правило было мягче: если заголовка нет и версию нельзя узнать иначе, серверу следует считать версию равной 2025-03-26.
Для сервера Dual-era это место нужно решить явно. Иначе один и тот же запрос без заголовка один сервер примет как Legacy, а другой отклонит.
Что отвечает сервер, который знает только Modern
Заголовок раздела «Что отвечает сервер, который знает только Modern»Серверу, который поддерживает только 2026-07-28, спецификация говорит, что на трафик старых клиентов следует отвечать так:
| Что пришло | Ответ |
|---|---|
| GET или DELETE на адрес MCP | 405 |
заголовок Mcp-Session-Id |
игнорировать, номера сессий не выдавать и не возвращать |
заголовок Last-Event-ID |
игнорировать, потоки не возобновляются |
initialize |
ошибка, в которой названы поддерживаемые версии |
Сервер Dual-era ведёт себя иначе: GET и DELETE для него - часть редакции Legacy, и отвечает он на них по её правилам.
Ошибка -32022
Заголовок раздела «Ошибка -32022»-32022 (UnsupportedProtocolVersionError) - ответ на версию, которую сервер не поддерживает. По HTTP она приходит со статусом 400. В поле data.supported сервер перечисляет свои версии.
{ "jsonrpc": "2.0", "id": 1, "error": { "code": -32022, "message": "Unsupported protocol version", "data": { "supported": ["2026-07-28", "2025-11-25"], "requested": "1900-01-01" } }}Пример взят из текста спецификации 2026-07-28 и здесь не запускался.
Клиенту следует выбрать общую версию из списка и повторить запрос. Для клиента Dual-era эта ошибка означает сервер Modern: откат на initialize здесь не нужен. Откат уместен, когда в теле ответа нет распознанной ошибки Modern.
Какие выпущенные SDK дают сервер для двух эпох
Заголовок раздела «Какие выпущенные SDK дают сервер для двух эпох»В таблице только выпущенные версии, сверенные по метке выпуска. Клиентская сторона SDK здесь не рассматривается.
| SDK | Выпуск | Сервер для двух эпох | При какой настройке |
|---|---|---|---|
| TypeScript | 2.3.0 | да | createMcpHandler по умолчанию обслуживает и трафик Legacy (legacy: 'stateless'); legacy: 'reject' его отклоняет |
| Python | 2.3.0 | да | streamable_http_app() обслуживает обе эпохи всегда; отключить одну из них нельзя |
| C# | 2.2.0 | да, зависит от режима | см. ниже |
| Go | v1.8.0 | да, при включённой настройке | по Streamable HTTP запросы 2026-07-28 принимаются только при StreamableHTTPOptions.Stateless = true; по умолчанию настройка выключена, и такие запросы отклоняются. Набор версий сужает ServerOptions.SupportedProtocolVersions |
| Rust | rmcp 3.5.0 | да | запросы 2026-07-28 всегда обслуживаются без состояния; legacy_session_mode (по умолчанию true) относится только к версиям Legacy |
| Ruby | 1.6.1 | да | встроенные транспорты обслуживают обе эпохи без настройки |
| PHP | v0.8.1 | да | один адрес отвечает и на initialize, и на server/discover; withoutModernEra() отключает Modern |
| Java | 2.0.1 | нет, только Legacy | новейшая редакция в выпуске - 2025-11-25 |
| Kotlin | 0.15.0 | нет, только Legacy | новейшая редакция в выпуске - 2025-11-25 |
| Swift | 0.12.1 | нет, только Legacy | новейшая редакция в выпуске - 2025-11-25 |
Источники: документация выпусков TypeScript, Python, C#, Go, Ruby, PHP; исходный код выпусков Rust, Java, Kotlin, Swift.
Уточнения к таблице:
- Старые линии TypeScript 1.x (1.32.0) и Python 1.x (1.30.0) знают только Legacy.
- PHP SDK сам называет себя экспериментальным до первой основной версии.
- У Java поддержка
2026-07-28записана в планах на линию 3.x. На 4 октября 2026 выпусков этой линии нет. - У Kotlin в ветке
mainесть экспериментальные типы дляserver/discover. В выпуск 0.15.0 они не входят.
Режимы сервера в C# SDK 2.2.0
Заголовок раздела «Режимы сервера в C# SDK 2.2.0»В C# поведение задаёт свойство SessionMode. Режимов три.
| Режим | Запрос 2026-07-28 |
Клиент с initialize |
|---|---|---|
Stateless (по умолчанию) |
обслуживается без состояния | работает на том же адресе без сессии, в пределах одного POST |
Stateful |
отклоняется ошибкой -32022 |
получает сессию |
StatefulForInitializeClients |
обслуживается без состояния | получает полноценную сессию |
В режиме Stateless клиентам Legacy недоступно то, что зависит от сессии: уведомления без запроса, подписки на ресурсы, разделение по клиентам. Документация SDK советует задавать SessionMode явно, а не полагаться на значение по умолчанию.
Как проверить свой сервер
Заголовок раздела «Как проверить свой сервер»Достаточно двух запросов. Оба примера схематичные: адрес условный, запросы составлены по тексту спецификации.
curl -i https://example.com/mcp \ -H 'Content-Type: application/json' \ -H 'Accept: application/json, text/event-stream' \ -H 'MCP-Protocol-Version: 2026-07-28' \ -H 'Mcp-Method: server/discover' \ -d '{"jsonrpc":"2.0","id":"discover-1","method":"server/discover","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientInfo":{"name":"ExampleClient","version":"1.0.0"},"io.modelcontextprotocol/clientCapabilities":{}}}}'Пример составлен по тексту спецификации 2026-07-28 и здесь не запускался.
Сервер с поддержкой Modern вернёт результат с полем supportedVersions. Если сервер ответил статусом 400 без ошибки JSON-RPC в теле, Modern он не обслуживает.
curl -i https://example.com/mcp \ -H 'Content-Type: application/json' \ -H 'Accept: application/json, text/event-stream' \ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"ExampleClient","version":"1.0.0"}}}'Пример составлен по тексту спецификации 2025-11-25 и здесь не запускался.
Как читать ответ на второй запрос:
- пришёл результат
initialize- сервер обслуживает Legacy. Если в ответе есть заголовокMCP-Session-Id, сервер выдал сессию; - пришёл статус
400с ошибкой JSON-RPC - сервер Legacy не обслуживает. Такому серверу следует назвать в ошибке поддерживаемые версии.
Если оба запроса удались, сервер работает как Dual-era. То же самое можно проверить в MCP Inspector, переключая настройку эпохи между legacy и modern.
Когда отключать Legacy
Заголовок раздела «Когда отключать Legacy»Срока, после которого сервер обязан отказаться от Legacy, в спецификации нет. Нет и требования поддерживать Legacy: сервер Dual-era разрешён, но не обязателен. Это решение владельца сервера.
На что опираться при решении - это практика, а не правило MCP:
- какие клиенты ходят к вашему серверу и какую редакцию они поддерживают - см. «Матрица клиентов»;
- сколько запросов
initializeприходит по журналу сервера; - нужны ли вам возможности, которые в Modern устроены иначе: сессии, поток по GET, собственные запросы сервера.
Не рассчитывайте на то, что клиент сам справится. Откат на initialize - поведение клиента Dual-era, и не каждый клиент так устроен. Клиент Legacy не умеет ничего, кроме initialize.
Что читать дальше
Заголовок раздела «Что читать дальше»- Streamable HTTP и переход с HTTP+SSE - чем различаются формы транспорта по редакциям.
- Редакции и версии протокола - как клиент определяет эпоху сервера.
- Переход на редакцию 2026-07-28 - что меняется в самом сервере.
- SDK - состояние каждого официального SDK.