Транспорты MCP: stdio и Streamable HTTP

Транспорт (transport) в Model Context Protocol (MCP) - привязка к каналу связи, которая определяет, как сообщения JSON-RPC между клиентом и сервером разбиваются на части и доставляются, как передаются метаданные запроса и как сообщается об отмене и завершении работы. Смысл сообщений транспорт не меняет: одни и те же запросы и результаты идут по любому транспорту. Спецификация определяет два стандартных транспорта: stdio для локальных серверов и Streamable HTTP для удалённых.

Первая версия спецификации, 2024-11-05, определяла stdio и HTTP с Server-Sent Events (HTTP+SSE). В версии 2025-03-26 HTTP+SSE заменил Streamable HTTP. Текущая версия спецификации - 2026-07-28: она убрала из Streamable HTTP сессии протокола, и с тех пор любой запрос может обработать любой экземпляр сервера. Протокол развивается в Agentic AI Foundation, входящем в Linux Foundation. Общая архитектура MCP описана в статье Что такое MCP.

Кратко

Параметр Значение
Уровень Транспортный уровень MCP
Формат сообщений JSON-RPC 2.0, UTF-8
Стандартные транспорты stdio, Streamable HTTP
Разбиение потока в stdio Одно сообщение JSON-RPC на строку
Эндпоинт Streamable HTTP Один MCP-эндпоинт, принимает POST
Ответ сервера по HTTP Объект JSON или поток SSE в рамках запроса
Сессии протокола Нет с версии 2026-07-28
Устаревший транспорт HTTP+SSE из 2024-11-05, устарел с 2025-03-26
Текущая спецификация 2026-07-28

Какую задачу решают транспорты

MCP-серверы работают двумя разными способами. Локальный сервер - программа на машине пользователя, которую часто запускает сам хост. Удалённый сервер - сетевой сервис, к которому многие пользователи обращаются через интернет. Этим случаям нужна разная доставка: в одном - канал между двумя процессами, в другом - HTTP с балансировщиками нагрузки, шлюзами и авторизацией.

Протокол отделяет смысл сообщений от их доставки. Уровень данных определяет сообщения JSON-RPC: обнаружение возможностей, примитивы, уведомления, ошибки. Транспортный уровень определяет, как эти сообщения доходят до другой стороны. Поэтому один раз написанный сервер можно развернуть и локально, и удалённо, а его логика не зависит от транспорта.

История

В таблице - изменения транспортов от версии к версии спецификации.

Версия Изменения в транспортах
2024-11-05 stdio и HTTP+SSE: поток SSE для сообщений сервера и отдельный эндпоинт для запросов POST; обязательная проверка Origin
2025-03-26 Streamable HTTP заменяет HTTP+SSE: один MCP-эндпоинт для POST и GET, необязательный SSE, сессии с Mcp-Session-Id, возобновляемые потоки с Last-Event-ID; пакетная отправка JSON-RPC
2025-06-18 Пакетная отправка убрана; заголовок MCP-Protocol-Version обязателен в HTTP-запросах
2025-11-25 HTTP 403 при неверном Origin; сервер может закрыть соединение SSE, а клиент переподключается с Last-Event-ID; stderr разрешён для журнала любого уровня
2026-07-28 Нет сессий, потока GET и возобновления потоков; заголовки Mcp-Method и Mcp-Name; нет запросов от сервера; subscriptions/listen для уведомлений; HTTP+SSE получил статус Deprecated

Что определяет транспорт

Все сообщения MCP - сообщения JSON-RPC в кодировке UTF-8. Транспорт доставляет запросы и уведомления от клиента к серверу и ответы и уведомления от сервера к клиенту. В версии 2026-07-28 сервер не отправляет запросы JSON-RPC: когда ему нужны данные от клиента, он возвращает InputRequiredResult в рамках механизма Multi Round-Trip Requests (MRTR), многоэтапных запросов.

Все метаданные протокола передаются в теле сообщения. Каждый запрос несёт версию протокола и возможности (capabilities) клиента в поле _meta. Транспорт может копировать отдельные поля тела в свою оболочку, как Streamable HTTP копирует их в заголовки. Источником истины остаётся тело, а транспорт, который копирует метаданные, определяет, как отклонять расхождения.

Каждый транспорт также определяет, как клиент отказывается от запроса, который ещё выполняется: в stdio клиент отправляет уведомление, в Streamable HTTP закрывает поток ответа на этот запрос. Модель запросов без состояния версии 2026-07-28 описана в статье MCP без состояния.

stdio

Запуск сервера

В транспорте stdio клиент запускает MCP-сервер как дочерний процесс и обменивается с ним данными через стандартные потоки этого процесса. Сервер читает сообщения из стандартного ввода (stdin) и пишет сообщения в стандартный вывод (stdout). Команду запуска хост берёт из своей конфигурации, например из файла claude_desktop_config.json приложения Claude Desktop, который приведён в документации MCP:

JSON
{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-filesystem",
        "/Users/username/Desktop"
      ]
    }
  }
}

Процесс сервера работает с теми же правами, что и хост. Спецификация указывает, что серверу на stdio не следует использовать механизм авторизации MCP: учётные данные он берёт из окружения, например из переменных окружения.

Разбиение потока на сообщения

Сообщения разделяются переводом строки и не должны содержать переводов строки внутри, поэтому каждая строка потока - ровно одно сообщение JSON-RPC. Запрос server/discover, записанный в stdin, выглядит так:

JSON
{"jsonrpc":"2.0","id":1,"method":"server/discover","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}}}}

Слоя заголовков нет: метод, аргументы и все метаданные находятся в теле JSON. Все сообщения сервера идут по одному каналу, stdout: ответы, которые сопоставляются с запросами по id JSON-RPC, уведомления о выполняющемся запросе и уведомления подписок subscriptions/listen. Подписки клиент различает по полю io.modelcontextprotocol/subscriptionId в _meta.

Стандартный вывод и стандартный поток ошибок

Сервер не должен писать в stdout ничего, кроме корректных сообщений MCP, а клиент не должен писать ничего другого в stdin сервера. Одна отладочная строка в stdout ломает поток. Поэтому руководства по созданию серверов в документации MCP предостерегают от print() в Python и console.log() в JavaScript в серверах на stdio: обе функции по умолчанию пишут в стандартный вывод.

Журнал пишется в стандартный поток ошибок (stderr). Сервер может писать туда любой текст в UTF-8: информационные, отладочные сообщения и сообщения об ошибках. Клиент может перехватывать этот поток, пересылать его или игнорировать и не должен считать вывод в stderr признаком ошибки. С версии 2026-07-28 функция протокола Logging устарела, и спецификация называет stderr её заменой для серверов на stdio.

Отмена запроса

Чтобы отменить выполняющийся запрос, клиент отправляет уведомление notifications/cancelled с идентификатором этого запроса:

JSON
{
  "jsonrpc": "2.0",
  "method": "notifications/cancelled",
  "params": { "requestId": 3, "reason": "User cancelled the operation" }
}

Серверу следует как можно скорее прекратить работу над запросом, и он не должен отправлять по нему новых сообщений. В stdio сервер тоже использует это уведомление, но только в одном случае: чтобы завершить открытый ранее поток subscriptions/listen.

Завершение и перезапуск

Клиенту следует завершать работу сервера в три шага:

  1. Закрыть входной поток дочернего процесса.
  2. Дождаться завершения сервера.
  3. Если сервер не завершился за разумное время, принудительно завершить процесс. В POSIX-системах это обычно SIGTERM, затем SIGKILL; в Windows - TerminateProcess или Job Objects.

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

Доверие между процессами

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

Streamable HTTP

Эндпоинт и запросы

В транспорте Streamable HTTP сервер работает как самостоятельный процесс и обслуживает соединения от многих клиентов. Он предоставляет один HTTP-эндпоинт, MCP-эндпоинт, который принимает запросы POST, например https://example.com/mcp.

Каждое сообщение JSON-RPC от клиента - новый HTTP-запрос POST к MCP-эндпоинту. Тело содержит ровно один запрос или одно уведомление. Клиент обязан передать заголовок Accept, в котором перечислены и application/json, и text/event-stream, и заголовки метаданных запроса, описанные ниже. Запрос промпта выглядит так:

HTTP
POST /mcp HTTP/1.1
Host: example.com
Content-Type: application/json
Accept: application/json, text/event-stream
MCP-Protocol-Version: 2026-07-28
Mcp-Method: prompts/get
Mcp-Name: code_review

{
  "jsonrpc": "2.0",
  "id": 7,
  "method": "prompts/get",
  "params": {
    "name": "code_review",
    "arguments": { "code": "def hello():\n    print('world')" },
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientInfo": { "name": "example-client", "version": "1.0.0" },
      "io.modelcontextprotocol/clientCapabilities": {}
    }
  }
}

Если тело - уведомление и сервер его принял, сервер отвечает 202 Accepted без тела. Ядро протокола версии 2026-07-28 не определяет уведомлений клиента для Streamable HTTP, так что это правило описывает только механику транспорта.

Ответы

Server-Sent Events (SSE) - формат HTTP-ответа, в котором сервер передаёт поток событий в рамках одного ответа. На запрос сервер отвечает в одной из двух форм, и клиент обязан поддерживать обе:

  • Content-Type: application/json: один объект JSON с ответом JSON-RPC.
  • Content-Type: text/event-stream: поток SSE в рамках этого запроса. Поток может передавать уведомления, связанные с запросом, например notifications/progress, а затем итоговый ответ, которым поток следует завершить.

В отличие от версий до 2025-11-25 включительно, сервер не должен отправлять в потоке собственные запросы JSON-RPC. Открывая поток SSE, серверу следует передать заголовок X-Accel-Buffering: no, чтобы обратные прокси, например nginx, не буферизовали события.

Потоки уведомлений

Отдельный поток GET прежних версий в 2026-07-28 убран. Чтобы получать уведомления об изменениях, например notifications/tools/list_changed, клиент отправляет запрос subscriptions/listen методом POST. Ответ на него - поток SSE, который остаётся открытым и передаёт только запрошенные клиентом типы уведомлений. Уведомления о прогрессе и другие уведомления отдельного запроса приходят только в поток ответа на этот запрос.

Заголовки запроса

Streamable HTTP копирует отдельные поля тела JSON-RPC в HTTP-заголовки. Балансировщики нагрузки, шлюзы и средства наблюдения могут тогда маршрутизировать и анализировать запросы, не разбирая тело. В таблице - заголовки версии 2026-07-28.

Заголовок Источник в теле Обязателен для
MCP-Protocol-Version io.modelcontextprotocol/protocolVersion в _meta Каждого запроса POST
Mcp-Method method Всех запросов
Mcp-Name params.name или params.uri tools/call, resources/read, prompts/get
Mcp-Param-{Name} Параметр инструмента, помеченный x-mcp-header Вызовов инструментов, которые объявляют такие параметры

Заголовки Mcp-Param-{Name} создаются из свойства x-mcp-header во входной схеме инструмента, см. статью Инструменты (tools) в MCP. Серверы могут использовать это свойство, клиенты обязаны его поддерживать.

Значение Mcp-Name или Mcp-Param-{Name}, которое нельзя передать как обычный заголовок ASCII, кодируется в Base64 в виде =?base64?{value}?=. Имена заголовков сравниваются без учёта регистра, а значения, например имена методов, чувствительны к регистру.

Проверка заголовков

Сервер, который обрабатывает тело, обязан отклонить запрос, если заголовок не совпадает с телом, если нет обязательного заголовка или если в заголовке недопустимые символы. Перед сравнением сервер декодирует значения в форме Base64. Отказ - 400 Bad Request с ошибкой JSON-RPC -32020 (HeaderMismatch):

JSON
{
  "jsonrpc": "2.0",
  "id": 7,
  "error": {
    "code": -32020,
    "message": "Header mismatch: Mcp-Method header value 'tools/call' does not match body value 'prompts/get'"
  }
}

Проверка не даёт сетевым компонентам опираться на разные источники истины, например когда балансировщик маршрутизирует по заголовку, а сервер выполняет тело.

Коды состояния HTTP

В таблице - коды состояния, которые спецификация определяет для транспорта.

Код Когда
200 OK Ответ на запрос: объект JSON или поток SSE
202 Accepted Уведомление принято; тела нет
400 Bad Request Расхождение заголовков (-32020), нет обязательной возможности клиента (-32021), неподдерживаемая версия протокола (-32022), уведомление, которое нельзя принять
403 Forbidden Неверный заголовок Origin
404 Not Found Сервер не реализует метод; ошибка JSON-RPC -32601
405 Method Not Allowed GET или DELETE от клиента прежней версии

Авторизация добавляет 401 Unauthorized и 403 Forbidden для отсутствующих, неверных или недостаточных токенов.

Отмена и обрыв потока

В Streamable HTTP закрытие потока SSE с ответом означает отмену этого запроса. У каждого запроса свой поток, поэтому разрыв однозначен, и уведомление notifications/cancelled не ожидается. Серверу следует прекратить работу над запросом, и он не должен отправлять по нему новых сообщений.

С версии 2026-07-28 потоки нельзя возобновить: заголовок Last-Event-ID и идентификаторы событий SSE убраны. Если поток ответа оборвался, выполнявшийся запрос теряется. Клиент обязан отправить его заново как новый запрос с новым идентификатором.

Отсутствие сессий и балансировка нагрузки

Версия 2026-07-28 убрала сессии протокола и заголовок Mcp-Session-Id. Согласно анонсу выпуска, теперь любой запрос может попасть на любой экземпляр сервера за обычным балансировщиком нагрузки с распределением по кругу (round-robin), без общего хранилища.

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

Безопасность локальных HTTP-серверов

Спецификация транспорта содержит три требования безопасности:

  1. Серверы обязаны проверять заголовок Origin всех входящих соединений, чтобы предотвратить атаки DNS rebinding. Если Origin передан и неверен, сервер обязан ответить 403 Forbidden.
  2. Серверу, который работает локально, следует слушать только localhost (127.0.0.1), а не все сетевые интерфейсы (0.0.0.0).
  3. Серверам следует реализовать аутентификацию для всех соединений.

Без этих мер, согласно спецификации, злоумышленники могут с помощью DNS rebinding обращаться к локальным MCP-серверам с удалённых веб-сайтов. Документация MCP добавляет, что порт на 127.0.0.1 доступен другим локальным процессам и, через браузер, веб-сайтам.

Привязка к 127.0.0.1 не является границей аутентификации. Локальному HTTP-серверу нужны также проверка Origin и аутентификация.

Другие риски описаны в статье Безопасность MCP.

Авторизация

Авторизация в MCP необязательна и относится к транспортам на основе HTTP. Реализациям на HTTP следует соблюдать механизм авторизации из спецификации, основанный на OAuth 2.1: клиент передаёт токен в заголовке Authorization каждого запроса. Серверам на stdio не следует его использовать: они берут учётные данные из окружения. Подробности - в статье Авторизация в MCP.

Обратная совместимость

Прежние редакции Streamable HTTP

Версии с 2025-03-26 по 2025-11-25 использовали Streamable HTTP с сессиями (Mcp-Session-Id, завершение запросом DELETE), отдельным потоком GET, запросами сервера в потоках SSE и возобновлением через Last-Event-ID. Сервер, который поддерживает только 2026-07-28, отвечает на GET и DELETE кодом 405 Method Not Allowed и игнорирует Mcp-Session-Id и Last-Event-ID.

Клиент, который поддерживает и версии с рукопожатием initialize, сначала отправляет запрос в текущем формате. При ответе 400 Bad Request он смотрит на тело: известная ошибка JSON-RPC текущей версии означает современный сервер, а пустое тело или неизвестная ошибка - сервер прежней версии, и тогда клиент переходит на initialize. В stdio клиент проверяет сервер запросом server/discover. Переход не должен зависеть от одного конкретного кода ошибки, потому что прежние серверы отвечают на неизвестные запросы по-разному или не отвечают вовсе.

Транспорт HTTP+SSE

Транспорт HTTP+SSE версии 2024-11-05 использовал два эндпоинта. Клиент открывал поток SSE, сервер отправлял событие endpoint с адресом для сообщений, и клиент отправлял все сообщения запросами POST на этот адрес.

HTTP+SSE устарел с версии 2025-03-26. Версия 2026-07-28 присвоила ему статус Deprecated по политике жизненного цикла функций (SEP-2596): новым реализациям не следует его использовать. Согласно реестру устаревших функций, его можно удалить не раньше чем через три месяца после того, как SEP-2596 получил статус Final.

Сервер может сохранить старые эндпоинты рядом с новым MCP-эндпоинтом. Клиент сначала отправляет запрос POST на адрес сервера. Если ответ - 400, 404 или 405 без известной ошибки JSON-RPC текущей версии, он отправляет запрос GET, и событие endpoint первым в потоке означает HTTP+SSE.

Собственные транспорты

Клиенты и серверы могут реализовать собственные транспорты. Протокол не зависит от транспорта и может работать поверх любого канала с двусторонним обменом сообщениями. Собственный транспорт обязан сохранять формат сообщений JSON-RPC, шаблоны обмена сообщениями и модель метаданных в каждом запросе. Ему следует документировать, как устанавливается соединение, как разбивается поток на сообщения и как работает отмена.

Собственному транспорту поверх надёжного двустороннего потока байтов, например сокетов Unix или TCP, следует использовать разбиение потока из stdio: одно сообщение JSON-RPC на строку.

Сравнение stdio и Streamable HTTP

В таблице сравниваются два стандартных транспорта.

stdio Streamable HTTP
Типичное развёртывание Локальный сервер на машине пользователя Удалённый сервер, сетевой сервис
Кто запускает сервер Клиент, как дочерний процесс Оператор; сервер работает самостоятельно
Клиентов у одного процесса сервера Один, тот, кто его запустил Много
Разбиение на сообщения Одно сообщение JSON-RPC на строку Одно сообщение на запрос POST
Метаданные запроса Только в теле В теле и в заголовках
Сообщения сервера Один общий канал stdout Объект JSON или поток SSE на каждый запрос
Уведомления об изменениях В stdout, с идентификатором подписки В потоке SSE запроса subscriptions/listen
Отмена notifications/cancelled Закрытие потока ответа
Журнал stderr На стороне сервера, например OpenTelemetry
Учётные данные Из окружения процесса Механизм авторизации на основе OAuth 2.1
Главный вопрос безопасности Общая зона доверия с хостом Проверка Origin, аутентификация

Выбор транспорта следует из того, где должен работать сервер. Согласно документации MCP, чтение локальных файлов, управление локальным инструментом или доступ к оборудованию - причины запускать сервер локально, а серверу, который только оборачивает удалённый API, это ничего не даёт. Удалённый сервер на Streamable HTTP не выполняет код на машине пользователя и использует механизм авторизации вместо токена в локальном файле конфигурации.

Логика сервера не зависит от транспорта, поэтому один сервер может поддерживать оба: тогда одни и те же инструменты, ресурсы и промпты доступны и локально, и удалённо.

Термины

Термин Значение
Транспорт (transport) Привязка к каналу связи, которая определяет, как сообщения MCP разбиваются и доставляются
stdio Транспорт через стандартные потоки дочернего процесса, который запускает клиент
Streamable HTTP Транспорт, в котором каждое сообщение - HTTP-запрос POST к одному MCP-эндпоинту
MCP-эндпоинт Единственный HTTP-путь сервера, который принимает запросы POST с сообщениями MCP
Server-Sent Events (SSE) Формат HTTP-ответа с потоком событий от сервера
HTTP+SSE Транспорт версии 2024-11-05 с двумя эндпоинтами; устарел с 2025-03-26
DNS rebinding Атака, при которой веб-сайт через браузер обращается к серверу на машине пользователя
Собственный транспорт Транспорт вне спецификации, который сохраняет её сообщения и метаданные

Источники

Серия

Model Context Protocol (MCP)
  1. Что такое MCP (Model Context Protocol)
  2. Инструменты (tools) в MCP
  3. Ресурсы и шаблоны ресурсов в MCP
  4. Промпты (prompts) в MCP
  5. Транспорты MCP: stdio и Streamable HTTPТекущая
  6. MCP без состояния: изменения 2026-07-28
  7. Авторизация в MCP
  8. Безопасность MCP
  9. Расширения MCP
  10. MCP-сервер поверх существующего API
  11. Тестирование MCP-серверов: основы
  12. Оценка MCP-сервера на моделях
  13. Регрессионное тестирование MCP-сервера
  14. Публикация MCP-сервера в MCP Registry

Статьи Wiki пишет и поддерживает команда ComplexQA.