1. Навіщо взагалі говорити про інтеграцію та міграції
Досі ми здебільшого проєктували API та інструменти так, як нам зручно. У реальному житті майже завжди навпаки: у вас уже є:
- моноліт або набір мікросервісів;
- REST/GraphQL API;
- бізнес‑логіка, що роками працює в продакшні.
І тут раптом з’являється завдання: «Підʼєднайте наш продукт до ChatGPT через Apps SDK і MCP».
Переписати все під «ідеальний MCP‑сервер» — не варіант. Потрібно акуратно «вдягнути» поверх наявної системи тонкий шар, який перекладе мову вашого бекенду на мову ChatGPT: інструменти, ресурси та схеми.
Друга проблема — продукт живий. Схеми й API змінюються. У звичайному фронтенді ви принаймні одразу отримаєте помилку TypeScript, щойно зміните поле. У світі LLM‑Apps усе підступніше: модель і далі впевнено надсилатиме старий формат, інструмент падатиме. А замість красивого «падіння» збірки ви отримаєте:
- помилки під час виконання на MCP‑сервері;
- галюцинації на кшталт «ну я приблизно здогадався, що ви хотіли від цього поля»;
- неприємні інциденти, пов’язані з якістю.
Тому в цій лекції ми дивимося на шар MCP + Apps як на:
- адаптер до наявного бекенду;
- контракт, який потрібно підтримувати роками;
- об’єкт міграцій: версій, анотацій, scopes і SDK.
2. Архітектура інтеграції: MCP як адаптер над наявним бекендом
Базова картина
Нагадаємо стек, але тепер — уже крізь призму продакшну:
flowchart LR U[Користувач у ChatGPT] --> G[Модель ChatGPT] G -->|викликає App| W["Віджет (Apps SDK, Next.js)"] G -->|tools.call| MCP[MCP‑сервер / Gateway] MCP --> S1["Gift Service (ваш наявний сервіс)"] MCP --> S2["Commerce Service (замовлення, ACP)"]
ChatGPT спілкується з вашим світом не напряму, а через MCP‑протокол: список tools/resources, виклики tools/call і стримінг подій.
MCP‑сервер у цій схемі — це рівно той самий адаптер. Він знає і про ChatGPT (JSON‑RPC, інструменти), і про ваші сервіси (REST/DB/черги) та перекладає одне в інше.
MCP як Gateway/Adapter
Класична постановка: у вас уже є Gift Service з REST‑ендпойнтами:
// Приклад наявного REST API
GET /api/gifts/recommendations?budget=100&occasion=birthday
POST /api/orders
Замість того щоб писати нову бізнес‑логіку, MCP‑шар просто загортає це в Tool:
// mcp/tools/recommendGifts.ts
import { z } from "zod";
import { server } from "./mcpServer"; // умовний інстанс SDK
const recommendGiftsInput = z.object({
occasion: z.string(),
budgetUsd: z.number().int().positive(),
});
server.registerTool({
name: "recommend_gifts",
description: "Підбирає ідеї подарунків у межах бюджету",
inputSchema: recommendGiftsInput,
async execute(args) {
const { occasion, budgetUsd } = recommendGiftsInput.parse(args);
const res = await fetch(
`https://api.myapp.com/gifts/recommendations?budget=${budgetUsd}&occasion=${occasion}`,
);
return res.json(); // важливо: повертаємо JSON, зручний і для моделі, і для віджета
},
});
Уся логіка добору подарунків лишається всередині вашого наявного сервісу. MCP‑шар — це «тонкий перекладач» з мови ChatGPT на мову ваших API.
Іноді MCP‑шар ще й маршрутизує запити до кількох бекенд‑сервісів. У такому разі він перетворюється на повноцінний MCP Gateway. Його роль ви розбиратимете глибше в модулі про продакшн і мережу.
Monolith-integrated MCP vs Sidecar MCP
Є два базові варіанти, куди цей MCP‑шар можна «під’єднати».
Якщо описати це словами, матимемо таку картину:
| Варіант | Опис | Де живе MCP‑код |
|---|---|---|
| Monolith-integrated | Усе в одному Next.js/Node‑сервісі | В API‑роутах Next.js або Express |
| Sidecar MCP | Окремий контейнер/сервіс, що спілкується з API | Окремий Node/Go‑застосунок |
У невеликих проєктах часто достатньо першого варіанта: Next.js‑застосунок, розгортання на Vercel, там само маршрут /mcp або /api/mcp, і MCP‑сервер живе поруч із рештою API.
Приклад (сильно спрощений):
// app/api/mcp/route.ts (Next.js 16)
import { NextRequest } from "next/server";
import { mcpHandler } from "@/mcp/server";
export async function POST(req: NextRequest) {
const body = await req.json();
const response = await mcpHandler.handle(body); // запит JSON-RPC
return new Response(JSON.stringify(response), {
headers: { "content-type": "application/json" },
});
}
У зрілішій архітектурі, де є кілька доменних сервісів (Gift, Commerce, Analytics), MCP‑шар зручніше винести в окремий Gateway‑сервіс. Він прийматиме MCP‑трафік від ChatGPT і вже сам маршрутизуватиме виклики до різних бекендів за іменем інструмента.
Важливо пам’ятати: з точки зору ChatGPT і Apps SDK це все одно один MCP‑сервер. Де саме він працює — усередині моноліту чи як окремий мікросервіс — уже ваша архітектурна турбота.
З архітектурою MCP‑шару розібралися. Він може жити всередині моноліту або бути окремим Gateway. Далі постає питання: що саме цей шар приймає і що віддає? І тут на сцену виходять схеми та контракти.
3. Single Source of Truth: схеми, типи і контрактні тести
Якщо у вас є внутрішні DTO, зовнішні REST‑контракти і ще MCP‑схеми для інструментів, спокуса «намалювати схеми на око» величезна. Результат передбачуваний:
- змінюєте поле в бекенді та забуваєте оновити schema інструмента;
- модель продовжує надсилати старий формат;
- отримуєте веселий «зоопарк» помилок під час виконання.
Нормальний шлях — зробити одну точку істини для структури даних і використовувати її всюди. У світі TypeScript це дуже зручно робити через Zod або подібні бібліотеки, які MCP SDK уміє конвертувати в JSON Schema.
Загальна Zod‑схема для GiftGenius
Припустімо, ваш Gift‑сервіс у нашому навчальному GiftGenius уже використовує Zod для валідації вхідних даних:
// domain/gifts.ts
import { z } from "zod";
export const giftRecommendationInputSchema = z.object({
occasion: z.string().describe("Привід: birthday, wedding тощо."),
budgetUsd: z.number().int().positive(),
recipientProfile: z.string().describe("Короткий опис людини"),
});
export type GiftRecommendationInput = z.infer<
typeof giftRecommendationInputSchema
>;
Ця ж схема використовується:
- у REST‑ендпойнті (для перевірки тіла запиту);
- в MCP‑інструменті (як inputSchema);
- у тестах (як основа для фікстур).
Підключаємо схему до MCP‑інструмента
// mcp/tools/recommendGifts.ts
import { giftRecommendationInputSchema } from "@/domain/gifts";
import { server } from "../mcpServer";
server.registerTool({
name: "recommend_gifts",
description: "Підбір подарунків за профілем і бюджетом",
inputSchema: giftRecommendationInputSchema,
async execute(args) {
const input = giftRecommendationInputSchema.parse(args);
const res = await fetch("https://api.myapp.com/gifts/recommendations", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(input),
});
return res.json();
},
});
SDK сам сконвертує Zod‑схему в JSON Schema, яку ChatGPT побачить у tools/list. Це розв’язує одразу дві проблеми:
- типи аргументів інструмента і коду жорстко пов’язані;
- у разі зміни схеми компілятор TypeScript змусить вас оновити й обробник.
Контрактні тести для MCP ↔ backend
Контрактні тести тут — не страшне слово, а кілька цілком приземлених перевірок.
Найпростіший unit/contract‑тест може виглядати так:
// tests/mcp/recommendGifts.contract.test.ts
import { giftRecommendationInputSchema } from "@/domain/gifts";
test("приклад запиту відповідає схемі інструмента", () => {
const sample = {
occasion: "birthday",
budgetUsd: 150,
recipientProfile: "колега, любить гаджети",
};
expect(() => giftRecommendationInputSchema.parse(sample)).not.toThrow();
});
Такий тест не гарантує, що «весь світ прекрасний». Але принаймні він ловить розсинхрон між очікуваннями бекенду й MCP‑шару, якщо ви змінили схему та забули оновити фікстури.
Далі цей самий підхід легко розширюється на:
- замокані відповіді зовнішніх API (Stripe, CMS);
- прогін MCP‑клієнта проти реального MCP‑сервера в тестовому середовищі.
4. Стратегії версіонування tools і resources
Схеми рано чи пізно змінюються. Головне — не робити це в дусі «я просто перейменую поле, що може піти не так». У LLM‑світі так легко зламати не лише збірку, а й поведінку моделі: старі промпти, збережені діалоги та golden‑кейси продовжать очікувати старого контракту.
Адитивні vs breaking‑зміни
Умовно зміни можна поділити на дві категорії.
Адитивні зміни — ви щось додаєте, але нікого не ламаєте:
- нове необов’язкове поле у відповіді;
- новий необов’язковий аргумент зі значенням за замовчуванням;
- додаткові значення enum, до яких UI і модель можуть ставитися нейтрально.
Наприклад, ви додали у відповідь інструмента поле deliveryEstimateDays, але старий віджет його просто ігнорує. Це безпечно: схема може розширитися, проте ніхто не зобов’язаний це поле використовувати.
Breaking‑зміни — ви ламаєте наявні очікування:
- робите поле обов’язковим, хоча його раніше не було;
- змінюєте тип (рядок → об’єкт);
- змінюєте сенс аргументів (бюджет у USD → бюджет у локальній валюті, але при цьому не змінюєте назви полів).
У таких випадках єдиний безпечний шлях — заводити нову версію інструмента.
Патерн Tool_v2
Класичний патерн: у вас був recommend_gifts, і ви хочете суттєво змінити схему. Старий інструмент ви не чіпаєте. Натомість створюєте новий — recommend_gifts_v2.
// v1
const recommendGiftsInput_v1 = z.object({
occasion: z.string(),
budgetUsd: z.number().int().positive(),
});
// v2: підтримка валют і фільтрів доставки
const recommendGiftsInput_v2 = z.object({
occasion: z.string(),
maxPrice: z.number().int().positive(),
currency: z.enum(["USD", "EUR", "GBP"]),
deliverByDate: z.string().optional(); // ISO-рядок
});
server.registerTool({
name: "recommend_gifts",
description: "DEPRECATED: використовуйте recommend_gifts_v2",
inputSchema: recommendGiftsInput_v1,
async execute(args) { /* стара логіка */ },
});
server.registerTool({
name: "recommend_gifts_v2",
description:
"Підбір подарунків за бюджетом, валютою і дедлайном доставки",
inputSchema: recommendGiftsInput_v2,
async execute(args) { /* нова логіка */ },
});
Модель і старі промпти/агенти продовжать використовувати recommend_gifts, доки ви їх не оновите. Нові сценарії ви вже пишете під recommend_gifts_v2.
Після періоду міграції:
- golden‑кейси й агенти перемкнені на v2;
- метрики показують, що v1 майже не викликається;
можна почати акуратно згортати v1 (наприклад, спершу сховати його зі списку інструментів у dev/staging, а потім — у проді).
Версіонування ресурсів
Tools — не єдине, що потребує версій. Якщо у вас є ресурси (resources) — наприклад, статичний каталог подарунків, — їх теж краще версіонувати.
Популярні варіанти:
- версію «зашити» в ім’я ресурсу: gift_catalog.v1.json, gift_catalog.v2.json;
- або передавати версію в URI/параметрі: /api/catalog?version=1.
Сенс той самий: не підміняти дані «під ногами» в уже запущених сценаріях, а давати їм явно зафіксовану версію каталогу.
Міграції без даунтайму
Типовий цикл міграції інструмента:
- Додаєте нову версію інструмента (_v2) паралельно до старої.
- Оновлюєте App/агентів/system‑prompt так, щоб вони використовували нову версію.
- Проганяєте golden‑кейси і LLM‑eval для обох варіантів та переконуєтесь, що для критичних сценаріїв якість не впала.
- Спостерігаєте метрики використання v1 і v2 (а також помилки).
- Після того як трафік на v1 близький до нуля, починаєте його деактивувати.
Такий підхід добре працює і для міграцій схем, і для оновлень SDK/протоколу, і для змін в Auth. Ми розібрали, як еволюціонують самі інструменти й ресурси — через v1/v2 та акуратні адитивні зміни. Друга велика частина контракту — автентифікація й авторизація: OAuth, scopes і .well-known. Вони теж живуть роками й потребують обережних міграцій.
5. Еволюція автентифікації: .well-known, scopes і наявний OAuth
Якщо ваш продукт уже живе у світі OAuth 2.1/OpenID Connect, інтеграція з ChatGPT через MCP — це не «ще один логін». Це новий клієнт, який має спілкуватися з вашим Authorization Server за спільними правилами.
MCP і .well-known/oauth-protected-resource
Про повний OAuth 2.1/OpenID Connect і налаштування Auth Server ми докладно говоримо в окремому модулі курсу (див. модуль про автентифікацію). Тут нас цікавить лише прикладний аспект: як MCP‑ресурс повідомляє ChatGPT, що він захищений OAuthʼом, і як запустити процес прив’язування облікового запису.
Стандартний патерн для захищених MCP‑ресурсів:
- ваш MCP‑сервер експонує спеціальний ендпойнт /.well-known/oauth-protected-resource;
- у відповіді він повідомляє, який це ресурс і якими AS (Authorization Server) він захищений;
- у разі 401 на MCP‑виклику сервер повертає заголовок WWW-Authenticate з посиланням на цей .well-known, і ChatGPT сам запускає OAuth‑процес («Link account»).
Мінімальний приклад на Express:
// mcp-auth/.well-known.ts
import express from "express";
const app = express();
app.get("/.well-known/oauth-protected-resource", (_req, res) => {
res.json({
resource: "https://mcp.myapp.com",
authorization_servers: [
"https://auth.myapp.com/.well-known/openid-configuration",
],
});
});
app.listen(3000);
І обробник 401 з підказкою для клієнта:
res
.status(401)
.set(
"WWW-Authenticate",
'Bearer resource_metadata="https://mcp.myapp.com/.well-known/oauth-protected-resource"',
)
.end();
ChatGPT, побачивши цей заголовок, розуміє, до якого AS іти і як запускати OAuth‑процес для вашого MCP‑ресурсу.
Scopes і міграції авторизації
Scopes — ще одне джерело міграцій. Ми вже докладно розбирали це в модулі про Auth, але в контексті інтеграції та міграцій важливі кілька моментів.
Уявіть, що GiftGenius спочатку вмів лише читати каталог (gifts.read), а згодом ви додали gifts.write для створення замовлень. Вам потрібно:
- додати новий scope у конфігурацію клієнта (ChatGPT App);
- оновити MCP‑сервер, щоб він вимагав цей scope лише для інструментів, які справді щось змінюють;
- описати зміни в .well-known, якщо це потрібно.
З погляду UX користувач під час наступної спроби скористатися новим функціоналом може побачити запит «розширити права» для ChatGPT‑застосунку. Ви не хочете, щоб це траплялося посеред уже поточного діалогу — без попередження. Тому такі зміни потрібно:
- анонсувати (release notes, документація);
- протестувати на staging із тестовим AS;
- поєднати з оновленням описів інструментів (destructiveHint тощо), щоб модель усвідомлено викликала «небезпечні» tools.
6. Метадані й анотації: hint‑шар поверх контракту
Auth‑шар відповідає на запитання, хто і що може робити через ваш App. Але навіть за коректних токенів і scopes важливо й те, як модель викликатиме ваші інструменти та як вона пояснюватиме дії користувачеві. Тут у справу вступає додатковий hint‑шар: метадані й анотації.
Контракт (schema) каже, що інструмент приймає і що повертає. Метадані й анотації допомагають моделі зрозуміти, як і коли його викликати. Це стає особливо важливим, коли ви еволюціонуєте App: додаєте нові destructive‑дії, змінюєте UI, вводите інтеграції із зовнішнім світом.
_meta["openai/widgetDescription"] і widgetCSP
В Apps SDK і MCP‑описах є спецполе _meta, куди OpenAI додає свої розширення протоколу. Наприклад:
- _meta["openai/widgetDescription"] — короткий опис того, що показує ваш віджет; модель може використовувати його, щоб не «переказувати» UI і правильно анонсувати App;
- _meta["openai/widgetCSP"] — декларація CSP‑доменів, які потрібні вашому віджету (для fetch/зображень/скриптів).
Коли ви змінюєте UI (наприклад, додаєте новий крок оформлення замовлення), корисно оновити widgetDescription, щоб модель і далі коректно пояснювала користувачеві, що відбувається.
Анотації інструментів (readOnlyHint, destructiveHint, openWorldHint)
Анотації — прості булеві прапорці, які помітно впливають на UX і безпеку:
- readOnlyHint: true — інструмент нічого не змінює (читання). Модель може викликати його без зайвих підтверджень.
- destructiveHint: true — інструмент може щось видалити/змінити. ChatGPT проситиме явне підтвердження.
- openWorldHint: true — інструмент публікує дані назовні або може повернути «дуже багато всього», що вимагатиме підсумовування.
Приклад дескриптора інструмента з анотаціями:
server.registerTool({
name: "delete_saved_gift",
description: "Видаляє збережений подарунок користувача",
inputSchema: z.object({ giftId: z.string() }),
annotations: {
readOnlyHint: false,
destructiveHint: true,
openWorldHint: false,
},
async execute({ giftId }) {
// ...видаляємо подарунок
},
});
Під час міграції, коли ви додаєте нові «небезпечні» інструменти, анотації — ваші друзі. Вони допомагають ChatGPT не виконувати їх потай і підштовхують до обережнішої поведінки.
Важливо розуміти: анотації — це не захист «по‑справжньому». Вони впливають лише на поведінку клієнта і моделі. Справжню безпеку, як і раніше, забезпечує ваш сервер (Auth, scopes, валідація).
7. Міграції SDK і MCP‑специфікацій
MCP і Apps SDK активно розвиваються: з’являються нові поля в capabilities, нові типи повідомлень, нові _meta/annotations. Документація чесно попереджає: «станом на 2025 рік» — і нам із цим жити.
Тому міграції версій SDK і специфікації — нормальна частина життя App, а не рідкісна подія «колись потім».
Типовий процес оновлення
Здоровий сценарій оновлення приблизно такий:
- Читаєте changelog нової версії Apps SDK/MCP SDK. Позначаєте всі потенційно breaking‑зміни.
- Оновлюєте залежності в dev/staging‑оточенні, не чіпаючи продакшн.
- Проганяєте MCP Inspector / Jam чи інший клієнт:
- перевіряєте handshake;
- tools/list / resources/list;
- кілька тестових tools/call.
- Оновлюєте описи інструментів і _meta згідно з новими можливостями:
- наприклад, додаєте нові annotations або widgetDescription.
- Проганяєте golden‑кейси і LLM‑eval, про які говорили в попередніх лекціях, щоб упевнитися: поведінка App з точки зору якості не зіпсувалася.
- Лише після цього викочуєте оновлення в продакшн, за можливості використовуючи canary/feature‑flag на підмножині трафіку.
Приклад: додаємо openWorldHint у новій версії SDK
Припустімо, нова версія Apps SDK додала підтримку openWorldHint, і ви вирішили позначити ним інструмент search_public_reviews, який звертається до зовнішніх відгуків і може повернути багато шуму.
Кроки виглядають так:
- оновлюєте SDK і типи;
- дописуєте annotations.openWorldHint = true у дескриптор інструмента;
- оновлюєте system‑prompt, щоб агент явно пояснював користувачеві, що зараз буде запит у зовнішній світ;
- проганяєте safety‑golden‑кейси (особливо на питання про приватність/PII), щоб упевнитися, що модель не стала надмірно «балакучою».
Ми обговорили загальний процес оновлення SDK і анотацій. Тепер погляньмо на все це в одному конкретному сценарії — еволюції інструмента recommend_gifts.
8. Міні‑кейс: еволюція recommend_gifts у GiftGenius
Давайте зберемо все разом на конкретному сценарії.
Початкова версія
Базовий інструмент виглядав так:
const recommendGiftsInput_v1 = z.object({
occasion: z.string(),
budgetUsd: z.number().int().positive(),
recipientProfile: z.string(),
});
server.registerTool({
name: "recommend_gifts",
description: "Підбирає ідеї подарунків у USD",
inputSchema: recommendGiftsInput_v1,
async execute(args) {
const input = recommendGiftsInput_v1.parse(args);
return giftService.recommend(input); // внутрішня функція
},
});
Усе чудово, доки у вас лише користувачі зі США й одна валюта.
Нові бізнес‑вимоги: мультивалюта і дедлайн
Продуктова команда приходить із новими вимогами:
- потрібно підтримати EUR/GBP;
- потрібно враховувати дедлайн доставки (не показувати подарунки, які прийдуть за місяць, якщо день народження за три дні);
- бажано додавати у відповідь оцінку часу доставки.
Наївний підхід: просто змінюємо поля.
- budgetUsd перейменовуємо на maxPrice;
- додаємо currency;
- у відповідь додаємо deliveryEstimateDays.
Що піде не так?
Старі промпти (зокрема golden‑кейси й опис у system‑prompt) і збережені діалоги продовжують надсилати budgetUsd. Модель не знає, що цього поля більше немає. MCP‑шар почне падати під час спроби parse. Поведінка ChatGPT App раптово ламається в реальних користувачів.
Правильний шлях:
- Додаємо нову схему і новий інструмент _v2.
const recommendGiftsInput_v2 = z.object({
occasion: z.string(),
maxPrice: z.number().int().positive(),
currency: z.enum(["USD", "EUR", "GBP"]),
recipientProfile: z.string(),
deliverByDate: z.string().optional(),
});
server.registerTool({
name: "recommend_gifts_v2",
description:
"Підбір подарунків із урахуванням валюти та бажаної дати доставки",
inputSchema: recommendGiftsInput_v2,
async execute(args) {
const input = recommendGiftsInput_v2.parse(args);
return giftService.recommendV2(input); // нова логіка
},
});
- Залишаємо recommend_gifts як є, додавши в description позначку DEPRECATED.
- Оновлюємо system‑prompt і описи App так, щоб модель віддавала перевагу recommend_gifts_v2 (можна прямо вказати це в інструкціях).
- Оновлюємо віджет GiftGenius, щоб він розумів новий формат відповіді: поле deliveryEstimateDays тощо.
- Проганяємо golden‑кейси для типових сценаріїв (підбір подарунків до певної дати) через LLM‑eval.
Тести й спостережуваність
Кілька тестів, які варто мати:
Контракт‑тест для нового входу:
test("v2 приймає сценарій з EUR і дедлайном", () => {
const sample = {
occasion: "birthday",
maxPrice: 100,
currency: "EUR",
recipientProfile: "колега",
deliverByDate: "2025-12-24",
};
expect(() => recommendGiftsInput_v2.parse(sample)).not.toThrow();
});
Спостереження в проді:
- метрика частки викликів recommend_gifts_v2 порівняно з recommend_gifts;
- error‑rate по v1 (очікуємо, що він не зростає);
- LLM‑eval‑score по golden‑кейсах до/після міграції (за результатами попередніх лекцій ви вже знаєте, як це робити).
Коли v2 «перемагає» і за якістю, і за метриками використання, можна акуратно планувати деактивацію v1.
Якщо спростити до трьох тез: (1) MCP — це тонкий адаптер, а не новий моноліт; (2) схеми, auth і анотації — це довгоживучий контракт між ChatGPT і вашим бекендом; його потрібно версіонувати й тестувати так само ретельно, як звичайні API; (3) будь‑які міграції SDK/специфікації — це нормальний інженерний процес зі staging, golden‑кейсами і спостережуваністю, а не «оновили пакет у п’ятницю ввечері». Якщо ви дивитиметеся на ChatGPT App крізь цю призму, інтеграції з наявним продуктом перестануть здаватися хаосом.
9. Типові помилки під час інтеграції та міграцій MCP/SDK
Помилка № 1: MCP як «новий бекенд», а не тонкий адаптер.
Іноді хочеться затягнути в MCP‑шар усю бізнес‑логіку: звернення до БД, доменні правила, розрахунки. Це перетворює MCP‑сервер на ще один моноліт, який важко синхронізувати з рештою бекенду. Значно здоровіше тримати MCP як Gateway/Adapter над наявними сервісами: уся доменна логіка живе там само, де й до ChatGPT, а MCP лише «перекладає» JSON туди‑назад.
Помилка № 2: Різні схеми для одного й того самого об’єкта.
Поширений антипатерн — мати три визначення «подарунка»: одне в БД, одне в REST‑API, одне в MCP‑інструменті, і всі трохи різні. У результаті ламаються статична типізація, контракти, тести й здоровий глузд. Використання єдиної схеми (Zod/TypeBox тощо) як Single Source of Truth і генерація JSON Schema для MCP значно зменшують цей ризик.
Помилка № 3: Неправильні міграції схем — «тиха» breaking‑зміна.
Перейменування поля або зміна його сенсу без зміни імені інструмента — шлях до прихованої регресії. Модель продовжить надсилати старий формат, інцидент проявиться лише в частини користувачів і далеко не відразу. У разі серйозних змін заводьте *_v2, залишайте стару версію працювати паралельно, використовуйте deprecation‑позначки й моніторинг.
Помилка № 4: Ігнорування Auth‑змін і scopes.
Додали новий інструмент із побічними ефектами, але забули оновити scopes і .well-known? Користувач може або отримати 401 посеред сценарію, або навпаки — ваш MCP почне виконувати destructive‑операції без адекватної авторизації. Плануйте міграції auth‑шару так само обережно, як міграції схем: через staging, тести й плавне розширення прав.
Помилка № 5: Невикористання анотацій (destructiveHint, readOnlyHint, openWorldHint).
Якщо не підказати моделі, які інструменти безпечні, а які потенційно небезпечні, вона може поводитися непередбачувано: просити підтвердження на безневинний get_catalog і без попередження виконувати видалення даних. Правильні анотації роблять поведінку передбачуваною для користувача та зменшують ризик інцидентів якості й безпеки.
Помилка № 6: Оновлення SDK у продакшні без прогону golden‑кейсів.
Нова версія SDK/специфікації може додавати поля, змінювати поведінку handshake або структуру повідомлень. Якщо просто «оновити залежності й розгорнути», ви ризикуєте зловити регрес якості (модель перестала викликати потрібний інструмент, змінилися формулювання помилок тощо). Спочатку — dev/staging, MCP Inspector, далі — golden‑кейси і LLM‑eval, і лише після цього — продакшн.
Помилка № 7: Жорстка прив’язка бізнес‑логіки до однієї версії інструмента.
Коли внутрішня логіка Gift Service напряму залежить від конкретного recommend_gifts, важко мігрувати на recommend_gifts_v2 без болю. Найкраща практика — мати внутрішній сервіс, який еволюціонує за своїми правилами, а інструменти *_v1, *_v2 — лише thin‑adapterʼи, що маплять старі й нові зовнішні контракти на спільні доменні структури.
Помилка № 8: Відсутність спостережуваності за версіями інструментів.
Якщо в логах і метриках ви не розрізняєте, який саме інструмент і яку версію було викликано, налагодження міграцій перетворюється на вгадування. Логуйте ім’я інструмента, версію схеми/SDK і ключові параметри — тоді будь‑які регресії легше прив’язати до конкретної зміни.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ