CodeGym /Cursos /ChatGPT Apps /Integração do ChatGPT App em um produto existente e migra...

Integração do ChatGPT App em um produto existente e migrações de SDK/MCP

ChatGPT Apps
Nível 20 , Lição 2
Disponível

1. Por que falar de integração e migrações

Até agora, nós principalmente projetávamos APIs e ferramentas do jeito que era conveniente para nós. Na vida real, quase sempre é o contrário: você já tem:

  • um monólito ou um punhado de microsserviços;
  • REST/GraphQL API;
  • lógica de negócio rodando em produção há anos.

E de repente surge a tarefa: “Conecte nosso produto ao ChatGPT via Apps SDK e MCP”.

Reescrever tudo para um “servidor MCP ideal” — não é opção. É preciso “colocar” com cuidado por cima do mundo existente uma camada fina que traduza a linguagem do seu backend para a linguagem do ChatGPT: ferramentas, recursos e esquemas.

Segundo problema: o produto é vivo. Esquemas e APIs mudam. No frontend comum você ao menos recebe um erro de TypeScript assim que muda um campo. No mundo de LLM Apps é mais traiçoeiro: o modelo continuará enviando com confiança o formato antigo, o tool vai falhar, e em vez de uma queda bonita na compilação você terá:

  • erros em runtime no servidor MCP;
  • alucinações “acho que entendi mais ou menos o que você queria desse campo”;
  • incidentes de qualidade irritantes.

Por isso, nesta aula olhamos para a camada MCP+Apps como:

  • um adaptador para o backend existente;
  • um contrato que precisa ser mantido por anos;
  • um objeto de migrações: versões, anotações, scopes e SDK.

2. Arquitetura de integração: MCP como adaptador sobre o backend existente

Visão básica

Relembrando a stack, agora pela lente de produção:

flowchart LR
  U[Usuário no ChatGPT] --> G[Modelo do ChatGPT]
  G -->|chama o App| W["Widget (Apps SDK, Next.js)"]
  G -->|tools.call| MCP[Servidor MCP / Gateway]
  MCP --> S1["Gift Service (seu serviço existente)"]
  MCP --> S2["Commerce Service (pedidos, ACP)"]

O ChatGPT se comunica com seu mundo não diretamente, mas via o protocolo MCP: lista de tools/resources, chamadas tools/call, streaming de eventos.

O servidor MCP nessa arquitetura é exatamente o tal adaptador: ele entende o ChatGPT (JSON‑RPC, ferramentas) e os seus serviços (REST/DB/filas) e traduz um no outro.

MCP como Gateway/Adapter

Situação clássica: você já tem o Gift Service com endpoints REST:

// Exemplo de REST API existente
GET  /api/gifts/recommendations?budget=100&occasion=birthday
POST /api/orders

Em vez de escrever nova lógica de negócio, a camada MCP apenas embrulha isso em um Tool:

// mcp/tools/recommendGifts.ts
import { z } from "zod";
import { server } from "./mcpServer"; // instância hipotética do SDK

const recommendGiftsInput = z.object({
  occasion: z.string(),
  budgetUsd: z.number().int().positive(),
});

server.registerTool({
  name: "recommend_gifts",
  description: "Seleciona ideias de presentes dentro do orçamento",
  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(); // importante: retornamos JSON, conveniente tanto para o modelo quanto para o widget
  },
});

Toda a lógica de seleção de presentes permanece dentro do seu serviço existente. A camada MCP é um “tradutor fino” da linguagem do ChatGPT para a linguagem das suas APIs.

Às vezes a camada MCP também roteia solicitações para vários serviços de backend. Nesse caso, ela se torna um MCP Gateway completo — você verá esse papel com mais profundidade no módulo sobre produção e rede.

Monolith-integrated MCP vs Sidecar MCP

Há duas opções básicas de onde “acoplar” essa camada MCP.

Textualmente fica assim:

Opção Descrição Onde o código do MCP vive
Monolith-integrated Tudo em um único serviço Next.js/Node Nas rotas de API do Next.js ou no Express
Sidecar MCP Um contêiner/serviço separado que conversa com o API Aplicativo separado em Node/Go

Em projetos pequenos, geralmente o primeiro caminho basta: aplicativo Next.js, deploy na Vercel, ali mesmo uma rota /mcp ou /api/mcp, e o servidor MCP vive ao lado das outras APIs.

Exemplo (bem simplificado):

// 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); // requisição JSON-RPC
  return new Response(JSON.stringify(response), {
    headers: { "content-type": "application/json" },
  });
}

Em uma arquitetura mais madura, com vários serviços de domínio (Gift, Commerce, Analytics), é mais conveniente extrair a camada MCP para um serviço Gateway separado. Ele receberá o tráfego MCP do ChatGPT e fará o roteamento das chamadas para diferentes backends pelo nome do tool.

Importante lembrar: do ponto de vista do ChatGPT e do Apps SDK, ainda é um único servidor MCP. Onde exatamente ele roda — dentro do monólito ou como um microserviço separado — é uma preocupação arquitetural sua.

Arquitetura da camada MCP resolvida: ela pode viver dentro do monólito ou como um Gateway separado. A próxima questão é o que exatamente essa camada recebe e entrega — e aí entram esquemas e contratos.

3. Single Source of Truth: esquemas, tipos e testes de contrato

Se você tem DTOs internos, contratos REST externos e ainda esquemas MCP para tools — a tentação de “desenhar os esquemas no olho” é grande. O resultado é previsível:

  • você muda um campo no backend e esquece de atualizar o schema do tool;
  • o modelo continua enviando o formato antigo;
  • você ganha um zoológico de falhas em runtime.

Caminho saudável: criar um único ponto de verdade para a estrutura dos dados e usá-lo em todo lugar. No mundo TypeScript, é muito conveniente fazer isso com Zod ou bibliotecas similares, que o MCP SDK converte para JSON Schema.

Esquema Zod unificado para o GiftGenius

Suponha que seu serviço Gift no nosso GiftGenius didático já use Zod para validar a entrada:

// domain/gifts.ts
import { z } from "zod";

export const giftRecommendationInputSchema = z.object({
  occasion: z.string().describe("Ocasião: birthday, wedding etc."),
  budgetUsd: z.number().int().positive(),
  recipientProfile: z.string().describe("Breve descrição da pessoa"),
});

export type GiftRecommendationInput = z.infer<
  typeof giftRecommendationInputSchema
>;

Esse mesmo esquema é usado:

  • no endpoint REST (para checar o corpo da requisição);
  • no tool MCP (como inputSchema);
  • em testes (como base para fixtures).

Conectando o esquema ao tool MCP

// mcp/tools/recommendGifts.ts
import { giftRecommendationInputSchema } from "@/domain/gifts";
import { server } from "../mcpServer";

server.registerTool({
  name: "recommend_gifts",
  description: "Seleção de presentes por perfil e orçamento",
  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();
  },
});

O SDK converte automaticamente o esquema Zod em JSON Schema, que o ChatGPT verá em tools/list. Isso resolve dois problemas de uma vez:

  • os tipos dos argumentos do tool e do código ficam rigidamente conectados;
  • ao alterar o esquema, o compilador TypeScript forçará você a atualizar também o handler.

Testes de contrato para MCP ↔ backend

“Testes de contrato” aqui não é um bicho de sete cabeças, e sim algumas verificações bem práticas.

O unit/contract test mais simples pode ser assim:

// tests/mcp/recommendGifts.contract.test.ts
import { giftRecommendationInputSchema } from "@/domain/gifts";

test("o exemplo de requisição está em conformidade com o esquema do tool", () => {
  const sample = {
    occasion: "birthday",
    budgetUsd: 150,
    recipientProfile: "colega, gosta de gadgets",
  };

  expect(() => giftRecommendationInputSchema.parse(sample)).not.toThrow();
});

Esse teste não garante que “tudo está perfeito”, mas captura o descompasso entre as expectativas do backend e da camada MCP se você mudou o esquema e esqueceu de atualizar as fixtures.

Depois, é fácil estender essa abordagem para:

  • respostas mockadas de APIs externas (Stripe, CMS);
  • rodar o cliente MCP contra um servidor MCP real em ambiente de teste.

4. Estratégias de versionamento de tools e resources

Esquemas cedo ou tarde mudam. O principal é não fazer isso no estilo “vou apenas renomear o campo, o que pode dar errado?”. No mundo LLM, você pode quebrar não só a build, mas também o comportamento do modelo: prompts antigos, diálogos salvos e golden cases continuarão esperando o contrato antigo.

Mudanças aditivas vs breaking changes

De forma geral, as mudanças caem em duas categorias.

Mudanças aditivas — você adiciona algo, sem quebrar ninguém:

  • novo campo opcional na resposta;
  • novo argumento opcional com valor padrão;
  • valores adicionais de um enum, com os quais o UI e o modelo podem lidar de forma neutra.

Por exemplo, você adicionou à resposta do tool o campo deliveryEstimateDays, mas o widget antigo apenas o ignora. É seguro: o esquema pode se expandir, e ninguém é obrigado a usá-lo.

Breaking changes — você quebra expectativas existentes:

  • torna um campo obrigatório quando antes não era;
  • muda o tipo (string → objeto);
  • muda o significado de argumentos (orçamento em USD → orçamento na moeda local), mas sem mudar os nomes dos campos.

Nesses casos, o único caminho seguro é criar uma nova versão do tool.

Padrão Tool_v2

Padrão clássico: você tinha recommend_gifts e quer mudar o esquema seriamente. Não mexa no tool antigo; crie um novo — recommend_gifts_v2.

// v1
const recommendGiftsInput_v1 = z.object({
  occasion: z.string(),
  budgetUsd: z.number().int().positive(),
});

// v2: suporte a moedas e filtros de entrega
const recommendGiftsInput_v2 = z.object({
  occasion: z.string(),
  maxPrice: z.number().int().positive(),
  currency: z.enum(["USD", "EUR", "GBP"]),
  deliverByDate: z.string().optional(); // string ISO
});

server.registerTool({
  name: "recommend_gifts",
  description: "DEPRECATED: use recommend_gifts_v2",
  inputSchema: recommendGiftsInput_v1,
  async execute(args) { /* lógica antiga */ },
});

server.registerTool({
  name: "recommend_gifts_v2",
  description:
    "Seleção de presentes por orçamento, moeda e prazo de entrega",
  inputSchema: recommendGiftsInput_v2,
  async execute(args) { /* lógica nova */ },
});

O modelo e os prompts/agentes antigos continuarão usando recommend_gifts até você atualizá-los. Novos cenários você já escreve para recommend_gifts_v2.

Após um período de migração:

  • os golden cases e agentes foram migrados para a v2;
  • as métricas mostram que a v1 quase não é chamada;

você pode começar a desativar a v1 com cuidado (por exemplo, primeiro ocultá-la da lista de tools em dev/staging, depois — em produção).

Versionamento de resources

Tools não são a única coisa que precisa de versões. Se você tem resources (resources) — por exemplo, um catálogo de presentes estático — também é melhor versioná-los.

Opções populares:

  • colocar a versão no nome do resource: gift_catalog.v1.json, gift_catalog.v2.json;
  • ou passar a versão no URI/parâmetro: /api/catalog?version=1.

A ideia é a mesma: não trocar os dados “por baixo dos pés” de cenários já em execução, mas fornecer explicitamente uma versão fixada do catálogo.

Migrações sem downtime

Ciclo típico de migração de um tool:

  1. Adicione a nova versão do tool (_v2) em paralelo à antiga.
  2. Atualize o App/agentes/system prompt para que usem a nova versão.
  3. Rode os golden cases e o LLM‑eval para ambos os caminhos e garanta que a qualidade não caiu em cenários críticos.
  4. Observe as métricas de uso de v1 vs v2 (e os erros).
  5. Quando o tráfego na v1 estiver próximo de zero, comece a desativá-la.

Essa abordagem funciona bem para migrações de esquemas, atualizações de SDK/protocolo e mudanças de Auth. Vimos como as ferramentas e resources evoluem — via v1/v2 e mudanças aditivas cuidadosas. A segunda grande parte do contrato é autenticação e autorização: OAuth, scopes e .well-known. Eles também vivem por anos e exigem migrações cuidadosas.

5. Evolução da autenticação: .well-known, scopes e o OAuth existente

Se seu produto já vive no mundo de OAuth 2.1/OpenID Connect, a integração com o ChatGPT via MCP não é “mais um login”, mas sim um novo cliente que deve falar com seu Authorization Server seguindo as mesmas regras.

MCP e .well-known/oauth-protected-resource

Falamos em detalhes sobre OAuth 2.1/OpenID Connect e configuração do Auth Server em um módulo separado do curso (veja o módulo de autenticação). Aqui nos interessa o aspecto aplicado: como um recurso MCP informa ao ChatGPT que é protegido por OAuth e como iniciar o fluxo de vinculação de contas.

Padrão para recursos MCP protegidos:

  • seu servidor MCP expõe um endpoint especial /.well-known/oauth-protected-resource;
  • na resposta ele informa qual é o recurso e por quais AS (Authorization Server) ele é protegido;
  • em caso de 401 em uma chamada MCP, o servidor retorna o cabeçalho WWW-Authenticate com um link para esse .well-known, e o ChatGPT inicia o fluxo OAuth (“Link account”).

Exemplo mínimo em 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);

E o handler de 401 com uma dica para o cliente:

res
  .status(401)
  .set(
    "WWW-Authenticate",
    'Bearer resource_metadata="https://mcp.myapp.com/.well-known/oauth-protected-resource"',
  )
  .end();

Ao ver esse cabeçalho, o ChatGPT entende a qual AS ir e como iniciar o fluxo OAuth para o seu recurso MCP.

Scopes e migrações de autorização

Scopes são outra fonte de migrações. Já discutimos isso no módulo de Auth, mas, no contexto de integração/migrações, alguns pontos são importantes.

Imagine que o GiftGenius primeiro só sabia ler o catálogo (gifts.read), e depois você adicionou gifts.write para criar pedidos. Você precisa:

  • adicionar o novo scope na configuração do cliente (ChatGPT App);
  • atualizar o servidor MCP para exigir esse scope apenas para tools que realmente fazem alterações;
  • descrever as mudanças no .well-known, se necessário.

Do ponto de vista de UX, o usuário, ao tentar usar a nova funcionalidade, pode ver um pedido para “ampliar permissões” do aplicativo do ChatGPT. Você não quer que isso aconteça no meio de um diálogo em andamento sem aviso — por isso, essas mudanças precisam ser:

  • anunciadas (release notes, documentação);
  • testadas em staging com um AS de teste;
  • acompanhadas da atualização das descrições dos tools (destructiveHint etc.), para que o modelo chame “tools perigosos” de forma consciente.

6. Metadados e anotações: camada de hints sobre o contrato

A camada de Auth responde à pergunta quem pode fazer o quê através do seu App. Mas, mesmo com tokens e scopes corretos, é importante como o modelo vai chamar seus tools e explicar as ações ao usuário. Entra aqui a camada adicional de hints: metadados e anotações.

O contrato (schema) diz o que o tool aceita e retorna. Metadados e anotações ajudam o modelo a entender como e quando chamá-lo. Isso fica especialmente importante quando você evolui o App: adiciona novas ações destrutivas, muda o UI, introduz integrações com o mundo externo.

_meta["openai/widgetDescription"] e widgetCSP

No Apps SDK e nas descrições do MCP há o campo especial _meta, onde a OpenAI adiciona extensões do protocolo. Por exemplo:

  • _meta["openai/widgetDescription"] — uma descrição breve do que o seu widget mostra; o modelo pode usá-la para não “recontar” o UI e anunciar corretamente o App;
  • _meta["openai/widgetCSP"] — declaração dos domínios de CSP de que seu widget precisa (para fetch/imagens/scripts).

Quando você altera o UI (por exemplo, adiciona uma nova etapa de finalização do pedido), é útil atualizar o widgetDescription para que o modelo continue explicando corretamente ao usuário o que está acontecendo.

Anotações de tools (readOnlyHint, destructiveHint, openWorldHint)

Anotações são flags booleanas simples que impactam bastante o UX e a segurança:

  • readOnlyHint: true — o tool não altera nada (leitura). O modelo pode chamá-lo sem confirmações adicionais.
  • destructiveHint: true — o tool pode excluir/alterar algo. O ChatGPT pedirá confirmação explícita.
  • openWorldHint: true — o tool publica dados para fora ou pode retornar “muita coisa”, exigindo sumarização.

Exemplo de descritor de tool com anotações:

server.registerTool({
  name: "delete_saved_gift",
  description: "Exclui um presente salvo do usuário",
  inputSchema: z.object({ giftId: z.string() }),
  annotations: {
    readOnlyHint: false,
    destructiveHint: true,
    openWorldHint: false,
  },
  async execute({ giftId }) {
    // ...excluímos o presente
  },
});

Em migrações, quando você adiciona novos tools “perigosos”, as anotações são suas aliadas: ajudam o ChatGPT a não executá-los de forma oculta e estimulam um comportamento mais cauteloso.

Importante entender que anotações não são “segurança de verdade”. Elas afetam apenas o comportamento do cliente e do modelo. A segurança real continua sendo responsabilidade do seu servidor (Auth, scopes, validação).

7. Migrações de SDK e especificações MCP

MCP e Apps SDK evoluem ativamente — surgem novos campos em capabilities, novos tipos de mensagens, novos _meta/annotations. A documentação avisa honestamente: “na situação de 2025” — e é com isso que vamos conviver.

Portanto, migrações de versões de SDK e specs são parte normal da vida do App, não um evento raro “para algum dia”.

Processo típico de upgrade

Um cenário saudável de atualização é mais ou menos assim:

  1. Ler o changelog da nova versão do Apps SDK/MCP SDK. Marcar todas as possíveis breaking changes.
  2. Atualizar as dependências no ambiente de dev/staging, sem tocar produção.
  3. Rodar o MCP Inspector / Jam ou outro cliente:
    • verificar o handshake;
    • tools/list / resources/list;
    • algumas tools/call de teste.
  4. Atualizar descrições dos tools e o _meta conforme as novas capacidades:
    • por exemplo, adicionar novas annotations ou widgetDescription.
  5. Rodar os golden cases e o LLM‑eval, como falamos em aulas anteriores, para garantir que o comportamento do App não piorou em termos de qualidade.
  6. Só então implantar em produção, preferencialmente usando canary/feature flag para um subconjunto do tráfego.

Exemplo: adicionando openWorldHint na nova versão do SDK

Suponha que a nova versão do Apps SDK adicionou suporte a openWorldHint, e você decidiu marcar com ela o tool search_public_reviews, que percorre avaliações externas e pode retornar muito ruído.

Os passos são:

  • atualizar o SDK e os tipos;
  • adicionar annotations.openWorldHint = true no descritor do tool;
  • atualizar o system prompt para que o agente explique explicitamente ao usuário que haverá uma consulta ao mundo externo;
  • rodar golden cases de segurança (especialmente sobre privacidade/PII) para garantir que o modelo não ficou excessivamente prolixo.

Discutimos o processo geral de atualização de SDK e anotações. Vamos agora ver tudo isso em um cenário concreto — a evolução do tool recommend_gifts.

8. Mini‑case: evolução de recommend_gifts no GiftGenius

Vamos juntar tudo em um cenário específico.

Versão inicial

O tool básico era assim:

const recommendGiftsInput_v1 = z.object({
  occasion: z.string(),
  budgetUsd: z.number().int().positive(),
  recipientProfile: z.string(),
});

server.registerTool({
  name: "recommend_gifts",
  description: "Seleciona ideias de presentes em USD",
  inputSchema: recommendGiftsInput_v1,
  async execute(args) {
    const input = recommendGiftsInput_v1.parse(args);
    return giftService.recommend(input); // função interna
  },
});

Tudo bem enquanto você tem apenas usuários dos EUA e uma única moeda.

Novas exigências de negócio: multimoeda e prazo

A equipe de produto traz novas exigências:

  • precisa suportar EUR/GBP;
  • precisa considerar o prazo de entrega (não mostrar presentes que chegam só daqui a um mês se o aniversário é em três dias);
  • idealmente adicionar no response uma estimativa do tempo de entrega.

Abordagem ingênua: simplesmente mudar os campos:

  • renomear budgetUsd para maxPrice;
  • adicionar currency;
  • adicionar na resposta deliveryEstimateDays.

O que vai dar errado?

Prompts antigos (incluindo golden cases e a descrição no system prompt) e diálogos salvos continuam enviando budgetUsd. O modelo não sabe que ele não existe mais. A camada MCP começará a falhar ao tentar fazer parse. O comportamento do ChatGPT App quebra repentinamente para usuários reais.

Caminho correto:

  1. Adicionar um novo esquema e um novo tool _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:
    "Seleção de presentes considerando a moeda e a data desejada de entrega",
  inputSchema: recommendGiftsInput_v2,
  async execute(args) {
    const input = recommendGiftsInput_v2.parse(args);
    return giftService.recommendV2(input); // nova lógica
  },
});
  1. Manter recommend_gifts como está, adicionando no description a marcação DEPRECATED.
  2. Atualizar o system prompt e descrições do App para que o modelo prefira recommend_gifts_v2 (pode ser indicado explicitamente nas instruções).
  3. Atualizar o widget do GiftGenius para entender o novo formato de resposta: o campo deliveryEstimateDays etc.
  4. Rodar os golden cases para cenários típicos (seleção de presentes até uma data) via LLM‑eval.

Testes e observabilidade

Alguns testes que vale a pena ter:

Teste de contrato para a nova entrada:

test("v2 aceita um cenário com EUR e prazo", () => {
  const sample = {
    occasion: "birthday",
    maxPrice: 100,
    currency: "EUR",
    recipientProfile: "colega",
    deliverByDate: "2025-12-24",
  };

  expect(() => recommendGiftsInput_v2.parse(sample)).not.toThrow();
});

Observabilidade em produção:

  • métrica da participação de chamadas de recommend_gifts_v2 vs recommend_gifts;
  • error rate da v1 (esperamos que não cresça);
  • pontuação de LLM‑eval nos golden cases antes/depois da migração (pelas aulas anteriores você já sabe como fazer).

Quando a v2 “vence” tanto em qualidade quanto em métricas de uso, é possível planejar com cuidado a desativação da v1.

Se simplificarmos para três ideias: (1) MCP é um adaptador fino, não um novo monólito; (2) esquemas, auth e anotações são um contrato de longa duração entre o ChatGPT e seu backend, e precisam ser versionados e testados com o mesmo rigor que APIs comuns; (3) qualquer migração de SDK/specs é um processo de engenharia normal com staging, golden cases e observabilidade — não “atualizar pacote na sexta à noite”. Se você olhar para o ChatGPT App por essa lente, integrações com um produto existente deixarão de parecer caos.

9. Erros comuns em integrações e migrações MCP/SDK

Erro nº 1: MCP como “novo backend”, e não um adaptador fino.
Às vezes dá vontade de puxar toda a lógica de negócio para a camada MCP: acesso a DB, regras de domínio, cálculos. Isso transforma o servidor MCP em mais um monólito, difícil de sincronizar com o restante do backend. Muito mais saudável manter o MCP como um Gateway/Adapter sobre os serviços existentes: toda a lógica de domínio vive onde sempre viveu, e o MCP apenas traduz JSON para lá e para cá.

Erro nº 2: Esquemas diferentes para o mesmo objeto.
Antipadrão comum — ter três definições de “presente”: uma no DB, outra no REST API, outra no tool MCP, e todas um pouco diferentes. No fim quebram tipagem estática, contratos, testes e o bom senso. Usar um esquema único (Zod/TypeBox etc.) como Single Source of Truth e gerar o JSON Schema para o MCP reduz muito esse risco.

Erro nº 3: Migrações de esquema incorretas — breaking change “silenciosa”.
Renomear um campo ou mudar seu significado sem mudar o nome do tool é caminho para um regresso oculto. O modelo continuará enviando o formato antigo, e o incidente aparecerá apenas para parte dos usuários e não imediatamente. Em mudanças sérias, crie *_v2, mantenha a versão antiga em paralelo, use marcações de depreciação e monitore.

Erro nº 4: Ignorar mudanças de Auth e scopes.
Adicionou um tool com efeitos colaterais, mas esqueceu de atualizar scopes e o .well-known? O usuário pode receber 401 no meio do fluxo; ou, ao contrário, seu MCP pode começar a executar operações destrutivas sem autorização adequada. Planeje migrações da camada de auth com o mesmo cuidado das migrações de esquema: com staging, testes e ampliação gradual de permissões.

Erro nº 5: Não usar anotações (destructiveHint, readOnlyHint, openWorldHint).
Se você não indicar ao modelo quais tools são seguros e quais são potencialmente perigosos, ele pode se comportar de forma inesperada: pedir confirmação para um inocente get_catalog e, sem aviso, executar uma exclusão de dados. As anotações corretas tornam o comportamento previsível para o usuário e diminuem o risco de incidentes de qualidade e segurança.

Erro nº 6: Atualizar o SDK “em produção” sem rodar golden cases.
A nova versão do SDK/spec pode adicionar campos, mudar o comportamento do handshake ou a estrutura das mensagens. Se “só atualizar dependências e fazer deploy”, você arrisca um regresso de qualidade (o modelo parou de chamar o tool certo, mudou a formulação de erros etc.). Primeiro — dev/staging, MCP Inspector, depois golden cases e LLM‑eval, e só então — produção.

Erro nº 7: Forte acoplamento da lógica de negócio a uma única versão do tool.
Quando a lógica interna do Gift Service depende diretamente do recommend_gifts específico, é difícil migrar para recommend_gifts_v2 sem dor. A boa prática é ter um serviço interno que evolui pelas suas próprias regras, e os tools *_v1, *_v2 serem apenas thin adapters, mapeando contratos externos antigos e novos para estruturas de domínio comuns.

Erro nº 8: Falta de observabilidade por versões de tools.
Se nos logs e métricas você não diferencia qual tool e versão foram chamados, a depuração de migrações vira adivinhação. Logue o nome do tool, a versão do esquema/SDK e parâmetros-chave — assim qualquer regresso fica mais fácil de vincular a uma mudança específica.

Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION