Il mio server MCP prima smetteva semplicemente di funzionare. Nessun crash dump. Nessuna stack trace nei log. I client si connettevano senza lamentarsi, poi, dopo poche ore, tutto diventava muto. Le richieste scomparivano e l'agente AI dall'altra parte non riceveva altro che il vuoto.

Questa è una storia frustrantemente comune nell'ecosistema Model Context Protocol (MCP). Il protocollo definisce come gli agenti AI scoprono e chiamano strumenti esterni, ma la specifica presuppone che tu gestisca gli errori autonomamente. La maggior parte dei tutorial e delle implementazioni iniziali salta questa parte. Si concentrano sul "happy path": annotare una funzione, esporla tramite il server e restituire un risultato pulito. Raramente mostrano cosa succede quando un glitch di rete colpisce la tua API esterna, o quando il modello allucina il nome di un parametro e invia input errati. Il risultato è un server fragile che sembra in salute, ma che in realtà è morto da ore.

Perché le risposte vuote sono peggiori dei crash

Quando un'eccezione non gestita scivola attraverso un gestore di strumenti MCP, lo strato di trasporto spesso la inghiotte. Il processo del server rimane attivo, il socket resta aperto, ma il client riceve una risposta vuota. Questo è più pericoloso di un crash rumoroso perché il tuo monitoraggio potrebbe non accorgersene. Il processo è ancora in esecuzione. La porta è ancora in ascolto. Eppure, ogni chiamata allo strumento non restituisce nulla.

Il modello AI non interpreta il silenzio come un fallimento. Lo interpreta come una chiamata riuscita che non ha prodotto dati. Quella risposta vuota addestra il modello a improvvisare. Inizia ad allucinare fatti per colmare il vuoto, o entra in un ciclo di tentativi ripetuti della stessa chiamata interrotta. Problemi minori, come un timeout di rete transitorio o un argomento di uno strumento non valido, non dovrebbero mai essere lasciati causare questo tipo di comportamento.

Il Wrapper Pattern: tre linee di difesa

Ho risolto il problema avvolgendo ogni gestore di strumenti in un sottile strato di recupero degli errori. Il wrapper non cerca di prevedere ogni possibile guasto. Li categorizza e risponde di conseguenza.

ConnectionError e TimeoutError
Questi si verificano quando il tuo server comunica con un'API esterna e la rete vacilla. La soluzione istintiva è riavviare l'intero processo del server MCP. Non farlo. Il riavvio interrompe le connessioni attive dei client, cancella qualsiasi stato in memoria e forza una completa ri-inizializzazione. Invece, cattura l'errore di connessione e riconnetti solo lo strato di trasporto o il client HTTP utilizzato dal tuo strumento. Il server rimarrà attivo e pronto per la richiesta successiva immediatamente.

ValueError
Questo è ciò che vedi quando il client AI invia argomenti malformati. Magari il modello ha inventato un parametro, ha passato una stringa dove era richiesto un intero, o ha dimenticato un campo obbligatorio. Se lasci che questo errore risalga senza essere gestito, il client riceverà o un crash o una risposta vuota. Catturalo all'interno del wrapper, quindi costruisci un messaggio chiaro e specifico che spieghi al modello esattamente cosa è andato storto. Spiega quale parametro è fallito e cosa era previsto. La maggior parte dei moderni modelli AI leggerà quel messaggio e si autocorreggerà al turno successivo. Un errore vago spreca un ciclo di ragionamento. Un errore preciso risolve il problema immediatamente.

Eccezioni Generali
Mantieni una rete di sicurezza. Se un errore esce dalle categorie sopra citate, registra i dettagli per te stesso e restituisci al client una risposta di errore generica e pulita. Questo evita che un singolo caso limite insolito interrompa la sessione per tutti. Il server sopravvive, il client riceve un segnale che qualcosa è fallito e tu mantieni abbastanza contesto nei tuoi log per il debug successivo.

Il flag isError non è negoziabile

Ecco il dettaglio che determina effettivamente se la tua soluzione funziona. Le risposte MCP includono un campo booleano isError. Se si verifica un'eccezione e restituisci un messaggio di errore senza impostare isError a true, il client tratterà quel testo di errore come il risultato riuscito di uno strumento.

Immagina che la tua API esterna raggiunga un limite di frequenza (rate limit). Catturi l'eccezione e restituisci la stringa "API rate limit exceeded" ma lasci isError su false. Il client passa quella stringa nella finestra di contesto del modello come se fosse l'output reale dello strumento. Il modello cercherà quindi di ragionare su quel testo come se fossero dati. Potrebbe citare l'errore in un riassunto o, peggio, potrebbe allucinare relazioni tra quel testo di errore e altri fatti. Hai trasformato un temporaneo intoppo infrastrutturale in una fonte di disinformazione.

Imposta sempre isError a true quando restituisci un payload di errore. Questo fornisce al client un segnale chiaro che la chiamata allo strumento è fallita, permettendo al modello di decidere se riprovare, chiedere chiarimenti o provare un tool completamente diverso.

Sapere cosa intercettare e cosa interrompere

Non avvolgere l'intero server in un try-catch cieco che inghiotte tutto. Alcuni errori indicano che il server dovrebbe fermarsi immediatamente. Se una variabile d'ambiente richiesta manca all'avvio, o se il file di configurazione è corrotto, nessun tipo di intercettazione a livello di richiesta potrà aiutare. Crea una classe di eccezione specifica per errori fatali come questi e lascia che interrompano il processo.

La regola è semplice. Se l'errore è temporaneo o isolato a una singola richiesta, intercettalo e recupera. Se l'errore implica che ogni richiesta successiva fallirà sicuramente, lascia che il server si arresti in modo esplicito. Un fallimento rapido all'avvio è infinitamente meglio di un server che procede a fatica per giorni in uno stato di malfunzionamento.

Aggiungi l'osservabilità prima di averne bisogno

Una volta implementato il wrapper, abbinalo a un logging strutturato. Registra ogni chiamata allo strumento e il suo esito in formato JSON. Includi il nome dello strumento, gli argomenti grezzi, la latenza e se l'operazione è riuscita, è fallita o è stata riprovata.

Questa disciplina ripaga rapidamente. Quando noti un picco di errori, puoi filtrare per strumento e individuare pattern in pochi minuti. Magari una specifica API esterna inizia a generare timeout alla stessa ora ogni giorno, indicando una finestra di manutenzione programmata di cui non eri a conoscenza. Magari uno strumento riceve costantemente argomenti malformati, rivelando un difetto di prompt engineering a monte. I log in testo semplice sepolti negli stack trace rendono questo lavoro investigativo doloroso. Il JSON strutturato lo rende banale.

Il risultato in produzione

Ho utilizzato questo pattern di wrapper su due server MCP in produzione per le ultime tre settimane. In questo arco di tempo, non ho riscontrato alcun fallimento silenzioso. Prima di aggiungere il wrapper, registravo in media circa un fallimento inspiegabile al giorno. Il pattern non è complesso, ma il suo impatto è enorme perché separa il rumore gestibile dai problemi reali.

I fallimenti silenziosi costano più dei crash. Un crash attiva il tuo sistema di alerting. Il silenzio erode semplicemente la fiducia. Un giorno il tuo agente AI restituisce dati utili dallo strumento, e il giorno dopo inizia a inventare cose perché il server ha smesso di rispondere ore prima. Il pattern del wrapper colma questo divario. Mantiene il server in funzione durante piccole turbolenze, fornisce al modello un contesto sufficiente per correggere i propri errori e garantisce che, quando accade qualcosa di veramente fatale, tu ne venga informato immediatamente.

Se stai costruendo strumenti MCP oggi, parti dal wrapper e dal flag isError. Tutto il resto è solo rifinitura.