Перейти до змісту

Версії протоколу

Машинний переклад

Цю сторінку перекладено автоматично з англомовної документації, і основною версією є англомовна сторінка. Якщо щось читається неправильно, на сторінці Переклади пояснено, як про це повідомити.

У 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 на кожному транспорті — in-memory, 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 ставить вам запитання посеред виклику? Він його повертає: Багатораундові запити.