Mijn MCP-server stopte vroeger gewoon met werken. Geen crash dump. Geen stack trace in de logs. Clients verbonden zonder klachten, maar na een paar uur viel alles stil. Verzoeken verdwenen en de AI-agent aan de andere kant ontving niets anders dan stilte.

Dit is een frustrerend veelvoorkomend verhaal in het Model Context Protocol (MCP)-ecosysteem. Het protocol definieert hoe AI-agents externe tools ontdekken en aanroepen, maar de specificatie gaat ervan uit dat je fouten zelf afhandelt. De meeste tutorials en starter-implementaties slaan dat deel over. Ze focussen op het happy path: een functie annoteren, deze via de server blootstellen en een schoon resultaat teruggeven. Ze laten zelden zien wat er gebeurt als er een netwerkstoring optreedt bij je externe API, of wanneer het model een parameternaam hallucineert en ongeldige input stuurt. Het resultaat is een broze server die er gezond uitziet, maar in werkelijkheid al uren dood is.

Waarom lege reacties erger zijn dan crashes

Wanneer een niet-afgehandelde uitzondering (exception) door een MCP-toolhandler glipt, wordt deze vaak opgevangen door de transportlaag. Het serverproces blijft draaien, de socket blijft open, maar de client krijgt een lege reactie. Dit is gevaarlijker dan een duidelijke crash, omdat je monitoring het misschien niet opmerkt. Het proces draait nog steeds. De poort luistert nog steeds. Toch geeft elke tool-aanroep niets terug.

Het AI-model interpreteert stilte niet als een fout. Het interpreteert stilte als een succesvolle aanroep die geen gegevens heeft opgeleverd. Die lege reactie traint het model om te improviseren. Het begint feiten te hallucineren om het gat te vullen, of het komt in een loop terecht waarbij steeds dezelfde kapotte aanroep wordt herhaald. Kleine problemen, zoals een tijdelijke netwerk-timeout of een ongeldig tool-argument, mogen nooit dit soort gedrag veroorzaken.

Het Wrapper-patroon: drie verdedigingslinies

Ik heb dit opgelost door elke toolhandler te wikkelen in een dunne foutherstel-laag (error-recovery layer). De wrapper probeert niet elke mogelijke fout te voorspellen. Hij categoriseert ze en reageert daarop.

ConnectionError en TimeoutError
Deze ontstaan wanneer je server communiceert met een externe API en het netwerk hapert. De instinctieve oplossing is om het hele MCP-serverproces te herstarten. Doe dat niet. Een reboot verbreekt actieve clientverbindingen, wist alle in-memory status en dwingt een volledige re-initialisatie af. Vang in plaats daarvan de verbindingsfout op en herstel alleen de verbinding van de transportlaag of de HTTP-client die je tool gebruikt. De server blijft 'warm' en is direct klaar voor het volgende verzoek.

ValueError
Dit is wat je ziet wanneer de AI-client ongeldige argumenten stuurt. Misschien heeft het model een parameter verzonnen, een string doorgegeven waar een integer vereist was, of een verplicht veld vergeten. Als je dit ongehandeld doorlaat, krijgt de client ofwel een crash of een lege reactie. Vang het op binnen de wrapper en stel vervolgens een duidelijke, specifieke melding samen die het model precies vertelt wat er mis is gegaan. Leg uit welk parameter faalde en wat er werd verwacht. De meeste moderne AI-modellen lezen die melding en corrigeren zichzelf bij de volgende beurt. Een vage fout verspilt een redeneercyclus. Een precieze fout lost het probleem direct op.

General Exceptions
Houd een vangnet aan. Als een fout buiten de bovenstaande categorieën valt, log dan de details voor jezelf en geef een schone, generieke foutmelding terug aan de client. Dit voorkomt dat één vreemd randgeval de sessie voor iedereen beëindigt. De server blijft draaien, de client krijgt een signaal dat er iets mis is gegaan, en je behoudt genoeg context in je logs om later te debuggen.

De isError-flag is niet onderhandelbaar

Dit is het detail dat er werkelijk voor bepaalt of je oplossing werkt. MCP-reacties bevatten een isError boolean veld. Als er een uitzondering optreedt en je een foutmelding teruggeeft zonder isError op true te zetten, behandelt de client die fouttekst als een succesvol resultaat van de tool.

Stel dat je externe API een rate limit bereikt. Je vangt de uitzondering op en geeft de string "API rate limit exceeded" terug, maar laat isError op false staan. De client geeft die string door aan het contextvenster van het model alsof het echte output van de tool is. Het model probeert vervolgens te redeneren over die tekst alsof het data is. Het kan de fout citeren in een samenvatting, of erger nog, het kan relaties hallucineren tussen die foutmelding en andere feiten. Je hebt een tijdelijke infrastructuurstoring veranderd in een bron van misinformatie.

Stel isError altijd in op true wanneer je een error payload teruggeeft. Dit geeft de client een duidelijk signaal dat de tool call is mislukt, waardoor het model kan beslissen of het opnieuw moet proberen, om verduidelijking moet vragen of een geheel andere tool moet proberen.

Weet wat je moet opvangen en wat je moet beëindigen

Wikkel je hele server niet in een blinde try-catch die alles opslokt. Sommige fouten betekenen dat de server onmiddellijk moet stoppen. Als een vereiste omgevingsvariabele bij het opstarten ontbreekt, of je configuratiebestand is corrupt, zal geen enkele foutafhandeling op verzoekniveau helpen. Maak een specifieke exception class voor fatale fouten zoals deze en laat het proces crashen.

De regel is simpel. Als de fout tijdelijk is of beperkt blijft tot een enkel verzoek, vang hem dan op en herstel de situatie. Als de fout betekent dat elk volgend verzoek gegarandeerd zal mislukken, laat de server dan luidruchtig crashen. Een snelle fout bij het opstarten is oneindig veel beter dan een server die dagenlang in een defecte staat voortkabbelt.

Voeg observability toe voordat je het nodig hebt

Zodra je de wrapper hebt geïmplementeerd, koppel deze dan aan gestructureerde logging. Log elke tool call en de uitkomst ervan in JSON-formaat. Voeg de naam van de tool, de ruwe argumenten, de latentie en of het is geslaagd, mislukt of opnieuw is geprobeerd toe.

Deze discipline loont snel. Wanneer je een piek in fouten opmerkt, kun je filteren op tool en binnen enkele minuten patronen ontdekken. Misschien begint een specifieke externe API elke dag op hetzelfde tijdstip timeouts te geven, wat wijst op een gepland onderhoudsvenster waarvan je niets wist. Misschien ontvangt een tool consequent foutief geformatteerde argumenten, wat duidt op een fout in de prompt engineering stroomopwaarts. Plain text logs die begraven liggen in stack traces maken dit detectivewerk pijnlijk. Gestructureerde JSON maakt het triviaal.

Het resultaat in productie

Ik heb dit wrapper-patroon de afgelopen drie weken op twee productie MCP-servers gedraaid. In die periode heb ik nul stille fouten gezien. Voordat ik de wrapper toevoegde, had ik gemiddeld ongeveer één onverklaarbare fout per dag. Het patroon is niet complex, maar de impact is enorm omdat het overleefbare ruis scheidt van echte problemen.

Stille fouten kosten meer dan crashes. Een crash activeert je waarschuwingssysteem. Stilte tast alleen maar het vertrouwen aan. De ene dag geeft je AI-agent nuttige tool-data terug, en de volgende dag begint hij dingen te verzinnen omdat de server uren geleden is gestopt met antwoorden. Het wrapper-patroon overbrugt die kloof. Het houdt je server draaiende tijdens kleine turbulenties, geeft het model voldoende context om zijn eigen fouten te herstellen, en zorgt ervoor dat wanneer er echt iets fatals misgaat, je dat onmiddellijk hoort.

Als je vandaag MCP-tools bouwt, begin dan met de wrapper en de isError-flag. De rest is slechts opruimwerk.