1. Pourquoi parler d’intégration et de migrations
Jusqu’ici, nous avons surtout conçu des API et des outils comme cela nous arrangeait. Dans la vraie vie, c’est presque toujours l’inverse : vous avez déjà :
- un monolithe ou un ensemble de microservices ;
- des API REST/GraphQL ;
- de la logique métier qui tourne en production depuis des années.
Et soudain arrive la demande : « Connectez notre produit à ChatGPT via Apps SDK et MCP ».
Tout réécrire pour un « serveur MCP idéal » n’est pas une option. Il faut ajouter soigneusement une fine couche par‑dessus l’existant, qui traduira le langage de votre backend en celui de ChatGPT : outils, ressources et schémas.
Deuxième problème : le produit est vivant. Les schémas et les API évoluent. En frontend classique, vous obtenez au moins une erreur TypeScript dès qu’un champ change. Dans le monde des LLM‑Apps, c’est plus retors : le modèle continuera d’envoyer l’ancien format, l’outil plantera, et au lieu d’un bel échec de build, vous obtiendrez :
- des erreurs en exécution (runtime) sur le serveur MCP ;
- des hallucinations du type « je devine à peu près ce que vous vouliez de ce champ » ;
- des incidents de qualité frustrants.
Dans ce cours, nous considérons donc la couche MCP+Apps comme :
- un adaptateur vers le backend existant ;
- un contrat à maintenir pendant des années ;
- un objet de migrations : versions, annotations, scopes et SDK.
2. Architecture d’intégration : MCP comme adaptateur au‑dessus du backend existant
Vue d’ensemble
Rappelons la pile, mais cette fois à travers le prisme de la production :
flowchart LR U[Utilisateur dans ChatGPT] --> G[Modèle ChatGPT] G -->|appelle l’App| W["Widget (Apps SDK, Next.js)"] G -->|tools.call| MCP[Serveur MCP / Gateway] MCP --> S1["Gift Service (votre service existant)"] MCP --> S2["Commerce Service (commandes, ACP)"]
ChatGPT ne parle pas directement à votre monde, mais via le protocole MCP : liste des outils/ressources, appels tools/call, streaming d’événements.
Dans ce schéma, le serveur MCP est précisément cet adaptateur : il connaît à la fois ChatGPT (JSON‑RPC, outils) et vos services (REST/DB/files d’attente) et traduit l’un vers l’autre.
MCP comme Gateway/Adapter
Le cas classique : vous avez déjà un Gift Service avec des endpoints REST :
// Exemple d'API REST existante
GET /api/gifts/recommendations?budget=100&occasion=birthday
POST /api/orders
Au lieu d’écrire une nouvelle logique métier, la couche MCP encapsule simplement ceci dans un Tool :
// mcp/tools/recommendGifts.ts
import { z } from "zod";
import { server } from "./mcpServer"; // instance SDK fictive
const recommendGiftsInput = z.object({
occasion: z.string(),
budgetUsd: z.number().int().positive(),
});
server.registerTool({
name: "recommend_gifts",
description: "Suggère des idées de cadeaux dans la limite du budget",
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(); // important : on renvoie du JSON pratique à la fois pour le modèle et pour le widget
},
});
Toute la logique de sélection des cadeaux reste dans votre service existant. La couche MCP est un « traducteur fin » du langage de ChatGPT vers celui de vos API.
Parfois, la couche MCP achemine aussi les requêtes vers plusieurs services backend. Dans ce cas, elle devient une véritable passerelle MCP (MCP Gateway) — vous étudierez ce rôle plus en détail dans le module production et réseau.
MCP intégré au monolithe vs MCP sidecar
Deux options de base pour « accrocher » cette couche MCP.
Textuellement, cela ressemble à ceci :
| Variante | Description | Où vit le code MCP |
|---|---|---|
| Monolith-integrated | Tout dans un service Next.js/Node unique | Dans les routes API de Next.js ou Express |
| Sidecar MCP | Conteneur/service séparé communiquant avec l’API | Application Node/Go séparée |
Dans les petits projets, la première option suffit souvent : application Next.js, déploiement sur Vercel, route /mcp ou /api/mcp au même endroit, et le serveur MCP vit à côté des autres API.
Exemple (très simplifié) :
// 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); // requête JSON-RPC
return new Response(JSON.stringify(response), {
headers: { "content-type": "application/json" },
});
}
Dans une architecture plus mature, avec plusieurs services métiers (Gift, Commerce, Analytics), il est plus pratique d’extraire la couche MCP dans un service Gateway séparé. Il recevra le trafic MCP de ChatGPT et achemine ensuite les appels vers différents backends selon le nom de l’outil.
À retenir : du point de vue de ChatGPT et de l’Apps SDK, cela reste un seul serveur MCP. L’endroit où il tourne — à l’intérieur du monolithe ou comme microservice séparé — relève de vos choix d’architecture.
Nous avons clarifié l’architecture de la couche MCP : elle peut vivre dans le monolithe ou comme passerelle séparée. La question suivante est : ce que cette couche réceptionne et renvoie — c’est là qu’entrent en scène les schémas et les contrats.
3. Single Source of Truth : schémas, types et tests de contrat
Si vous avez des DTO internes, des contrats REST externes et, en plus, des schémas MCP pour les outils, la tentation de « dessiner les schémas au feeling » est grande. Le résultat est prévisible :
- vous changez un champ dans le backend, vous oubliez de mettre à jour le schéma de l’outil ;
- le modèle continue d’envoyer l’ancien format ;
- vous obtenez un joyeux zoo en exécution.
La bonne voie : une source unique de vérité pour la structure des données, utilisée partout. Dans l’écosystème TypeScript, c’est très pratique avec Zod ou bibliothèques similaires que le SDK MCP sait convertir en JSON Schema.
Schéma Zod commun pour GiftGenius
Supposons que votre service Gift dans notre GiftGenius de formation utilise déjà Zod pour valider l’entrée :
// domain/gifts.ts
import { z } from "zod";
export const giftRecommendationInputSchema = z.object({
occasion: z.string().describe("Occasion : birthday, wedding, etc."),
budgetUsd: z.number().int().positive(),
recipientProfile: z.string().describe("Brève description de la personne"),
});
export type GiftRecommendationInput = z.infer<
typeof giftRecommendationInputSchema
>;
Ce même schéma est utilisé :
- dans l’endpoint REST (pour valider le corps de la requête) ;
- dans l’outil MCP (comme inputSchema) ;
- dans les tests (comme base pour les fixtures).
Connecter le schéma à l’outil MCP
// mcp/tools/recommendGifts.ts
import { giftRecommendationInputSchema } from "@/domain/gifts";
import { server } from "../mcpServer";
server.registerTool({
name: "recommend_gifts",
description: "Sélection de cadeaux selon profil et budget",
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();
},
});
Le SDK convertira le schéma Zod en JSON Schema, que ChatGPT verra dans tools/list. Cela résout deux problèmes à la fois :
- les types des arguments de l’outil et du code sont strictement liés ;
- en cas de changement du schéma, le compilateur TypeScript vous forcera à mettre à jour le handler.
Tests de contrat pour MCP ↔ backend
Les tests de contrat ici ne sont pas un gros mot, mais quelques vérifications très pragmatiques.
Un test unitaire/de contrat minimal peut ressembler à ceci :
// tests/mcp/recommendGifts.contract.test.ts
import { giftRecommendationInputSchema } from "@/domain/gifts";
test("l’exemple de requête correspond au schéma de l’outil", () => {
const sample = {
occasion: "birthday",
budgetUsd: 150,
recipientProfile: "collègue, aime les gadgets",
};
expect(() => giftRecommendationInputSchema.parse(sample)).not.toThrow();
});
Ce test ne garantit pas que tout est parfait, mais il détecte au moins les désalignements entre les attentes du backend et la couche MCP si vous avez modifié le schéma et oublié de mettre à jour les fixtures.
Cette approche s’étend facilement à :
- des réponses mockées d’API externes (Stripe, CMS) ;
- l’exécution d’un client MCP contre un vrai serveur MCP en environnement de test.
4. Stratégies de versionnage des tools et resources
Les schémas finissent tôt ou tard par évoluer. L’essentiel est d’éviter l’approche « je renomme juste un champ, que pourrait‑il arriver ? ». Dans l’univers LLM, vous pouvez casser non seulement le build, mais aussi le comportement du modèle : anciens prompts, conversations sauvegardées et cas de référence continueront d’attendre l’ancien contrat.
Modifications additives vs breaking
De façon approximative, les changements se répartissent en deux catégories.
Modifications additives — vous ajoutez quelque chose sans rien casser :
- un nouveau champ optionnel dans la réponse ;
- un nouvel argument optionnel avec une valeur par défaut ;
- des valeurs supplémentaires dans un enum, que l’UI et le modèle peuvent ignorer.
Par exemple, vous ajoutez dans la réponse de l’outil un champ deliveryEstimateDays, mais l’ancien widget l’ignore simplement. C’est sûr : le schéma peut s’élargir, sans obligation de l’utiliser.
Breaking changes — vous brisez des attentes existantes :
- vous rendez un champ obligatoire alors qu’il ne l’était pas ;
- vous changez le type (chaîne → objet) ;
- vous changez le sens des arguments (budget en USD → budget en devise locale) sans renommer les champs.
Dans ces cas, la seule voie sûre est d’introduire une nouvelle version de l’outil.
Patron Tool_v2
Patron classique : vous aviez recommend_gifts, vous voulez changer sérieusement le schéma. Vous ne touchez pas à l’ancien outil, vous créez un nouveau — recommend_gifts_v2.
// v1
const recommendGiftsInput_v1 = z.object({
occasion: z.string(),
budgetUsd: z.number().int().positive(),
});
// v2: prise en charge des devises et des filtres de livraison
const recommendGiftsInput_v2 = z.object({
occasion: z.string(),
maxPrice: z.number().int().positive(),
currency: z.enum(["USD", "EUR", "GBP"]),
deliverByDate: z.string().optional(); // chaîne ISO
});
server.registerTool({
name: "recommend_gifts",
description: "DEPRECATED: utilisez recommend_gifts_v2",
inputSchema: recommendGiftsInput_v1,
async execute(args) { /* ancienne logique */ },
});
server.registerTool({
name: "recommend_gifts_v2",
description:
"Sélection de cadeaux selon budget, devise et délai de livraison",
inputSchema: recommendGiftsInput_v2,
async execute(args) { /* nouvelle logique */ },
});
Le modèle et les anciens prompts/agents continueront d’utiliser recommend_gifts tant que vous ne les aurez pas mis à jour. Les nouveaux scénarios sont écrits pour recommend_gifts_v2.
Après une période de migration :
- les cas de référence (golden cases) et les agents sont passés sur v2 ;
- les métriques montrent que v1 est presque plus appelé ;
vous pouvez commencer à retirer v1 progressivement (par exemple, d’abord le masquer dans la liste des outils en dev/staging, puis en prod).
Versionnage des ressources
Les tools ne sont pas les seuls à nécessiter des versions. Si vous avez des ressources (resources) — par exemple un catalogue de cadeaux statique — il vaut mieux aussi les versionner.
Options courantes :
- inclure la version dans le nom de la ressource : gift_catalog.v1.json, gift_catalog.v2.json ;
- ou passer la version dans l’URI/paramètre : /api/catalog?version=1.
L’idée est la même : ne pas remplacer les données « sous les pieds » des scénarios déjà lancés, mais leur fournir explicitement une version figée du catalogue.
Migrations sans interruption
Cycle typique de migration d’un outil :
- Ajouter une nouvelle version de l’outil (_v2) en parallèle de l’ancienne.
- Mettre à jour l’App/les agents/le system prompt pour utiliser la nouvelle version.
- Exécuter les cas de référence et l’LLM‑eval pour les deux variantes et s’assurer que la qualité n’a pas chuté sur les scénarios critiques.
- Surveiller les métriques d’utilisation v1 vs v2 (et les erreurs).
- Quand le trafic sur v1 est proche de zéro, commencer à le désactiver.
Cette approche fonctionne bien pour les migrations de schémas, les mises à jour du SDK/protocole et les changements d’authentification. Nous avons vu comment les outils et ressources évoluent — via v1/v2 et des changements additifs prudents. L’autre grande partie du contrat — l’authentification et l’autorisation : OAuth, scopes et .well-known. Eux aussi vivent des années et exigent des migrations soigneuses.
5. Évolution de l’authentification : .well-known, scopes et OAuth existant
Si votre produit vit déjà dans l’univers OAuth 2.1/OpenID Connect, l’intégration avec ChatGPT via MCP n’est pas « une connexion de plus », mais un nouveau client qui doit dialoguer avec votre serveur d’autorisation selon les mêmes règles.
MCP et .well-known/oauth-protected-resource
Nous détaillons OAuth 2.1/OpenID Connect et la configuration du serveur d’auth dans un module séparé (voir le module sur l’authentification). Ici, concentrons‑nous sur l’aspect pratique : comment une ressource MCP signale à ChatGPT qu’elle est protégée par OAuth, et comment lancer le flux de liaison de compte (linking).
Patron standard pour des ressources MCP protégées :
- votre serveur MCP expose un endpoint spécial /.well-known/oauth-protected-resource ;
- dans la réponse, il indique de quelle ressource il s’agit et par quels AS (Authorization Server) elle est protégée ;
- sur un 401 lors d’un appel MCP, le serveur renvoie l’en‑tête WWW-Authenticate avec un lien vers ce .well-known, et ChatGPT lance lui‑même le flux OAuth (« Link account »).
Exemple minimal avec 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);
Et un handler de 401 avec un indice pour le client :
res
.status(401)
.set(
"WWW-Authenticate",
'Bearer resource_metadata="https://mcp.myapp.com/.well-known/oauth-protected-resource"',
)
.end();
En voyant cet en‑tête, ChatGPT comprend vers quel AS se tourner et comment lancer le flux OAuth pour votre ressource MCP.
Scopes et migrations d’autorisation
Les scopes sont une autre source de migrations. Nous les avons déjà étudiés en détail dans le module Auth, mais dans le contexte intégration/migrations, quelques points sont importants.
Imaginez que GiftGenius ne savait au départ que lire le catalogue (gifts.read), puis vous avez ajouté gifts.write pour créer des commandes. Il vous faut :
- ajouter le nouveau scope dans la configuration du client (ChatGPT App) ;
- mettre à jour le serveur MCP pour exiger ce scope uniquement pour les outils qui modifient réellement quelque chose ;
- documenter les changements dans .well-known, si nécessaire.
Du point de vue UX, l’utilisateur peut voir une demande d’« extension des droits » pour l’app ChatGPT lors de la prochaine utilisation de la nouvelle fonctionnalité. Vous ne voulez pas que cela survienne au milieu d’un dialogue sans prévenir — il faut donc :
- annoncer ces changements (release notes, documentation) ;
- tester en staging avec un AS de test ;
- coordonner avec la mise à jour des descriptions d’outils (destructiveHint, etc.) pour que le modèle appelle consciemment les tools « sensibles ».
6. Métadonnées et annotations : couche d’indices (hint) au‑dessus du contrat
La couche Auth répond à la question : qui peut faire quoi via votre App. Mais même avec des jetons et scopes corrects, il est crucial de savoir comment le modèle appellera vos outils et comment il expliquera ses actions à l’utilisateur. C’est là qu’intervient une couche d’indices supplémentaire : métadonnées et annotations.
Le contrat (schéma) dit ce que l’outil accepte et retourne. Les métadonnées et annotations aident le modèle à comprendre comment et quand l’appeler. Cela devient particulièrement important quand votre App évolue : nouvelles actions destructrices, changements d’UI, intégrations externes.
_meta["openai/widgetDescription"] et widgetCSP
Dans l’Apps SDK et les descriptions MCP, il existe un champ spécial _meta où OpenAI ajoute ses extensions de protocole. Par exemple :
- _meta["openai/widgetDescription"] — courte description de ce que votre widget affiche ; le modèle peut l’utiliser pour éviter de « paraphraser » l’UI et pour présenter correctement l’App ;
- _meta["openai/widgetCSP"] — déclaration des domaines CSP nécessaires à votre widget (pour fetch/images/scripts).
Quand vous modifiez l’UI (par exemple, en ajoutant une nouvelle étape de commande), il est utile de mettre à jour widgetDescription, afin que le modèle continue d’expliquer correctement ce qui se passe à l’utilisateur.
Annotations des outils (readOnlyHint, destructiveHint, openWorldHint)
Les annotations sont de simples indicateurs booléens qui influencent fortement l’UX et la sécurité :
- readOnlyHint: true — l’outil ne modifie rien (lecture). Le modèle peut l’appeler sans confirmations superflues.
- destructiveHint: true — l’outil peut supprimer/modifier des données. ChatGPT demandera une confirmation explicite.
- openWorldHint: true — l’outil publie des données vers l’extérieur ou peut renvoyer « énormément de choses », nécessitant une synthèse.
Exemple de descripteur d’outil avec annotations :
server.registerTool({
name: "delete_saved_gift",
description: "Supprime un cadeau enregistré par l’utilisateur",
inputSchema: z.object({ giftId: z.string() }),
annotations: {
readOnlyHint: false,
destructiveHint: true,
openWorldHint: false,
},
async execute({ giftId }) {
// ...nous supprimons le cadeau
},
});
Lors d’une migration, quand vous ajoutez de nouveaux outils « sensibles », les annotations sont vos alliées : elles aident ChatGPT à ne pas les exécuter en douce et incitent à un comportement plus prudent.
Il est important de comprendre que les annotations ne sont pas une « vraie » protection. Elles n’influencent que le comportement du client et du modèle. La véritable sécurité reste du ressort de votre serveur (Auth, scopes, validation).
7. Migrations du SDK et des spécifications MCP
MCP et l’Apps SDK évoluent activement — de nouveaux champs apparaissent dans les capabilities, de nouveaux types de messages, de nouveaux _meta/annotations. La documentation prévient honnêtement : « à l’état de 2025 » — et il faut faire avec.
Les migrations de versions de SDK et de specs font donc partie normale de la vie d’une App, pas un événement rare « un jour peut‑être ».
Processus de mise à niveau typique
Un scénario sain de mise à jour ressemble à ceci :
- Lire le changelog de la nouvelle version d’Apps SDK/MCP SDK. Noter tous les changements potentiellement breaking.
- Mettre à jour les dépendances en environnement dev/staging, sans toucher à la prod.
- Exécuter MCP Inspector / Jam ou un autre client :
- vérifier le handshake ;
- tools/list / resources/list ;
- quelques tools/call de test.
- Mettre à jour les descriptions d’outils et le _meta selon les nouvelles capacités :
- par exemple, ajouter de nouvelles annotations ou un widgetDescription.
- Exécuter les cas de référence et l’LLM‑eval évoqués dans les cours précédents pour s’assurer que la qualité perçue de l’App n’a pas chuté.
- Ne déployer en production qu’ensuite, si possible en canary/feature‑flag sur un sous‑ensemble de trafic.
Exemple : ajout de openWorldHint dans une nouvelle version du SDK
Supposons que la nouvelle version de l’Apps SDK ajoute la prise en charge de openWorldHint, et que vous décidiez d’annoter l’outil search_public_reviews, lequel parcourt des avis externes et peut renvoyer beaucoup de bruit.
Les étapes :
- mettre à jour le SDK et les types ;
- ajouter annotations.openWorldHint = true dans le descripteur de l’outil ;
- mettre à jour le system prompt pour que l’agent annonce explicitement qu’il va interroger le monde extérieur ;
- exécuter des cas de référence de sécurité (en particulier sur la vie privée/PII) pour s’assurer que le modèle n’est pas devenu trop bavard.
Nous avons décrit le processus général de mise à jour du SDK et des annotations. Voyons maintenant tout cela dans un scénario concret — l’évolution de l’outil recommend_gifts.
8. Mini‑cas : évolution de recommend_gifts dans GiftGenius
Rassemblons tout sur un scénario concret.
Version initiale
L’outil de base ressemblait à ceci :
const recommendGiftsInput_v1 = z.object({
occasion: z.string(),
budgetUsd: z.number().int().positive(),
recipientProfile: z.string(),
});
server.registerTool({
name: "recommend_gifts",
description: "Suggère des idées de cadeaux en USD",
inputSchema: recommendGiftsInput_v1,
async execute(args) {
const input = recommendGiftsInput_v1.parse(args);
return giftService.recommend(input); // fonction interne
},
});
Tout va bien tant que vous n’avez que des utilisateurs aux États‑Unis et une seule devise.
Nouveaux besoins métier : multidevise et date limite
L’équipe produit arrive avec de nouvelles exigences :
- prise en charge de EUR/GBP ;
- prise en compte d’une date limite de livraison (ne pas proposer des cadeaux livrés dans un mois si l’anniversaire est dans trois jours) ;
- ajout souhaité d’une estimation du délai de livraison dans la réponse.
Approche naïve : changer simplement les champs :
- renommer budgetUsd en maxPrice ;
- ajouter currency ;
- ajouter deliveryEstimateDays dans la réponse.
Que va‑t‑il se passer ?
Les anciens prompts (y compris les cas de référence et la description dans le system prompt) et les conversations sauvegardées continuent d’envoyer budgetUsd. Le modèle ne sait pas qu’il n’existe plus. La couche MCP commencera à échouer lors du parse. Le comportement de l’app ChatGPT casse soudainement pour des utilisateurs réels.
La bonne voie :
- Ajouter un nouveau schéma et un nouvel outil _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:
"Sélection de cadeaux avec devise et date souhaitée de livraison",
inputSchema: recommendGiftsInput_v2,
async execute(args) {
const input = recommendGiftsInput_v2.parse(args);
return giftService.recommendV2(input); // nouvelle logique
},
});
- Laisser recommend_gifts en l’état, avec une mention description DEPRECATED.
- Mettre à jour le system prompt et les descriptions de l’App pour que le modèle privilégie recommend_gifts_v2 (vous pouvez l’indiquer explicitement dans les instructions).
- Mettre à jour le widget GiftGenius pour qu’il comprenne le nouveau format de réponse : champ deliveryEstimateDays, etc.
- Exécuter des cas de référence (sélection de cadeaux avant une certaine date) via LLM‑eval.
Tests et observabilité
Quelques tests souhaitables :
Test de contrat pour la nouvelle entrée :
test("v2 accepte un scénario avec EUR et date limite", () => {
const sample = {
occasion: "birthday",
maxPrice: 100,
currency: "EUR",
recipientProfile: "collègue",
deliverByDate: "2025-12-24",
};
expect(() => recommendGiftsInput_v2.parse(sample)).not.toThrow();
});
Observation en production :
- métrique de la part des appels recommend_gifts_v2 vs recommend_gifts ;
- taux d’erreur sur v1 (on s’attend à ce qu’il n’augmente pas) ;
- score LLM‑eval sur les cas de référence avant/après migration (d’après les cours précédents, vous savez déjà comment faire).
Quand v2 « gagne » à la fois en qualité et en utilisation, vous pouvez planifier prudemment la désactivation de v1.
En simplifiant en trois idées : (1) MCP est un adaptateur fin, pas un nouveau monolithe ; (2) schémas, auth et annotations constituent un contrat de longue durée entre ChatGPT et votre backend, à versionner et tester aussi soigneusement que des API classiques ; (3) toute migration de SDK/specs est un processus d’ingénierie normal avec staging, cas de référence et observabilité, pas un « update de paquet un vendredi soir ». Si vous regardez l’app ChatGPT sous cet angle, les intégrations avec un produit existant cessent de paraître chaotiques.
9. Erreurs typiques lors des intégrations et migrations MCP/SDK
Erreur n° 1 : considérer MCP comme un « nouveau backend » et non un adaptateur fin.
La tentation est grande d’embarquer toute la logique métier dans la couche MCP : accès DB, règles métier, calculs. Cela transforme le serveur MCP en un monolithe supplémentaire, difficile à synchroniser avec le reste du backend. Bien plus sain de conserver MCP comme Gateway/Adapter au‑dessus des services existants : toute la logique métier vit là où elle vivait avant ChatGPT, et MCP ne fait que traduire le JSON aller‑retour.
Erreur n° 2 : des schémas différents pour un même objet.
Antipattern courant : trois définitions du « cadeau » : une en base, une dans l’API REST, une dans l’outil MCP, toutes légèrement différentes. À la clé, typage statique cassé, contrats bancals, tests et bon sens mis à mal. Utiliser un schéma unique (Zod/TypeBox, etc.) comme Single Source of Truth et générer le JSON Schema pour MCP réduit fortement ce risque.
Erreur n° 3 : mauvaises migrations de schéma — breaking change « silencieux ».
Renommer un champ ou changer son sens sans changer le nom de l’outil conduit à un régressif caché. Le modèle continue d’envoyer l’ancien format, l’incident n’apparaîtra que pour une partie des utilisateurs et pas immédiatement. Pour les changements sérieux, introduisez *_v2, laissez l’ancienne version tourner en parallèle, utilisez les mentions de dépréciation et le monitoring.
Erreur n° 4 : ignorer les changements d’Auth et les scopes.
Vous avez ajouté un nouvel outil avec effets de bord, mais oublié de mettre à jour les scopes et le .well-known ? L’utilisateur risque d’avoir un 401 au milieu d’un scénario, ou au contraire, votre MCP pourrait exécuter des opérations destructrices sans autorisation adéquate. Planifiez les migrations de la couche auth avec autant de soin que celles des schémas : staging, tests et extension progressive des droits.
Erreur n° 5 : ne pas utiliser les annotations (destructiveHint, readOnlyHint, openWorldHint).
Si vous n’indiquez pas au modèle quels outils sont sûrs et lesquels sont potentiellement dangereux, son comportement peut être surprenant : demander une confirmation pour un get_catalog anodin et exécuter une suppression de données sans avertissement. De bonnes annotations rendent le comportement prévisible pour l’utilisateur et réduisent le risque d’incidents de qualité et de sécurité.
Erreur n° 6 : mise à jour du SDK « en prod » sans exécuter les cas de référence.
Une nouvelle version du SDK/spec peut ajouter des champs, modifier le handshake ou la structure des messages. Si vous « mettez à jour les dépendances et déployez » directement, vous risquez un régressif de qualité (le modèle n’appelle plus l’outil attendu, la formulation des erreurs change, etc.). D’abord — dev/staging, MCP Inspector, puis cas de référence et LLM‑eval, et seulement ensuite — la prod.
Erreur n° 7 : logique métier rigidement liée à une version d’outil.
Si la logique interne de Gift Service dépend directement de recommend_gifts, migrer vers recommend_gifts_v2 devient pénible. Bonne pratique : disposer d’un service interne qui évolue selon ses propres règles, et des outils *_v1, *_v2 comme de simples adaptateurs mappant anciens et nouveaux contrats externes vers des structures métier communes.
Erreur n° 8 : absence d’observabilité par version d’outil.
Si logs et métriques ne distinguent pas l’outil et la version appelés, le débogage des migrations devient de la divination. Journalisez le nom de l’outil, la version du schéma/SDK et les paramètres clés — toute régression se rattache alors plus facilement à un changement précis.
GO TO FULL VERSION