Что нового в 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 объект один:
import anyio
from mcp import Client
async def main() -> None:
async with Client("http://localhost:8000/mcp") as client:
print(client.server_info)
print(client.server_capabilities)
print(client.protocol_version)
print(client.instructions)
if __name__ == "__main__":
anyio.run(main)
Client принимает URL (Streamable HTTP), StdioServerParameters (подпроцесс stdio), любой другой контекстный менеджер транспорта, например sse_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, но до модели доходит только сообщениеToolError: любое другое исключение теперь читается какError executing tool <name>, а трассировка попадает в лог сервера. Разграничение — на странице Обработка ошибок.- Результаты проверяются перед отправкой. Собранный вручную
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.server import MCPServer
from mcp.server.mcpserver import AcceptedElicitation, Elicit, ElicitationResult, Resolve
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."
import anyio
from mcp import Client
from mcp.client import ClientRequestContext
from mcp.types import ElicitRequestParams, ElicitResult
async def answer(context: ClientRequestContext, params: ElicitRequestParams) -> ElicitResult:
return ElicitResult(action="accept", content={"copies": 2})
async def main() -> None:
async with (
Client("http://localhost:8000/mcp", mode="legacy", elicitation_callback=answer) as legacy,
Client("http://localhost:8000/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)
if __name__ == "__main__":
anyio.run(main)
В этих двух файлах вся идея целиком: один сервер, один инструмент на 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 — читают всё.