Що нового у 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 її віддає; вона теж змінилася (працює на новому рушії диспетчера, і деякі її власні сигнатури змінилися), тож прочитайте Посібник з міграції, перш ніж спускатися нижче.
Клієнт знайомить із ним, Транспорти клієнта описує три форми під'єднання, Колбеки клієнта — самі колбеки, а Тестування показує шаблон роботи в пам'яті, що замінює допоміжну функцію 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за сховищем довіри операційної системи, а не за вбудованим списком CA від certifi. Більшість середовищ цього не помітять; мінімальний контейнер без системного сховища CA або приватний CA, про який знав лише набір 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, і це головна новина для експлуатації: ніщо не прив'язує сучасний запит до робочого процесу, тож відповісти на нього може будь-яка репліка за звичайним балансувальником із циклічним розподілом. Два чесні застереження. Ваші клієнти покоління 2025 (а сьогодні це більшість клієнтів) і далі відкривають сесії й потребують тієї самої прив'язки, що й на v1; для них нічого не змінюється. А єдине, що багатораундова повторна спроба мусить перенести між робочими процесами, — це її запечатаний request_state, типовий ключ якого карбується окремо в кожному процесі, тож горизонтально масштабоване розгортання передає RequestStateSecurity(keys=[...]). (stateless_http=True тут ні до чого: він впливає лише на обслуговування клієнтів покоління 2025, а трафік 2026 його ніколи не читає; якщо ви вже задали його у v1, нічого не змінюється.)
Версії протоколу — клієнтський бік цього, Розгортання й масштабування — контрольний список оператора (список дозволених Host, ключ request_state, сповіщення між репліками), а Обслуговування клієнтів старого покоління — розповідь про обидва покоління водночас.
Сервер не може викликати клієнта: багатораундові запити
На 2026-07-28 зникли всі запити, ініційовані сервером: push-еліцитація, семплювання, roots/list. На з'єднанні 2026 для них немає каналу, тож 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) за замовчуванням узгоджує з вашим сервером v2
версію 2026-07-28, тож інструмент, що викликає ctx.elicit(), падає в тесті, який на v1 проходив. Перенесіть
запитання в параметр Resolve(...) (працює в обох поколіннях) або зафіксуйте тестовий клієнт на
mode="legacy", якщо вам справді потрібна push-поведінка.
Кореневі каталоги, семплювання та протокольне логування застарілі; ping вилучено
SEP-2577 оголошує застарілими три цілі можливості на всіх версіях протоколу: кореневі каталоги, семплювання та логування рівня MCP (ctx.info() і подібні). Це окрема вісь щодо відсутнього зворотного каналу (back-channel) вище; статус застарілого — рекомендаційний, усе й далі працює із сесіями покоління 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 — підмножина, яку сервер погодився доставляти.
Підписки описує публікацію й обслуговування, сторінка-близнюк у розділі про клієнт — бік спостереження, а Розгортання й масштабування — шину.
Решта, коротко
- Ідентичність — необов'язкові метадані кожного повідомлення. Ключ
_metaclientInfoна боці запиту необов'язковий (обов'язкова пара —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; ми читаємо все.