28 июля 2026 года вышла новая ревизия Model Context Protocol — 2026-07-28. Она заменяет 2025-11-25 и стала самым крупным изменением протокола с момента запуска: протокол стал stateless, из него убрали сессии и handshake, серверные запросы к клиенту заменили новым механизмом, появился формальный каркас расширений и политика устаревания. Для команды, у которой десяток внутренних MCP-серверов и несколько клиентов, это не повод всё бросить: старые реализации продолжают работать. Но это повод спланировать миграцию, пока окно устаревания открыто. Ниже — что именно поменялось в спецификации MCP 2026-07-28, что ломается, что можно не трогать и в каком порядке переходить.
Главное за минуту
- Stateless-ядро. Нет
initialize/initialized, нет заголовкаMcp-Session-Id. Каждый запрос самодостаточен. - Multi Round-Trip Requests (SEP-2322). Вместо серверных запросов
elicitation/create,sampling/createMessageиroots/listсервер возвращает результат «нужен ввод», а клиент повторяет вызов с ответами. - Маршрутизация по заголовкам (SEP-2243). HTTP-запросы несут
Mcp-MethodиMcp-Name— шлюзы маршрутизируют и авторизуют без разбора JSON. - Кэшируемые списки (SEP-2549).
tools/listи соседи отдаютttlMsиcacheScope. - Авторизация жёстче. Проверка
issпо RFC 9207,application_typeв DCR, привязка клиентских учётных данных к issuer’у, DCR устарел в пользу Client ID Metadata Documents. - Расширения. Формальный каркас; Tasks, MCP Apps и Enterprise Managed Authorization — расширения.
- Устаревание. Roots, Sampling, Logging и транспорт HTTP+SSE помечены deprecated с окном минимум 12 месяцев.
Stateless-ядро: прощай, сессия
Раньше клиент открывал соединение, выполнял initialize, получал Mcp-Session-Id и дальше жил внутри сессии. Сервер хранил состояние, а балансировщику приходилось держать sticky sessions, чтобы запросы одной сессии попадали на один экземпляр.
В 2026-07-28 это убрали (SEP-2575 — handshake, SEP-2567 — сессии). Каждый запрос несёт версию протокола, информацию о клиенте и его возможностях в _meta — ключами вида io.modelcontextprotocol/protocolVersion, io.modelcontextprotocol/clientInfo, io.modelcontextprotocol/clientCapabilities. Если версия не поддерживается, сервер отвечает UnsupportedProtocolVersionError. Чтобы узнать возможности сервера заранее, клиент может вызвать новый необязательный server/discover.
Практический эффект: любой экземпляр сервера может обработать любой запрос. Round-robin балансировка работает без общего хранилища сессий, горизонтальное масштабирование становится обычной задачей для stateless-сервиса.
Так выглядит вызов инструмента по новой спецификации — пример из анонса release candidate:
POST /mcp HTTP/1.1
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: search
Content-Type: application/json
{"jsonrpc":"2.0","id":1,"method":"tools/call",
"params":{"name":"search","arguments":{"q":"otters"},
"_meta":{"io.modelcontextprotocol/clientInfo":
{"name":"my-app","version":"1.0"}}}}
В запросе нет ни идентификатора сессии, ни ссылки на предыдущий handshake. Всё, что нужно серверу, — версия протокола в заголовке, метод и имя инструмента в заголовках для инфраструктуры и сведения о клиенте в _meta тела.
Что ломается. Серверы, которые хранили состояние в сессии: выбранный проект, открытый курсор, контекст пользователя. Разбор Stacktree предлагает прямой рецепт: выдавать явные серверные дескрипторы (handle) и передавать их как обычные аргументы инструментов. Вместо «в этой сессии выбран проект X» — инструмент select_project возвращает project_handle, а следующие вызовы принимают его параметром.
Пример такого перехода для внутреннего сервера трекера задач (схемы упрощены):
// было: состояние в сессии
{"name": "select_project", "arguments": {"key": "DEV"}}
{"name": "list_issues", "arguments": {"status": "open"}}
// стало: явный handle в аргументах
{"name": "open_project", "arguments": {"key": "DEV"}}
// → {"project_handle": "ph_7f3a…", "expires_in": 900}
{"name": "list_issues", "arguments": {"project_handle": "ph_7f3a…", "status": "open"}}
У handle есть обязательные свойства, о которых легко забыть. Он должен быть привязан к пользователю, который его получил: чужой handle, переданный в аргументах, сервер отвергает. Он должен иметь срок жизни. И он не должен быть предсказуемым: последовательные номера вроде project_1, project_2 превращают handle в способ перебирать чужие данные. Раньше эти свойства давала сессия транспорта, теперь их надо обеспечить в самом инструменте.
Multi Round-Trip Requests вместо серверных запросов
В старой модели сервер посреди выполнения инструмента мог сам спросить клиента: «уточни у пользователя» (elicitation), «сгенерируй текст своей моделью» (sampling), «какие корни файловой системы доступны» (roots). Для этого нужен открытый поток от сервера к клиенту — то есть состояние.
SEP-2322 переворачивает схему. Каждый результат теперь несёт resultType: complete или input_required. Если серверу нужен ввод, он возвращает InputRequiredResult с inputRequests (что нужно) и непрозрачным requestState (где он остановился). Клиент собирает ответы — например, спрашивает пользователя — и повторяет исходный вызов с inputResponses и тем же requestState. Состояние едет в запросе, а не в соединении, поэтому продолжить работу может любой экземпляр сервера.
Пример из анонса release candidate — сервер просит подтверждения перед удалением файлов:
{
"resultType": "input_required",
"inputRequests": {
"confirm": {
"type": "elicitation",
"message": "Delete 3 files?",
"schema": { "type": "boolean" }
}
},
"requestState": "eyJzdGVwIjoxLCJmaWxlcyI6WyJhIiwiYiIsImMiXX0="
}
Клиент показывает пользователю вопрос, получает ответ и повторяет исходный tools/call, добавив inputResponses с ответом на confirm и тот же requestState.
Для безопасности это хорошая новость. Каждый шаг, где сервер хочет что-то от пользователя, теперь — явный цикл запрос-ответ через клиент, который видно в журнале и который шлюз может проверить.
Но присмотритесь к requestState из примера. Это обычный base64, и внутри лежит {"step":1,"files":["a","b","c"]} — список файлов, которые сервер собирается удалить. Для документации пример нормальный, а в продакшене такой подход опасен: клиент или посредник может заменить список файлов, и сервер удалит не то, на что пользователь дал согласие. Спецификация описывает requestState как непрозрачный токен, а защиту его целостности оставляет реализации. Поэтому сервер должен подписывать или шифровать состояние (например, HMAC с серверным ключом), привязывать его к пользователю и исходному вызову, ограничивать срок жизни и отвергать повторное использование для необратимых операций.
Заголовки, кэш и другие совместимые изменения
Mcp-Method и Mcp-Name (SEP-2243). POST-запросы Streamable HTTP обязаны нести метод и имя (например, инструмента) в заголовках. Для платформенной команды это одно из самых полезных изменений: шлюз, rate limiter или WAF может разрешать, ограничивать и маршрутизировать вызовы конкретных инструментов без разбора тела запроса. Важно: заголовок — подсказка для инфраструктуры, а не источник истины. Шлюз, который принимает решение по заголовку, должен сверять его с телом, иначе клиент сможет вызвать delete_repo с заголовком Mcp-Name: list_repos.
Кэшируемые списки (SEP-2549). Ответы tools/list, prompts/list, resources/list, resources/templates/list и resources/read содержат ttlMs и cacheScope (public или private). Изменение аддитивное, но полезное: клиенты перестают перезапрашивать каталог инструментов на каждый шаг агента. Следите за cacheScope: список инструментов, который зависит от прав пользователя, должен быть private, иначе общий кэш покажет одному пользователю инструменты другого.
Авторизация: что усилили
Раздел авторизации получил несколько точечных, но важных правок:
- Проверка issuer’а (SEP-2468, RFC 9207). Сервер авторизации обязан возвращать параметр
iss, а клиент — проверить его до обмена кода на токен. Это закрывает атаку mix-up, когда клиент, работающий с несколькими серверами авторизации, отправляет код не тому. application_typeв Dynamic Client Registration (SEP-837). Десктопные и CLI-клиенты указывают свой тип, и сервер авторизации перестаёт отвергать redirect наlocalhost.- Учётные данные привязаны к issuer’у (SEP-2352). Клиентские креды, полученные у одного сервера авторизации, нельзя переиспользовать у другого.
- DCR устарел. Рекомендуемый путь — Client ID Metadata Documents (CIMD): клиент идентифицируется URL своего метадокумента. DCR продолжает работать для совместимости.
Почему DCR уступает место CIMD. Dynamic Client Registration позволял любому клиенту зарегистрироваться на сервере авторизации и получить client_id. Для открытой экосистемы MCP, где клиентов тысячи, это означало, что сервер авторизации накапливает бесконечный список анонимных регистраций, а администратор не может сказать, кому он выдал доступ. В модели Client ID Metadata Documents идентификатором клиента служит HTTPS-адрес его метадокумента: сервер авторизации загружает документ по этому адресу и узнаёт из него имя клиента, допустимые redirect URI и другие параметры. Регистрации нет, а идентичность клиента привязана к домену, которым он управляет. Для корпоративной политики это удобнее: разрешать или запрещать можно конкретные домены клиентов. Как устроена авторизация MCP в целом, мы разбирали в статье «OAuth-авторизация в MCP».
Если ваш MCP-сервер сам реализует OAuth, это обязательная часть миграции. Если авторизацию делает шлюз или внешний IdP, проверьте, что они поддерживают iss и CIMD.
Расширения: Tasks, MCP Apps, EMA
Спецификация ввела формальный каркас расширений. Три важных расширения:
- Tasks (
io.modelcontextprotocol/tasks, SEP-2663). Долгие операции, которые раньше были экспериментальной частью ядра. Блокирующийtasks/resultубран — теперь опрос черезtasks/get, ввод от клиента черезtasks/update, а уведомления собраны в один потокsubscriptions/listenс подпиской на нужные типы. - MCP Apps. Интерактивный UI, который инструмент возвращает в чат, — стал частью каркаса расширений. Риски этого расширения подробно разобраны в статье «MCP Apps: интерактивный UI инструментов в чате и как его обезопасить».
- Enterprise Managed Authorization (EMA). Интеграция с корпоративной идентификацией оформлена расширением.
Смысл каркаса — ядро остаётся маленьким и стабильным, а новые возможности развиваются отдельно и подключаются по согласованию.
Tasks нужны там, где инструмент работает дольше, чем разумно держать HTTP-запрос: сборка отчёта по всем репозиториям, выгрузка большой таблицы, прогон тестов, транскрибация записи. Сервер сразу возвращает идентификатор задачи, клиент опрашивает её через tasks/get, а если задаче нужен ввод — передаёт его через tasks/update. С точки зрения безопасности идентификатор задачи — такой же handle, как описанные выше: он должен быть привязан к пользователю, непредсказуем и ограничен по времени. Иначе tasks/get с чужим идентификатором отдаст результат чужой выгрузки. Отдельно продумайте, сколько хранятся результаты завершённых задач: выгрузка с персональными данными не должна лежать на сервере неделями только потому, что клиент её не забрал.
Что устарело и сколько есть времени
Roots, Sampling и Logging (SEP-2577) и транспорт HTTP+SSE (SEP-2596) помечены deprecated. Новая политика устаревания гарантирует минимум 12 месяцев до удаления (для срочных случаев предусмотрено ускоренное окно не меньше 90 дней). Устаревшие функции продолжают работать, но строить на них новое не стоит.
Самое срочное здесь — HTTP+SSE. Если у вас остались серверы на старом транспорте, это первый кандидат на перевод в Streamable HTTP.
План миграции для команды
- Инвентаризация. Список всех MCP-серверов и клиентов: версия протокола, транспорт, используются ли сессии, elicitation, sampling, roots, Tasks, собственный OAuth.
- Обновить SDK. По данным официального анонса, SDK первого уровня — TypeScript, Python, Go и C# — обновлены до 2026-07-28, а поддержка в Rust SDK на момент анонса была в бете; актуальный статус смотрите в репозиториях SDK. Большая часть изменений транспорта уйдёт вместе с обновлением.
- Убрать состояние из сессий. Всё, что сервер помнил «про сессию», превратить в явные handle в аргументах инструментов.
- Перевести серверные запросы на MRTR. Elicitation и sampling — через
input_required. Добавить проверку целостностиrequestState. - Tasks. Если использовали экспериментальные Tasks — перейти на расширение:
tasks/getвместо блокирующегоtasks/result. - Авторизация. Проверка
iss, CIMD вместо DCR для новых клиентов,application_typeдля десктопных и CLI-клиентов. - Инфраструктура. Убрать sticky sessions, настроить маршрутизацию и лимиты по
Mcp-Method/Mcp-Nameс проверкой соответствия телу, учестьcacheScopeв кэшах. - Транспорт. HTTP+SSE → Streamable HTTP.
- Тестирование на двух версиях. Пока окно открыто, клиенты и серверы разных ревизий будут жить вместе — прогоняйте интеграционные тесты для обеих.
Что меняется для каждого компонента инфраструктуры
Изменения спецификации по-разному затрагивают разные части системы. Удобно пройтись по ним по очереди.
MCP-сервер. Убрать зависимость от сессии, перейти на handle, перевести elicitation и sampling на input_required, защитить requestState, добавить ttlMs и cacheScope в ответы списков, ответить на неподдерживаемую версию ошибкой. Если сервер сам реализует OAuth — проверка iss, CIMD, application_type.
Клиент или агент. Передавать версию и сведения о себе в каждом запросе, уметь обрабатывать input_required и повторять вызов с ответами, кэшировать списки с учётом ttlMs, проверять iss при авторизации. Для десктопных и CLI-клиентов — указывать application_type.
Балансировщик. Отказаться от sticky sessions для серверов, которые перешли на новую ревизию. Для новых запросов маршрутизация возможна по Mcp-Method и Mcp-Name.
Шлюз и rate limiter. Лимиты и правила доступа на уровне конкретных инструментов по заголовкам — со сверкой с телом запроса. Журнал вызовов с версией протокола, методом, именем инструмента и пользователем.
Кэш. Учитывать cacheScope: public можно разделять между пользователями, private — нет. Ошибка здесь приводит к утечке списка инструментов или ресурсов между пользователями.
Сервер авторизации и IdP. Возвращать iss, поддерживать CIMD, не отвергать redirect на localhost для клиентов с соответствующим application_type, не принимать клиентские учётные данные, выданные другим issuer’ом.
Мониторинг. Метрики по версиям протокола, доля ответов input_required, ошибки несовпадения версий. По ним видно, как идёт миграция и когда можно выключать старое.
Переходный период: две версии в одной инфраструктуре
Ревизия 2026-07-28 не выключает старые реализации: в компании ещё долго будут жить клиенты и серверы на 2025-11-25. Практические следствия:
- Сервер должен понимать, с кем говорит. Старый клиент придёт с
initializeи будет ждатьMcp-Session-Id, новый — с версией в заголовке и_meta. Серверный SDK, поддерживающий обе ревизии, решает эту задачу; самописной реализации придётся различать их явно и на неподдерживаемую версию отвечать ошибкой, а не молча вести себя по-старому. - Инфраструктура должна пропускать обе формы. Балансировщик, настроенный на новые заголовки, не должен отбрасывать старые запросы без
Mcp-Method, пока такие клиенты есть. Но и правила доступа не должны ослабевать для старых запросов: если лимиты и разрешения считаются поMcp-Name, для запросов без заголовка их придётся считать по телу. - Sticky sessions остаются, пока жив старый транспорт. Отказаться от них можно только тогда, когда все клиенты сервера перешли на новую ревизию. Узнать это помогает журнал: доля запросов старой ревизии по каждому серверу — хорошая метрика готовности.
- Тесты — на обе ревизии. Интеграционный тест на каждую пару «клиент — сервер» в двух вариантах протокола ловит большинство проблем до пользователей.
Типичные ошибки миграции
- Перенести состояние сессии в память процесса. Сервер «временно» хранит выбранный проект в словаре по идентификатору клиента. Работает на одном экземпляре, ломается при масштабировании и смешивает данные пользователей, если идентификатор клиента не уникален.
- Доверять заголовкам больше, чем телу.
Mcp-Nameв заголовке и имя инструмента в теле обязаны совпадать; расхождение — повод отклонить запрос, а не выбрать одно из двух. - Хранить в
requestStateданные без подписи. Об этом выше: подменённое состояние превращает подтверждение пользователя в подтверждение чего-то другого. - Сделать список инструментов публичным в кэше. Если набор инструментов зависит от прав пользователя,
cacheScopeдолжен бытьprivate. - Отложить авторизацию «на потом». Проверка
issзакрывает реальную атаку mix-up. Её стоит сделать в первую очередь у клиентов, работающих с несколькими серверами авторизации. - Мигрировать сервер за сервером без инвентаризации. Без списка серверов и клиентов с версиями непонятно, когда можно выключать старое, и переходный период растягивается бесконечно.
Вопросы и ответы
Перестанут ли работать серверы на 2025-11-25? Нет, сразу — нет. Ревизия не выключает старые реализации, а устаревшие функции по новой политике получают окно минимум 12 месяцев. Но новые клиенты будут ориентироваться на 2026-07-28, так что откладывать миграцию надолго нет смысла.
Что делать с sampling, если сервер им пользуется? Sampling помечен как устаревший. На время окна он продолжает работать, но строить на нём новое не стоит. Если серверу нужна генерация текста, надёжнее вызывать модель на своей стороне или вернуть задачу агенту через обычный результат инструмента.
Нужно ли переписывать сервер с нуля? Обычно нет. Большую часть транспорта берёт на себя обновлённый SDK. Ручная работа — вынести состояние из сессий в handle, перевести elicitation на input_required и проверить авторизацию.
Обязателен ли server/discover? Нет. Клиент может сразу вызывать инструменты, передавая версию и возможности в каждом запросе. server/discover полезен, когда клиенту нужно заранее узнать, что умеет сервер, например какие расширения он поддерживает, — вместо того чтобы выяснять это по ошибкам.
Как понять, какие клиенты ещё на старой ревизии? По журналу запросов: версия протокола есть в каждом запросе новой ревизии и в handshake старой. Если журнал ведётся централизованно, этот отчёт строится одним запросом.
Где здесь Codenik
В компании с десятками MCP-серверов миграция протокола — это не одна задача, а десятки: у каждого сервера свой владелец, своя очередь и свой OAuth. Codenik стоит между агентами и MCP-инструментами как управляемый слой доступа: авторизация пользователей и агентов, права на инструменты, секреты систем и журнал вызовов живут в одном месте, а не в каждом сервере. В такой архитектуре усиление авторизации из 2026-07-28 — проверка iss, CIMD, привязка учётных данных к issuer’у — ложится на слой доступа, а не на каждый сервер отдельно, и внутренние серверы могут оставаться простыми stateless-сервисами без собственной OAuth-логики. Stateless-ядро и заголовки Mcp-Method/Mcp-Name хорошо ложатся на такую архитектуру: решения о доступе принимаются по каждому запросу, а не по сессии.
Короткий вывод
MCP 2026-07-28 делает протокол пригодным для обычной серверной инфраструктуры: без сессий, с явными циклами ввода, с заголовками для шлюзов и с более строгой авторизацией. Ломается немного — состояние в сессиях, блокирующие Tasks и handshake, — и почти всё закрывается обновлением SDK и явными handle. Начните с инвентаризации и HTTP+SSE, закончите авторизацией и инфраструктурой, и уложитесь в окно устаревания без авралов.