Перейти к содержанию

Версии протокола

Машинный перевод

Эта страница переведена с английской документации автоматически, и основной версией остаётся английская страница. Если что-то читается неправильно, на странице Переводы объясняется, как об этом сообщить.

У MCP два поколения.

Серверы, выпущенные до 2026-07-28, открывают каждое подключение рукопожатием initialize: клиент предлагает версию, сервер отвечает встречным предложением, клиент подтверждает — и всё это до первого полезного запроса. Серверы на 2026-07-28 от рукопожатия отказываются. Клиент отправляет один пробный запрос server/discover, и сервер отвечает на него всем сразу в одном результате.

Заботиться об этом почти никогда не приходится: Client договаривается за вас. Эта страница — об одном аргументе конструктора, который этим управляет, mode=, и о трёх случаях, когда его меняют.

mode="auto"

client.py
from mcp import Client
from mcp.server import MCPServer

mcp = MCPServer("Bookshop")


@mcp.tool()
def search_books(query: str) -> str:
    """Search the catalog by title or author."""
    return f"Found 3 books matching {query!r}."


async def main() -> None:
    async with Client(mcp) as client:
        print(client.protocol_version)

mode не передан, поэтому действует значение по умолчанию — "auto". Вход в async with отправляет один пробный запрос server/discover на самой новой версии, которую понимает этот SDK. Дальше:

  • Современный сервер на него отвечает. Клиент принимает результат. Один раунд обмена — и готово.
  • Более старый сервер никогда не слышал о server/discover и возвращает ошибку. Клиент откатывается к классическому рукопожатию initialize и берёт то, о чём оно договорится.

В любом случае подключение установлено, а client.protocol_version сообщает, как именно:

2026-07-28

Вот и вся механика. Один Client, сервер любого поколения, никаких ветвлений в коде.

Info

MCPServer отвечает на server/discover на любом транспорте — в памяти, stdio, Streamable HTTP, — поэтому с собственным сервером auto всегда приходит к 2026-07-28. Откат срабатывает только с настоящим сервером до 2026 года — ровно тогда, когда он и нужен.

mode="legacy"

client.py
from mcp import Client
from mcp.server import MCPServer

mcp = MCPServer("Bookshop")


@mcp.tool()
def search_books(query: str) -> str:
    """Search the catalog by title or author."""
    return f"Found 3 books matching {query!r}."


async def main() -> None:
    async with Client(mcp, mode="legacy") as client:
        print(client.protocol_version)

mode="legacy" никогда не отправляет пробный запрос. Он выполняет рукопожатие initialize — то же подключение, которое открывает клиент до 2026 года.

2025-11-25

Тот же сервер. Он прекрасно говорит на 2026-07-28 — это вы велели клиенту не спрашивать.

Этот режим нужен ради push-возможностей.

Запрос, инициированный сервером, — это когда сервер вызывает вас: ctx.elicit(...) показывает форму вашему пользователю, сэмплирование (sampling) запрашивает у вашей модели генерацию прямо посреди вызова инструмента. Такой канал существует только в сессии поколения рукопожатия.

На 2026-07-28 его больше нет. Сервер возвращает свои вопросы, а вы повторяете вызов уже с ответами (Многораундовые запросы, multi-round-trip).

mode="auto" даёт рукопожатие, только когда сервер слишком стар для чего-либо ещё. mode="legacy" его гарантирует. Берите его всякий раз, когда передаёте в Client(...) sampling_callback, elicitation_callback, который должен работать как запрос, или message_handler. Каждый из них разобран на странице Колбэки клиента.

Фиксация версии

mode принимает и строку современной версии протокола. Сегодня это множество ровно ["2026-07-28"].

client.py
from mcp import Client
from mcp.server import MCPServer

mcp = MCPServer("Bookshop")


@mcp.tool()
def search_books(query: str) -> str:
    """Search the catalog by title or author."""
    return f"Found 3 books matching {query!r}."


async def main() -> None:
    async with Client(mcp, mode="2026-07-28") as client:
        print(client.protocol_version)

Фиксированная версия не отправляет ничего. Ни пробного запроса, ни рукопожатия. Клиент локально принимает 2026-07-28, и подключение готово к работе в тот же миг, когда async with возвращает управление.

Фиксация — это обещание, которое даёте вы: вам уже известно, что сервер говорит на этой версии. Клиент не проверяет.

Check

Фиксация — не обнаружение. Выведите client.server_info, и цена сразу видна:

None

Клиент так и не спросил у сервера, кто он, поэтому server_info равен None. С client.server_capabilities та же история: каждая возможность — None. Вызовы инструментов по-прежнему работают (протоколу ничего из этого не нужно), а вот код, который читает server_capabilities, чтобы решить, что предлагать, — нет.

Следующий раздел это исправляет.

Фиксировать можно только современные версии. Строка поколения рукопожатия отклоняется при создании объекта, до любого ввода-вывода, а ошибка подсказывает, что написать вместо неё:

ValueError: mode must be 'legacy', 'auto', or one of ['2026-07-28']; got '2025-06-18' ('2025-06-18' is a handshake-era version; use mode='legacy')

Переподключение с prior_discover

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

Так что сохраните его. После подключения в режиме auto в client.session.discover_result лежит ровно тот DiscoverResult, который прислал сервер: его supported_versions, capabilities, instructions и идентификационные данные, которые сервер записал в _meta результата. В следующий раз передайте его обратно как prior_discover=:

client.py
from mcp import Client
from mcp.server import MCPServer

mcp = MCPServer("Bookshop")


@mcp.tool()
def search_books(query: str) -> str:
    """Search the catalog by title or author."""
    return f"Found 3 books matching {query!r}."


async def main() -> None:
    async with Client(mcp) as client:
        saved = client.session.discover_result

    async with Client(mcp, mode="2026-07-28", prior_discover=saved) as client:
        print(client.protocol_version)
        if client.server_info is not None:
            print(client.server_info.name)
2026-07-28
Bookshop

Второе подключение сделало ноль раундов согласования и всё равно точно знает, с кем говорит. Это и есть режим с фиксацией, сделанный как надо: mode= называет версию, prior_discover= даёт идентификационные данные. ✨

DiscoverResult — модель Pydantic. saved.model_dump_json() уходит в файл или кэш; DiscoverResult.model_validate_json(...) восстанавливает его в следующем процессе.

Tip

prior_discover= что-то делает только тогда, когда mode — фиксированная версия. В режиме "auto" клиент всё равно опрашивает сервер, а в режиме "legacy" аргумент игнорируется.

Четыре режима

Вы пишете Трафик согласования Вы получаете
Client(target) один пробный запрос server/discover; рукопожатие initialize, если он не удался самую новую версию, на которой говорят обе стороны, любого поколения
Client(target, mode="legacy") рукопожатие initialize версию поколения рукопожатия; запросы, инициированные сервером, работают
Client(target, mode="2026-07-28") нет эту версию, зафиксированную, с server_info, равным None
Client(target, mode="2026-07-28", prior_discover=saved) нет эту версию, зафиксированную, и идентификационные данные, сохранённые в прошлый раз

Итоги

  • У MCP есть поколение рукопожатия (до 2025-11-25 включительно, рукопожатие initialize) и современное поколение (2026-07-28, server/discover). Client соединяет их.
  • mode="auto" — значение по умолчанию: пробный запрос, затем откат. Не трогайте его, если только вас не описывает одна из трёх других строк таблицы.
  • client.protocol_version — всегда ответ на вопрос «что я получил?».
  • mode="legacy" принудительно включает рукопожатие. Это то, что нужно для запросов, инициированных сервером: сэмплирования, push-элицитации (elicitation), message_handler.
  • Фиксация версии (mode="2026-07-28") не отправляет вообще никакого трафика согласования — ценой того, что client.server_info равен None.
  • prior_discover= возвращает эту цену: сохраните client.session.discover_result, переподключитесь с ним — и получите и то и другое.

У современного подключения нет push-канала — так как же сервер 2026 года задаёт вопрос посреди вызова? Он его возвращает: Многораундовые запросы.