Mi servidor MCP solía dejar de funcionar sin más. Sin volcados de memoria. Sin trazas de la pila en los registros. Los clientes se conectaban sin quejas, pero después de unas horas, todo quedaba en silencio. Las solicitudes desaparecían y el agente de IA al otro lado no recibía más que el vacío.

Esta es una historia frustrantemente común en el ecosistema del Model Context Protocol (MCP). El protocolo define cómo los agentes de IA descubren y llaman a herramientas externas, pero la especificación asume que tú mismo gestionarás los errores. La mayoría de los tutoriales e implementaciones iniciales pasan por alto esa parte. Se centran en el "camino feliz" (happy path): anotar una función, exponerla a través del servidor y devolver un resultado limpio. Rara vez muestran qué sucede cuando un pequeño fallo de red afecta a tu API externa, o cuando el modelo alucina el nombre de un parámetro y envía una entrada basura. El resultado es un servidor frágil que parece estar sano, pero que en realidad lleva horas muerto.

Por qué las respuestas en blanco son peores que los fallos

Cuando una excepción no controlada se filtra en un manejador de herramientas MCP, la capa de transporte a menudo la absorbe. El proceso del servidor permanece vivo, el socket sigue abierto, pero el cliente recibe una respuesta vacía. Esto es más peligroso que un fallo evidente porque tu monitorización podría no detectarlo. El proceso sigue ejecutándose. El puerto sigue escuchando. Sin embargo, cada llamada a la herramienta no devuelve nada.

El modelo de IA no interpreta el silencio como un fallo. Lo interpreta como una llamada exitosa que no produjo datos. Esa respuesta en blanco entrena al modelo para improvisar. Comienza a alucinar hechos para llenar el vacío, o entra en un bucle de reintentar la misma llamada fallida. Problemas menores, como un tiempo de espera de red transitorio o un argumento de herramienta inválido, nunca deberían permitir que se produzca este tipo de comportamiento.

El patrón Wrapper: tres líneas de defensa

Solucioné esto envolviendo cada manejador de herramientas en una fina capa de recuperación de errores. El wrapper no intenta predecir cada posible fallo; los categoriza y responde en consecuencia.

ConnectionError y TimeoutError
Estos surgen cuando tu servidor se comunica con una API externa y la red flaquea. La solución instintiva es reiniciar todo el proceso del servidor MCP. No hagas eso. Reiniciar corta las conexiones activas de los clientes, borra cualquier estado en memoria y fuerza una reinicialización completa. En su lugar, captura el fallo de conexión y reconecta únicamente la capa de transporte o el cliente HTTP que utiliza tu herramienta. El servidor se mantiene listo y preparado para la siguiente solicitud de inmediato.

ValueError
Esto es lo que ves cuando el cliente de IA envía argumentos malformados. Quizás el modelo inventó un parámetro, pasó una cadena de texto donde se requería un entero o se olvidó de un campo obligatorio. Si permites que esto se propague sin control, el cliente recibirá un fallo o una respuesta en blanco. Captúralo dentro del wrapper y luego construye un mensaje claro y específico que le diga al modelo exactamente qué salió mal. Explica qué parámetro falló y qué se esperaba. La mayoría de los modelos de IA modernos leerán ese mensaje y se autocorregirán en el siguiente turno. Un error vago desperdicia un ciclo de razonamiento. Un error preciso soluciona el problema de inmediato.

Excepciones generales
Mantén una red de seguridad. Si un error cae fuera de las categorías anteriores, registra los detalles para ti y devuelve una respuesta de fallo limpia y genérica al cliente. Esto evita que un caso extremo extraño termine matando la sesión para todos. El servidor sobrevive, el cliente recibe una señal de que algo falló y tú conservas suficiente contexto en tus registros para depurar más tarde.

La bandera isError no es negociable

Aquí está el detalle que realmente determina si tu solución funciona. Las respuestas de MCP incluyen un campo booleano isError. Si ocurre una excepción y devuelves un mensaje de error sin establecer isError como true, el cliente tratará ese texto de error como el resultado exitoso de una herramienta.

Imagina que tu API externa alcanza un límite de tasa (rate limit). Capturas la excepción y devuelves la cadena "API rate limit exceeded", pero dejas isError como false. El cliente pasa esa cadena a la ventana de contexto del modelo como si fuera la salida real de la herramienta. El modelo entonces intenta razonar sobre ese texto como si fueran datos. Podría citar el error en un resumen o, peor aún, podría alucinar relaciones entre ese texto de error y otros hechos. Has convertido un hipo temporal de la infraestructura en una fuente de desinformación.

Always set isError to true when you are returning an error payload. This gives the client a clear signal that the tool call failed, which lets the model decide whether to retry, ask for clarification, or try a different tool entirely.

Know What to Catch and What to Kill

Do not wrap your entire server in a blind try-catch that swallows everything. Some errors mean the server should stop immediately. If a required environment variable is missing on startup, or your configuration file is corrupt, no amount of request-level catching will help. Create a specific exception class for fatal errors like these and let them crash the process.

The rule is simple. If the error is temporary or isolated to a single request, catch it and recover. If the error means every subsequent request is guaranteed to fail, let the server die loudly. A fast failure on startup is infinitely better than a server that limps along for days in a broken state.

Add Observability Before You Need It

Once you have the wrapper in place, pair it with structured logging. Log every tool call and its outcome in JSON format. Include the tool name, the raw arguments, the latency, and whether it succeeded, failed, or retried.

This discipline pays off quickly. When you notice a spike in errors, you can filter by tool and spot patterns in minutes. Maybe a specific external API starts throwing timeouts at the same time every day, pointing to a scheduled maintenance window you did not know about. Maybe one tool receives consistently malformed arguments, revealing a prompt engineering flaw upstream. Plain text logs buried in stack traces make this detective work painful. Structured JSON makes it trivial.

The Production Result

I have run this wrapper pattern on two production MCP servers for the past three weeks. In that window, I have seen zero silent failures. Before adding the wrapper, I averaged roughly one unexplained failure every day. The pattern is not complex, but its impact is outsized because it separates survivable noise from real problems.

Silent failures cost more than crashes. A crash triggers your alerting system. Silence just erodes trust. One day your AI agent returns useful tool data, and the next day it starts making things up because the server stopped answering hours ago. The wrapper pattern closes that gap. It keeps your server running through minor turbulence, gives the model enough context to fix its own mistakes, and ensures that when something truly fatal goes wrong, you hear about it immediately.

If you are building MCP tools today, start with the wrapper and the isError flag. Everything else is just cleanup.