Saltar a contenido

Manejo de errores

Traducción automática

Esta página se tradujo automáticamente a partir de la documentación en inglés, y la página en inglés es la versión de referencia. Si algo no se lee bien, Traducciones explica cómo avisarnos.

Una herramienta puede fallar de dos maneras, y el SDK las trata de forma muy distinta.

Lanza una excepción ordinaria y la ve el modelo. Lanza MCPError y la ve el protocolo.

Esta página trata de cómo elegir.

Un error que el modelo puede corregir

Toma una herramienta que busca algo, y deja que la búsqueda falle:

server.py
from mcp.server import MCPServer

mcp = MCPServer("Bookshop")

CATALOG = {"Dune": "Frank Herbert", "Neuromancer": "William Gibson"}


@mcp.tool()
def get_author(title: str) -> str:
    """Look up the author of a book in the catalog."""
    if title not in CATALOG:
        raise ValueError(f"No book titled {title!r} in the catalog.")
    return CATALOG[title]

No hay nada de MCP en esas dos líneas. get_author lanza un ValueError común y corriente, como lo haría cualquier función de Python.

Llámala con un título que no esté en el catálogo y observa el resultado:

result.is_error            # True
result.content             # [TextContent(text="Error executing tool get_author: No book titled 'Nothing' in the catalog.")]
result.structured_content  # None
  • La solicitud tuvo éxito. Hay un resultado; no se lanzó nada del lado de quien llama.
  • is_error es True, y el mensaje de tu excepción (con el nombre de la herramienta como prefijo) está en content, justo donde lee el modelo.
  • structured_content es None. Una llamada fallida no tiene valor devuelto que estructurar.

Esto es un error de herramienta, y es el comportamiento por defecto para cualquier excepción que lance tu herramienta. Además, casi siempre es lo que quieres.

El modelo es quien llama a tu herramienta. Él eligió los argumentos. Así que un error de herramienta es un turno de la conversación: el modelo lee "No book titled 'Nothing' in the catalog.", se da cuenta de que adivinó mal el título y vuelve a llamar con uno mejor. Escribiste un raise y obtuviste un agente que se corrige solo.

Tip

Nunca devuelvas con return un mensaje de error desde una herramienta. Una cadena devuelta tiene is_error=False, así que para el modelo (y para toda interfaz de cliente) parece que la herramienta funcionó y que esa cadena era la respuesta. Usa raise. El indicador es la señal.

Un error que el modelo no puede corregir

Ahora cambia ValueError por MCPError.

server.py
from mcp import MCPError
from mcp.server import MCPServer
from mcp.types import INVALID_PARAMS

mcp = MCPServer("Bookshop")

CATALOG = {"Dune": "Frank Herbert", "Neuromancer": "William Gibson"}


@mcp.tool()
def get_author(title: str) -> str:
    """Look up the author of a book in the catalog."""
    if title not in CATALOG:
        raise MCPError(code=INVALID_PARAMS, message=f"No book titled {title!r} in the catalog.")
    return CATALOG[title]

MCPError es el error de protocolo del SDK. Es la única excepción que el envoltorio de la herramienta no captura: se propaga, y toda la solicitud tools/call falla con un error JSON-RPC en lugar de un resultado.

{
  "code": -32602,
  "message": "No book titled 'Nothing' in the catalog."
}
  • No hay resultado. No hay content ni is_error: nada que el modelo pueda leer.
  • En su lugar, el error lo recibe la aplicación host, igual que si la herramienta no existiera.
  • code, message y data llegan intactos. INVALID_PARAMS es -32602; mcp.types lo exporta, junto con los demás códigos de error JSON-RPC (INVALID_REQUEST, INTERNAL_ERROR, ...), como constantes para que nunca escribas un número mágico.

Check

La misma búsqueda, el mismo fallo, pero ahora la llamada lanza una excepción del lado del cliente en lugar de devolver un resultado:

mcp.shared.exceptions.MCPError: No book titled 'Nothing' in the catalog.

La primera versión le entregaba al modelo una frase a la que podía reaccionar. Esta no le entrega nada. Para get_author eso es estrictamente peor, y de eso trata la siguiente sección.

Cuál lanzar

Los dos caminos responden a dos preguntas distintas.

  • Lanza cualquier excepción ante un fallo de ejecución: lo que tu herramienta intentó hacer no funcionó. El modelo eligió la llamada, así que el modelo debería ver la consecuencia y tener la oportunidad de recuperarse. Un título mal escrito, una API externa que agotó el tiempo de espera, una fila que no existe: todos son errores de herramienta.
  • Lanza MCPError cuando debe rechazarse la solicitud misma: al cliente le falta una capacidad de la que depende tu herramienta, el servidor no está en condiciones de atender a nadie, quien llama se saltó un paso obligatorio. Ningún reintento del modelo arregla nada de eso, así que no se gana nada entregándole el mensaje.

Una sola pregunta lo decide: ¿podría haberlo evitado un modelo más inteligente? Sí -> excepción ordinaria. No -> MCPError.

Según ese criterio, la segunda versión de get_author eligió mal: un título mejor lo arregla, así que el modelo merecía ver el mensaje. Está ahí para mostrarte el mecanismo, no para recomendarlo.

Info

MCPError se importa con from mcp import MCPError y recibe code, message y un payload opcional data. Lo que pongas en ellos es lo que recibe el cliente: el SDK reenvía un MCPError lanzado tal cual, en lugar de sanearlo.

Un recurso que no existe

Los recursos trazan la misma línea, e incluyen una excepción con nombre propio para el caso común.

server.py
from mcp.server import MCPServer
from mcp.server.mcpserver.exceptions import ResourceNotFoundError

mcp = MCPServer("Bookshop")

CATALOG = {"Dune": "Frank Herbert", "Neuromancer": "William Gibson"}


@mcp.resource("books://{title}")
def book(title: str) -> str:
    """The catalog entry for one book."""
    if title not in CATALOG:
        raise ResourceNotFoundError(f"No book titled {title!r} in the catalog.")
    return f"{title} by {CATALOG[title]}"

books://{title} es una plantilla. Coincide con cualquier título, así que "la URI está bien formada" y "el libro existe" son dos preguntas distintas, y solo tu función puede responder la segunda.

Cuando no pueda, lanza ResourceNotFoundError. El SDK lo convierte en el error de protocolo que la especificación asigna a un recurso que falta: -32602 con la URI solicitada en data, para que el cliente sepa cuál lectura falló.

{
  "code": -32602,
  "message": "No book titled 'Nothing' in the catalog.",
  "data": {"uri": "books://Nothing"}
}

Fíjate en que aquí no hay un resultado a medias con is_error=True. La lectura de un recurso devuelve contenido o falla: los recursos solo tienen el camino del protocolo. Las plantillas y todo lo demás sobre recursos están en Recursos.

Errores que nunca lanzas

Un argumento incorrecto nunca llega a tu función.

Envíale a get_author un title que no sea una cadena y el SDK lo rechaza contra el esquema de entrada antes de llamarte, como el mismo tipo de error de herramienta con is_error=True que el modelo puede leer y corregir. Herramientas muestra el mismo rechazo con una restricción Field(le=50).

Eso significa toda una clase de sentencias raise que no escribes: no vuelvas a validar tus propias anotaciones de tipo.

Info

Todo lo de esta página es lo que ve un cliente, y el Client en memoria con el que escribirás pruebas ve exactamente lo mismo. Ni siquiera raise_exceptions=True convierte un error de herramienta de nuevo en un traceback: para cuando ese indicador podría actuar, tu excepción ya es el resultado con is_error=True. Haz las aserciones sobre el resultado. Pruebas cubre el patrón.

Resumen

  • Lanza cualquier excepción en una herramienta -> la llamada devuelve is_error=True con tu mensaje en content. El modelo lo lee y puede reintentar. Este es el comportamiento por defecto.
  • Lanza MCPError -> la llamada misma falla con un error JSON-RPC. El modelo no ve nada; el host se encarga. code, message y data sobreviven intactos.
  • La pregunta decisiva: ¿podría haberlo evitado un modelo más inteligente? Sí -> excepción. No -> MCPError.
  • ResourceNotFoundError desde un handler de recurso -> el -32602 del protocolo, con la URI en data.
  • Los argumentos incorrectos se rechazan contra el esquema antes de que se ejecute tu función; para esos no usas raise.
  • from mcp import MCPError; las constantes de códigos de error vienen de mcp.types.

Errores resueltos. Eso es todo lo que un servidor expone. Lo que cada handler puede leer, y hacer de vuelta hacia el cliente mientras se ejecuta, es la siguiente sección: Dentro de tu handler.

El texto exacto de los errores del SDK que es más probable que encuentres, qué significa cada uno y la solución de un solo paso para cada uno están en Solución de problemas.