1. Por qué hablar de integración y migraciones
Hasta ahora sobre todo diseñábamos APIs e instrumentos como nos parecía más cómodo. En la vida real casi siempre es al revés: ya tenéis:
- un monolito o un conjunto de microservicios;
- REST/GraphQL API;
- lógica de negocio que lleva años ejecutándose en producción.
Y de repente aparece la tarea: «Conecta nuestro producto a ChatGPT mediante Apps SDK y MCP».
Reescribirlo todo para un «servidor MCP ideal» no es una opción. Hay que «poner» con cuidado una capa fina encima del mundo existente que traduzca el lenguaje de vuestro backend al lenguaje de ChatGPT: tools, resources y esquemas.
Segundo problema: el producto está vivo. Los esquemas y las APIs cambian. En un frontend normal al menos enseguida obtienes un error de TypeScript cuando cambias un campo. En el mundo de las LLM‑Apps es más traicionero: el modelo seguirá enviando con seguridad el formato antiguo, la tool fallará, y en lugar de una bonita caída de build obtendrás:
- errores en tiempo de ejecución en el servidor MCP;
- alucinaciones del tipo «más o menos he deducido lo que querías de este campo»;
- incidentes de calidad irritantes.
Por eso en esta lección miramos a la capa MCP+Apps como:
- un adaptador hacia el backend existente;
- un contrato que hay que mantener durante años;
- un objeto de migraciones: versiones, anotaciones, scopes y SDK.
2. Arquitectura de la integración: MCP como adaptador sobre el backend existente
La imagen básica
Recordemos el stack, pero ahora a través del prisma de producción:
flowchart LR U[Usuario en ChatGPT] --> G[Modelo de ChatGPT] G -->|invoca la App| W["Widget (Apps SDK, Next.js)"] G -->|tools.call| MCP[Servidor MCP / Gateway] MCP --> S1["Gift Service (tu servicio existente)"] MCP --> S2["Commerce Service (pedidos, ACP)"]
ChatGPT no habla con vuestro mundo directamente, sino a través del protocolo MCP: lista de tools/resources, llamadas tools/call, streaming de eventos.
En este esquema, el servidor MCP es exactamente ese adaptador: conoce tanto ChatGPT (JSON‑RPC, herramientas) como vuestros servicios (REST/DB/colas) y traduce de uno a otro.
MCP como Gateway/Adapter
Planteamiento clásico: ya tenéis un Gift Service con endpoints REST:
// Ejemplo de una API REST existente
GET /api/gifts/recommendations?budget=100&occasion=birthday
POST /api/orders
En lugar de escribir nueva lógica de negocio, la capa MCP simplemente lo envuelve en una Tool:
// mcp/tools/recommendGifts.ts
import { z } from "zod";
import { server } from "./mcpServer"; // instancia hipotética del SDK
const recommendGiftsInput = z.object({
occasion: z.string(),
budgetUsd: z.number().int().positive(),
});
server.registerTool({
name: "recommend_gifts",
description: "Sugiere ideas de regalos dentro del presupuesto",
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: devolvemos JSON, cómodo tanto para el modelo como para el widget
},
});
Toda la lógica de selección de regalos se queda dentro de vuestro servicio existente. La capa MCP es un «traductor fino» del lenguaje de ChatGPT al lenguaje de vuestras APIs.
A veces la capa MCP también enruta peticiones a varios servicios backend. En ese caso se convierte en un MCP Gateway en toda regla: su papel lo veréis con más profundidad en el módulo sobre producción y red.
Monolith-integrated MCP vs Sidecar MCP
Hay dos opciones básicas de dónde «acoplar» esa capa MCP.
Textualmente se ve así:
| Variante | Descripción | Dónde vive el código MCP |
|---|---|---|
| Monolith-integrated | Todo en un único servicio Next.js/Node | En las rutas API de Next.js o Express |
| Sidecar MCP | Contenedor/servicio separado que se comunica con la API | Aplicación Node/Go independiente |
En proyectos pequeños, a menudo el primer enfoque es suficiente: aplicación Next.js, despliegue en Vercel, allí una ruta /mcp o /api/mcp, y el servidor MCP vive junto al resto de APIs.
Ejemplo (muy 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); // solicitud JSON-RPC
return new Response(JSON.stringify(response), {
headers: { "content-type": "application/json" },
});
}
En una arquitectura más madura, con varios servicios de dominio (Gift, Commerce, Analytics), es más cómodo extraer la capa MCP a un servicio Gateway separado. Recibirá tráfico MCP de ChatGPT y él mismo enrutará las llamadas a distintos backends por nombre de herramienta.
Importante recordar: desde el punto de vista de ChatGPT y Apps SDK sigue siendo un único servidor MCP. Dónde se ejecuta —dentro del monolito o como un microservicio separado— es ya una decisión arquitectónica vuestra.
Ya hemos visto la arquitectura de la capa MCP: puede vivir dentro del monolito o como Gateway separado. La siguiente cuestión es qué exactamente recibe y devuelve esa capa — y aquí entran en juego los esquemas y los contratos.
3. Single Source of Truth: esquemas, tipos y tests de contrato
Si tenéis DTO internos, contratos REST externos y además esquemas MCP para las herramientas — la tentación de «dibujar los esquemas a ojo» es enorme. El resultado es predecible:
- cambiáis un campo en el backend y olvidáis actualizar el schema de la herramienta;
- el modelo sigue enviando el formato antiguo;
- obtenéis un zoológico de runtime divertido.
El camino correcto: tener un único punto de verdad para la estructura de datos y usarlo en todas partes. En el mundo TypeScript es muy cómodo hacerlo con Zod u otras bibliotecas similares, que el MCP SDK sabe convertir a JSON Schema.
Esquema Zod común para GiftGenius
Supongamos que vuestro servicio Gift en nuestro GiftGenius de ejemplo ya usa Zod para validar la entrada:
// domain/gifts.ts
import { z } from "zod";
export const giftRecommendationInputSchema = z.object({
occasion: z.string().describe("Motivo: birthday, wedding, etc."),
budgetUsd: z.number().int().positive(),
recipientProfile: z.string().describe("Breve descripción de la persona"),
});
export type GiftRecommendationInput = z.infer<
typeof giftRecommendationInputSchema
>;
Este mismo esquema se usa:
- en el endpoint REST (para validar el cuerpo de la petición);
- en la herramienta MCP (como inputSchema);
- en los tests (como base de los fixtures).
Conectamos el esquema a la herramienta MCP
// mcp/tools/recommendGifts.ts
import { giftRecommendationInputSchema } from "@/domain/gifts";
import { server } from "../mcpServer";
server.registerTool({
name: "recommend_gifts",
description: "Selección de regalos por perfil y presupuesto",
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();
},
});
El SDK convertirá automáticamente el esquema Zod a JSON Schema, que ChatGPT verá en tools/list. Esto resuelve dos problemas a la vez:
- los tipos de los argumentos de la herramienta y del código están estrictamente vinculados;
- al cambiar el esquema, el compilador TypeScript te obligará a actualizar también el manejador.
Tests de contrato para MCP ↔ backend
Los tests de contrato aquí no son nada misterioso, sino un puñado de comprobaciones muy aterrizadas.
Un unit/contract test simple puede verse así:
// tests/mcp/recommendGifts.contract.test.ts
import { giftRecommendationInputSchema } from "@/domain/gifts";
test("el ejemplo de petición cumple el esquema de la herramienta", () => {
const sample = {
occasion: "birthday",
budgetUsd: 150,
recipientProfile: "compañero de trabajo, le gustan los gadgets",
};
expect(() => giftRecommendationInputSchema.parse(sample)).not.toThrow();
});
Este test no garantiza que todo sea perfecto, pero al menos detecta desincronizaciones entre las expectativas del backend y la capa MCP si cambiasteis el esquema y olvidasteis actualizar los fixtures.
Luego este mismo enfoque se amplía fácilmente a:
- respuestas mockeadas de APIs externas (Stripe, CMS);
- ejecutar un cliente MCP contra un servidor MCP real en un entorno de tests.
4. Estrategias de versionado de tools y resources
Los esquemas tarde o temprano cambian. Lo importante es no hacerlo en plan «solo renombro un campo, ¿qué puede salir mal?». En el mundo LLM puedes romper no solo el build, sino también el comportamiento del modelo: prompts antiguos, diálogos guardados y golden cases seguirán esperando el contrato anterior.
Cambios aditivos vs breaking
Convencionalmente, los cambios se dividen en dos categorías.
Cambios aditivos — añades algo, pero no rompes a nadie:
- un nuevo campo opcional en la respuesta;
- un nuevo argumento opcional con valor por defecto;
- valores adicionales de un enum, a los que el UI y el modelo pueden ser indiferentes.
Por ejemplo, añadiste a la respuesta de la herramienta el campo deliveryEstimateDays, pero el widget antiguo simplemente lo ignora. Es seguro: el esquema puede ampliarse, pero nadie está obligado a usarlo.
Cambios breaking — rompes expectativas existentes:
- vuelves obligatorio un campo que antes no lo era;
- cambias el tipo (cadena → objeto);
- cambias el significado de los argumentos (presupuesto en USD → presupuesto en moneda local), pero sin cambiar los nombres de los campos.
En estos casos, el único camino seguro es introducir una nueva versión de la herramienta.
Patrón Tool_v2
Patrón clásico: teníais recommend_gifts y queréis cambiar el esquema de forma importante. No tocáis la herramienta antigua, sino que creáis una nueva — recommend_gifts_v2.
// v1
const recommendGiftsInput_v1 = z.object({
occasion: z.string(),
budgetUsd: z.number().int().positive(),
});
// v2: soporte de divisas y 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(); // cadena ISO
});
server.registerTool({
name: "recommend_gifts",
description: "DEPRECATED: utiliza recommend_gifts_v2",
inputSchema: recommendGiftsInput_v1,
async execute(args) { /* lógica antigua */ },
});
server.registerTool({
name: "recommend_gifts_v2",
description:
"Selección de regalos por presupuesto, divisa y fecha límite de entrega",
inputSchema: recommendGiftsInput_v2,
async execute(args) { /* lógica nueva */ },
});
El modelo y los prompts/agentes antiguos seguirán usando recommend_gifts hasta que los actualices. Los nuevos escenarios ya los escribes para recommend_gifts_v2.
Tras un periodo de migración:
- los golden cases y los agentes están conmutados a v2;
- las métricas muestran que v1 casi no se invoca;
puedes empezar a retirar v1 con cuidado (por ejemplo, primero ocultándolo de la lista de herramientas en dev/staging y luego en producción).
Versionado de recursos
Las tools no son lo único que necesita versiones. Si tienes recursos (resources) —por ejemplo, un catálogo de regalos estático— también es mejor versionarlos.
Opciones populares:
- incluir la versión en el nombre del recurso: gift_catalog.v1.json, gift_catalog.v2.json;
- o pasar la versión en el URI/parámetro: /api/catalog?version=1.
La idea es la misma: no cambiar los datos «bajo los pies» de los escenarios ya en ejecución, sino darles una versión del catálogo fijada explícitamente.
Migraciones sin downtime
Ciclo típico de migración de una herramienta:
- Añade la nueva versión de la herramienta (_v2) en paralelo a la antigua.
- Actualiza la App/los agentes/el system prompt para que utilicen la nueva versión.
- Ejecuta golden cases y LLM‑eval para ambas variantes y asegúrate de que en los escenarios críticos la calidad no ha empeorado.
- Observa las métricas de uso de v1 vs v2 (y errores).
- Cuando el tráfico hacia v1 esté cerca de cero, empieza a desactivarla.
Este enfoque funciona bien tanto para migraciones de esquemas como para actualizaciones de SDK/protocolo y cambios de Auth. Ya hemos visto cómo evolucionan las herramientas y los recursos — mediante v1/v2 y cambios aditivos cuidadosos. La segunda gran parte del contrato es la autenticación y autorización: OAuth, scopes y .well-known. También viven años y requieren migraciones cuidadosas.
5. Evolución de la autenticación: .well-known, scopes y el OAuth existente
Si tu producto ya vive en el mundo de OAuth 2.1/OpenID Connect, la integración con ChatGPT mediante MCP no es «otro login más», sino un nuevo cliente que debe hablar con tu Authorization Server con las reglas comunes.
MCP y .well-known/oauth-protected-resource
Sobre OAuth 2.1/OpenID Connect y la configuración del Auth Server hablamos en detalle en otro módulo del curso (ver módulo de autenticación). Aquí nos interesa el aspecto práctico: cómo un recurso MCP indica a ChatGPT que está protegido por OAuth y cómo lanzar el flujo de linking.
Patrón estándar para recursos MCP protegidos:
- tu servidor MCP expone un endpoint especial /.well-known/oauth-protected-resource;
- en la respuesta indica qué recurso es y con qué AS (Authorization Server) está protegido;
- ante un 401 en una llamada MCP, el servidor devuelve la cabecera WWW-Authenticate con un enlace a ese .well-known, y ChatGPT lanza por sí mismo el flujo OAuth («Link account»).
Ejemplo mínimo en 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);
Y el manejador de 401 con una pista para el cliente:
res
.status(401)
.set(
"WWW-Authenticate",
'Bearer resource_metadata="https://mcp.myapp.com/.well-known/oauth-protected-resource"',
)
.end();
Viendo esta cabecera, ChatGPT entiende a qué AS ir y cómo iniciar el flujo OAuth para tu recurso MCP.
Scopes y migraciones de autorización
Los scopes son otra fuente de migraciones. Ya lo tratamos con detalle en el módulo de Auth, pero en el contexto de integración/migraciones hay varios puntos importantes.
Imagina que GiftGenius al principio solo podía leer el catálogo (gifts.read), y más tarde añadiste gifts.write para crear pedidos. Necesitas:
- añadir el nuevo scope en la configuración del cliente (ChatGPT App);
- actualizar el servidor MCP para exigir ese scope solo en las herramientas que realmente modifican algo;
- describir los cambios en .well-known, si es necesario.
Desde el punto de vista de UX, el usuario puede ver una petición para «ampliar permisos» de la aplicación de ChatGPT la próxima vez que intente usar la funcionalidad nueva. No quieres que eso ocurra en medio de un diálogo en curso sin aviso — por eso estos cambios debes:
- anunciarlos (release notes, documentación);
- probarlos en staging con un AS de pruebas;
- acompañarlos de la actualización de las descripciones de herramientas (destructiveHint, etc.), para que el modelo invoque conscientemente las tools «peligrosas».
6. Metadatos y anotaciones: capa de hints sobre el contrato
La capa de Auth responde a la pregunta de quién y qué puede hacer a través de tu App. Pero incluso con tokens y scopes correctos es importante cómo el modelo invocará tus herramientas y explicará acciones al usuario. Aquí entra una capa extra de hints: metadatos y anotaciones.
El contrato (schema) dice qué recibe y devuelve la herramienta. Los metadatos y anotaciones ayudan al modelo a entender cómo y cuándo invocarla. Esto es especialmente importante cuando evolucionas la App: añades acciones destructivas nuevas, cambias el UI, introduces integraciones con el mundo exterior.
_meta["openai/widgetDescription"] y widgetCSP
En Apps SDK y en las descripciones MCP hay un campo especial _meta, donde OpenAI añade sus extensiones del protocolo. Por ejemplo:
- _meta["openai/widgetDescription"]: breve descripción de lo que muestra tu widget; el modelo puede usarla para no «recontar» el UI y anunciar correctamente la App;
- _meta["openai/widgetCSP"]: declaración de los dominios CSP que tu widget necesita (para fetch/imágenes/scripts).
Cuando cambies el UI (por ejemplo, añadiendo un paso nuevo de checkout), conviene actualizar widgetDescription para que el modelo siga explicando correctamente al usuario lo que está ocurriendo.
Anotaciones de herramientas (readOnlyHint, destructiveHint, openWorldHint)
Las anotaciones son flags booleanos simples que impactan mucho en UX y seguridad:
- readOnlyHint: true — la herramienta no cambia nada (solo lectura). El modelo puede invocarla sin confirmaciones extra.
- destructiveHint: true — la herramienta puede borrar/modificar algo. ChatGPT pedirá confirmación explícita.
- openWorldHint: true — la herramienta publica datos hacia fuera o puede devolver «muchísimo contenido» que requiera sumarización.
Ejemplo de descriptor de herramienta con anotaciones:
server.registerTool({
name: "delete_saved_gift",
description: "Elimina un regalo guardado del usuario",
inputSchema: z.object({ giftId: z.string() }),
annotations: {
readOnlyHint: false,
destructiveHint: true,
openWorldHint: false,
},
async execute({ giftId }) {
// ...eliminamos el regalo
},
});
En una migración, cuando añades herramientas «peligrosas» nuevas, las anotaciones son tus amigas: ayudan a que ChatGPT no las ejecute de forma encubierta y empujan hacia un comportamiento más prudente.
Es importante entender que las anotaciones no son «seguridad de verdad». Solo afectan al comportamiento del cliente y del modelo. La seguridad real la sigue garantizando tu servidor (Auth, scopes, validación).
7. Migraciones de SDK y especificaciones MCP
MCP y Apps SDK están evolucionando activamente — aparecen campos nuevos en capabilities, nuevos tipos de mensajes, nuevos _meta/annotations. La documentación avisa honestamente: «a fecha de 2025» — y tenemos que convivir con ello.
Por tanto, las migraciones de versiones de SDK y specs son una parte normal de la vida de una App, no un evento raro de «algún día».
Proceso típico de actualización
Un escenario sano de actualización sería algo así:
- Lee el changelog de la nueva versión de Apps SDK/MCP SDK. Señala todos los cambios potencialmente breaking.
- Actualiza las dependencias en el entorno de dev/staging, sin tocar producción.
- Ejecuta MCP Inspector / Jam u otro cliente:
- verifica el handshake;
- tools/list / resources/list;
- varias tools/call de prueba.
- Actualiza las descripciones de las herramientas y _meta según las nuevas capacidades:
- por ejemplo, añade nuevas annotations o widgetDescription.
- Ejecuta golden cases y LLM‑eval, de los que hablamos en lecciones anteriores, para asegurarte de que el comportamiento de la App no se ha degradado en calidad.
- Solo después despliega en producción, si es posible usando canary/feature‑flag en un subconjunto del tráfico.
Ejemplo: añadimos openWorldHint en una versión nueva del SDK
Supón que la nueva versión de Apps SDK añade soporte de openWorldHint, y decides marcar con él la herramienta search_public_reviews, que explora reseñas externas y puede devolver mucho ruido.
Los pasos serían:
- actualizar el SDK y los tipos;
- añadir annotations.openWorldHint = true en el descriptor de la herramienta;
- actualizar el system prompt para que el agente explique explícitamente al usuario que ahora habrá una consulta al exterior;
- ejecutar golden cases de seguridad (sobre todo preguntas de privacidad/PII) para verificar que el modelo no se ha vuelto excesivamente locuaz.
Hemos comentado el proceso general de actualización de SDK y anotaciones. Veamos ahora todo esto en un escenario concreto: la evolución de la herramienta recommend_gifts.
8. Mini‑caso: evolución de recommend_gifts en GiftGenius
Juntémoslo todo en un escenario concreto.
Versión inicial
La herramienta básica tenía este aspecto:
const recommendGiftsInput_v1 = z.object({
occasion: z.string(),
budgetUsd: z.number().int().positive(),
recipientProfile: z.string(),
});
server.registerTool({
name: "recommend_gifts",
description: "Selecciona ideas de regalos en USD",
inputSchema: recommendGiftsInput_v1,
async execute(args) {
const input = recommendGiftsInput_v1.parse(args);
return giftService.recommend(input); // función interna
},
});
Todo va bien mientras solo tengas usuarios de EE. UU. y una única divisa.
Nuevos requisitos de negocio: multimoneda y fecha límite
El equipo de producto viene con nuevos requisitos:
- hay que soportar EUR/GBP;
- hay que tener en cuenta la fecha límite de entrega (no mostrar regalos que llegarán en un mes si el cumpleaños es en tres días);
- preferiblemente añadir en la respuesta una estimación del tiempo de entrega.
Enfoque ingenuo: simplemente cambiar los campos:
- renombrar budgetUsd a maxPrice;
- añadir currency;
- añadir en la respuesta deliveryEstimateDays.
¿Qué puede salir mal?
Los prompts antiguos (incluidos los golden cases y la descripción en el system prompt) y los diálogos guardados siguen enviando budgetUsd. El modelo no sabe que ya no existe. La capa MCP empezará a fallar al intentar parse. El comportamiento de la App de ChatGPT se rompe de repente para usuarios reales.
El camino correcto:
- Añadir un esquema nuevo y una herramienta nueva _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:
"Selección de regalos con divisa y fecha deseada de entrega",
inputSchema: recommendGiftsInput_v2,
async execute(args) {
const input = recommendGiftsInput_v2.parse(args);
return giftService.recommendV2(input); // nueva lógica
},
});
- Mantener recommend_gifts como está, añadiendo en description la marca DEPRECATED.
- Actualizar el system prompt y las descripciones de la App para que el modelo prefiera recommend_gifts_v2 (puedes indicarlo explícitamente en las instrucciones).
- Actualizar el widget de GiftGenius para que entienda el nuevo formato de respuesta: el campo deliveryEstimateDays, etc.
- Ejecutar golden cases para escenarios típicos (selección de regalos con fecha tope) mediante LLM‑eval.
Tests y observabilidad
Un par de tests que conviene tener:
Test de contrato para la nueva entrada:
test("v2 acepta el escenario con EUR y fecha límite", () => {
const sample = {
occasion: "birthday",
maxPrice: 100,
currency: "EUR",
recipientProfile: "compañero de trabajo",
deliverByDate: "2025-12-24",
};
expect(() => recommendGiftsInput_v2.parse(sample)).not.toThrow();
});
Observabilidad en producción:
- métrica de la proporción de llamadas recommend_gifts_v2 vs recommend_gifts;
- tasa de errores de v1 (esperamos que no aumente);
- LLM‑eval score en golden cases antes/después de la migración (por las lecciones anteriores ya sabes cómo hacerlo).
Cuando v2 «gana» tanto en calidad como en métricas de uso, puedes planificar con cuidado la desactivación de v1.
Si lo reducimos a tres ideas: (1) MCP es un adaptador fino, no un nuevo monolito; (2) los esquemas, el auth y las anotaciones conforman un contrato de larga duración entre ChatGPT y tu backend que hay que versionar y testear tan cuidadosamente como las APIs normales; (3) cualquier migración de SDK/spec es un proceso de ingeniería normal con staging, golden cases y observabilidad, no «actualizar el paquete un viernes por la tarde». Si miras ChatGPT App con este prisma, las integraciones con el producto existente dejarán de parecer un caos.
9. Errores típicos en la integración y migraciones MCP/SDK
Error n.º 1: tratar MCP como «un backend nuevo» en lugar de como un adaptador fino.
A veces apetece llevarse a la capa MCP toda la lógica de negocio: acceso a BD, reglas de dominio, cálculos. Eso convierte el servidor MCP en otro monolito, difícil de sincronizar con el resto del backend. Es mucho más sano mantener MCP como Gateway/Adapter sobre los servicios existentes: toda la lógica de dominio vive donde ya estaba antes de ChatGPT, y MCP solo traduce JSON de ida y vuelta.
Error n.º 2: esquemas distintos para el mismo objeto.
Antipatrón común: tener tres definiciones de «regalo»: una en la BD, otra en la REST API y otra en la herramienta MCP, todas ligeramente diferentes. Al final se rompe la tipificación estática, los contratos, los tests y el sentido común. Usar un esquema único (Zod/TypeBox, etc.) como Single Source of Truth y generar JSON Schema para MCP reduce significativamente este riesgo.
Error n.º 3: migraciones de esquemas incorrectas — un breaking change «silencioso».
Renombrar un campo o cambiar su significado sin cambiar el nombre de la herramienta lleva a un regresión escondida. El modelo seguirá enviando el formato antiguo y el incidente se manifestará solo en parte de los usuarios y no de inmediato. Para cambios serios, introduce *_v2, deja la versión antigua funcionando en paralelo, usa marcas de deprecación y monitorización.
Error n.º 4: ignorar cambios de Auth y scopes.
¿Añadiste una herramienta nueva con efectos secundarios pero olvidaste actualizar scopes y .well-known? El usuario puede recibir un 401 en mitad de un escenario o, al contrario, tu MCP puede empezar a ejecutar operaciones destructivas sin la autorización adecuada. Planifica las migraciones de la capa de auth con el mismo cuidado que las migraciones de esquemas: con staging, tests y ampliación gradual de permisos.
Error n.º 5: no usar anotaciones (destructiveHint, readOnlyHint, openWorldHint).
Si no indicas al modelo qué herramientas son seguras y cuáles potencialmente peligrosas, puede comportarse de forma inesperada: pedir confirmación para un inocuo get_catalog y ejecutar sin aviso un borrado de datos. Las anotaciones adecuadas hacen el comportamiento predecible para el usuario y reducen el riesgo de incidentes de calidad y seguridad.
Error n.º 6: actualizar el SDK «en producción» sin ejecutar golden cases.
Una versión nueva del SDK/spec puede añadir campos, cambiar el comportamiento del handshake o la estructura de mensajes. Si simplemente «actualizas dependencias y despliegas», te arriesgas a un regresión de calidad (el modelo deja de invocar la herramienta correcta, cambia la formulación de errores, etc.). Primero — dev/staging, MCP Inspector, luego golden cases y LLM‑eval, y solo después — producción.
Error n.º 7: acoplar firmemente la lógica de negocio a una versión de la herramienta.
Cuando la lógica interna de Gift Service depende directamente de recommend_gifts, es difícil migrar a recommend_gifts_v2 sin dolor. La mejor práctica es tener un servicio interno que evolucione según sus propias reglas, y las herramientas *_v1, *_v2 sean solo thin adapters que mapean contratos externos antiguos y nuevos a estructuras de dominio comunes.
Error n.º 8: falta de observabilidad por versiones de herramientas.
Si en logs y métricas no distingues qué herramienta y versión se invocaron, depurar migraciones se convierte en adivinación. Registra el nombre de la herramienta, la versión del esquema/SDK y parámetros clave — así cualquier regresión será más fácil de vincular a un cambio concreto.
GO TO FULL VERSION