Docker
Когда MCP-серверу нужен контейнер, чем образ для stdio отличается от образа для Streamable HTTP, и где кончаются правила протокола и начинается практика Docker
описана редакция 2026-07-28, сверено со спецификацией и документацией Docker 4 октября 2026
Два вида образа
Заголовок раздела «Два вида образа»| Образ для stdio | Образ для Streamable HTTP | |
|---|---|---|
| Кто запускает | клиент, как дочерний процесс | вы или система управления контейнерами |
| Время жизни | пока нужен клиенту | постоянно |
| Сеть | порт не нужен | один адрес, принимающий POST |
| Журнал | stderr | stdout и stderr |
| Проверка состояния | не нужна | возможна, вне адреса MCP |
Какой транспорт выбрать, решает не Docker, а то, где работает сервер. Правила обоих транспортов разобраны на странице Транспорты MCP.
Что требует протокол
Заголовок раздела «Что требует протокол»Эти требования взяты из спецификации и действуют в контейнере так же, как без него.
Сервер stdio
Заголовок раздела «Сервер stdio»По странице stdio:
- Сервер не должен писать в
stdoutничего, кроме сообщений MCP. Строка от программы установки пакетов или приветствие сценария запуска ломает обмен. - Сервер может писать журнал в
stderr. Клиенту не следует считать вывод вstderrпризнаком сбоя. - Клиент не должен писать в
stdinсервера ничего, кроме сообщений MCP. - Серверу следует завершаться сразу, как только
stdinзакрыт или чтение вернуло конец файла. Спецификация называет это основным и единственным переносимым сигналом завершения. - Если процесс сервера завершился неожиданно, клиенту следует запустить его заново. Незавершённые запросы теряются.
Сервер Streamable HTTP
Заголовок раздела «Сервер Streamable HTTP»По странице 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 не требует привязывать клиента к одному экземпляру. Если ваш сервер хранит собственное состояние, оно обязано адресоваться явным идентификатором, который клиент передаёт в каждом запросе. Где хранить такое состояние, чтобы его видели все экземпляры, спецификация не определяет.
Сервер для обеих эпох на одном адресе описан на странице Сервер для двух эпох.
Практика, не правило MCP
Заголовок раздела «Практика, не правило MCP»Контейнеру с сервером Streamable HTTP нужна машина с постоянным адресом. Виртуальный сервер с Docker можно арендовать у FirstVDS или Aéza.
Всё ниже - рекомендации Docker из его собственной документации. Спецификация MCP их не требует и о них не упоминает.
Запуск сервера stdio
Заголовок раздела «Запуск сервера stdio»Клиент запускает контейнер так же, как любую другую команду. В настройках клиента командой становится docker, а параметрами - run, -i, --rm и имя образа.
{ "command": "docker", "args": ["run", "-i", "--rm", "ВАШ_ОБРАЗ:ВАШ_ТЕГ"]}Пример схематичный, составлен по справке Docker и здесь не запускался. Имена внешних полей зависят от клиента, смотрите страницу Клиенты MCP.
По справке команды docker run:
-iоставляетstdinоткрытым. Без него сервер stdio не получит ни одного сообщения.--rmудаляет контейнер и его безымянные тома после выхода.
Параметр -t в этой схеме не используется: по той же справке он выделяет псевдотерминал, а обмен по stdio идёт по обычным потокам.
Образ для сервера на Node.js
Заголовок раздела «Образ для сервера на Node.js»# Тег базового образа выберите сами и закрепитеFROM node:ВАШ_ТЕГ
WORKDIR /appCOPY package.json package-lock.json ./RUN npm ci --omit=devCOPY . .
# Пользователь node входит в официальный образ nodeUSER node
CMD ["node", "server.js"]Пример схематичный, составлен по документации Docker и здесь не запускался. Имя файла server.js условное.
Образ для сервера на Python
Заголовок раздела «Образ для сервера на Python»# Тег базового образа выберите сами и закрепитеFROM python:ВАШ_ТЕГ
WORKDIR /appCOPY requirements.txt ./RUN pip install --no-cache-dir -r requirements.txtCOPY . .
RUN groupadd -r mcp && useradd --no-log-init -r -g mcp mcpUSER 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 он не связан и здесь не разбирается.