Что нового в v2
Машинный перевод
Эта страница переведена с английской документации автоматически, и основной версией остаётся английская страница. Если что-то читается неправильно, на странице Переводы объясняется, как об этом сообщить.
В v2 одновременно произошли две перемены. SDK перестроен: новый движок под клиентом и под сервером, полноценный Client и набор переименований, с которыми кодовая база на v1 сталкивается при первом же импорте. И протокол ушёл вперёд: v2 говорит на ревизии MCP 2026-07-28, которая убирает рукопожатие при подключении, сессию и все запросы, инициируемые сервером, — не оставляя при этом за бортом клиенты, которые у вас уже есть.
Эта страница — обзор обеих половин: по разделу на каждую главную новость, и каждый заканчивается ссылкой на страницу, которой принадлежит тема. Это не руководство по переносу. Им служит Руководство по миграции: все ломающие изменения с кодом до и после.
v2 — стабильная ветка
pip install mcp устанавливает 2.x, а на странице Установка есть
готовая строка установки для копирования. Если что-то в v2 ломается, удивляет или тормозит вас,
сообщите нам.
SDK: от v1 к v2
FastMCP теперь MCPServer
Высокоуровневый класс сервера переименован, а вместе с ним и его модуль. Это первое, на что натыкается каждый сервер на v1, потому что старый путь импорта удалён, а не объявлен устаревшим:
from mcp.server import MCPServer # v1: from mcp.server.fastmcp import FastMCP
mcp = MCPServer("Demo") # v1: FastMCP("Demo")
Для сервера, собранного на декораторах, это заодно и почти весь перенос. @mcp.tool(), @mcp.resource() и @mcp.prompt() принимают то же, что принимали в v1 (@mcp.resource() добавляет один необязательный именованный аргумент security=), а входная схема по-прежнему строится по аннотациям типов. По мелочам: всё, что лежало под mcp.server.fastmcp.*, теперь живёт под mcp.server.mcpserver.*, ctx.fastmcp стал ctx.mcp_server, get_context() удалён (вместо него объявите параметр ctx: Context), а базовый класс исключений FastMCPError теперь MCPServerError. Таблица импортов — в Руководстве по миграции.
Resolve: новый способ запросить ввод у пользователя
Не всё, что нужно инструменту, должно приходить от модели. Новое в v2: параметр инструмента с аннотацией Resolve(fn) заполняет функция, которую пишете вы, незаметно для модели, и эта функция может вернуть Elicit(...), чтобы задать вопрос пользователю. Это предпочтительный способ получить что-либо от клиента посреди вызова: SDK передаёт вопрос тем механизмом, который поддерживает подключение, — живой запрос элицитации (elicitation) для клиента старого поколения или многораундовый запрос (multi-round-trip) на 2026-07-28, — так что одно тело инструмента обслуживает оба поколения. Подробнее — на странице Зависимости.
Note
Две другие формы остаются на случай, когда они нужны: ctx.elicit() по-прежнему работает для клиентов на
подключениях старого поколения (Элицитация), а обработчик может сам вернуть
InputRequiredResult и вести раунды вручную — именно так на 2026-07-28 путешествуют и запросы
сэмплирования (sampling) и корневых каталогов (roots) (Многораундовые запросы).
Полноценный Client
v1 выдавала три вложенных слоя: контекстный менеджер транспорта, отдающий сырые потоки, обёрнутый вокруг них ClientSession и вызываемый вручную await session.initialize(). В v2 объект один:
from mcp import Client
from mcp.server import MCPServer
mcp = MCPServer("Bookshop", instructions="Search the catalog before recommending a book.")
@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.server_info)
print(client.server_capabilities)
print(client.protocol_version)
print(client.instructions)
Client принимает объект сервера (в памяти, без транспорта — это сценарий для тестов), URL (Streamable HTTP) или любой контекстный менеджер транспорта, например stdio_client(...). Вход в async with подключается и согласует версию протокола, на каком бы поколении ни говорил сервер; после этого client.server_capabilities и client.protocol_version просто доступны, как и client.server_info, когда сервер себя идентифицирует (теперь это Implementation | None, поскольку в поколении 2026 идентификация необязательна). Колбэки сэмплирования и элицитации, зарегистрированные в v1, по-прежнему работают (их тела затрагивает то же переименование атрибутов в snake_case, что и всё остальное на этой странице), теперь они ещё и отвечают на запросы внутри результатов в стиле 2026 (см. ниже) и выполняются параллельно, а не по одному. ClientSession по-прежнему лежит в основе для тех, кому нужна низкоуровневая поверхность, и client.session её отдаёт; она тоже изменилась (работает на новом движке-диспетчере, и некоторые её собственные сигнатуры поменялись), так что прежде чем спускаться на этот уровень, прочитайте Руководство по миграции.
Страница Объект Client знакомит с ним, Транспорты клиента описывает три формы подключения, Колбэки клиента — сами колбэки, а Тестирование показывает шаблон работы в памяти, который заменяет вспомогательную функцию create_connected_server_and_client_session() из v1.
Низкоуровневый Server перестроен, а не переименован
Если вы работаете на уровне JSON-RPC, это та часть v2, где «всё по-другому». Вот один и тот же сервер с одним инструментом в обоих вариантах; нажмите на маркеры, чтобы увидеть, что изменилось.
from typing import Any
import mcp.types as types
from mcp.server.lowlevel import Server
server = Server("Bookshop")
@server.list_tools() # (1)!
async def list_tools() -> list[types.Tool]:
return [ # (2)!
types.Tool(
name="search_books",
description="Search the catalog by title or author.",
inputSchema={ # (3)!
"type": "object",
"properties": {"query": {"type": "string"}},
"required": ["query"],
},
)
]
@server.call_tool()
async def call_tool(name: str, arguments: dict[str, Any]) -> list[types.ContentBlock]: # (4)!
if name != "search_books":
raise ValueError(f"Unknown tool: {name}") # (5)!
ctx = server.request_context # (6)!
return [types.TextContent(type="text", text=f"Found 3 books matching {arguments['query']!r}.")] # (7)!
- Обработчики регистрируются декораторами (вызываемыми, со скобками) в любой момент после создания сервера.
- Возвращается голый
list[Tool], а SDK оборачивает его вListToolsResult. - Поля в Python — в camelCase, а схема применяется принудительно: SDK проверяет по ней аргументы
call_toolчерез jsonschema до запуска вашей функции, поэтому обращениеarguments["query"]ниже безопасно. - Один обработчик
call_toolобслуживает все инструменты и получает имя инструмента и уже проверенные аргументы — распакованные и никогда неNone. - Исключение — так инструмент в v1 сообщает о неудаче: любое исключение перехватывается и возвращается как
CallToolResult(isError=True)с текстомstr(e), так что вызывающая модель читает это сообщение и может повторить попытку. - Контекст берётся из фоновой ContextVar, к которой посреди запроса обращаются через объект сервера.
- Голые блоки содержимого оборачиваются в
CallToolResultза вас.
from mcp import MCPError
from mcp.server import Server, ServerRequestContext
from mcp.types import (
INVALID_PARAMS,
CallToolRequestParams,
CallToolResult,
ListToolsResult,
PaginatedRequestParams,
TextContent,
Tool,
)
SEARCH_BOOKS = Tool(
name="search_books",
description="Search the catalog by title or author.",
input_schema={ # (1)!
"type": "object",
"properties": {"query": {"type": "string"}},
"required": ["query"],
},
)
async def list_tools(ctx: ServerRequestContext, params: PaginatedRequestParams | None) -> ListToolsResult: # (2)!
return ListToolsResult(tools=[SEARCH_BOOKS]) # (3)!
async def call_tool(ctx: ServerRequestContext, params: CallToolRequestParams) -> CallToolResult: # (4)!
if params.name != "search_books":
raise MCPError(INVALID_PARAMS, f"Unknown tool: {params.name}") # (5)!
args = params.arguments or {} # (6)!
text = f"Found 3 books matching {args['query']!r}."
return CallToolResult(content=[TextContent(type="text", text=text)]) # (7)!
server = Server("Bookshop", on_list_tools=list_tools, on_call_tool=call_tool) # (8)!
- Поля теперь в snake_case, а схема объявляется, но никогда не применяется: до запуска обработчика аргументы ничто не проверяет.
- У всех обработчиков одна форма:
async (ctx, params) -> result. Контекст — первый аргумент (на нём живутctx.session,ctx.request_id,ctx.protocol_version); сюда и переехалserver.request_context. - Полный
ListToolsResultвы собираете сами. Возврат голого списка теперь даётTypeErrorна стороне сервера, а не оборачивается SDK. - На входе — типизированные параметры (
params.name,params.arguments), на выходе — полный результат. Ничего не распаковывается, не оборачивается и не преобразуется за вас. - Та же проверка, другой глагол.
ValueErrorздесь дошёл бы до модели как непрозрачный-32603(см. ниже), поэтому намеренная ошибка уровня протокола выбрасывается какMCPError: она проходит насквозь с нетронутыми кодом и сообщением, а-32602с этим текстом — ответ на неизвестный инструмент, прописанный в самой спецификации. params.argumentsможет бытьNone; v1 подставляла{}ещё до того, как ваш код его видел. Раз перед обработчиком нет проверки, без этой строки не обойтись.- Неожиданное исключение, выброшенное здесь, становится очищенной ошибкой протокола,
-32603"Internal server error": модель никогда не увидит сообщения. Для неудачи, которую модель должна прочитать и на которую должна отреагировать, возвращайтеCallToolResult(is_error=True, ...). - Обработчики — аргументы конструктора, так что поверхность сервера полна в момент его создания;
add_request_handler()— запасной выход после создания и дверь к пользовательским методам.
Пример и есть шаблон. В общем виде: у всех обработчиков одна форма — типизированные параметры на входе и полный тип результата на выходе; прежней проверки аргументов инструмента через jsonschema больше нет; исключение — это ошибка протокола и никогда не результат инструмента с is_error=True; фоновой ContextVar server.request_context больше нет. Пользовательские методы в пространстве имён поставщика полноценно поддерживаются через add_request_handler(method, params_type, handler), который проверяет входящие параметры по вашей модели до запуска обработчика. А список middleware (намеренно помеченный как предварительный) оборачивает каждое входящее сообщение, заменяя приватные методы _handle_*, которые раньше переопределяли.
Внутри приёмный цикл BaseSession из v1 заменён движком-диспетчером, который теперь общий для клиента и сервера, и именно он делает верными сразу несколько утверждений этой страницы: один объект Server обслуживает оба поколения протокола, Client(server) диспетчеризует внутри процесса без JSON-RPC-обрамления, а запрос клиента, у которого истёк таймаут, теперь действительно отменяет обработчик на стороне сервера.
Подробнее — на странице Низкоуровневый Server; Руководство по миграции проходит по каждому удалённому хуку. Если вы никогда не спускались ниже MCPServer, ничто из этого вас не касается.
Типы протокола переехали в mcp-types, и все поля теперь в snake_case
Типы протокола теперь живут в собственном дистрибутиве mcp-types. Он не зависит ни от чего, кроме pydantic и typing-extensions, так что шлюз, прокси или генератор кода может использовать формы передаваемых MCP данных, не устанавливая HTTP-стек: такой проект устанавливает mcp-types и импортирует mcp_types. Сам mcp зависит от этого пакета с точной версией и реэкспортирует его, так что код, зависящий от SDK, по-прежнему пишет import mcp.types as types и from mcp.types import Tool (постоянный псевдоним, каждое имя — тот же объект) и объявляет только одну свою настоящую зависимость, mcp. Правило простое: импортируйте через тот пакет, от которого действительно зависите.
У этих типов каждый атрибут в Python теперь в snake_case: result.is_error, tool.input_schema, listing.next_cursor. JSON в передаваемых данных — в camelCase, ровно как раньше; изменилось только написание атрибутов. Заодно появились два более строгих значения по умолчанию: неизвестные поля игнорируются, а не возвращаются обратно (дополнительное кладите в _meta), и обе стороны проверяют трафик по согласованной версии протокола. Таблица переименований — в Руководстве по миграции.
Настройка транспорта переехала в run()
MCPServer(...) описывает, чем ваш сервер является: имя, инструкции, жизненный цикл (lifespan), авторизацию. То, как он обслуживается, теперь относится к run() и сборщикам приложений — туда ушли host, port, stateless_http, json_response, пути эндпоинтов и transport_security (MCPServer("x", port=9000) — это TypeError). Перегрузки типизированы по транспортам, так что редактор подскажет, какие параметры принимает stdio, а какие — streamable-http. Одно удаление стоит знать: mount_path больше нет; поддерживаемый способ обслуживать под префиксом — монтировать ASGI-приложение.
Параметры описаны на странице Запуск сервера; монтирование — на странице Добавление в существующее приложение.
Поведение, которое меняется без ошибки импорта
Переименования заявляют о себе сами. А вот эти изменения — нет:
- Синхронные функции выполняются в рабочем потоке. Инструмент (а также ресурс, промпт или резолвер), объявленный через
def, больше не блокирует цикл событий; плата за это — его тело больше не выполняется в потоке цикла событий, что важно для кода, привязанного к потоку. Обработчикиasync defне затронуты. Руководство по миграции. MCPError(McpErrorв v1), выброшенный внутри инструмента, теперь ошибка протокола. Модель его никогда не видит. Любое другое исключение по-прежнему становится результатом сis_error=True, который модель может прочитать и на который может отреагировать. Разграничение — на странице Обработка ошибок.- Результаты проверяются перед отправкой. Собранный вручную
Tool, у которогоinput_schemaравна{}, теперь проваливаетtools/list(спецификация требует"type": "object"). Серверы, построенные на@mcp.tool(), с этим не сталкиваются: их схемы пишет SDK. - Ваш клиент проверяет то, что получает.
list_tools()иcall_tool()сверяют ответ сервера с согласованной версией протокола, так что не совсем валидный сервер, который терпел снисходительный разбор v1, теперь вызываетpydantic.ValidationError. Если подключаетесь к серверам, которые не контролируете, будьте готовы оказаться тем, кто их обнаружит; подробности — в Руководстве по миграции. - Шаблоны URI теперь настоящий RFC 6570.
{+path},{?query}и им подобные работают, сопоставление точное, а не приблизительное через регулярные выражения, и обход путей в извлечённых значениях по умолчанию отклоняется. Более строгие шаблоны падают в момент декорирования, а не на первом запросе. Шаблоны URI. - Жизненный цикл streamable HTTP выполняется один раз, при запуске, и его состояние общее для всех сессий и запросов. В v1 он выполнялся один раз на сессию, а при
stateless_http=True— один раз на запрос. Пулы и кэши, созданные в жизненном цикле, становятся радикально дешевле; всё, что захватывало там ресурс на одно подключение, теперь относится к телу обработчика. Жизненный цикл. mcp devиmcp installзакрепляют порождаемое окружение за установленной у вас версией SDK. Обе команды запускают сервер в свежем окруженииuv run --with ..., которое раньше разрешалоmcpв новейший стабильный выпуск, а не в версию, против которой вы разрабатываете. Руководство по миграции.- HTTP-клиент теперь
httpx2, а неhttpx. Смена зависимости меняет то, что ваш код перехватывает и передаёт (httpx2.AsyncClient,httpx2.ConnectError), и меняет способ проверки TLS-сертификатов:httpx2проверяет черезtruststoreпо хранилищу доверия операционной системы, а не по встроенному списку УЦ из certifi. Большинство окружений ничего не заметят; минимальный контейнер без системного хранилища УЦ или частный УЦ, о котором знал только набор certifi, начинает проваливать TLS-рукопожатие. ЗадайтеSSL_CERT_FILE/SSL_CERT_DIRили передайте клиентуverify=ssl_context. Руководство по миграции.
Удалено полностью
Каждому пункту посвящён раздел в Руководстве по миграции:
- Транспорт WebSocket с обеих сторон и дополнение
mcp[ws]. Он никогда не входил в спецификацию MCP. - API экспериментальных Tasks (
mcp.*.experimental). 2026-07-28 выносит задачи из ядра протокола в официальное расширение (SEP-2663), которое этот SDK пока не реализует. mcp.shared.version,mcp.shared.progressиmcp.shared.session(с заглушкойRequestResponder, которую импортировали аннотацииmessage_handlerв v1) как пути импорта. (mcp.typesне удалён: он остаётся постоянным псевдонимом отдельного пакетаmcp_types.)- Устаревшее написание
streamablehttp_clientи колбэкget_session_idуstreamable_http_client(который теперь отдаёт ровно два потока). McpError, переименованный вMCPErrorс прямым конструктором(code, message, data).MCPServer.get_context(),mount_path=, а также методы-декораторы, ContextVar и словари обработчиков низкоуровневогоServer.
Протокол: от 2025-11-25 к 2026-07-28
v2 реализует ревизию 2026-07-28 и обслуживает обе ревизии одновременно: одно и то же streamable_http_app() (и один и тот же stdio-сервер) отвечает на initialize клиента поколения 2025 и на запросы клиента поколения 2026 — ничего не нужно настраивать, переключать флаг или разворачивать отдельно. Обслуживание новой ревизии не оставляет за бортом клиент на старой. Дальше — о том, что меняет сама новая ревизия.
Ни рукопожатия, ни сессии
Клиент 2026-07-28 не открывает подключение, не договаривается и лишь потом говорит. Каждый запрос несёт версию протокола, сведения о клиенте и возможности клиента в _meta, а единственный вызов обнаружения, server/discover, — обычный запрос, как любой другой. Client по умолчанию поступает правильно: один раз пробует server/discover и откатывается к рукопожатию initialize, если сервер старше.
По Streamable HTTP на пути 2026 нет Mcp-Session-Id, и это главная эксплуатационная новость: ничто не привязывает современный запрос к воркеру, так что ответить на него может любая реплика за обычным балансировщиком с round-robin. Две честные оговорки. Ваши клиенты поколения 2025 (сегодня это большинство клиентов) по-прежнему открывают сессии и по-прежнему требуют той же привязки, что требовали на v1; для них ничего не меняется. А единственное, что повтор многораундового запроса должен перенести между воркерами, — это его запечатанный request_state, ключ для которого по умолчанию создаётся на каждый процесс, поэтому масштабированное развёртывание передаёт RequestStateSecurity(keys=[...]). (stateless_http=True тут ни при чём: он влияет только на обслуживание клиентов поколения 2025, и трафик 2026 его никогда не читает; если вы уже задали его в v1, ничего не меняется.)
Клиентская сторона этого — на странице Версии протокола, чек-лист оператора (список разрешённых Host, ключ request_state, уведомления между репликами) — Развёртывание и масштабирование, а история об обоих поколениях сразу — Обслуживание клиентов старого поколения.
Сервер не может вызывать клиент: многораундовые запросы
На 2026-07-28 исчезли все запросы, инициируемые сервером: push-элицитация, сэмплирование, roots/list. На подключении 2026 для них нет обратного канала (back-channel), поэтому ctx.elicit() и ctx.session.create_message() там падают с NoBackChannelError (для клиентов старого поколения они по-прежнему работают).
Замена разворачивает вызов. Инструмент, которому что-то нужно от пользователя, возвращает вопрос (InputRequiredResult), клиент отвечает на него теми же колбэками, что были всегда, и вызов повторяется с приложенными ответами. Client ведёт этот цикл за вас. На сервере вы редко собираете результат сами, потому что это делает зависимость: аннотируйте параметр Resolve(ask_quantity), где ask_quantity — обычная функция, которую вы пишете, и SDK спросит тем механизмом, который поддерживает подключение: живым запросом элицитации на сессии старого поколения или многораундовым запросом на 2026. Одно тело инструмента, оба поколения:
from typing import Annotated
from pydantic import BaseModel
from mcp import Client
from mcp.client import ClientRequestContext
from mcp.server import MCPServer
from mcp.server.mcpserver import AcceptedElicitation, Elicit, ElicitationResult, Resolve
from mcp.types import ElicitRequestParams, ElicitResult
mcp = MCPServer("Bookshop")
class Quantity(BaseModel):
copies: int
async def ask_quantity() -> Elicit[Quantity]:
"""Resolver: ask the user how many copies to put aside."""
return Elicit("How many copies?", Quantity)
@mcp.tool()
async def reserve(title: str, quantity: Annotated[ElicitationResult[Quantity], Resolve(ask_quantity)]) -> str:
"""Reserve copies of a book, asking the user how many."""
if isinstance(quantity, AcceptedElicitation):
return f"Reserved {quantity.data.copies} of {title!r}."
return "Nothing reserved."
async def answer(context: ClientRequestContext, params: ElicitRequestParams) -> ElicitResult:
return ElicitResult(action="accept", content={"copies": 2})
async def main() -> None:
async with (
Client(mcp, mode="legacy", elicitation_callback=answer) as legacy,
Client(mcp, elicitation_callback=answer) as modern,
):
for client in (legacy, modern):
result = await client.call_tool("reserve", {"title": "Dune"})
print(client.protocol_version, result.structured_content)
В этом файле вся идея собрана в одном месте: один сервер, один инструмент на Resolve, а также клиент старого поколения и современный клиент, оба получающие свой ответ, — всё в памяти. Многораундовые запросы объясняет механизм (включая request_state, который SDK запечатывает и проверяет за вас); Элицитация описывает, как спрашивать.
Это единственное место, где перенесённый сервер v1 меняет поведение
Первыми на это натыкаются ваши собственные тесты: Client(mcp) по умолчанию согласует 2026-07-28 с вашим
сервером v2, так что инструмент, вызывающий ctx.elicit(), падает в тесте, который проходил на v1. Перенесите
вопрос в параметр Resolve(...) (переносимо между поколениями) или закрепите тестовый клиент на
mode="legacy", если push-поведение вам действительно нужно.
Корневые каталоги, сэмплирование и протокольное логирование объявлены устаревшими; ping удалён
SEP-2577 объявляет устаревшими три возможности целиком, на всех версиях протокола: корневые каталоги, сэмплирование и логирование на уровне MCP (ctx.info() и ему подобные). Это отдельная ось, не связанная с отсутствующим обратным каналом выше; статус устаревшего — рекомендательный, всё продолжает работать с сессиями поколения 2025, и в передаваемых данных ничего не меняется. Заметите вы MCPDeprecationWarning — это UserWarning, поэтому он выводится по умолчанию; ожидайте, что первый же ctx.info(...) после обновления об этом сообщит.
С ping строже: он удалён из протокола, а не объявлен устаревшим. Так же на 2026-07-28 удалены два отдельных метода устаревших возможностей — logging/setLevel и клиентское notifications/roots/list_changed, — а уведомления о ходе выполнения теперь идут только от сервера к клиенту.
Полная таблица, замена для каждого пункта и однострочный фильтр на случай, если нужен тихий лог, пока вы обслуживаете клиенты старого поколения, — на странице Устаревшие возможности.
Уведомления об изменениях становятся одним потоком
На 2026-07-28 отдельный поток HTTP GET и resources/subscribe заменены на subscriptions/listen: клиент открывает один долгоживущий поток и называет виды уведомлений, которые хочет получать. MCPServer обслуживает его по умолчанию; публикуете вы через await ctx.notify_resource_updated(uri) (а также notify_tools_changed() и так далее), middleware может отклонить запрос на прослушивание для конкретного вызывающего, а развёртывания с несколькими репликами подключают общую SubscriptionBus. На клиенте поток открывает async with client.listen(...): фильтр передаётся именованными аргументами, обратно приходят типизированные события изменений, а sub.honored — подмножество, которое сервер согласился доставлять.
Публикация и обслуживание — на странице Подписки, наблюдающая сторона — на её клиентской паре, а шина — на странице Развёртывание и масштабирование.
Остальное, вкратце
- Идентификация — необязательные метаданные каждого сообщения. Ключ
clientInfoв_metaна стороне запроса необязателен (обязательная пара —protocolVersion+clientCapabilities), аserverInfoушёл из тела результатаserver/discover: вместо этого серверы проставляют его в_metaкаждого результата поколения 2026 (spec #3002). SDK проставляет всегда;client.server_infoравенNone, когда сервер себя не идентифицирует (например, middleware убрал ключ). Низкоуровневый Server показывает эту отметку в передаваемых данных. - Запросы маршрутизируются без разбора тел. Современные HTTP-запросы несут
Mcp-Method(а для трёх инструментоподобных вызовов — ещё иMcp-Name); свойство входной схемы инструмента с аннотациейx-mcp-headerдублируется в заголовокMcp-Param-*и перекрёстно проверяется сервером (SEP-2243). Шлюзы и ограничители частоты могут маршрутизировать по одним заголовкам; правила — в Руководстве по миграции. - Результаты несут подсказки кэширования. Результаты списков и чтения объявляют
ttlMsиcacheScope(SEP-2549); вы задаёте их по методам черезcache_hints=, аClientучитывает их встроенным кэшем ответов. Сервер, не отправляющий подсказок (любой сервер до 2026), видит идентичный, некэшированный трафик. Подсказки кэширования. - Расширения поддерживаются полноценно. Серверы и клиенты объявляют необязательные наборы возможностей под идентификаторами в обратной DNS-нотации (SEP-2133); встроенное расширение
Apps(MCP Apps) служит эталоном. Расширения и MCP Apps. - Коды ошибок стандартизированы. Отсутствующий ресурс — это
-32602с URI вerror.data, а новые коды, зарезервированные спецификацией, появляются как-32020(несовпадение заголовка),-32021(отсутствует обязательная возможность) и-32022(неподдерживаемая версия протокола). Устранение неполадок построено по точным сообщениям. - Авторизацию стало сложнее использовать неправильно. Клиент проверяет
iss, возвращаемый вместе с кодом авторизации (RFC 9207; вашcallback_handlerтеперь возвращаетAuthorizationCodeResult), отправляетapplication_typeпри регистрации и никогда не воспроизводит учётные данные на другом сервере авторизации. Новое в корпоративном углу: поток подтверждения идентичности из SEP-990. Все изменения OAuth перечислены в Руководстве по миграции; страницы — OAuth для клиентов и Подтверждение идентичности. - Каждый сервер трассируется. OpenTelemetry включён по умолчанию как middleware: каждый запрос получает серверный спан, и это ничего не стоит, пока процесс не настроит экспортёр. Когда на SDK работают обе стороны, клиент также распространяет контекст трассировки W3C в
_meta, так что трассы соединяются. OpenTelemetry.
Переходите с v1?
- Руководство по миграции — полный и точный список того, что менять; эта страница объясняла зачем.
- v1.x никуда не денется. Она переходит на поддержку, продолжает получать критические исправления и патчи безопасности, и ничто в выпуске спецификации 2026-07-28 её не ломает; её документация живёт по адресу /v1/. Если вы публикуете библиотеку, зависящую от
mcp, и не готовы мигрировать, оставьте верхнюю границу (например,mcp>=1.28,<2), чтобы незакреплённое разрешение зависимостей оставалось на 1.x. - Что-то сырое, непонятное или сломанное? Оставьте отзыв о v2 — читают всё.