Коротка відповідь. Специфікація MCP від 28 липня 2026 перетворила протокол із двонаправленого stateful на запит-відповідь stateless: initialize-handshake і Mcp-Session-Id видалені, усе необхідне їде в кожному запиті через _meta і HTTP-заголовки. Для тебе це означає: MCP server тепер пишеться і деплоїться як звичайний HTTP-сервіс – за load balancer-ом, без sticky sessions, із кешованими списками інструментів. Нижче – що саме змінилося, робочий сервер на SDK v2 і міграція старого.
Якщо ти взагалі вперше чуєш про MCP – спочатку прочитай що таке MCP простими словами: тут ми не пояснюємо основи, а одразу йдемо в архітектуру.
Що прибрали і чому це важливо
До цієї спеки MCP жив як розмова: клієнт відкривав з'єднання, відбувався initialize/initialized handshake, сервер видавав Mcp-Session-Id, і далі обидві сторони пам'ятали стан розмови. Красиво для desktop-сценарію «Claude ↔ локальний сервер», боляче для production HTTP: кожен запит мусив потрапити на той самий інстанс або сервер мусив тягати стан через Redis.
Спека 2026-07-28 вирізала це повністю:
- Жодного handshake. Перший запит – одразу робочий.
- Жодного
Mcp-Session-Id. Запити можуть лягати на будь-який інстанс за round-robin. - Самоописні запити. Версія протоколу, ідентичність клієнта і його capabilities їдуть у
_metaкожного запиту.
Ось як виглядає виклик інструмента тепер (приклад зі спеки):
POST /mcp HTTP/1.1
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: search
{"jsonrpc":"2.0","id":1,"method":"tools/call",
"params":{"name":"search","arguments":{"q":"otters"},
"_meta":{"io.modelcontextprotocol/clientInfo":{"name":"my-app","version":"1.0"}}}}Зверни увагу на заголовки Mcp-Method і Mcp-Name – тепер вони обов'язкові для Streamable HTTP. Це подарунок інфраструктурі: gateway, WAF чи rate limiter можуть роутити й тарифікувати виклики, не парсячи JSON-тіло.
server/discover: capabilities на вимогу
Handshake зник, але іноді клієнту треба знати можливості сервера наперед. Для цього є новий RPC server/discover – необов'язковий: якщо клієнт і так знає, що робити, він просто робить. Це інверсія старої моделі: замість «спершу познайомимось» – «запитуй, коли справді треба».
MRTR: що замінило серверні запити до клієнта
У stateful-світі сервер міг сам ініціювати запит до клієнта – наприклад, попросити підтвердження в користувача посеред виконання інструмента. Для цього потрібен був відкритий stream. У stateless-моделі це неможливо, тому з'явилися Multi Round-Trip Requests:
- Інструмент розуміє, що йому бракує відповіді користувача, і повертає
resultType: "input_required"зі списком запитань. - Клієнт показує запитання користувачу і повторює той самий виклик, додавши відповіді в
inputResponses.
Типовий кейс: інструмент видалення даних перед виконанням повертає «підтверди, що видаляємо проєкт X вартістю Y». Жодних відкритих з'єднань – просто ще один HTTP round-trip.
Кешовані списки інструментів
tools/list, prompts/list, resources/list і resources/read тепер повертають ttlMs і cacheScope. Клієнт більше не мусить смикати список інструментів на кожну розмову – сервер сам каже, скільки відповідь жива і в якому скоупі її можна шерити. Для серверів із сотнями інструментів це помітно знімає навантаження.
Auth: менше магії, більше RFC
Коротко, бо тема на окрему статтю: authorization server тепер зобов'язаний повертати iss за RFC 9207 і клієнт мусить його валідувати; креденшели клієнта прив'язані до issuer-а (жодного перевикористання між серверами); Dynamic Client Registration формально деприкований на користь Client ID Metadata Documents (CIMD). Якщо твій сервер за OAuth – міграційний гайд читати обов'язково.
Мінімальний stateless server на SDK v2
TypeScript SDK v2 вийшов разом зі спекою і розбитий на окремі пакети @modelcontextprotocol/server і @modelcontextprotocol/client. Реєстрація інструмента – через zod-схему, яку SDK валідує до виклику хендлера:
import { McpServer } from '@modelcontextprotocol/server';
import { z } from 'zod';
function buildServer() {
const server = new McpServer({ name: 'blog-tools', version: '1.0.0' });
server.registerTool(
'search-articles',
{
description: 'Пошук статей блогу за запитом',
inputSchema: z.object({
query: z.string().min(2),
limit: z.number().int().min(1).max(20).default(5),
}),
},
async ({ query, limit }) => {
const results = await searchIndex(query, limit);
return {
content: [{ type: 'text', text: JSON.stringify(results) }],
};
}
);
return server;
}Ключовий момент деплою: сервер створюється фабрикою на кожен запит – саме так працює stateless-ідіома. Для HTTP є адаптери під Express, Hono і Fastify (@modelcontextprotocol/express тощо), які загортають buildServer у handler; за замовчуванням вони обслуговують і клієнтів попередньої ери протоколу. Жодного стану в замиканнях – усе, що треба між запитами, живе в базі або кеші, як у будь-якому нормальному API.
Чесна примітка: приклад зібраний за офіційною документацією SDK v2; точний API ttlMs/cacheScope на рівні SDK я не перевіряв на виконання – звіряйся з доками в день імплементації.
Міграція старого сервера: чекліст
- Онови SDK до v2 і розведи імпорти на
@modelcontextprotocol/server/client. v1.x отримує лише фікси ще мінімум пів року. - Знайди все, що залежить від session-id. Це головне джерело breaking changes: логіка «запам'ятати клієнта між викликами» переїжджає в явний стан (БД/кеш) або зникає.
- Прибери server-initiated патерни – перепиши на MRTR (
input_required→ retry). - Віддавай
ttlMsдля списків – безкоштовне зниження трафіку. - Перевір деприкації: Roots, Sampling і Logging ще працюють мінімум 12 місяців, але нові фічі на них не будуй; старий HTTP+SSE-транспорт теж у річному вікні деприкації.
- Auth: валідація
iss, issuer-bound credentials, план переходу з DCR на CIMD.
Типові помилки
- Тягнути сесійний стан у stateless-сервер через глобальні змінні модуля. На одному інстансі «працює», за load balancer-ом – ні. Стан – тільки явний.
- Ігнорувати
Mcp-Method/Mcp-Nameзаголовки у власному транспорті – частина інфраструктури (gateway, логування) почне поводитись непередбачувано. - Вважати
server/discoverобов'язковим і робити його перед кожним викликом – це знову handshake, тільки збоку. - Кешувати tools/list назавжди, ігноруючи
ttlMs– після деплою нового інструмента клієнти його не побачать.
Якщо дивитись ширше, ця еволюція – класика розподілених систем: ми вже проходили той самий шлях від stateful до stateless у суперечці моноліта з мікросервісами. MCP просто наздогнав дорослі патерни. А як перевіряти, що твій AI-агент із цими інструментами реально працює, – у статті про eval-и агентів.
Практичне завдання
Візьми будь-який свій (або знайомий open-source) MCP server на v1 і проведи аудит за чеклістом міграції вище: випиши кожне місце, де код покладається на сесію чи server-initiated запит, і для кожного запиши stateless-альтернативу. Навіть без самої міграції цей список покаже, скільки прихованого стану жило у твоєму «простому» сервері.