Версии протокола
Машинный перевод
Эта страница переведена с английской документации автоматически, и основной версией остаётся английская страница. Если что-то читается неправильно, на странице Переводы объясняется, как об этом сообщить.
У MCP два поколения.
Серверы, выпущенные до 2026-07-28, открывают каждое подключение рукопожатием initialize: клиент предлагает версию, сервер отвечает встречным предложением, клиент подтверждает — и всё это до первого полезного запроса. Серверы на 2026-07-28 от рукопожатия отказываются. Клиент отправляет один пробный запрос server/discover, и сервер отвечает на него всем сразу в одном результате.
Заботиться об этом почти никогда не приходится: Client договаривается за вас. Эта страница — об одном аргументе конструктора, который этим управляет, mode=, и о трёх случаях, когда его меняют.
mode="auto"
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"
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"].
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=:
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 года задаёт вопрос посреди вызова? Он его возвращает: Многораундовые запросы.