En MCP, un error de ejecución de una tool y un error de protocolo usan canales diferentes. Si una tool recibió una llamada válida, pero no pudo encontrar un registro, consultar una API o aplicar una regla de negocio, devuelve un resultado con isError: true y un mensaje que el modelo pueda usar. Si la solicitud JSON-RPC está mal formada o el estado del protocolo no puede atenderla, devuelve un error de protocolo al host.
La decisión se reduce a una pregunta: ¿quién puede corregir el problema? El modelo puede ajustar argumentos, elegir otra tool o explicar que un registro no existe. El host debe reparar un método, una versión del protocolo, un mensaje JSON-RPC o la configuración de la conexión. Mezclar los canales hace que el agente repita algo que el modelo no puede arreglar, o convierte un fallo recuperable de una tool en un error genérico del host.
Este artículo amplía el servidor MCP en TypeScript para agentes de código. Ese tutorial explica cómo registrar y conectar tools. Esta página se concentra en el contrato de fallo después de que la llamada llega al servidor.
Ideas principales
- Usa
isError: truepara fallos de ejecución que el modelo puede entender y corregir.- Usa un error JSON-RPC cuando el problema está en la solicitud o en el estado del protocolo.
- Escribe el mensaje de
isErrorcomo una observación breve: qué falló y cuál es el siguiente paso válido.- Mantén fuera del texto para el modelo los stack traces, tokens, argumentos sensibles e identificadores internos.
- Prueba la visibilidad: un fallo de una tool debe ser un resultado y un fallo de protocolo debe llegar al host como error.
isError y los errores de protocolo llegan a consumidores distintos
La especificación de tratamiento de errores de Model Context Protocol separa dos familias. Los errores de protocolo usan el campo error de una respuesta JSON-RPC para problemas como solicitudes mal formadas, métodos desconocidos y fallos del servidor. Los errores de ejecución aparecen dentro del resultado de la tool con isError: true; el mensaje puede ayudar al modelo a corregir sus parámetros o cambiar de estrategia.
| Fallo | Canal | Quién debe actuar | Siguiente paso habitual |
|---|---|---|---|
| Una fecha tiene un formato incorrecto | result.isError: true |
Modelo y runtime | Corregir el argumento y reintentar dentro del presupuesto |
| No existe un registro | result.isError: true |
Modelo | Elegir otro identificador o explicar la ausencia |
| Una API externa rechazó la operación | result.isError: true |
Runtime y modelo | Clasificar el retry, usar una alternativa o detenerse |
| JSON-RPC inválido | error en JSON-RPC |
Host MCP | Reparar el mensaje o el cliente |
| Método o versión no compatible | error en JSON-RPC |
Host MCP | Ajustar la negociación o la configuración |
| El servidor no puede atender solicitudes | Error JSON-RPC o política del host | Host y operación | Recuperar el servidor antes de continuar |
Esta tabla describe el contrato en el wire. Los SDK pueden normalizar algunos caminos de otra manera. La guía de errores del SDK TypeScript de MCP muestra, por ejemplo, que una excepción dentro de un handler de tool se convierte en un resultado isError: true, mientras que callbacks de recursos y prompts pueden lanzar ProtocolError para producir una respuesta JSON-RPC. Lee el comportamiento de la versión instalada y prueba la forma que realmente recibe tu cliente.
El detalle importante es la visibilidad. Un resultado con isError: true sigue siendo una respuesta válida a una llamada de tool. El modelo recibe el texto y puede tomar la siguiente decisión. Un error JSON-RPC no tiene result; el fallo lo recibe el cliente MCP o el host que coordina la sesión.
¿Cómo escribir un mensaje de error que el agente pueda usar?
Un mensaje dirigido al modelo debe contener el hecho observable y la corrección permitida. No necesita toda la excepción. La documentación del SDK usa un registro inexistente e incluye identificadores conocidos para que el modelo elija un valor válido. El mensaje funciona porque describe un cambio que la siguiente llamada puede hacer.
Prefiere esta forma:
No note with id "drafts". Use one of: welcome, release-notes.
Evita estas dos:
Error: ENOTFOUND db.internal:5432 at QueryRunner.execute (query-runner.ts:81)
The operation failed. Please try again.
El primero expone infraestructura sin decir si un retry puede ayudar. El segundo no ofrece una observación nueva al modelo. Un mensaje útil responde en pocas palabras a cuatro preguntas: qué tool falló, qué condición se observó, qué puede cambiar en la siguiente llamada y cuándo debe detenerse el agente.
El texto del error sigue siendo contenido no estructurado. Una issue abierta del repositorio de MCP sobre errores de tools gobernados por schema analiza el riesgo de que stack traces, identificadores internos y datos personales crucen esta frontera. La issue es una discusión de implementadores, no una prueba de que todos los servidores filtren datos. La respuesta práctica sigue siendo clara: redacta el texto antes de devolverlo y trátalo como contexto del modelo.
¿Cómo devolver un error de tool en TypeScript?
La forma pequeña de abajo adapta el ejemplo actual del SDK TypeScript. El tipo evita deliberadamente un import del SDK para que el contrato de respuesta quede visible. Los nombres de los métodos de registro cambian entre versiones.
type TextContent = {
type: "text";
text: string;
};
type ToolResult = {
content: TextContent[];
isError?: true;
};
const notes = new Map([
["welcome", "Read tools.md first."],
["release-notes", "The latest release is staged."],
]);
export async function readNote(id: string): Promise<ToolResult> {
const note = notes.get(id);
if (!note) {
return {
content: [{
type: "text",
text: `No note with id "${id}". Choose another id.`,
}],
isError: true,
};
}
return { content: [{ type: "text", text: note }] };
}
En el servidor real, conecta este resultado con el método de registro de tools del SDK instalado. Un fallo conocido de una tool también puede lanzar la excepción de tool que el SDK convierte en isError: true, pero devolver el resultado de forma explícita te da control sobre el texto y deja visible la política en el código. No supongas que lanzar cualquier ProtocolError dentro de un handler de tool producirá un error JSON-RPC: la página de errores del SDK TypeScript indica que los handlers de tools usan el canal de resultados.
Valida los argumentos antes del efecto externo. Si id debe ser una cadena, el schema de entrada debe rechazar otros tipos. Si la cadena es válida pero ningún registro coincide, la ejecución llegó a la tool y puede devolver una observación con isError: true. Esta diferencia separa un problema de validación del transporte de una ausencia de dominio.
¿Cuándo es correcto usar un error JSON-RPC?
Usa el canal de protocolo cuando el mensaje no representa una solicitud válida de ejecución de una tool o cuando el host debe reparar la sesión. Algunos ejemplos son JSON inválido, un método no compatible, un mensaje que no cumple el schema de tools/call y una versión de protocolo que el servidor no admite.
Una respuesta de protocolo tiene esta forma conceptual:
{
"jsonrpc": "2.0",
"id": 7,
"error": {
"code": -32602,
"message": "Invalid tool call request"
}
}
El código de error debe seguir el contrato de la versión y de la biblioteca. No uses -32602 como etiqueta universal para cada fallo de la aplicación. Una regla de negocio como “una orden no se puede cancelar después de enviarse” pertenece al resultado de la tool, aunque el runtime use internamente una excepción para producirlo.
La guía de errores del SDK Python de MCP muestra la misma distinción en otro lenguaje: una excepción de tool aparece como is_error=True y el modelo lee el mensaje, mientras que una excepción de MCP produce un error JSON-RPC que maneja el cliente. La implementación cambia, pero la pregunta es la misma: ¿falló la tool elegida por el modelo o el host envió una solicitud de protocolo inválida?
¿Cómo probar que el agente recibe el fallo correcto?
Una prueba de errores no debe comprobar solo que ocurrió alguna excepción. Debe demostrar la forma que observa el consumidor. La fixture siguiente no abre una conexión MCP; fija el contrato local para que la capa de integración pueda probarse contra el SDK real.
import assert from "node:assert/strict";
const result = await readNote("missing");
assert.equal(result.isError, true);
assert.match(result.content[0].text, /Choose another id/);
assert.equal("error" in result, false);
const protocolFailure = {
jsonrpc: "2.0",
id: 7,
error: { code: -32602, message: "Invalid tool call request" },
};
assert.equal("result" in protocolFailure, false);
assert.equal(protocolFailure.error.code, -32602);
En una suite que llame al servidor MCP real, añade cuatro comprobaciones:
- Llama a una tool con un argumento de dominio válido que no exista y confirma
isError: true. - Confirma que el texto no contiene stack trace, ruta absoluta, token ni payload secreto.
- Envía una solicitud JSON-RPC mal formada y confirma que el cliente recibe un error, no un
CallToolResult. - Provoca un fallo conocido del upstream y confirma que no ocurrió ninguna mutación antes del resultado de error.
Si el cliente convierte un caso en isError: true cuando esperabas un rechazo JSON-RPC, no cambies la prueba para ocultar la diferencia. Comprueba la versión del SDK, el camino del handler y el wire log. La guía de pruebas de contrato para servidores MCP en TypeScript cubre conexión y schemas; añade este caso como una extensión enfocada en la semántica del fallo.
¿Cómo evitar un retry infinito después de isError?
isError: true hace visible el fallo para el modelo, pero no decide si debe intentarlo otra vez. El runtime aún necesita distinguir entre corregir argumentos, reintentar una dependencia, cambiar de tool y detenerse. Un error de dominio puede corregirse una vez. El mismo fingerprint repetido varias veces es una razón para detenerse o pedir intervención.
No devuelvas un mensaje que anime a reintentar una operación que quizá ya produjo un efecto. Si se perdió la respuesta de una escritura, consulta el estado externo o usa una clave de idempotencia antes de presentar la acción como repetible. El resultado de error de MCP debe describir lo observado, no inventar que la mutación no ocurrió.
Tampoco uses isError como control de acceso. La autorización, las allowlists, la confirmación humana y el alcance del servidor deben bloquear el efecto antes o dentro de la política del servicio. El canal de error informa de la decisión, pero no convierte una tool peligrosa en una tool segura.
Checklist para tratar errores de MCP
- ¿La llamada llegó como una solicitud válida de
tools/call? - ¿El fallo ocurrió durante la ejecución de la tool o antes?
- ¿El modelo puede corregir la entrada, elegir otra tool o usar la observación?
- ¿El fallo dirigido al modelo usa
isError: true? - ¿El error JSON-RPC queda reservado para que el host repare la solicitud o el protocolo?
- ¿El mensaje es corto, accionable y no contiene datos sensibles?
- ¿El runtime limita retries, registra el motivo y evita repetir una mutación incierta?
- ¿La prueba comprueba la forma que recibe el modelo y la que recibe el host?
- ¿El comportamiento observado coincide con la versión instalada del SDK?
El objetivo no es enviar todos los errores al modelo. Cada consumidor debe recibir la información que puede usar. Un resultado isError: true es una observación de la ejecución de una tool. Un error JSON-RPC es un fallo del contrato entre cliente y servidor. Cuando esa frontera aparece en el código y en las pruebas, el agente tiene menos probabilidades de tratar una indisponibilidad estructural como una invitación a reintentar.
Cómo se produjo este artículo
Samuel Fajreldines es responsable de este artículo. La investigación comparó la especificación de MCP, la documentación actual de errores del SDK TypeScript, la guía de errores del SDK Python y el comportamiento documentado de errores visibles para el modelo en un SDK de agentes. Las discusiones públicas se usaron solo para vocabulario, preocupaciones e ideas de prueba. El diagrama es una SVG original, y la fixture de formas de respuesta se comprobó localmente con Node.js sin iniciar un servidor MCP, llamar a un modelo ni acceder a una API externa. La asistencia de IA apoyó el descubrimiento, la organización, la redacción, la localización y la revisión de consistencia. El autor usa RemoteCode como herramienta de trabajo, pero no ejecutó pruebas de producción ni aportó experiencia operativa.
Fuentes consultadas
- Model Context Protocol, “Error Handling”, consultado el 14 de noviembre de 2026.
- MCP TypeScript SDK, “Errors”, consultado el 14 de noviembre de 2026.
- MCP Python SDK, “Handling errors”, consultado el 14 de noviembre de 2026.
- OpenAI Agents SDK for Python, “Running agents”, consultado el 14 de noviembre de 2026.