Начало / Протокол / Транспорты
Транспорты
stdio и Streamable HTTP
MCP поддерживает два транспорта. Формат сообщений JSON-RPC 2.0 у них одинаков, различается способ доставки.
Готовность своего сервера к требованиям этой страницы можно проверить бесплатной проверкой MCP-сервера: она сверяет заголовки, коды ошибок и поведение при чужом Origin.
Два транспорта#
| Транспорт | Как работает | Когда подходит |
|---|---|---|
| stdio | Потоки стандартного ввода и вывода между локальными процессами на одной машине. Без сетевых издержек. | Локальные серверы, которые обычно обслуживают одного клиента. |
| Streamable HTTP | HTTP POST для сообщений клиента, ответ обычным JSON или потоком Server-Sent Events. Поддерживает стандартные способы HTTP-аутентификации: токены предъявителя, ключи API, собственные заголовки. MCP рекомендует OAuth. | Удалённые серверы, которые обслуживают многих клиентов. |
На stdio клиентам НЕ СЛЕДУЕТ считать отдельную задачу, поток или разговор границей времени жизни процесса. Уведомление notifications/cancelled в базовом протоколе используется только на stdio. Проба совместимости для stdio описана на странице «Версии и совместимость».
Streamable HTTP#
Транспорт Streamable HTTP появился в версии 2025-03-26 вместо HTTP+SSE версии 2024-11-05. Ревизия 2026-07-28 изменила его поведение: убрана конечная точка GET для потока и отменены сессии на уровне протокола.
Сервер работает как независимый процесс, который обслуживает много клиентов:
- у сервера одна конечная точка HTTP (например,
https://example.com/mcp), которая принимает POST; - каждый запрос или уведомление клиента — отдельный HTTP POST;
- на каждый запрос сервер отвечает либо одним объектом JSON, либо потоком Server-Sent Events, привязанным к этому запросу: в нём уведомления по запросу и итоговый ответ;
- взаимодействия «сервер — клиент» (sampling, elicitation, roots) встроены в результаты как запросы входных данных по шаблону MRTR;
- долгие уведомления об изменениях идут в потоке ответа на
subscriptions/listen.
Безопасность и конечная точка#
- Серверы ДОЛЖНЫ проверять заголовок
Originу всех входящих соединений, чтобы предотвратить атаки через DNS rebinding. ЕслиOriginесть и недопустим, сервер ДОЛЖЕН ответить403 Forbidden. - При локальной работе серверам СЛЕДУЕТ слушать только localhost (127.0.0.1), а не все сетевые интерфейсы (0.0.0.0).
- Серверам СЛЕДУЕТ реализовать аутентификацию для всех соединений.
Без этих мер злоумышленник мог бы обращаться к локальным серверам MCP с чужих сайтов.
Отправка сообщений#
- Клиент ДОЛЖЕН использовать HTTP POST.
- Клиент ДОЛЖЕН указать в заголовке
Acceptиapplication/json, иtext/event-stream. - Клиент ДОЛЖЕН включить заголовки метаданных запроса в каждый POST.
- Тело POST ДОЛЖНО быть одним запросом или уведомлением JSON-RPC. Клиент НЕ ДОЛЖЕН отправлять ответы JSON-RPC.
- На уведомление сервер, если принимает его, ДОЛЖЕН вернуть
202 Acceptedбез тела, а если не может принять, то код ошибки HTTP, например400. - На запрос сервер ДОЛЖЕН вернуть
Content-Type: application/jsonилиContent-Type: text/event-stream. Клиент ДОЛЖЕН поддерживать оба.
В базовой ревизии нет уведомлений «клиент — сервер» по Streamable HTTP: notifications/cancelled используется только на stdio.
Получение сообщений (поток SSE)#
- Сервер МОЖЕТ присылать уведомления (например,
notifications/progressилиnotifications/message) до итогового ответа, и они ДОЛЖНЫ относиться к исходному запросу. - Сервер НЕ ДОЛЖЕН присылать в этом потоке собственные запросы JSON-RPC. Взаимодействия «сервер — клиент» встраиваются в
InputRequiredResult. Это изменение относительно версий 2025-03-26–2025-11-25, где сервер мог слать такие запросы по потокам SSE. - Поток СЛЕДУЕТ закрывать после итогового ответа.
- Начиная поток, серверам СЛЕДУЕТ добавлять заголовок
X-Accel-Buffering: no, чтобы обратные прокси (например, nginx) не буферизовали ответ. - Для долгих потоков, в частности
subscriptions/listen, рекомендуется периодически посылать строку-комментарий SSE (начинается с двоеточия) как «пульс», чтобы промежуточные узлы не закрывали соединение. Клиенты ДОЛЖНЫ игнорировать такие строки. - Возобновление потоков по
Last-Event-IDне поддерживается.
Отмена#
Закрытие потока ответа SSE сервер ДОЛЖЕН считать отменой этого запроса: прекратить работу как можно скорее и больше не присылать сообщений по нему.
Заголовки метаданных запроса#
Streamable HTTP дублирует часть полей тела JSON-RPC в заголовках HTTP, чтобы посредники (балансировщики, шлюзы, наблюдение) могли маршрутизировать и проверять запросы, не разбирая тело.
Заголовок версии протокола
Каждый POST на конечную точку ДОЛЖЕН содержать заголовок MCP-Protocol-Version, например MCP-Protocol-Version: 2026-07-28. Значение ДОЛЖНО совпадать с io.modelcontextprotocol/protocolVersion в _meta тела. Иначе сервер ДОЛЖЕН отклонить запрос статусом 400 Bad Request и ошибкой HeaderMismatch.
Если сервер не реализует запрошенную версию, он ДОЛЖЕН ответить 400 Bad Request и UnsupportedProtocolVersionError со списком поддерживаемых версий. Если не реализует запрошенный метод, ДОЛЖЕН ответить 404 Not Found и ошибкой JSON-RPC с кодом -32601. По тексту ошибки клиент отличает такой ответ от 404 старого сервера HTTP+SSE.
Сервер, который поддерживает клиентов версий до 2025-06-18 (в них заголовка не было), МОЖЕТ считать запрос без заголовка запросом версии 2025-03-26.
Стандартные заголовки запроса
| Заголовок | Источник | Обязателен для |
|---|---|---|
Mcp-Method |
method |
всех запросов |
Mcp-Name |
params.name или params.uri |
tools/call, resources/read, prompts/get |
Эти заголовки ОБЯЗАТЕЛЬНЫ. Пример tools/call:
POST /mcp HTTP/1.1
Content-Type: application/json
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: get_weather
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_weather",
"arguments": { "location": "Seattle, WA" },
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientInfo": { "name": "ExampleClient", "version": "1.0.0" },
"io.modelcontextprotocol/clientCapabilities": {}
}
}
}Заголовки из параметров инструмента
Сервер МОЖЕТ пометить параметры инструмента свойством x-mcp-header в inputSchema. Значение определяет имя заголовка Mcp-Param-{имя}. Серверу это необязательно, а клиенты ДОЛЖНЫ это поддерживать: если определение инструмента содержит такие пометки, клиент ДОЛЖЕН продублировать значения параметров в заголовки.
Ограничения на значения x-mcp-header:
- не пустое и совпадает с синтаксисом имени поля HTTP (
1*tchar, RFC 9110, раздел 5.1); - без управляющих символов, включая CR и LF;
- уникально без учёта регистра в пределах
inputSchema; - только для параметров примитивных типов: целое, строка, логическое значение (тип
numberне допускается); целые ДОЛЖНЫ лежать в безопасном диапазоне JavaScript (от −2⁵³+1 до 2⁵³−1); - только для свойств, статически достижимых от корня схемы через цепочку ключей
properties, безitems,oneOf,anyOf,allOf,not,if/then/elseи$ref.
Клиенты на Streamable HTTP ДОЛЖНЫ отклонять определения инструментов, где значение нарушает эти ограничения: такой инструмент исключается из результата tools/list, и клиенту СЛЕДУЕТ записать предупреждение. Клиенты на других транспортах (например, stdio) МОГУТ игнорировать x-mcp-header.
Пример определения и итогового запроса:
{
"name": "execute_sql",
"description": "Execute SQL on Google Cloud Spanner",
"inputSchema": {
"type": "object",
"properties": {
"region": { "type": "string", "description": "The region to execute the query in", "x-mcp-header": "Region" },
"query": { "type": "string", "description": "The SQL query to execute" }
},
"required": ["region", "query"]
}
}POST /mcp HTTP/1.1
Content-Type: application/json
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: execute_sql
Mcp-Param-Region: us-west1Кодирование значений
Клиенты ДОЛЖНЫ кодировать значения перед включением в заголовки. Значение приводится к строке: строка как есть, целое в десятичной записи, логическое в виде "true" или "false". Если значение нельзя безопасно передать обычным ASCII (содержит не-ASCII, управляющие символы или пробелы по краям), клиент ДОЛЖЕН использовать Base64 от UTF-8 в формате:
Mcp-Param-{Name}: =?base64?{Base64EncodedValue}?=Так же кодируется Mcp-Name. Метки =?base64? и ?= чувствительны к регистру. Чтобы избежать неоднозначности, клиент ДОЛЖЕН закодировать и обычное значение, похожее на этот шаблон.
| Исходное значение | Причина | Заголовок |
|---|---|---|
us-west1 |
Обычный ASCII | Mcp-Param-Region: us-west1 |
Hello, 世界 |
Не-ASCII | Mcp-Param-Greeting: =?base64?SGVsbG8sIOS4lueVjA==?= |
padded |
Пробелы по краям | Mcp-Param-Text: =?base64?IHBhZGRlZCA=?= |
Если значение параметра равно null или параметра нет в аргументах, клиент НЕ ДОЛЖЕН добавлять заголовок.
Проверка на сервере
Сервер, который обрабатывает тело запроса, ДОЛЖЕН отклонять запросы, где значения в заголовках не совпадают со значениями в теле. Это защищает от ситуации, когда балансировщик маршрутизирует по заголовку, а сервер исполняет по телу. Отказ оформляется статусом 400 Bad Request и ошибкой JSON-RPC -32020 (HeaderMismatch):
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32020,
"message": "Header mismatch: Mcp-Name header value 'foo' does not match body value 'bar'"
}
}Причины отказа: нет обязательного заголовка; значение не совпадает с телом (закодированные значения сначала декодируют); недопустимые символы. Целые сравниваются как числа, а не как строки. Имена заголовков регистронезависимы, значения регистрозависимы. Посредникам, которые применяют политику по заголовкам, СЛЕДУЕТ убедиться, что версия протокола требует такой проверки, и отклонять запросы без заголовка.
Обратная совместимость#
Клиент, который поддерживает и современные версии, и прежние (с рукопожатием initialize), МОЖЕТ определить эпоху сервера, попробовав сначала современный запрос. При 400 Bad Request ему СЛЕДУЕТ посмотреть тело: распознанная современная ошибка JSON-RPC значит современный сервер, и нужно повторить запрос с другой версией, а не откатываться. Если тело пустое или ошибка не распознана, нужно вернуться к initialize.
Сервер, который поддерживает только эту ревизию и получает трафик старого клиента, СЛЕДУЕТ действовать так:
- GET и DELETE на конечную точку: отвечать
405 Method Not Allowed; - заголовок
Mcp-Session-Id: игнорировать, не создавать и не возвращать идентификаторы сессий; - заголовок
Last-Event-ID: игнорировать, потоки не возобновляются.
HTTP+SSE (2024-11-05) устарел с версии 2025-03-26 и признан Deprecated по политике жизненного цикла функций. Новым реализациям его применять НЕ СЛЕДУЕТ, существующим СЛЕДУЕТ перейти на Streamable HTTP. Клиент, который хочет поддерживать старые серверы, сначала отправляет POST, а при 400, 404 или 405 без современной ошибки в теле пробует GET и ожидает событие endpoint: так он узнаёт старый транспорт.