Перейти к содержимому
Выберите тему

Docker

Когда MCP-серверу нужен контейнер, чем образ для stdio отличается от образа для Streamable HTTP, и где кончаются правила протокола и начинается практика Docker

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

Образ для stdio Образ для Streamable HTTP
Кто запускает клиент, как дочерний процесс вы или система управления контейнерами
Время жизни пока нужен клиенту постоянно
Сеть порт не нужен один адрес, принимающий POST
Журнал stderr stdout и stderr
Проверка состояния не нужна возможна, вне адреса MCP

Какой транспорт выбрать, решает не Docker, а то, где работает сервер. Правила обоих транспортов разобраны на странице Транспорты MCP.

Эти требования взяты из спецификации и действуют в контейнере так же, как без него.

По странице stdio:

  • Сервер не должен писать в stdout ничего, кроме сообщений MCP. Строка от программы установки пакетов или приветствие сценария запуска ломает обмен.
  • Сервер может писать журнал в stderr. Клиенту не следует считать вывод в stderr признаком сбоя.
  • Клиент не должен писать в stdin сервера ничего, кроме сообщений MCP.
  • Серверу следует завершаться сразу, как только stdin закрыт или чтение вернуло конец файла. Спецификация называет это основным и единственным переносимым сигналом завершения.
  • Если процесс сервера завершился неожиданно, клиенту следует запустить его заново. Незавершённые запросы теряются.

По странице Streamable HTTP:

  • Сервер обязан давать один адрес, который принимает POST.
  • Сервер обязан проверять заголовок Origin у всех входящих соединений. Если заголовок есть и он недопустим, сервер обязан ответить 403 Forbidden.
  • При локальном запуске серверу следует слушать только 127.0.0.1, а не все сетевые интерфейсы.
  • Серверу следует проверять подлинность всех соединений.
  • Открывая поток SSE в ответ на запрос, серверу следует добавить заголовок X-Accel-Buffering: no.

В самом образе - ничего. Меняется только то, как сервис стоит за балансировщиком.

  • В Legacy (редакции с 2025-03-26 по 2025-11-25) сервер мог выдать клиенту номер сессии в заголовке MCP-Session-Id, и клиент был обязан присылать его в каждом следующем запросе. Если состояние сессии жило в памяти одного экземпляра, запросы приходилось направлять на тот же экземпляр.
  • В Modern сессий протокола нет: журнал изменений редакции 2026-07-28 удаляет их вместе с заголовком Mcp-Session-Id. По общим правилам протокола сервер не должен полагаться на прежние запросы по тому же соединению, и ему не следует требовать от клиента того же соединения или процесса для связанных операций.

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

Сервер для обеих эпох на одном адресе описан на странице Сервер для двух эпох.

Контейнеру с сервером Streamable HTTP нужна машина с постоянным адресом. Виртуальный сервер с Docker можно арендовать у FirstVDS или Aéza.

Всё ниже - рекомендации Docker из его собственной документации. Спецификация MCP их не требует и о них не упоминает.

Клиент запускает контейнер так же, как любую другую команду. В настройках клиента командой становится docker, а параметрами - run, -i, --rm и имя образа.

Схема записи в настройках клиента
{
"command": "docker",
"args": ["run", "-i", "--rm", "ВАШ_ОБРАЗ:ВАШ_ТЕГ"]
}

Пример схематичный, составлен по справке Docker и здесь не запускался. Имена внешних полей зависят от клиента, смотрите страницу Клиенты MCP.

По справке команды docker run:

  • -i оставляет stdin открытым. Без него сервер stdio не получит ни одного сообщения.
  • --rm удаляет контейнер и его безымянные тома после выхода.

Параметр -t в этой схеме не используется: по той же справке он выделяет псевдотерминал, а обмен по stdio идёт по обычным потокам.

Dockerfile, схема для Node.js
# Тег базового образа выберите сами и закрепите
FROM node:ВАШ_ТЕГ
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY . .
# Пользователь node входит в официальный образ node
USER node
CMD ["node", "server.js"]

Пример схематичный, составлен по документации Docker и здесь не запускался. Имя файла server.js условное.

Dockerfile, схема для Python
# Тег базового образа выберите сами и закрепите
FROM python:ВАШ_ТЕГ
WORKDIR /app
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
RUN groupadd -r mcp && useradd --no-log-init -r -g mcp mcp
USER mcp
CMD ["python", "server.py"]

Пример схематичный, составлен по документации Docker и здесь не запускался. Имя файла server.py условное.

  • Закреплённый тег базового образа. По рекомендациям Docker по сборке теги образов изменяемы: издатель может перевести тег на новый образ. Там же описано закрепление версии базового образа. Какой тег взять, решаете вы. Для Node.js страница выпусков советует в рабочей среде только ветки в статусе Active LTS или Maintenance LTS. Для Python сроки поддержки веток приведены в руководстве разработчика Python.
  • Непривилегированный пользователь. Те же рекомендации: если сервис может работать без привилегий, переключитесь инструкцией USER на пользователя без прав root. Что делает USER, описано в справке по Dockerfile. В образах node для этого уже есть пользователь node - так сказано в рекомендациях к официальному образу.
  • Никаких секретов в образе. По странице о секретах сборки аргументы сборки и переменные окружения для секретов не подходят, потому что остаются в готовом образе. Для секретов, нужных при сборке, Docker предлагает подключаемые секреты. Ключи доступа, нужные серверу во время работы, передаются при запуске контейнера, а не записываются в образ.
  • Сборка в несколько этапов. Рекомендации Docker описывают её как способ уменьшить итоговый образ, отделив сборку от результата. В схемах выше её нет, чтобы не усложнять пример.

По странице о журналах контейнеров команда docker logs по умолчанию показывает stdout и stderr процесса.

Для сервера Streamable HTTP подходят оба потока. Для сервера stdio поток stdout занят сообщениями MCP, поэтому журнал идёт только в stderr. Контейнер, запущенный клиентом с --rm, после выхода удаляется вместе со своим журналом. Сохраняет ли клиент вывод stderr, зависит от клиента: спецификация разрешает ему собирать этот вывод, передавать дальше или не читать вовсе.

Инструкция HEALTHCHECK по справке по Dockerfile сообщает Docker, как проверить, что контейнер ещё работает.

  • Для сервера Streamable HTTP она имеет смысл. Спецификация MCP адреса для такой проверки не задаёт. Не используйте для неё GET к адресу MCP: серверу только для Modern следует отвечать на GET кодом 405 Method Not Allowed. Удобнее отдельный адрес вашего приложения.
  • Для сервера stdio она не нужна. Контейнер живёт столько, сколько нужен клиенту, порта у него нет, и опрашивать нечего.
  • Docker MCP Catalog, Toolkit и Gateway на этой странице не разбираются: их документация для этой страницы не проверялась.
  • Размеры образов, объём памяти, время запуска и другие числа не приводятся. Их нет ни в спецификации, ни в документации Docker, и измерять их нужно на вашем сервере.
  • Оркестрация (Kubernetes, Compose, Swarm) не рассматривается.
  • Как конкретный клиент ведёт себя с командой docker в настройках (сколько ждёт выхода, сохраняет ли stderr), по спецификации установить нельзя. Смотрите страницу своего клиента.
  • Режим Docker без прав root для самого демона описан в документации Docker. С MCP он не связан и здесь не разбирается.