docs: refresh translations for recent English changes (#3379)
This commit is contained in:
parent
d8b638304e
commit
c5d7d0b6d0
1 changed files with 3149 additions and 1047 deletions
|
|
@ -1,13 +1,13 @@
|
|||
---
|
||||
translation:
|
||||
sections: [e33d441f12d50535, 7099694c603e0f5f, c1df4cf9673433e6, c9cd294541422e6e, 6cec073617bfd037, efa92b8f99e908c8, 6a22a29e27fb4601]
|
||||
sections: [7be05607887e6853, e7375894888d9750, c36f73fc7e3af13b, 2fec2d7e129e62fe, 809b0e0a7c27295a, b4395a04d2a5d906, 1a436007f5f54779, c6b2078ed1e63ba5]
|
||||
tool: 1
|
||||
---
|
||||
# Manejo de errores {#handling-errors}
|
||||
|
||||
Una herramienta puede fallar de dos maneras, y el SDK las trata de forma muy distinta.
|
||||
Una herramienta puede fallar de tres maneras, y el SDK trata cada una de forma distinta.
|
||||
|
||||
Lanza una excepción ordinaria y la ve el **modelo**. Lanza `MCPError` y la ve el **protocolo**.
|
||||
Lanza `ToolError` y el **modelo** ve tu mensaje. Lanza `MCPError` y lo ve el **protocolo**. Lanza cualquier otra cosa y es un fallo inesperado: el modelo solo se entera de que la llamada falló, y el traceback va a tu log.
|
||||
|
||||
Esta página trata de cómo elegir.
|
||||
|
||||
|
|
@ -15,11 +15,11 @@ Esta página trata de cómo elegir.
|
|||
|
||||
Toma una herramienta que busca algo, y deja que la búsqueda falle:
|
||||
|
||||
```python title="server.py" hl_lines="11-12"
|
||||
```python title="server.py" hl_lines="2 12-13"
|
||||
--8<-- "docs_src/handling_errors/tutorial001.py"
|
||||
```
|
||||
|
||||
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.
|
||||
`ToolError`, de `mcp.server.mcpserver.exceptions`, es la forma en que una herramienta le dice al modelo que algo salió mal.
|
||||
|
||||
Llámala con un título que no esté en el catálogo y observa el resultado:
|
||||
|
||||
|
|
@ -30,13 +30,15 @@ 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.
|
||||
* `is_error` es `True`, y tu mensaje (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.
|
||||
Esto es un **error de herramienta**, y 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.
|
||||
|
||||
En el servidor, un `ToolError` es una sola línea `INFO` en el log, sin traceback. Lo veías venir, así que no hay nada que investigar.
|
||||
|
||||
!!! 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
|
||||
|
|
@ -44,7 +46,7 @@ El modelo es quien llama a tu herramienta. Él eligió los argumentos. Así que
|
|||
|
||||
## Un error que el modelo no puede corregir {#an-error-the-model-cannot-fix}
|
||||
|
||||
Ahora cambia `ValueError` por `MCPError`.
|
||||
Ahora cambia `ToolError` por `MCPError`.
|
||||
|
||||
```python title="server.py" hl_lines="1 3 14"
|
||||
--8<-- "docs_src/handling_errors/tutorial002.py"
|
||||
|
|
@ -77,10 +79,10 @@ Ahora cambia `ValueError` por `MCPError`.
|
|||
|
||||
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 `ToolError`** 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`.
|
||||
Una sola pregunta lo decide: **¿podría haberlo evitado un modelo más inteligente?** Sí -> `ToolError`. 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.
|
||||
|
||||
|
|
@ -89,6 +91,25 @@ Según ese criterio, la segunda versión de `get_author` eligió mal: un título
|
|||
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.
|
||||
|
||||
## Cualquier otra excepción {#any-other-exception}
|
||||
|
||||
Ahora quita la comprobación y deja que la búsqueda en el diccionario falle por sí sola:
|
||||
|
||||
```python title="server.py" hl_lines="11"
|
||||
--8<-- "docs_src/handling_errors/tutorial004.py"
|
||||
```
|
||||
|
||||
`CATALOG[title]` lanza `KeyError`. No lo tenías previsto, así que el SDK lo trata como un fallo inesperado:
|
||||
|
||||
```python
|
||||
result.is_error # True
|
||||
result.content # [TextContent(text="Error executing tool get_author")]
|
||||
```
|
||||
|
||||
La llamada sigue devolviendo `is_error=True`, así que el modelo sabe que falló y puede seguir adelante. Lo que no recibe es el texto de la excepción: un `KeyError` de tu código, o un montón de SQL de un driver tres bibliotecas más abajo, puede describir el funcionamiento interno de tu servidor, así que nunca sale del servidor.
|
||||
|
||||
Lo recibes tú. El servidor registra el fallo inesperado en nivel `ERROR` con el traceback completo, como `Tool 'get_author' raised an unexpected exception`. Así, un log de producción en `WARNING` se mantiene en silencio ante cada `ToolError` y habla en cuanto algo está realmente roto.
|
||||
|
||||
## Un recurso que no existe {#a-resource-that-doesnt-exist}
|
||||
|
||||
Los recursos trazan la misma línea, e incluyen una excepción con nombre propio para el caso común.
|
||||
|
|
@ -109,7 +130,7 @@ Cuando no pueda, lanza `ResourceNotFoundError`. El SDK lo convierte en el error
|
|||
}
|
||||
```
|
||||
|
||||
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](resources.md)**.
|
||||
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. `ResourceError` es lo mismo para un fallo que no es "no encontrado" (`-32603`, tu mensaje), y ambos son una sola línea `INFO` en tu log. Cualquier otra excepción salvo `MCPError` es un fallo inesperado: el cliente recibe un `-32603` que solo nombra la URI, y el traceback va a tu log en nivel `ERROR`. Las plantillas y todo lo demás sobre recursos están en **[Recursos](resources.md)**.
|
||||
|
||||
## Errores que nunca lanzas {#errors-you-never-raise}
|
||||
|
||||
|
|
@ -120,20 +141,22 @@ Envíale a `get_author` un `title` que no sea una cadena y el SDK lo rechaza con
|
|||
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.
|
||||
Todo lo que ve un **cliente** en esta página lo ve también el `Client` en memoria con el que
|
||||
escribirás pruebas. Ni siquiera `raise_exceptions=True` le devuelve a quien llama la excepción
|
||||
de una herramienta que falla: para cuando ese indicador podría actuar, tu excepción ya es el
|
||||
resultado con `is_error=True`. Haz las aserciones sobre el resultado. Si necesitas el traceback
|
||||
de un fallo inesperado, está en el log del servidor, y el `caplog` de pytest lo captura.
|
||||
**[Pruebas](../get-started/testing.md)** cubre el patrón.
|
||||
|
||||
## Resumen {#recap}
|
||||
|
||||
* 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 **`ToolError`** en una herramienta -> la llamada devuelve `is_error=True` con tu mensaje en `content`. El modelo lo lee y puede reintentar.
|
||||
* 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`.
|
||||
* La pregunta decisiva: *¿podría haberlo evitado un modelo más inteligente?* Sí -> `ToolError`. No -> `MCPError`.
|
||||
* Cualquier **otra excepción** es un fallo inesperado -> `is_error=True` con solo `Error executing tool <name>` para el modelo, y un registro `ERROR` con el traceback para ti.
|
||||
* `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`.
|
||||
* Importaciones: `from mcp import MCPError`, `from mcp.server.mcpserver.exceptions import ToolError, ResourceError, ResourceNotFoundError`, y las constantes de códigos de error 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](../handlers/index.md)**.
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue