Що нового у 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 її віддає; вона теж змінилася (працює на новому рушії диспетчера, і деякі її власні сигнатури змінилися), тож прочитайте Посібник з міграції, перш ніж спускатися нижче.
Клієнт знайомить із ним, Транспорти клієнта описує чотири форми під'єднання, Колбеки клієнта — самі колбеки, а Тестування показує шаблон роботи в пам'яті, що замінює допоміжну функцію 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за сховищем довіри операційної системи, а не за вбудованим списком 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.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) за замовчуванням узгоджує з вашим сервером 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; ми читаємо все.