CodeGym /Corsi /ChatGPT Apps /Integrazione di ChatGPT App in un prodotto esistente e mi...

Integrazione di ChatGPT App in un prodotto esistente e migrazioni SDK/MCP

ChatGPT Apps
Livello 20 , Lezione 2
Disponibile

1. Perché parlare di integrazioni e migrazioni

Finora abbiamo progettato API e strumenti principalmente come ci tornava comodo. Nella vita reale quasi sempre è il contrario: avete già:

  • un monolite o un pacchetto di microservizi;
  • API REST/GraphQL;
  • business logic che gira in produzione da anni.

E all’improvviso arriva il compito: «Collegate il nostro prodotto a ChatGPT tramite Apps SDK e MCP».

Rifare tutto per un «MCP server ideale» — non è un’opzione. Serve «indossare» con cura sopra il mondo esistente un sottile livello che traduca il linguaggio del vostro backend nel linguaggio di ChatGPT: strumenti, risorse e schemi.

Secondo problema: il prodotto è vivo. Schemi e API cambiano. Nel frontend tradizionale almeno ottenete subito un errore TypeScript quando cambiate un campo. Nel mondo delle LLM‑Apps è più subdolo: il modello continuerà a inviare con sicurezza il vecchio formato, il tool cadrà, e invece di un bel fallimento in fase di build otterrete:

  • errori a runtime sul server MCP;
  • allucinazioni «più o meno ho capito cosa volevi da questo campo»;
  • spiacevoli incidenti di qualità.

Pertanto in questa lezione guardiamo allo strato MCP+Apps come a:

  • un adattatore al backend esistente;
  • un contratto da mantenere per anni;
  • un soggetto di migrazioni: versioni, annotazioni, scopes e SDK.

2. Architettura di integrazione: MCP come adattatore sopra il backend esistente

Quadro di base

Ricordiamo lo stack, ma ora con la lente del production:

flowchart LR
  U[Utente in ChatGPT] --> G[Modello ChatGPT]
  G -->|chiama App| W["Widget (Apps SDK, Next.js)"]
  G -->|tools.call| MCP[Server MCP / Gateway]
  MCP --> S1["Gift Service (il vostro servizio esistente)"]
  MCP --> S2["Commerce Service (ordini, ACP)"]

ChatGPT comunica con il vostro mondo non direttamente, ma tramite il protocollo MCP: elenco di tools/resources, chiamate tools/call, streaming di eventi.

Il server MCP in questo schema è esattamente quell’adattatore: conosce sia ChatGPT (JSON‑RPC, strumenti) sia i vostri servizi (REST/DB/code), e traduce dall’uno all’altro.

MCP come Gateway/Adapter

Impostazione classica: avete già un Gift Service con endpoint REST:

// Esempio di REST API esistente
GET  /api/gifts/recommendations?budget=100&occasion=birthday
POST /api/orders

Invece di scrivere nuova business logic, lo strato MCP incapsula semplicemente questo in un Tool:

// mcp/tools/recommendGifts.ts
import { z } from "zod";
import { server } from "./mcpServer"; // istanza ipotetica dell'SDK

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

server.registerTool({
  name: "recommend_gifts",
  description: "Suggerisce idee regalo entro il 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(); // importante: ritorniamo JSON, comodo sia per il modello sia per il widget
  },
});

Tutta la logica di selezione dei regali resta dentro il vostro servizio esistente. Lo strato MCP è un «traduttore sottile» dal linguaggio di ChatGPT al linguaggio delle vostre API.

Talvolta lo strato MCP instrada anche le richieste verso più servizi backend. In tal caso diventa un vero e proprio MCP Gateway — approfondirete il suo ruolo nel modulo su production e rete.

Monolith-integrated MCP vs Sidecar MCP

Ci sono due opzioni di base su dove «agganciare» questo strato MCP.

Testualmente appare così:

Variante Descrizione Dove vive il codice MCP
Monolith-integrated Tutto in un unico servizio Next.js/Node Nelle API routes di Next.js o in Express
Sidecar MCP Container/servizio separato che comunica con le API Applicazione separata in Node/Go

Nei progetti piccoli spesso basta la prima opzione: applicazione Next.js, deploy su Vercel, lì una route /mcp o /api/mcp, e il server MCP vive accanto alle altre API.

Esempio (molto semplificato):

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

In un’architettura più matura, con più servizi di dominio (Gift, Commerce, Analytics), conviene estrarre lo strato MCP in un Gateway separato. Riceverà traffico MCP da ChatGPT e instraderà le chiamate ai vari backend in base al nome dello strumento.

Importante ricordare: per ChatGPT e l’Apps SDK è comunque un solo server MCP. Dove esattamente gira — dentro al monolite o come microservizio separato — è una vostra decisione architetturale.

Chiarita l’architettura dello strato MCP: può vivere dentro il monolite o come Gateway separato. Resta la domanda cosa esattamente questo strato riceve e restituisce — ed è qui che entrano in scena schemi e contratti.

3. Single Source of Truth: schemi, tipi e test di contratto

Se avete DTO interni, contratti REST esterni e anche schemi MCP per gli strumenti — la tentazione di «disegnare gli schemi a occhio» è enorme. Il risultato è prevedibile:

  • modificate un campo nel backend, vi dimenticate di aggiornare la schema dello strumento;
  • il modello continua a inviare il vecchio formato;
  • ottenete un allegro zoo a runtime.

La via sana: creare un unico punto di verità per la struttura dei dati e usarlo ovunque. Nel mondo TypeScript è molto comodo farlo con Zod o librerie simili, che l’MCP SDK sa convertire in JSON Schema.

Schema Zod condiviso per GiftGenius

Supponiamo che il vostro servizio Gift nel nostro GiftGenius didattico usi già Zod per la validazione dell’input:

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

export const giftRecommendationInputSchema = z.object({
  occasion: z.string().describe("Occasione: birthday, wedding, ecc."),
  budgetUsd: z.number().int().positive(),
  recipientProfile: z.string().describe("Breve descrizione della persona"),
});

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

La stessa schema è usata:

  • nel REST endpoint (per convalidare il corpo della richiesta);
  • nello strumento MCP (come inputSchema);
  • nei test (come base per i fixture).

Colleghiamo lo schema allo strumento MCP

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

server.registerTool({
  name: "recommend_gifts",
  description: "Selezione di idee regalo per profilo e 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();
  },
});

L’SDK convertirà automaticamente lo schema Zod in JSON Schema, che ChatGPT vedrà in tools/list. Questo risolve due problemi in una volta:

  • i tipi degli argomenti dello strumento e del codice sono strettamente legati;
  • quando cambia lo schema, il compilatore TypeScript vi costringerà ad aggiornare anche l’handler.

Test di contratto per MCP ↔ backend

I test di contratto qui non sono una parolaccia, ma alcune verifiche molto concrete.

Un semplice unit/contract test può apparire così:

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

test("l'esempio di richiesta è conforme allo schema dello strumento", () => {
  const sample = {
    occasion: "birthday",
    budgetUsd: 150,
    recipientProfile: "collega, ama i gadget",
  };

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

Un test del genere non garantisce che il mondo sia perfetto, ma almeno cattura il disallineamento tra le aspettative del backend e lo strato MCP, se cambiate lo schema e vi dimenticate di aggiornare i fixture.

Questo approccio si estende facilmente a:

  • risposte mock di API esterne (Stripe, CMS);
  • esecuzione di un client MCP contro un server MCP reale in ambiente di test.

4. Strategie di versioning di tools e resources

Gli schemi prima o poi cambiano. L’importante è non farlo alla «rinomino il campo, cosa potrà mai andare storto». Nel mondo LLM si può rompere non solo la build, ma anche il comportamento del modello: vecchi prompt, dialoghi salvati e golden case continueranno ad aspettarsi il vecchio contratto.

Modifiche additive vs breaking

Grossomodo le modifiche si dividono in due categorie.

Modifiche additive — aggiungete qualcosa, ma non rompete nessuno:

  • nuovo campo opzionale nella risposta;
  • nuovo argomento opzionale con valore di default;
  • valori aggiuntivi di un enum cui UI e modello possono essere indifferenti.

Ad esempio, aggiungete nella risposta dello strumento il campo deliveryEstimateDays, ma il vecchio widget lo ignora. È sicuro: lo schema può ampliarsi, ma nessuno è obbligato a usarlo.

Modifiche breaking — rompete aspettative esistenti:

  • rendete obbligatorio un campo che prima non c’era;
  • cambiate tipo (stringa → oggetto);
  • cambiate il significato degli argomenti (budget in USD → budget in valuta locale, senza cambiare i nomi dei campi).

In questi casi l’unica strada sicura è introdurre una nuova versione dello strumento.

Pattern Tool_v2

Pattern classico: avevate recommend_gifts, e volete cambiare seriamente lo schema. Non toccate lo strumento vecchio, ma create il nuovo — recommend_gifts_v2.

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

// v2: supporto di valute e filtri di consegna
const recommendGiftsInput_v2 = z.object({
  occasion: z.string(),
  maxPrice: z.number().int().positive(),
  currency: z.enum(["USD", "EUR", "GBP"]),
  deliverByDate: z.string().optional(); // stringa ISO
});

server.registerTool({
  name: "recommend_gifts",
  description: "DEPRECATED: usate recommend_gifts_v2",
  inputSchema: recommendGiftsInput_v1,
  async execute(args) { /* logica vecchia */ },
});

server.registerTool({
  name: "recommend_gifts_v2",
  description:
    "Selezione di regali per budget, valuta e scadenza di consegna",
  inputSchema: recommendGiftsInput_v2,
  async execute(args) { /* logica nuova */ },
});

Il modello e i vecchi prompt/agent continueranno a usare recommend_gifts finché non li aggiornate. I nuovi scenari li scrivete già per recommend_gifts_v2.

Dopo un periodo di migrazione:

  • i golden case e gli agent sono stati portati su v2;
  • le metriche mostrano che v1 è quasi inutilizzato;

si può iniziare a dismettere con cura v1 (ad esempio, prima nasconderlo dall’elenco strumenti in dev/staging, poi in production).

Versioning delle risorse

I tools non sono gli unici a necessitare versioni. Se avete risorse (resources) — per esempio un catalogo statico di regali — è bene versionarle.

Varianti popolari:

  • inserire la versione nel nome della risorsa: gift_catalog.v1.json, gift_catalog.v2.json;
  • oppure passare la versione in URI/parametro: /api/catalog?version=1.

Il senso è lo stesso: non sostituire i dati «sotto i piedi» di scenari già avviati, ma fornire loro una versione del catalogo esplicitamente fissata.

Migrazioni senza downtime

Ciclo tipico di migrazione di uno strumento:

  1. Aggiungete la nuova versione dello strumento (_v2) in parallelo alla vecchia.
  2. Aggiornate App/agent/system prompt affinché usino la nuova versione.
  3. Eseguite i golden case e l’LLM‑eval per entrambe le varianti e assicuratevi che per gli scenari critici la qualità non sia calata.
  4. Osservate le metriche d’uso di v1 vs v2 (e gli errori).
  5. Dopo che il traffico su v1 è vicino a zero, iniziate a disattivarlo.

Questo approccio funziona bene sia per migrazioni di schemi, sia per aggiornamenti di SDK/protocollo, sia per modifiche di Auth. Abbiamo capito come evolvono strumenti e risorse — tramite v1/v2 e modifiche additive caute. La seconda grande parte del contratto è autenticazione e autorizzazione: OAuth, scopes e .well-known. Anche questi vivono per anni e richiedono migrazioni accurate.

5. Evoluzione dell’autenticazione: .well-known, scopes e OAuth esistente

Se il vostro prodotto vive già nel mondo OAuth 2.1/OpenID Connect, l’integrazione con ChatGPT via MCP non è «un altro login», ma un nuovo client che deve parlare con il vostro Authorization Server secondo le regole comuni.

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

Parliamo in dettaglio di OAuth 2.1/OpenID Connect e della configurazione dell’Auth Server in un modulo separato del corso (vedi il modulo sull’autenticazione). Qui ci interessa l’aspetto pratico: come una risorsa MCP comunica a ChatGPT che è protetta da OAuth e come avviare il linking flow.

Pattern standard per risorse MCP protette:

  • il vostro server MCP espone un endpoint speciale /.well-known/oauth-protected-resource;
  • nella risposta dichiara che risorsa è e da quali AS (Authorization Server) è protetta;
  • a fronte di 401 su una chiamata MCP, il server restituisce l’header WWW-Authenticate con il link a questo .well-known, e ChatGPT avvia da sé l’OAuth flow («Link account»).

Esempio minimo con 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 handler di 401 con suggerimento per il client:

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

Vedendo questo header, ChatGPT capisce a quale AS rivolgersi e come avviare l’OAuth flow per la vostra risorsa MCP.

Scopes e migrazioni dell’autorizzazione

Gli scope sono un’altra fonte di migrazioni. Ne abbiamo parlato in dettaglio nel modulo su Auth, ma nel contesto di integrazione/migrazioni contano alcuni aspetti.

Immaginate che GiftGenius all’inizio sapesse solo leggere il catalogo (gifts.read), e poi avete aggiunto gifts.write per creare ordini. Dovete:

  • aggiungere il nuovo scope nella configurazione del client (ChatGPT App);
  • aggiornare il server MCP affinché richieda questo scope solo per gli strumenti che realmente modificano qualcosa;
  • descrivere le modifiche nel .well-known, se necessario.

Dal punto di vista UX, al successivo tentativo di usare la nuova funzionalità l’utente può vedere una richiesta di «estendere i permessi» per l’app di ChatGPT. Non volete che accada nel bel mezzo di un dialogo in corso senza preavviso — quindi tali modifiche vanno:

  • annunciate (release notes, documentazione);
  • testate in staging con un AS di prova;
  • coordinate con l’aggiornamento delle descrizioni degli strumenti (destructiveHint ecc.), in modo che il modello invochi consapevolmente i tools «pericolosi».

6. Metadati e annotazioni: strato di hint sopra il contratto

Lo strato Auth risponde alla domanda chi e cosa può fare tramite la vostra App. Ma anche con token e scopes corretti è importante come il modello chiamerà i vostri strumenti e spiegherà le azioni all’utente. Qui entra in gioco uno strato aggiuntivo di hint: metadati e annotazioni.

Il contratto (schema) dice cosa lo strumento accetta e restituisce. Metadati e annotazioni aiutano il modello a capire come e quando chiamarlo. Diventa particolarmente importante quando evolvete l’App: aggiungete azioni destructive, cambiate l’UI, introducete integrazioni col mondo esterno.

_meta["openai/widgetDescription"] e widgetCSP

Nell’Apps SDK e nelle descrizioni MCP c’è un campo speciale _meta, dove OpenAI aggiunge le proprie estensioni di protocollo. Per esempio:

  • _meta["openai/widgetDescription"] — breve descrizione di cosa mostra il vostro widget; il modello può usarla per non «rispiegare» l’UI e annunciare correttamente l’App;
  • _meta["openai/widgetCSP"] — dichiarazione dei domini CSP di cui il vostro widget ha bisogno (per fetch/immagini/script).

Quando cambiate l’UI (ad esempio, aggiungete un nuovo step del checkout), è utile aggiornare widgetDescription, affinché il modello continui a spiegare correttamente all’utente cosa sta succedendo.

Annotazioni degli strumenti (readOnlyHint, destructiveHint, openWorldHint)

Le annotazioni sono semplici flag booleani che incidono molto su UX e sicurezza:

  • readOnlyHint: true — lo strumento non modifica nulla (lettura). Il modello può chiamarlo senza conferme superflue.
  • destructiveHint: true — lo strumento può eliminare/modificare qualcosa. ChatGPT chiederà una conferma esplicita.
  • openWorldHint: true — lo strumento pubblica dati all’esterno o può restituire «tantissime cose», richiedendo una sintesi.

Esempio di descrittore di strumento con annotazioni:

server.registerTool({
  name: "delete_saved_gift",
  description: "Elimina un regalo salvato dell'utente",
  inputSchema: z.object({ giftId: z.string() }),
  annotations: {
    readOnlyHint: false,
    destructiveHint: true,
    openWorldHint: false,
  },
  async execute({ giftId }) {
    // ...eliminiamo il regalo
  },
});

In migrazione, quando aggiungete nuovi strumenti «pericolosi», le annotazioni sono vostre alleate: aiutano ChatGPT a non eseguirli di nascosto e spingono verso un comportamento più cauto.

È importante capire che le annotazioni non sono «sicurezza vera». Influenzano solo il comportamento del client e del modello. La sicurezza reale la assicura comunque il vostro server (Auth, scopes, validazione).

7. Migrazioni dell’SDK e delle specifiche MCP

MCP e Apps SDK si evolvono attivamente — compaiono nuovi campi nelle capabilities, nuovi tipi di messaggi, nuovi _meta/annotations. La documentazione avverte onestamente: «allo stato del 2025» — e a questo dobbiamo adeguarci.

Perciò le migrazioni di versioni di SDK e spec sono una parte normale della vita dell’App, non un evento raro «prima o poi».

Processo tipico di upgrade

Uno scenario sano di aggiornamento è all’incirca questo:

  1. Leggete il changelog della nuova versione di Apps SDK/MCP SDK. Segnate tutte le potenziali modifiche breaking.
  2. Aggiornate le dipendenze in ambiente dev/staging, senza toccare la produzione.
  3. Eseguite MCP Inspector / Jam o un altro client:
    • verificate l’handshake;
    • tools/list / resources/list;
    • alcune tools/call di prova.
  4. Aggiornate le descrizioni degli strumenti e il _meta in accordo con le nuove capacità:
    • per esempio, aggiungete nuove annotations o widgetDescription.
  5. Eseguite i golden case e l’LLM‑eval, come detto nelle lezioni precedenti, per assicurarvi che il comportamento dell’App dal punto di vista della qualità non sia peggiorato.
  6. Solo dopo fate il deploy in produzione, possibilmente usando canary/feature flag per un sottoinsieme di traffico.

Esempio: aggiungiamo openWorldHint nella nuova versione dell’SDK

Supponiamo che la nuova versione dell’Apps SDK abbia aggiunto il supporto di openWorldHint, e decidete di contrassegnare con esso lo strumento search_public_reviews, che esplora recensioni esterne e può restituire molto rumore.

I passi sono:

  • aggiornate SDK e tipi;
  • aggiungete annotations.openWorldHint = true nel descrittore dello strumento;
  • aggiornate il system prompt affinché l’agente spieghi esplicitamente all’utente che ora ci sarà una richiesta verso l’esterno;
  • eseguite i safety golden case (soprattutto su temi di privacy/PII) per assicurarvi che il modello non sia diventato eccessivamente loquace.

Abbiamo discusso il processo di aggiornamento di SDK e annotazioni. Vediamo ora tutto questo in uno scenario concreto — l’evoluzione dello strumento recommend_gifts.

8. Mini‑case: evoluzione di recommend_gifts in GiftGenius

Mettiamo tutto insieme su uno scenario concreto.

Versione iniziale

Lo strumento di base appariva così:

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

server.registerTool({
  name: "recommend_gifts",
  description: "Suggerisce idee regalo in USD",
  inputSchema: recommendGiftsInput_v1,
  async execute(args) {
    const input = recommendGiftsInput_v1.parse(args);
    return giftService.recommend(input); // funzione interna
  },
});

Tutto bene finché avete solo utenti dagli USA e una sola valuta.

Nuovi requisiti di business: multivaluta e scadenza

Il team di prodotto arriva con nuovi requisiti:

  • serve supportare EUR/GBP;
  • serve considerare la scadenza di consegna (non mostrare regali che arrivano tra un mese se il compleanno è tra tre giorni);
  • idealmente aggiungere nella risposta una stima dei tempi di consegna.

Approccio ingenuo: cambiare semplicemente i campi:

  • rinominare budgetUsd in maxPrice;
  • aggiungere currency;
  • aggiungere nella risposta deliveryEstimateDays.

Cosa andrà storto?

I vecchi prompt (inclusi golden case e descrizione nel system prompt) e i dialoghi salvati continuano a inviare budgetUsd. Il modello non sa che non esiste più. Lo strato MCP inizierà a fallire tentando il parse. Il comportamento dell’App ChatGPT si rompe all’improvviso per utenti reali.

La strada giusta:

  1. Aggiungere una nuova schema e un nuovo strumento _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:
    "Selezione di regali con valuta e data di consegna desiderata",
  inputSchema: recommendGiftsInput_v2,
  async execute(args) {
    const input = recommendGiftsInput_v2.parse(args);
    return giftService.recommendV2(input); // nuova logica
  },
});
  1. Lasciare recommend_gifts com’è, aggiungendo in description la dicitura DEPRECATED.
  2. Aggiornare system prompt e descrizioni dell’App in modo che il modello preferisca recommend_gifts_v2 (si può indicarlo esplicitamente nelle istruzioni).
  3. Aggiornare il widget GiftGenius affinché capisca il nuovo formato di risposta: il campo deliveryEstimateDays ecc.
  4. Eseguire i golden case per gli scenari tipici (selezione regali entro una certa data) con LLM‑eval.

Test e osservabilità

Un paio di test che è bene avere:

Test di contratto per il nuovo input:

test("v2 accetta uno scenario con EUR e scadenza", () => {
  const sample = {
    occasion: "birthday",
    maxPrice: 100,
    currency: "EUR",
    recipientProfile: "collega",
    deliverByDate: "2025-12-24",
  };

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

Osservabilità in produzione:

  • metrica della quota di chiamate a recommend_gifts_v2 vs recommend_gifts;
  • error rate per v1 (ci aspettiamo che non cresca);
  • LLM‑eval score sui golden case prima/dopo la migrazione (dalle lezioni precedenti sapete già come farlo).

Quando v2 «vince» sia in qualità sia nelle metriche d’uso, si può pianificare con cautela la disattivazione di v1.

Se riduciamo a tre idee: (1) MCP è un adattatore sottile, non un nuovo monolite; (2) schemi, auth e annotazioni sono un contratto di lunga durata tra ChatGPT e il vostro backend, da versionare e testare con la stessa cura delle normali API; (3) qualsiasi migrazione di SDK/spec è un processo ingegneristico normale con staging, golden case e osservabilità, non «abbiamo aggiornato il pacchetto di venerdì sera». Guardando ChatGPT App con questa lente, le integrazioni con il prodotto esistente smetteranno di sembrare caotiche.

9. Errori tipici nell’integrazione e nelle migrazioni MCP/SDK

Errore n. 1: MCP come «nuovo backend», non come sottile adattatore.
Talvolta si vorrebbe trascinare nello strato MCP tutta la business logic: accessi al DB, regole di dominio, calcoli. Questo trasforma il server MCP in un altro monolite, difficile da sincronizzare col resto del backend. Molto più sano mantenere MCP come Gateway/Adapter sopra i servizi esistenti: tutta la logica di dominio vive dove viveva prima di ChatGPT, e MCP traduce solo JSON avanti‑indietro.

Errore n. 2: Schemi diversi per lo stesso oggetto.
Antipattern diffuso — avere tre definizioni di «regalo»: una nel DB, una nella REST API, una nello strumento MCP, tutte leggermente diverse. Ne conseguono tipizzazione statica rotta, contratti fragili, test e buon senso in crisi. Usare uno schema unico (Zod/TypeBox ecc.) come Single Source of Truth e generare JSON Schema per MCP riduce notevolmente il rischio.

Errore n. 3: Migrazioni di schema sbagliate — breaking change «silenzioso».
Rinominare un campo o cambiarne il senso senza cambiare il nome dello strumento porta a regressi nascosti. Il modello continua a inviare il vecchio formato, e l’incidente emerge solo per una parte degli utenti e non subito. Per cambiamenti seri introducete *_v2, lasciate la vecchia versione in parallelo, usate note di deprecation e monitoraggio.

Errore n. 4: Ignorare modifiche di Auth e scopes.
Avete aggiunto un nuovo strumento con effetti collaterali, ma vi siete dimenticati di aggiornare scopes e .well-known? L’utente può ricevere 401 a metà scenario, oppure, al contrario, il vostro MCP può eseguire operazioni destructive senza autorizzazione adeguata. Pianificate le migrazioni dello strato auth con la stessa cura delle migrazioni degli schemi: tramite staging, test e progressiva estensione dei permessi.

Errore n. 5: Non usare le annotazioni (destructiveHint, readOnlyHint, openWorldHint).
Se non suggerite al modello quali strumenti sono sicuri e quali potenzialmente pericolosi, può comportarsi in modo inatteso: chiedere conferme per un innocuo get_catalog ed eseguire cancellazioni senza preavviso. Le annotazioni corrette rendono il comportamento prevedibile per l’utente e riducono il rischio di incidenti di qualità e sicurezza.

Errore n. 6: Aggiornare l’SDK «in produzione» senza eseguire i golden case.
La nuova versione dell’SDK/spec può aggiungere campi, cambiare il comportamento dell’handshake o la struttura dei messaggi. Se «aggiornate le dipendenze e fate deploy» e basta, rischiate regressi di qualità (il modello smette di chiamare lo strumento giusto, cambia la formulazione degli errori ecc.). Prima — dev/staging, MCP Inspector, poi golden case e LLM‑eval, e solo dopo — produzione.

Errore n. 7: Forte dipendenza della business logic da una versione di strumento.
Quando la logica interna del Gift Service dipende direttamente da uno specifico recommend_gifts, è difficile migrare a recommend_gifts_v2 senza dolore. Best practice — avere un servizio interno che evolve secondo le proprie regole, mentre gli strumenti *_v1, *_v2 sono solo thin adapter che mappano i contratti esterni vecchi e nuovi su strutture di dominio comuni.

Errore n. 8: Mancanza di osservabilità per versione degli strumenti.
Se in log e metriche non distinguete quale strumento e versione sono stati chiamati, il debug delle migrazioni diventa un’ipotesi. Loggate nome dello strumento, versione di schema/SDK e parametri chiave — così qualunque regresso si associa più facilmente a uno specifico cambiamento.

Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION