JavaRush /Курсы /ChatGPT Apps /Интеграция ChatGPT App в существующий продукт и миграции ...

Интеграция ChatGPT App в существующий продукт и миграции SDK/MCP

ChatGPT Apps
20 уровень , 2 лекция
Открыта

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.

Смысл тот же: не подменять данные «под ногами» у уже запущенных сценариев, а давать им явно зафиксированную версию каталога.

Миграции без даунтайма

Типичный цикл миграции инструмента:

  1. Добавляете новую версию инструмента (_v2) параллельно старой.
  2. Обновляете App/агентов/system‑prompt так, чтобы они использовали новую версию.
  3. Прогоняете golden‑кейсы и LLM‑eval для обоих вариантов и убеждаетесь, что для критичных сценариев качество не упало.
  4. Наблюдаете метрики использования v1 vs v2 (и ошибки).
  5. После того как трафик на 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, а не редкое событие «когда-нибудь потом».

Типичный процесс апгрейда

Здоровый сценарий обновления примерно такой:

  1. Читаете changelog новой версии Apps SDK/MCP SDK. Отмечаете все потенциально breaking‑изменения.
  2. Обновляете зависимости в dev/staging‑окружении, не трогая прод.
  3. Прогоняете MCP Inspector / Jam или другой клиент:
    • проверяете handshake;
    • tools/list / resources/list;
    • несколько тестовых tools/call.
  4. Обновляете описания инструментов и _meta в соответствии с новыми возможностями:
    • например, добавляете новые annotations или widgetDescription.
  5. Прогоняете golden‑кейсы и LLM‑eval, о которых говорили в предыдущих лекциях, чтобы убедиться, что поведение App с точки зрения качества не испортилось.
  6. Только после этого выкатываете обновления на прод, по возможности используя 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 внезапно ломается у реальных пользователей.

Правильный путь:

  1. Добавляем новую схему и новый инструмент _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); // новая логика
  },
});
  1. Оставляем recommend_gifts как есть, добавив в description пометку DEPRECATED.
  2. Обновляем system‑prompt и описания App так, чтобы модель предпочитала recommend_gifts_v2 (можно явно указать в инструкциях).
  3. Обновляем виджет GiftGenius, чтобы он понимал новый формат ответа: поле deliveryEstimateDays и т.д.
  4. Прогоняем 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 и ключевые параметры — тогда любые регрессы легче привязать к конкретному изменению.

1
Задача
ChatGPT Apps, 20 уровень, 2 лекция
Недоступна
Single Source of Truth для входа инструмента (Zod → MCP + REST)
Single Source of Truth для входа инструмента (Zod → MCP + REST)
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ