Разбираем stateless MCP: почему убрали сессии, как теперь передаётся состояние и что менять в серверах. Читайте, чтобы адаптироваться.
Model Context Protocol (MCP) соединяет ИИ-ассистентов с инструментами, базами данных и внешними приложениями. В спецификации от 2026-07-28 MCP отказался от сессий на уровне протокола и стал stateless. Это упрощает работу удалённых MCP-серверов: они могут масштабироваться за обычными балансировщиками без sticky-маршрутизации и общего хранилища сессий. Клиенты безопасно кэшируют определения инструментов, а агенты получают больше контроля над тем, какие ресурсы приложений они используют.
Однако stateless не означает, что серверы больше ничего не помнят. Браузер может держать открытые вкладки, транзакция — незакоммиченные изменения, корзина — товары. Разница в том, как клиент ссылается на это состояние.
Состояние — это информация, которую система помнит между запросами. Представьте MCP-сервер, управляющий браузером. Агент вызывает open_browser, затем navigate, click и take_screenshot. Серверу нужно знать, что все четыре действия относятся к одному браузеру.
В ранних версиях MCP контекст обеспечивало соединение. Клиент начинал с запроса initialize. В Streamable HTTP сервер мог ответить с Mcp-Session-Id, который клиент прикреплял к последующим запросам. Сервер использовал этот ID для восстановления информации, связанной с сессией.
Это работало, когда один клиент общался с одним серверным процессом. Но усложнялось, когда MCP-сервис работал на нескольких машинах за балансировщиком. Если сервер A создал сессию, а следующий запрос попал на сервер B, то B должен был как-то восстановить сессию. Инфраструктурные команды обычно решали это через sticky-роутинг (привязку клиента к серверу A) или общую базу данных для поиска сессий.
Сессии также значили разное в разных клиентах. Сессия могла длиться один вызов инструмента, один диалог, одну загрузку страницы или всё время работы приложения. Авторы серверов могли хранить браузер или корзину внутри сессии, но не могли надёжно предсказать, как долго это состояние проживёт и какие диалоги будут его разделять.
В ревизии от 2026-07-28 рукопожатие initialize и notifications/initialized удалено. Серверы больше не выдают MCP session ID, а клиенты не хранят и не пересылают их.
Вместо этого каждый запрос несёт всю информацию протокола: версию протокола и возможности клиента. Клиент может вызвать server/discover, чтобы узнать, какие версии и функции поддерживает сервер, без открытия сессии.
Это значит, что любой совместимый экземпляр сервера может понять входящий MCP-запрос без восстановления информации из предыдущего рукопожатия. Если инструмент управляет состоянием приложения (например, запущенным браузером), сервису всё равно нужен способ найти этот ресурс. Stateless MCP убирает состояние сессии протокола, но не состояние приложения.
Когда инструменту нужно состояние между вызовами, сервер возвращает явный идентификатор — часто называемый handle (дескриптор). Handle — это не специальный тип данных MCP, а обычное значение, которое возвращает один инструмент и передаёт другому.
Например, сервер браузера может работать так:
# Открываем браузер и получаем его handle
open_browser()
# Возвращает: { "browser_id": "browser_abc123" }
# Передаём handle в последующие вызовы
navigate({
"browser_id": "browser_abc123",
"url": "https://example.com"
})
Браузер по-прежнему живёт на сервере и хранит вкладки, куки и историю. Связь между вызовами теперь видна в аргументах инструмента, а не спрятана внутри соединения.
Явные handle дают оркестраторам больше контроля над общим состоянием. Если три агента работают вместе, они могут использовать один cart_id, но каждый получит отдельный browser_id. Одна MCP-сессия не могла бы чисто выразить эти две границы состояния.
Некоторым инструментам нужно больше информации, прежде чем завершить работу. Например, инструмент развёртывания может запросить подтверждение перед публикацией в продакшн. По паттерну Multi Round-Trip Requests (MRTR) инструмент возвращает результат input_required, описывающий, что ему нужно. Результат может включать непрозрачное значение requestState, которое клиент возвращает вместе с запрошенными ответами при повторном вызове исходного инструмента. Информация, необходимая для продолжения, путешествует с повторным запросом, а не остаётся привязанной к открытому соединению.
Для длительных или устойчивых операций серверы могут использовать task handle через расширение Tasks. Клиент может позже проверить или обновить задачу, не держа соединение открытым.
В предыдущих версиях MCP список инструментов tools/list мог зависеть от сессии. Например, сервер сначала показывал connect_database, а после установления соединения добавлял query_database. Из-за зависимости от истории сессии клиенты не могли безопасно переиспользовать этот список.
Теперь списки инструментов могут меняться при обновлении сервера или изменении прав пользователя, но они больше не зависят от MCP-соединения. Серверы возвращают значения ttlMs и cacheScope, которые говорят клиенту, как долго результат остаётся свежим и можно ли его разделять. Это позволяет оркестратору переиспользовать определения инструментов для субагентов, уменьшая избыточные запросы и улучшая переиспользование промпт-кэша.
Если вы разрабатываете или поддерживаете MCP-сервер:
browser_id или connection_id) и требуйте их в последующих вызовах.MCP начинался с модели, ориентированной на соединения, которая хорошо работала для локальных процессов на одной машине. С ростом экосистемы в облачные сервисы, хостинг-шлюзы и мультиагентные системы состояние на уровне соединения стало узким местом масштабирования.
Спецификация от 2026-07-28 разделяет эти проблемы: детали протокола путешествуют с запросом, состояние приложения использует явные handle, интерактивные рабочие процессы передают состояние продолжения, а определения инструментов кэшируются чисто. MCP-серверы по-прежнему помнят всё, что им нужно помнить, просто называют это состояние прямо в запросе.
Практический вывод: если вы работаете с MCP, начните с изучения документации вашего SDK на предмет поддержки stateless-режима, затем перепишите серверные инструменты, чтобы они принимали явные handle, и добавьте идемпотентность для критичных операций. Это займёт пару часов, но сэкономит дни отладки в будущем.
Хочешь закрепить знания на практике?
Решай задачи на Algolit — интерактивная платформа для обучения
Начать бесплатно →