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

Быстрое решение: если публикация события и подписанный поток находятся в одном процессе, начните с уведомления в памяти; если они могут оказаться в разных процессах, добавьте межпроцессную шину, а после разрыва всегда выполняйте повторную подписку и повторное чтение данных.

Кому нужна эта схема выбора

Материал предназначен разработчикам MCP Server, которые меняют списки tools, resources или prompts без перезапуска сервиса. Он также полезен платформенным инженерам, распределяющим длинные соединения между несколькими экземплярами, и командам, отвечающим за восстановление MCP Client, сброс кэша и проверку совместимости.

Ниже используется термин 2026 MCP subscriptions/listen как обозначение механизма уведомлений, подтверждённого в спецификации MCP 2026-07-28. По состоянию на 5 сентября 2026 года это не означает, что все SDK имеют одинаковые серверные вспомогательные API: границы поддержки нужно проверять отдельно для каждой реализации. Дата и изменения сверены с официальным описанием MCP 2026-07-28.

Что именно передаёт уведомление

Главная ошибка в проектировании — считать событие полной записью состояния. В этой архитектуре полезно разделять три слоя:

  • сигнал изменения — сообщает клиенту, что список или состояние могло измениться;
  • фактические данные — запрашиваются отдельной операцией, например повторным чтением списка tools или ресурса;
  • журнал событий — долговременная последовательность, из которой можно восстановить все пропущенные изменения.

subscriptions/listen не следует автоматически трактовать как третий слой. Универсальной гарантии хранения, повторной доставки и воспроизведения пропущенных событий нет. Поэтому сервер может отправить сигнал «состояние изменилось», а клиент после него должен получить актуальный список заново.

Серверу также необходимо фильтровать события по подписке. Изменение одного ресурса не должно заставлять каждый клиент перечитывать все доступные данные, если протокол и ваша модель доступа позволяют передать более узкий идентификатор изменения. При этом нельзя делать вид, что фильтрация заменяет проверку прав: после повторного чтения клиент всё равно должен получить только разрешённое ему содержимое.

В документации клиентских подписок Python SDK и серверных обработчиков подписок эти стороны описаны раздельно. Это важная граница: пример API конкретного SDK нельзя автоматически объявлять обязательным поведением всех MCP-серверов.

2026 MCP subscriptions/listen: когда одного процесса достаточно

Для локального прототипа и небольшого сервиса простейшая схема часто надёжнее распределённой. Публикация изменения вызывает внутренний обработчик, обработчик проходит по активным подпискам, а соединение доставляет событие клиенту. Все три операции выполняются в одном адресном пространстве, поэтому отдельный брокер не нужен.

Такой вариант подходит, если одновременно выполняются следующие условия:

  • процесс публикации и процесс, удерживающий поток, один и тот же;
  • после перезапуска допустимо заново построить состояние из первоисточника;
  • количество подписчиков не создаёт заметной нагрузки на цикл обработки;
  • вы не обещаете клиенту воспроизведение событий, пропущенных во время разрыва;
  • тесты проверяют не только отправку события, но и отмену подписки.

Минимальная реализация должна иметь явный жизненный цикл:

  1. принять запрос на подписку и вернуть подтверждение только после успешной регистрации;
  2. сохранить идентификатор подписки и критерии фильтрации;
  3. при изменении сформировать событие нужного типа;
  4. доставить его только подходящим потокам;
  5. удалить подписку при закрытии соединения, отмене запроса или исключении записи в поток;
  6. после нового соединения позволить клиенту снова прочитать актуальное состояние.

Проблема локальной шины в памяти не в том, что она «непрофессиональна». Её ограничение конкретнее: данные не пересекают границу процесса и исчезают вместе с процессом. Если этого достаточно для выбранного режима отказа, внешняя инфраструктура только добавит точки отказа, права доступа, настройку соединений и отдельный сценарий тестирования.

Опыт для прототипа: сначала зафиксируйте контракт «уведомление → повторное чтение», а не пытайтесь доказать надёжность через подключение внешней очереди. Иначе вы будете тестировать компонент доставки раньше, чем подтвердите корректность подписки и отмены.

Когда Worker уже ломает локальную модель

Web Worker, пул процессов или запросный runtime меняют вопрос. Теперь событие может быть опубликовано в одном контексте, а поток удерживаться в другом. Внешне сервис остаётся одним MCP Server, но фактически у него появляются несколько независимых экземпляров памяти.

Симптомы обычно выглядят так:

  • событие получает клиент, подключённый к тому же Worker, где оно возникло;
  • после балансировки часть подписчиков перестаёт видеть изменения;
  • обычный запрос на получение списка возвращает новые данные, но уведомление не приходит;
  • тест проходит при одном Worker и нестабильно завершается при запуске пула;
  • закрытие одного соединения удаляет не ту подписку из-за несовпадения локальных идентификаторов.

Почему в многопроцессной схеме подписки не получают уведомления? Чаще всего причина не в самом формате MCP, а в том, что публикация и доставка используют разные локальные реестры. Worker A не знает о подписке, зарегистрированной в Worker B. Реже событие теряется на уровне обратного прокси или клиента, который не выполнил повторное чтение после изменения.

Проверяйте границу поэтапно:

  1. добавьте идентификатор процесса или экземпляра в журнал публикации;
  2. запишите идентификатор процесса, где зарегистрирован поток;
  3. отправьте искусственное тестовое событие с уникальным значением;
  4. проверьте, дошло ли оно до общего канала приложения;
  5. выясните, какой Worker получил его и какой Worker удерживает соединение;
  6. отдельно проверьте передачу через прокси;
  7. сравните результат с прямым подключением к конкретному экземпляру.

Если событие не появляется в межпроцессном канале, проблема находится в приложении. Если в канале оно есть, но поток молчит, проверяйте маршрутизацию доставки и состояние подписки. Если поток получил событие, но пользовательский список не обновился, ошибка, вероятно, находится в клиентском кэше или в повторном чтении.

Многозвенная схема требует общей доставки, но не журнала

При нескольких узлах нагрузочный балансировщик может направить запрос на экземпляр A, а следующую публикацию изменения — на экземпляр B. Поток при этом остаётся связанным с A. Без общего канала B просто не знает, каким клиентам на A нужно отправить сигнал.

Здесь нужна шина уведомлений с межпроцессной или межузловой видимостью. Она должна передавать событие между компонентами приложения, а экземпляр, удерживающий поток, уже доставляет его своим подписчикам. В зависимости от требований это может быть общий транспорт событий, внутренний канал платформы или другой управляемый компонент.

Но шину нельзя описывать как обязательный журнал MCP. У неё могут отсутствовать:

  • долговременное хранение;
  • гарантированное воспроизведение после простоя;
  • порядок событий между всеми типами ресурсов;
  • дедупликация;
  • подтверждение доставки конкретному MCP Client.

Если вам нужен журнал с позициями, повторным чтением и восстановлением после длительного отключения, это отдельный проект хранения событий. Не подменяйте его subscriptions/listen: уведомление лишь запускает синхронизацию.

Для Streamable HTTP особенно важно разделить транспорт и прикладную доставку. Официальное описание Streamable HTTP в Ruby SDK показывает, что транспортный слой отвечает за поток и жизненный цикл HTTP-соединения, а не за универсальное хранение всех изменений приложения. Также описание SSE на MDN связывает доставку с открытым HTTP-потоком: закрытие этого потока не превращается само по себе в механизм восстановления.

Решение по архитектуре

Вариант Когда выбирать Нужна общая шина Основной риск Оценка для соответствующего режима
Один процесс и локальная память Прототип, локальная разработка, один поток выполнения Нет Потеря реестра при перезапуске 5/5
Несколько Worker Пул процессов или разделённые обработчики Обычно да Событие осталось в другом Worker 3/5
Несколько узлов за балансировщиком Длинные потоки распределяются между экземплярами Да Публикация и доставка попали на разные узлы 4/5 при общей доставке
Полный журнал событий Требуется восстановление по истории Да, но этого недостаточно Сложность порядка, хранения и дедупликации Отдельная задача

Оценка в последнем столбце — редакционная оценка пригодности для обозначенного сценария, а не измерение производительности.

Обязательно ли использовать очередь сообщений? Нет. Для одного процесса — обычно нет; для нескольких процессов — нужен не обязательно продукт с функциями очереди, а надёжный общий путь доставки. Если ваше требование ограничивается сигналом «перечитай состояние», достаточно канала уведомлений. Если требуется доказать, какие события были пропущены и в каком порядке, простой канал уже не решает задачу.

Ответственность MCP Client после разрыва

Клиент не должен считать, что восстановление HTTP-соединения автоматически восстановило подписку. После сетевого сбоя, перезапуска прокси или остановки экземпляра состояние подписки на сервере может быть удалено. Даже если транспорт вновь подключился, это не доказывает, что сервер знает о прежнем фильтре и должен отправить прошлые события.

Будут ли автоматически отправлены пропущенные события после разрыва? Универсальной гарантии нет. Без специально согласованного журнала клиент должен считать пропущенные события потерянными и восстановить согласованность через повторную подписку и повторное чтение данных.

Рекомендуемый порядок на стороне клиента:

  1. обнаружить завершение потока, ошибку чтения или превышение допустимого времени ожидания;
  2. закрыть локальные ресурсы старой подписки;
  3. установить соединение заново;
  4. повторить регистрацию нужных подписок;
  5. запросить актуальные tools, resources или prompts;
  6. атомарно заменить локальный кэш результатом чтения;
  7. только после этого считать клиентское состояние согласованным;
  8. применить защиту от слишком частых повторных подключений.

Не следует сначала показывать пользователю старый список как актуальный, а затем ждать уведомления. Уведомление могло произойти до завершения повторной подписки. Сначала — чтение фактического состояния, затем — ожидание новых изменений.

Как старый MCP Client получает изменения списка? Это зависит от того, поддерживает ли конкретная версия клиента нужный механизм подписок и какие вспомогательные API предоставляет его SDK. Если подписка не поддерживается, совместимый путь — периодически перечитывать список, получать состояние по запросу или использовать серверный режим, в котором изменения обнаруживаются при следующем обращении. Нельзя выводить поведение старой версии из документации нового SDK: заметки о миграции Python SDK прямо показывают, что интерфейсы и поддержка могут меняться между версиями.

Прокси и runtime проверяются отдельно

Даже идеальная шина не поможет, если обратный прокси буферизует поток, закрывает неактивное соединение или не пропускает корректный тип ответа. Для длинного запроса проверяйте не только код успешного ответа, но и фактическое поступление нескольких событий во времени.

Может ли обратный прокси прервать MCP-поток уведомлений? Да. Причинами бывают буферизация, ограничение времени простоя, ограничение длительности запроса, принудительное закрытие при обновлении конфигурации и некорректное завершение TLS-соединения. Документация MDN по SSE полезна для проверки формата потока, но настройки конкретного прокси нужно тестировать в вашей цепочке.

Проведите проверку в таком порядке:

  1. откройте поток напрямую на одном экземпляре без балансировщика;
  2. подтвердите Content-Type: text/event-stream и поступление событий;
  3. оставьте соединение без прикладных изменений и проверьте, не закрывается ли оно по тайм-ауту;
  4. повторите тест через обратный прокси;
  5. принудительно измените список tools и проверьте задержку до события;
  6. перезапустите экземпляр, удерживающий поток;
  7. выполните повторную подписку и убедитесь, что клиент перечитал состояние;
  8. повторите сценарий во время плавного обновления нескольких экземпляров.

Настройки runtime также нельзя считать нейтральными. Например, параметры Uvicorn позволяют отдельно управлять режимом запуска, Worker и keep-alive. Это означает, что конфигурация одного процесса и конфигурация пула могут иметь разные свойства даже при одинаковом коде приложения. Зафиксируйте параметры запуска в тесте, иначе результат нельзя будет воспроизвести.

Предупреждение для приёмки: тест «соединение установилось» проверяет только начальный HTTP-обмен. Для уведомлений обязательны сценарии изменения, простоя, закрытия, повторного подключения и обновления экземпляра.

Порядок внедрения без преждевременной инфраструктуры

Шаг первый: опишите источник истины

Определите, где находятся актуальные tools, resources и prompts. Если это база данных или API, уведомление должно лишь запускать новое чтение. Если состояние хранится только в памяти, заранее решите, как оно восстанавливается после перезапуска.

Шаг второй: реализуйте локальный цикл

В одном процессе проверьте подтверждение подписки, фильтрацию, отмену, закрытие потока и повторное чтение. Не переходите к нескольким Worker, пока эти операции не имеют предсказуемого результата.

Шаг третий: добавьте идентификаторы

Логируйте идентификатор события, подписки и экземпляра. Для отказоустойчивости этого достаточно, чтобы отличить потерю события от ситуации, когда событие пришло, но клиент не обновил кэш.

Шаг четвёртый: разделите публикацию и доставку

В многопроцессном режиме сначала передайте тестовое событие через общий канал, затем найдите экземпляр, удерживающий поток. Не объединяйте эти две проверки: иначе непонятно, сломалась шина или поток.

Шаг пятый: проверьте отсутствие replay

Остановите клиент после публикации изменения, затем подключите его снова. Ожидаемый результат для базовой схемы — новая подписка и повторное чтение, а не гарантированная последовательная доставка старых сигналов.

Шаг шестой: включите прокси в тест

Проверьте поток напрямую и через боевой маршрут. Сравните заголовки, закрытие по тайм-ауту, поведение при прокрутке логов и реакцию на плавное обновление.

Шаг седьмой: закрепите совместимость

Протестируйте минимум текущую версию клиента и поддерживаемый вами старый путь. Если старый клиент не умеет подписки, задокументируйте polling или повторное чтение как совместимый режим, а не оставляйте это неявным поведением.

Матрица выбора для команды

Ваша среда Рекомендуемый путь Тестовый узел Восстановление Совместимый маршрут
Локальный прототип Один процесс, уведомления в памяти Один сервер без прокси Повторная подписка и чтение Запрос списка вручную
Одиночный production-узел Один runtime с контролем закрытия Прямой поток и поток через прокси Перезапуск процесса и синхронизация Повторное чтение после ошибки
Несколько Worker Общая доставка между процессами Искусственное событие между Worker Перерегистрация подписок Периодическое обновление
Несколько узлов Межузловая шина плюс явное чтение состояния Балансировщик, rolling update, разрыв Новое соединение и полная сверка Клиентский fallback без подписок

Если вам нужен удалённый стенд для проверки именно таких сценариев, можно начать с аренды Mac mini для тестовой среды, а затем сверить доступы и сетевой маршрут через русскую страницу SSHMac. Такой стенд полезен для временной проверки прокси, нескольких процессов и клиентского восстановления, но не заменяет анализ вашей постоянной платформы.

Что выбрать на практике

Для локальной разработки и одного стабильного процесса выбирайте память процесса. Она дешевле по операционной сложности и помогает сначала проверить сам контракт уведомлений. Внешняя шина на этом этапе не доказывает корректность подписки, а только расширяет область диагностики.

Для пула Worker переходите к общей доставке, когда вы действительно подтверждаете разделение публикации и удержания потока. Для нескольких узлов она становится обычным требованием архитектуры, потому что балансировщик не обязан направлять событие туда же, где находится клиентский поток.

Текущий вариант — локальный процесс или самодельный пул — имеет реальные недостатки: события могут исчезать при перезапуске, подписчики разных экземпляров видят разное состояние, а проверка прокси и восстановления часто остаётся на вашей команде. Если нужен временный стенд для MCP-разработки, удалённый Mac от SSHMac позволяет отделить проверку Streamable HTTP, Worker и клиентского восстановления от вашей основной рабочей машины. Это особенно разумный выбор для коротких тестов и сравнений; для постоянной тяжёлой нагрузки, физического оборудования или долгосрочной предсказуемой эксплуатации выгоднее оценить собственную инфраструктуру.

Перед началом работы определите свою строку в матрице: один процесс, несколько Worker или несколько узлов. Затем отдельно проверьте путь уведомления, повторное чтение после разрыва и поведение прокси — именно эти три проверки показывают, готова ли ваша реализация subscriptions/listen к реальной эксплуатации.