1. Зачем вообще говорить об интеграции и миграциях
До сих пор мы в основном проектировали API и инструменты так, как нам удобно. В реальной жизни почти всегда наоборот: у вас уже есть:
- монолит или пачка микросервисов;
- REST/GraphQL API;
- бизнес-логика, которая крутится в проде годами.
И вдруг появляется задача: «Подключите наш продукт к ChatGPT через Apps SDK и MCP».
Переписать всё под «идеальный MCP-сервер» — не вариант. Нужно аккуратно «надеть» поверх существующего мира тонкий слой, который переведёт язык вашего бэкенда в язык ChatGPT: инструменты, ресурсы и схемы.
Вторая проблема: продукт живой. Схемы и API меняются. В обычном фронтенде вы хотя бы сразу получаете TypeScript‑ошибку, когда поменяли поле. В мире LLM‑Apps всё коварнее: модель продолжит уверенно слать старый формат, tool будет падать, и вместо красивого падения сборки вы получите:
- runtime‑ошибки на 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 инструмента;
- модель продолжает слать старый формат;
- получаете весёлый runtime‑зоопарк.
Нормальный путь: сделать одну точку истины для структуры данных и использовать её везде. В 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 vs 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’ом, и как запустить linking‑флоу.
Стандартный паттерн для защищённых MCP‑ресурсов:
- ваш MCP‑сервер экспонирует специальный endpoint /.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 vs 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 change.
Переименование поля или смена его смысла без изменения имени инструмента — путь к скрытому регрессу. Модель продолжит слать старый формат, инцидент проявится только у части пользователей и далеко не сразу. При серьёзных изменениях заводите *_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 и ключевые параметры — тогда любые регрессы легче привязать к конкретному изменению.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ