docs: refresh translations for recent English changes (#3379)

This commit is contained in:
Max 2026-08-24 19:29:54 +01:00 • committed by GitHub
commit c5d7d0b6d0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 3149 additions and 1047 deletions

View file

@ -1,6 +1,6 @@
---
translation:
sections: [2c79b6338e09b7ac, 7edc43b3fae11314, 1086e77ce561cd7f, a3f71823df5efc31, 9fc7109f72201cae, 7bf25983df655b66, 6330e1f4c6029683, 2f1749c8c133fa1c, b3530fcf4d11fd56, ebc33704fbd74262, cd0e9c933350390e]
sections: [2c79b6338e09b7ac, 7edc43b3fae11314, 1086e77ce561cd7f, a3f71823df5efc31, 9fc7109f72201cae, d50fe7faead8cf68, 7bf25983df655b66, 6330e1f4c6029683, 2f1749c8c133fa1c, 8db7116fc8ddd0ee, ebc33704fbd74262, cd0e9c933350390e]
tool: 1
---
# El Server de bajo nivel {#the-low-level-server}
@ -116,6 +116,17 @@ El bloque `_meta` es el sello de identidad del servidor: el SDK lo añade a cada
El servidor nunca compara los dos campos. El `Client` de este SDK sí: devuelve un `structured_content` que no cumpla el `output_schema` que declaraste y `call_tool` lanza un `RuntimeError` que empieza por `Invalid structured content returned by tool search_books` y sigue citando el fallo de `jsonschema`. Prometer un esquema es barato; cumplirlo depende de ti. Toda la escalera de tipos de retorno y esquemas está en **[Salida estructurada](../servers/structured-output.md)**.
## El dialecto es JSON Schema 2020-12 {#the-dialect-is-json-schema-2020-12}
`input_schema` y `output_schema` son JSON Schema, y la [especificación de MCP](https://modelcontextprotocol.io/specification/latest/basic#json-schema-usage) fija el dialecto: un esquema sin clave `$schema` es **JSON Schema 2020-12**. Los esquemas que genera `MCPServer` se apoyan en ese valor por defecto (Pydantic escribe 2020-12 y omite la clave), y un dict escrito a mano también se rige por él, así que tienes disponible todo el vocabulario de 2020-12:
```python title="server.py" hl_lines="8 14-15"
--8<-- "docs_src/lowlevel/tutorial007.py"
```
* La raíz de `input_schema` debe ser `"type": "object"`. A su lado, `oneOf`, `additionalProperties`, `anyOf`, `if`/`then`/`else`, `prefixItems`, `$defs` con `$ref` locales y el resto de las palabras clave de 2020-12 llegan al cliente exactamente como las escribiste.
* No hace falta ninguna clave `$schema`. Añade una solo para optar por un draft más antiguo: el `Client` de este SDK, que valida `structured_content` contra el `output_schema` de una herramienta, elige su validador según `$schema` y usa 2020-12 cuando no hay ninguna.
## `_meta`: para la aplicación, no para el modelo {#\_meta-for-the-application-not-the-model}
`content` es la parte de la respuesta que lee el modelo. `structured_content` es la misma respuesta como datos tipados. `_meta` es el tercer canal: datos que viajan con el resultado para la **aplicación cliente**, sin formar parte de la respuesta en absoluto.
@ -167,7 +178,7 @@ El constructor cubre los métodos que MCP define. `add_request_handler` cubre to
--8<-- "docs_src/lowlevel/tutorial006.py"
```
* El primer argumento es la cadena del método. Las notificaciones tienen un gemelo, `add_notification_handler`.
* El primer argumento es la cadena del método. Las notificaciones tienen un gemelo, `add_notification_handler`. Sus handlers se disparan en stdio y en conexiones HTTP de la generación del handshake; en la ruta Streamable HTTP de `2026-07-28`, el POST de notificación de un cliente se confirma con un `202` y no se despacha, porque esa revisión no define notificaciones de cliente a servidor sobre HTTP.
* `params_type` es el modelo contra el que se validan los `params` entrantes **antes** de que se ejecute tu handler, así que los métodos personalizados *sí* reciben la validación que las herramientas no. Hereda de `RequestParams` para que el campo `_meta` se analice como el de cualquier otro método.
* El handler devuelve un `BaseModel`, un `dict` o `None`. El SDK lo serializa en el resultado JSON-RPC.

View file

@ -1,6 +1,6 @@
---
translation:
sections: [6048b4f308edbb8c, 068bda0f21ee9c1b, c3e565b61acd75c5, c62422b159c6ed09, 47204fab253cc45c]
sections: [6048b4f308edbb8c, 46056f318ef205e4, c3e565b61acd75c5, c62422b159c6ed09, 420968f514138f43]
tool: 1
---
# Middleware {#middleware}
@ -54,8 +54,11 @@ Ese es el punto. El middleware envuelve **cada** mensaje entrante:
* El establecimiento de la conexión: `server/discover`, o `initialize` y `notifications/initialized`
en una sesión heredada.
* Cada solicitud y cada notificación. Para una notificación, `ctx.request_id is None`,
`call_next(ctx)` devuelve `None` y lo que devuelvas se descarta.
* Cada solicitud y cada notificación que llega al servidor. Para una notificación,
`ctx.request_id is None`, `call_next(ctx)` devuelve `None` y lo que devuelvas se descarta.
(En la ruta Streamable HTTP de `2026-07-28`, el POST de notificación de un cliente se confirma
con `202` en el transporte y nunca se despacha, así que tampoco llega al middleware; esa
revisión no define notificaciones del cliente al servidor sobre HTTP.)
* Incluso un método para el que el servidor no tiene handler: `call_next` lanza el
`MCPError(-32601, "Method not found")` *a través de* tu middleware de camino al cliente.
@ -114,8 +117,8 @@ span de OpenTelemetry por cada mensaje. No lo añades y, la mayor parte del tiem
* Un middleware es `async (ctx, call_next) -> result`, se pasa como `MCPServer(middleware=[...])` (o
se añade a `mcp.middleware`), y se añade a `server.middleware` en el `Server` de bajo nivel.
* Envuelve **cada** mensaje entrante (`server/discover`, `initialize`, solicitudes, notificaciones,
métodos desconocidos) y se ejecuta de fuera hacia dentro.
* Envuelve **cada** mensaje entrante que llega al servidor (`server/discover`, `initialize`,
solicitudes, notificaciones, métodos desconocidos) y se ejecuta de fuera hacia dentro.
* `ctx.request_id is None` es la forma de distinguir una notificación de una solicitud.
* Lanza una excepción en lugar de llamar a `call_next` para rechazar un mensaje; la conexión sobrevive.
* El trazado con OpenTelemetry del propio SDK también es un middleware, ya incluido en la lista. Consulta

View file

@ -1,6 +1,6 @@
---
translation:
sections: [ebef1e7a0df854f4, a4c687d3d627d516, 8e79141fc2985342, b345dd05b9c3c7ab, 80ce41579825a6fa, 5f0fa90494de8f65, 83d10514eaa62fa5, 9190555aa39a5d28, 84a4c9d8bf14dddb, 927d71cf40b58c30]
sections: [ebef1e7a0df854f4, 8355cfaf1f76c9d5, 8e79141fc2985342, 46bdb07c7537e8a5, 80ce41579825a6fa, 5f0fa90494de8f65, 83d10514eaa62fa5, 9190555aa39a5d28, 84a4c9d8bf14dddb, 927d71cf40b58c30]
tool: 1
---
# El cliente {#the-client}
@ -27,9 +27,10 @@ El servidor del principio solo está ahí para que tengas algo a lo que conectar
* Una instancia de `MCPServer` (o del `Server` de bajo nivel): se conecta **en el mismo proceso**.
* Una cadena con una URL (`Client("http://localhost:8000/mcp")`): Streamable HTTP, el camino de producción.
* Un **transporte**: cualquier cosa que puedas usar con `async with ... as (read, write)`, como `stdio_client(...)` envolviendo un subproceso.
* Un `StdioServerParameters`: el comando que se lanza como **subproceso**, con el que se habla a través de su stdin y su stdout.
* Un **transporte**: cualquier cosa que puedas usar con `async with ... as (read, write)`, como `streamable_http_client(url, http_client=...)` envolviendo tu propio cliente HTTP.
Todo lo demás en esta página es idéntico en los tres casos. Los encabezados, los subprocesos, los timeouts y el protocolo `Transport` tienen su propia página: **[Transportes del cliente](transports.md)**.
Todo lo demás en esta página es idéntico en los cuatro casos. Los encabezados, los subprocesos, los timeouts y el protocolo `Transport` tienen su propia página: **[Transportes del cliente](transports.md)**.
### Qué hay en un cliente conectado {#whats-on-a-connected-client}
@ -85,7 +86,7 @@ Ese esquema es todo lo que una UI necesita para renderizar un formulario de argu
`call_tool(name, arguments)` ejecuta la herramienta y te devuelve un `CallToolResult`.
```python title="client.py" hl_lines="26-33"
```python title="client.py" hl_lines="27-34"
--8<-- "docs_src/client/tutorial003.py"
```
@ -117,7 +118,7 @@ Una herramienta que lanza una excepción **no** la lanza en tu cliente. Vuelve c
!!! check
Pídele `"Solaris"` a `lookup_book` (un título que no está en el catálogo) y la función lanza
`ValueError`. Aun así, la llamada devuelve un resultado normal:
`ToolError`. Aun así, la llamada devuelve un resultado normal:
```python
result.is_error # True
@ -125,9 +126,10 @@ Una herramienta que lanza una excepción **no** la lanza en tu cliente. Vuelve c
result.structured_content # None
```
El mensaje de la excepción acabó en `content`, donde el **modelo** puede leerlo y volver a intentarlo. Es
deliberado: un error de herramienta es parte de la conversación, no un fallo fatal. Mira siempre `is_error`
antes de confiar en `structured_content`.
El mensaje del `ToolError` acabó en `content`, donde el **modelo** puede leerlo y volver a intentarlo. Es
deliberado: un error de herramienta es parte de la conversación, no un fallo fatal. (Si la herramienta hubiera
fallado con alguna otra excepción, `content` diría solo `Error executing tool lookup_book`.) Mira siempre
`is_error` antes de confiar en `structured_content`.
!!! warning
`is_error=True` cubre más que tu propio `raise`. Pide una herramienta que el servidor ni siquiera tiene

View file

@ -1,6 +1,6 @@
---
translation:
sections: [9cac816674181eb0, 0700f337babcd4dd, 2bde0dd58cdf00f5, ff7401df479af877, 3d0832f39b0d7059, d4bf7e4479637768, 05e20c0a798860e7]
sections: [9cac816674181eb0, 0700f337babcd4dd, 2bde0dd58cdf00f5, 40b4916d82eaf1d4, 3d0832f39b0d7059, dfa4446556badef0, 5bd93be2ab2ecb9c]
tool: 1
---
# Transportes del cliente {#client-transports}
@ -87,15 +87,15 @@ estándar `SSL_CERT_FILE`/`SSL_CERT_DIR` o pasa un `verify=ssl_context` explíci
Un servidor **stdio** es un subproceso. El cliente lo lanza, escribe JSON-RPC en su stdin y lee JSON-RPC de su stdout. Así es como un host de escritorio ejecuta un servidor en tu máquina: un host *es* este código más una interfaz de usuario, y **[Conectar a un host real](../get-started/real-host.md)** es la misma relación vista desde el lado del host, como archivo de configuración.
Describe el proceso con `StdioServerParameters`, conviértelo en un transporte con `stdio_client` y entrega *eso* a `Client`:
Describe el proceso con `StdioServerParameters` y entrégaselo a `Client`:
```python title="client.py" hl_lines="4-8 12"
```python title="client.py" hl_lines="3-7 11"
--8<-- "docs_src/client_transports/tutorial004.py"
```
`Client` no acepta el objeto de parámetros por sí solo. `StdioServerParameters` es configuración; `stdio_client(server)` es el transporte que sabe lanzar un proceso a partir de ella. Envuélvelo siempre.
Entrar en el bloque lanza el proceso. Salir de él cierra el subproceso: cierra stdin, espera y lo mata si se queda. Nunca lo limpias tú.
Salir del bloque `async with` también cierra el subproceso: cierra stdin, espera y lo mata si se queda. Nunca lo limpias tú.
El stderr del proceso hijo va al tuyo. Para enviarlo a otro sitio, construye tú mismo el transporte con `stdio_client` (de `mcp`) y pasa eso en su lugar: `Client(stdio_client(server, errlog=log_file))`.
!!! warning
El proceso hijo **no** hereda tu entorno. Recibe una lista de permitidos mínima (`HOME`, `LOGNAME`,
@ -113,16 +113,16 @@ Salir del bloque `async with` también cierra el subproceso: cierra stdin, esper
Para `Client`, todo lo anterior es lo mismo.
Un **transporte** es cualquier gestor de contexto asíncrono que produce un par `(read, write)` de flujos de mensajes: formalmente, el protocolo `Transport` de `mcp.client`. `Client` resuelve su argumento por tipo: un objeto de servidor se conecta dentro del proceso, un `str` se convierte en `streamable_http_client(url)` y cualquier otra cosa se entra directamente como transporte. Esa última regla es la razón por la que `stdio_client(...)`, `streamable_http_client(...)` y `sse_client(...)` encajan todos en el mismo hueco, y por la que puedes escribir el tuyo.
Un **transporte** es cualquier gestor de contexto asíncrono que produce un par `(read, write)` de flujos de mensajes: formalmente, el protocolo `Transport` de `mcp.client`. `Client` resuelve su argumento por tipo: un objeto de servidor se conecta dentro del proceso, un `str` se convierte en `streamable_http_client(url)`, un `StdioServerParameters` se convierte en `stdio_client(params)` y cualquier otra cosa se entra directamente como transporte. Esa última regla es la razón por la que `stdio_client(...)`, `streamable_http_client(...)` y `sse_client(...)` encajan todos en el mismo hueco, y por la que puedes escribir el tuyo.
## Resumen {#recap}
* `Client(mcp)` (el objeto del servidor) se conecta en memoria. Úsalo para pruebas y para integración.
* `Client("http://.../mcp")` (una URL) se conecta por Streamable HTTP, el transporte de producción.
* Los encabezados, la autenticación, los proxies y los timeouts van en un `httpx2.AsyncClient` que pasas a `streamable_http_client(url, http_client=...)`. No existe el argumento nombrado `headers=`.
* stdio es `Client(stdio_client(StdioServerParameters(...)))`, nunca el objeto de parámetros solo.
* stdio es `Client(StdioServerParameters(...))`. Envuélvelo tú mismo en `stdio_client(...)` solo para redirigir el stderr del proceso hijo.
* El subproceso recibe un entorno con lista de permitidos, no el tuyo; `env=` se añade a él.
* Un transporte es cualquier cosa con la que puedas hacer `async with x as (read, write)`. `Client` entrega directamente a ese protocolo todo lo que no sea un objeto de servidor ni una URL.
* Un transporte es cualquier cosa con la que puedas hacer `async with x as (read, write)`. `Client` entrega directamente a ese protocolo todo lo que no sea un objeto de servidor, una URL ni un `StdioServerParameters`.
* Construir un `Client` elige el transporte. `async with` lo abre.
Una vez abierto el transporte, los dos lados tienen que acordar una versión del protocolo. Normalmente nunca piensas en ello; cuando lo hagas, **[Versiones del protocolo](../protocol-versions.md)** es la página.

View file

@ -1,11 +1,11 @@
---
translation:
sections: [20541a40dbdd5980, 01262a123ad9501d, 429db5b574a2ac08, 56b2d49da412cb28, 6a1717123fe4513c]
sections: [490237e61c3a7a44, 01262a123ad9501d, 429db5b574a2ac08, e2d0d273fbd2d74b, 64ab0331e868f3d4, 6c8878ce2d1f6d56, 4068f23e371bf0b3, eaef75b8725bc931]
tool: 1
---
# Funcionalidades obsoletas {#deprecated-features}
La especificación 2026-07-28 retira cinco cosas. El SDK sigue implementando todas y cada una, y todas llevan ahora un **aviso de obsolescencia**.
La especificación 2026-07-28 retira cinco cosas. El SDK sigue implementando todas y cada una, y todas llevan ahora un **aviso de obsolescencia**. Una función auxiliar del SDK queda obsoleta por su cuenta y aparece [al final](#deprecated-sdk-helpers).
La tabla siguiente nombra cada funcionalidad obsoleta, explica por qué desaparece e indica el reemplazo sobre el que construir.
@ -55,6 +55,55 @@ MCPDeprecationWarning: The logging capability is deprecated as of 2026-07-28 (SE
intenta enviar. Estas dos funcionalidades solo funcionan de extremo a extremo en una conexión
`mode="legacy"` cuyo cliente registró el callback correspondiente.
## `ping` en una sesión heredada {#ping-on-a-legacy-session}
Un **ping** es una solicitud vacía que cualquiera de los dos lados puede enviar para comprobar que el otro sigue respondiendo. La especificación 2026-07-28 lo elimina ([SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575)): cada solicitud que envía un cliente moderno ya demuestra que el servidor está ahí, y un servidor moderno no tiene canal por el que enviar uno. Ambos métodos del SDK siguen funcionando en una sesión de la generación del handshake. Desde el cliente:
```python
async def main() -> None:
async with Client("http://localhost:8000/mcp", mode="legacy") as client:
await client.send_ping() # warns; returns an EmptyResult
```
Y desde el servidor, dentro de cualquier handler:
```python
@mcp.tool()
async def check_client(ctx: Context) -> str:
"""A tool that still pings the client mid-call."""
await ctx.session.send_ping() # no warning; an EmptyResult while the client is connected
return "client answered"
```
* `client.send_ping()` avisa con `MCPDeprecationWarning` en cada llamada. En una conexión por defecto (`2026-07-28`), el servidor responde `MCPError: Method not found` en su lugar.
* `ctx.session.send_ping()` no lleva ningún aviso. En una conexión moderna lanza el mismo error de falta de canal de retorno (back-channel) que cualquier otra solicitud iniciada por el servidor.
* Ninguno de los dos lados registra nada para responder a un ping.
## Notificaciones de cambio de roots {#roots-change-notifications}
Un cliente de la generación 2025 que declaró la capacidad roots puede avisar al servidor de que sus carpetas del espacio de trabajo cambiaron enviando `notifications/roots/list_changed`; el servidor responde solicitando `roots/list` de nuevo. La especificación 2026-07-28 elimina la notificación junto con el resto del flujo de roots de estilo push. En el cliente, pasar `list_roots_callback=` (**[Callbacks del cliente](client/callbacks.md)**) es lo que declara `"roots": {"listChanged": true}`, y una llamada cumple esa promesa:
```python
async def open_folder(client: Client, uri: str, name: str) -> None:
"""The user opened another folder: expose it through the roots callback, then tell the server."""
workspace.append(Root(uri=FileUrl(uri), name=name))
await client.send_roots_list_changed()
```
En el servidor, el `Server` de bajo nivel acepta el handler que la recibe:
```python
async def roots_changed(ctx: ServerRequestContext, params: NotificationParams | None) -> None:
"""The client's roots changed: ask for the new list."""
roots = (await ctx.session.list_roots()).roots
server = Server("Bookshop", on_roots_list_changed=roots_changed)
```
* `workspace` es la lista que devuelve tu `list_roots_callback`. `client.send_roots_list_changed()` avisa, y necesita un cliente `mode="legacy"`: en una conexión moderna la notificación se descarta en silencio. Mantén la sesión abierta después, porque el `roots/list` posterior del servidor llega por ella.
* `MCPServer` no tiene ningún hook para la notificación. En el `Server` de bajo nivel, `on_roots_list_changed=` registra el handler (también obsoleto, y avisa en la construcción). La notificación no lleva payload, así que el handler llama a `ctx.session.list_roots()` para obtener la lista nueva.
## Silenciar el aviso {#silencing-the-warning}
No lo hagas en código nuevo.
@ -75,22 +124,33 @@ Esa es toda la API. No hay un interruptor por método, y tampoco lo quieres: la
Aplica el filtro al revés y obtienes una prueba de regresión gratis. Añade
`"error::mcp.MCPDeprecationWarning"` al ajuste `filterwarnings` de tu configuración de
pytest y la llamada obsoleta **lanza una excepción** en lugar de avisar. Una herramienta
llamada `old_log` que todavía llama a `ctx.info()` deja de pasar y empieza a informar:
llamada `old_log` que todavía llama a `ctx.info()` deja de pasar: la llamada vuelve con
`is_error=True` y `Error executing tool old_log`, y el log capturado del servidor señala
al culpable:
```text
Error executing tool old_log: The logging capability is deprecated as of 2026-07-28 (SEP-2577).
mcp.shared.exceptions.MCPDeprecationWarning: The logging capability is deprecated as of 2026-07-28 (SEP-2577).
```
Una línea de configuración de pytest, y una llamada obsoleta nunca podrá volver a colarse
en tu código sin que falle una prueba.
## Funciones auxiliares del SDK obsoletas {#deprecated-sdk-helpers}
No son cambios de la especificación, solo detalles internos del SDK con un reemplazo mejor. Avisan con el mismo `MCPDeprecationWarning` y se eliminarán en 3.0.
| Obsoleto | Qué hacer en su lugar |
|---|---|
| `FuncMetadata.call_fn_with_arg_validation()` | `FuncMetadata.validate_arguments()` y después `FuncMetadata.call_fn()`. Solo lo llamaba el código que maneja `FuncMetadata` directamente (una subclase propia de `Tool`, por ejemplo). |
## Resumen {#recap}
* La especificación 2026-07-28 deja obsoletos los **roots**, el **muestreo** iniciado por el servidor y el **registro de logs** del protocolo (todo en [SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577)), restringe el **progreso** a la dirección servidor a cliente y elimina **`ping`**.
* La columna de reemplazos te indica el camino: **[Solicitudes de varias idas y vueltas](handlers/multi-round-trip.md)** para el muestreo y los roots, **[Registro de logs](handlers/logging.md)** para los logs, **[Progreso](handlers/progress.md)** para el progreso. `ping` no necesita nada en absoluto.
* Obsoleto es solo un aviso: no hay cambios en lo que se transmite, todo sigue funcionando contra sesiones anteriores a 2026 y recibes un `MCPDeprecationWarning` visible (un `UserWarning`, así que está activo por defecto).
* El muestreo y los roots necesitan además un canal de retorno (back-channel) que una sesión 2026-07-28 no tiene. En una conexión moderna avisan y después lanzan una excepción.
* El muestreo y los roots necesitan además un canal de retorno que una sesión 2026-07-28 no tiene. En una conexión moderna avisan y después lanzan una excepción.
* `warnings.filterwarnings("ignore", category=MCPDeprecationWarning)` silencia toda la categoría; `"error::mcp.MCPDeprecationWarning"` en pytest la convierte en un fallo de prueba.
* Una función auxiliar del SDK, `FuncMetadata.call_fn_with_arg_validation()`, queda obsoleta por separado y se eliminará en 3.0.
* El código nuevo no debería construirse sobre nada de esto.
Todas las demás páginas de esta documentación enseñan la API actual.

View file

@ -1,6 +1,6 @@
---
translation:
sections: [3c4f2f06b4e978b6, 22520eecae3d1961, f4e1709db18d635a, 2eb57992049671d9, 1ba83e9af37cc1b4, 4822586344b08d9e, 1c93afef72478992, b6b448f9eddd51dc, fe55370fd931815b]
sections: [3c4f2f06b4e978b6, 51ea5fbcb0e93563, 32d8808606ffdae0, 2eb57992049671d9, 1ba83e9af37cc1b4, 4822586344b08d9e, 1c93afef72478992, b6b448f9eddd51dc, fe55370fd931815b]
tool: 1
---
# Conectarse a un host real {#connect-to-a-real-host}
@ -11,7 +11,7 @@ Esto significa que conectarse a un host es un único acto: le indicas **el coman
## Un servidor, todos los hosts {#one-server-every-host}
```python title="server.py" hl_lines="3 33-34"
```python title="server.py" hl_lines="4 34-35"
--8<-- "docs_src/real_host/tutorial001.py"
```
@ -50,7 +50,7 @@ También es el comando que `mcp install` escribe por ti en la configuración de
Y un host no es más que una aplicación con un cliente MCP dentro, así que tu propio código
Python puede hacer el papel del host: **[Transportes del cliente](../client/transports.md)** lanza
este mismo archivo como subproceso con `stdio_client(...)`, y **[Pruebas](testing.md)**
este mismo archivo como subproceso con `Client(StdioServerParameters(...))`, y **[Pruebas](testing.md)**
se conecta a él en memoria, sin ningún proceso.
## Claude Desktop {#claude-desktop}

View file

@ -1,6 +1,6 @@
---
translation:
sections: ['4926721070127497', c52a1de2b6b32f40, 2e410b412c25f314, 627195f7159e24ef]
sections: ['4926721070127497', c52a1de2b6b32f40, 8e792bf8c7489ec6, 627195f7159e24ef]
tool: 1
---
# Pruebas {#testing}
@ -84,9 +84,10 @@ async def test_call_add_tool(client: Client):
Pueden fallar dos cosas distintas, y este indicador solo afecta a una de ellas.
Una excepción dentro de una de **tus herramientas** no es un fallo del protocolo. Se convierte en un
resultado normal con `is_error=True`, y el modelo lee el mensaje. `raise_exceptions` no cambia eso:
con o sin él, `call_tool` devuelve el mismo resultado con `is_error=True`. Hay una página entera
dedicada a esto: **[Manejo de errores](../servers/handling-errors.md)**.
resultado normal con `is_error=True` (y si era un `ToolError`, el modelo lee tu mensaje).
`raise_exceptions` no cambia eso: con o sin él, `call_tool` devuelve el mismo resultado con
`is_error=True`. Hay una página entera dedicada a esto:
**[Manejo de errores](../servers/handling-errors.md)**.
Un fallo **fuera** del cuerpo de una herramienta es otra cosa. En la conexión que te da
`Client(mcp)`, el servidor lo depura y lo convierte en un genérico `"Internal server error"` antes de

View file

@ -1,6 +1,6 @@
---
translation:
sections: [335ca2a0b266f003, d1ad562d3fe87bc0, 0bb1396c86daeba4, d1cb1235bb9ee267, 833179c09d239c83, e5d6dec2d2e655e8]
sections: [335ca2a0b266f003, d1ad562d3fe87bc0, 25b49f89c9e6f9d0, d1cb1235bb9ee267, 833179c09d239c83, e5d6dec2d2e655e8]
tool: 1
---
# Elicitación {#elicitation}
@ -90,7 +90,8 @@ Ese esquema es el formulario. `Field(description=...)` es la etiqueta; un valor
Un esquema de elicitación no es tan expresivo como el esquema de entrada de una herramienta.
Solo campos planos y primitivos: `str`, `int`, `float`, `bool` o un `Literal` de cadenas (se
convierte en un `enum`). Pon un modelo dentro del modelo y `ctx.elicit` lanza una excepción
antes de que se envíe nada al cliente:
antes de que se envíe nada al cliente. La llamada a la herramienta falla con
`Error executing tool <name>`, y el log de tu servidor tiene el motivo:
```text
TypeError: Elicitation schema field 'address' rendered as {'$ref': '#/$defs/Address'}, which is not a valid PrimitiveSchemaDefinition
@ -113,8 +114,8 @@ Una negativa no es un error. La herramienta decide qué significa rechazar (aqu
!!! tip
La respuesta se valida contra tu modelo antes de que tu código la vea. Un cliente que envía
`"maybe"` para un `bool` no corrompe tu reserva: la llamada falla con un error de
discrepancia de esquema y tu `if` nunca se ejecuta.
`"maybe"` para un `bool` no corrompe tu reserva: `ctx.elicit` lanza `ValueError`, la llamada
falla y tu `if` nunca se ejecuta.
## Enviar al usuario a una URL {#send-the-user-to-a-url}

View file

@ -1,6 +1,6 @@
---
translation:
sections: [c93a3e1aefd77955, 7851abd5ec54393b, f49d1ca2f330f9cd, c03764bd9dfeef7b, 4a0391691a674ae4, 2df5cd279eabf9f5]
sections: [c93a3e1aefd77955, 7851abd5ec54393b, f49d1ca2f330f9cd, 4cc0a00347c3f534, 4a0391691a674ae4, 2df5cd279eabf9f5]
tool: 1
---
# Registro de logs {#logging}
@ -55,6 +55,8 @@ El valor por defecto es `"INFO"`.
`logging.basicConfig()` nunca reemplaza handlers que ya existen. Si configuras el logging tú mismo antes de crear el servidor, tu configuración gana.
Tampoco necesitas un `try`/`except` en cada handler solo para registrar los fallos. Cuando una función de herramienta o de recurso lanza una excepción, el SDK la registra por ti. **[Manejo de errores](../servers/handling-errors.md#any-other-exception)** explica qué se registra y con qué nivel.
## Pruébalo {#try-it}
Ejecuta el servidor con el MCP Inspector:

View file

@ -1,6 +1,6 @@
---
translation:
sections: [fea8d769ff9edeba, ce8e2ad42f29ef71, 0d705efb19cf99c2, 7a53ead3e704a7f0, 9adc400e8c88e854, 318893ad8e2e9924, 6b63ab96b34476c0]
sections: [fea8d769ff9edeba, ce8e2ad42f29ef71, 0d705efb19cf99c2, 2fd7cf825e6d2b2c, 9adc400e8c88e854, 318893ad8e2e9924, 6b63ab96b34476c0]
tool: 1
---
# Ejecutar el servidor {#running-your-server}
@ -72,7 +72,7 @@ Cada transporte tiene sus propios argumentos nombrados, todos en `run()`:
* `streamable_http_path`: dónde vive el endpoint MCP. Por defecto `/mcp`.
* `json_response=True`: responde a cada POST con un único cuerpo JSON en lugar de un flujo SSE. Ese cuerpo tiene sitio para la respuesta y nada más, así que una herramienta que llama de vuelta al cliente a mitad de solicitud (`ctx.elicit()`, muestreo (sampling)) lanza `NoBackChannelError` en este tramo, y las notificaciones ligadas a la llamada en curso (el progreso de `ctx.report_progress()`, los mensajes de log por llamada) se descartan; el flujo `GET` independiente sigue llevando las que no están relacionadas.
* `stateless_http=True`: un transporte nuevo por solicitud, sin seguimiento de sesión.
* `max_request_body_size`: el cuerpo POST más grande que se acepta, en bytes. Es 4 MiB por defecto; las solicitudes mayores
* `max_request_body_size`: el cuerpo de solicitud más grande que se acepta, en bytes. Es 4 MiB por defecto; las solicitudes mayores
reciben HTTP 413 antes del análisis o de la creación de la sesión. Súbelo solo cuando los mensajes MCP legítimos
superen ese tamaño.
* `event_store`, `retry_interval`, `transport_security`: reanudabilidad y protección contra DNS rebinding. Pueden esperar hasta que despliegues en algún lugar que no sea localhost; **[Desplegar y escalar](deploy.md)** cubre `transport_security`.

View file

@ -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)**.

View file

@ -1,6 +1,6 @@
---
translation:
sections: [496394d24d221bf1, 4ceb4591180dc6c3, 0fd63e4682d02e0c, 969ede0bd3686a16, 043f526230dd243d, 6ee3e9bcfd24047a]
sections: [496394d24d221bf1, 4ceb4591180dc6c3, 0fd63e4682d02e0c, 969ede0bd3686a16, 864137b5e9c61e91, 043f526230dd243d, db1ef91db7d6b3f3]
tool: 1
---
# Multimedia {#media}
@ -86,6 +86,24 @@ Un sufijo que no reconoce recurre a `application/octet-stream`.
`Audio` así a partir de bytes MP3 y al cliente se le dice `mime_type="audio/wav"`, y entonces
falla fielmente al decodificarlo. Cuando pases `data=`, pasa `format=`.
## Incrustar un recurso {#embedding-a-resource}
Una herramienta también puede devolver un documento: un texto o unos bytes junto con la URI donde vive y un tipo MIME. Eso es un **`EmbeddedResource`**, otro tipo de bloque de contenido. A diferencia de un `str` simple, le dice al cliente qué es el contenido, así que el cliente puede mostrarlo como un adjunto o reconocer un recurso que ya conoce.
```python title="server.py" hl_lines="7 14 16-18"
--8<-- "docs_src/media/tutorial005.py"
```
* `brand://guidelines` es un recurso normal y corriente (**[Recursos](resources.md)** los cubre). La herramienta le entrega el mismo documento al modelo cuando lo pide, y llamar a `guidelines()` directamente mantiene una única fuente de verdad.
* `EmbeddedResource` y `TextResourceContents` vienen de `mcp.types`. No hay una utilidad como la de las imágenes: el bloque que construyes entra en el resultado sin cambios, y no hay `structured_content`.
* Usa la URI con la que está registrado el recurso, para que un cliente pueda saber que el adjunto y `brand://guidelines` son el mismo documento. Cualquier URI es válida, registrada o no.
```python
result.content # [EmbeddedResource(type="resource", resource=TextResourceContents(uri="brand://guidelines", mime_type="text/markdown", text="# Brand guidelines\n\n..."))]
```
Para contenido binario, usa `BlobResourceContents(uri=..., mime_type=..., blob=...)` con los bytes codificados en base64 en `blob`, en lugar de `TextResourceContents`. Para enviar solo un puntero que el cliente pueda leer más tarde con `resources/read`, devuelve en su lugar un `ResourceLink(name=..., uri=...)`; también es un bloque de contenido.
## Iconos {#icons}
Un `Icon` es metadatos, no contenido. No lleva la imagen; apunta a una con una URI, y el cliente puede descargarla y mostrarla junto al nombre de tu servidor, una herramienta, un recurso o un prompt.
@ -115,6 +133,7 @@ Los iconos de una herramienta están en el objeto `Tool` de `tools/list`, los de
* Devuelve un `Image` o un `Audio` desde una herramienta y el cliente recibe un bloque `ImageContent` / `AudioContent`: tus bytes codificados en base64, con un tipo MIME.
* Constrúyelo a partir de un `path=` y deja que el sufijo decida el tipo MIME, o a partir de `data=` en memoria más un `format=` explícito.
* Devuelve un `EmbeddedResource` para poner un documento (texto o un blob en base64, con su URI y tipo MIME) en el resultado, o un `ResourceLink` para enviar solo el puntero.
* Los resultados multimedia no llevan `structured_content` ni esquema de salida.
* Un `Icon` es un puntero: una URI `src` más `mime_type`, `sizes` y `theme` opcionales.
* `icons=[...]` funciona en el servidor, en herramientas, en recursos y en prompts, y los clientes los encuentran en los objetos correspondientes.

View file

@ -1,6 +1,6 @@
---
translation:
sections: [d65c098f37f5b6c3, dd0c2724d6f2877e, 6835bb3570c6714c, ffe823cb0fedd488, f33651add1b59094]
sections: [d65c098f37f5b6c3, dd0c2724d6f2877e, 6835bb3570c6714c, d30d3c20168b88b2, f5ef38dad59d6f76, 6e38a699ba57fbdf, 2b984a3bf37a0ddd]
tool: 1
---
# Prompts {#prompts}
@ -140,10 +140,55 @@ La entrada de `prompts/list` ahora lleva todo lo que un cliente necesita para di
```
!!! info
Si has leído **[Herramientas](tools.md)**, ya sabes todo lo de esta página. El mismo decorador, el mismo
Si has leído **[Herramientas](tools.md)**, ya sabes todo lo visto hasta aquí. El mismo decorador, el mismo
docstring como descripción, el mismo `Annotated`/`Field`. Lo único que cambia es quién
lo dispara (el usuario) y adónde va el resultado (a la conversación).
## Más que texto {#more-than-text}
`UserMessage` y `AssistantMessage` también aceptan un bloque de contenido, o un helper `Image` / `Audio`, en cualquier lugar donde aceptan un `str`. En los prompts aparecen dos casos: adjuntar un documento y adjuntar una imagen.
### Incrustar un archivo {#embedding-a-file}
```python title="server.py" hl_lines="5 12 21 23"
--8<-- "docs_src/prompts/tutorial004.py"
```
* La guía de estilo es un recurso en `style://python` (**[Recursos](resources.md)** los cubre), leído de un `style-guide.md` junto a `server.py`. Pon ahí cualquier archivo Markdown.
* `EmbeddedResource(resource=TextResourceContents(...))`, ambos de `mcp.types`, lleva el archivo con su URI y su tipo MIME como primer mensaje; la solicitud que se refiere a él va después como texto plano.
* Incrustar la guía, en lugar de pegarla en el f-string, permite al cliente mostrarla como adjunto y volver a abrir `style://python` más tarde, y el modelo recibe el archivo tal cual. Para un archivo binario usa `BlobResourceContents` con un `blob` en base64.
Renderizado, el `content` del primer mensaje es un bloque `resource`:
```json
{"type": "resource", "resource": {"uri": "style://python", "mimeType": "text/markdown", "text": "* Prefer early returns.\n..."}}
```
### Adjuntar una imagen {#attaching-an-image}
```python title="server.py" hl_lines="4 15"
--8<-- "docs_src/prompts/tutorial005.py"
```
* `Image` es el helper de **[Imágenes, audio e iconos](media.md)**. `UserMessage` lo convierte en un bloque `ImageContent` (el archivo codificado en base64, el tipo MIME deducido de `.png`) cuando se renderiza el prompt; `Audio` se convierte en un `AudioContent` del mismo modo.
* Pon cualquier PNG llamado `architecture.png` junto a `server.py`. Los argumentos de un prompt son cadenas, así que la imagen siempre viene del servidor; `component` solo aporta las palabras.
```json
{"type": "image", "data": "iVBORw0KGgoAAAANSUhEUg...", "mimeType": "image/png"}
```
## Cambiar la lista en tiempo de ejecución {#changing-the-list-at-runtime}
Se pueden añadir prompts mientras hay clientes conectados, por ejemplo para que un usuario guarde una instrucción como entrada de menú propia. Registra el prompt y luego notifica:
```python title="server.py" hl_lines="5 23-27"
--8<-- "docs_src/prompts/tutorial006.py"
```
* `mcp.add_prompt(Prompt.from_function(fn, name=..., description=...))` registra una función exactamente como lo haría `@mcp.prompt()`, y `mcp.remove_prompt(name)` es lo inverso. `add_prompt` conserva una entrada existente con el mismo nombre en lugar de sobrescribirla, así que la herramienta elimina primero cualquier entrada anterior para que guardar equivalga a reemplazar. `prompts/list` refleja el cambio de inmediato.
* `await ctx.notify_prompts_changed()` envía `notifications/prompts/list_changed` a cada cliente `2026-07-28` que escucha en un stream `subscriptions/listen` (**[Suscripciones](../handlers/subscriptions.md)**). `await ctx.session.send_prompt_list_changed()` se lo envía al cliente que hace la llamada cuando ese cliente es anterior a 2026 (**[Atender clientes heredados](../run/legacy-clients.md)**). Llama a los dos; cada uno no hace nada cuando no hay nadie a quien avisar.
* Un cliente que recibe la notificación vuelve a llamar a `prompts/list`. En el `Client` de Python eso es `async with client.listen(prompts_list_changed=True) as sub:`, que produce un evento `PromptsListChanged`.
## Resumen {#recap}
* `@mcp.prompt()` en una función la convierte en un prompt. El nombre sale de la función y la descripción del docstring.
@ -152,5 +197,7 @@ La entrada de `prompts/list` ahora lleva todo lo que un cliente necesita para di
* Devuelve un `str` y se convierte en un mensaje de usuario. Devuelve una lista de `UserMessage` / `AssistantMessage` para sembrar una conversación de varios turnos.
* `title=` y `Field(description=...)` son lo que un cliente pone en su interfaz.
* Un argumento obligatorio que falta hace fallar toda la solicitud. No hay un resultado de error por prompt.
* Envuelve un `EmbeddedResource` o un `Image` en un `UserMessage` para adjuntar un documento o una imagen.
* Añade o quita prompts en tiempo de ejecución con `mcp.add_prompt(...)` / `mcp.remove_prompt(...)`, y luego `await ctx.notify_prompts_changed()` y `await ctx.session.send_prompt_list_changed()`.
El autocompletado en el servidor de los argumentos de un prompt (o de una plantilla de recurso) está en **[Autocompletado](completions.md)**.

View file

@ -1,6 +1,6 @@
---
translation:
sections: [a838d57f003aed44, 857d03886a0137ed, 42d9efcb9f542867, 2290ff08435b5573, e866c192e11d1c14, 6cdbad079f7b47f0, d4b607372fb28b51, 18dbf726ac45e0b7, c6f7d2a148aa49f4, c851964bb3301907, d715db6f8dccc9cc, ef86634aa70498a7]
sections: [a838d57f003aed44, 857d03886a0137ed, 42d9efcb9f542867, 2290ff08435b5573, 91be9b73602abcf1, 6cdbad079f7b47f0, d4b607372fb28b51, 18dbf726ac45e0b7, c7eff2a5698225fa, c851964bb3301907, 8f296f1f09e4c400, d715db6f8dccc9cc, a0c344a48450dbe4]
tool: 1
---
# Salida estructurada {#structured-output}
@ -105,7 +105,7 @@ No todas las formas merecen una clase. Un `TypedDict` produce el mismo esquema:
--8<-- "docs_src/structured_output/tutorial003.py"
```
Un `TypedDict` es un `dict` normal en tiempo de ejecución, así que eso es lo que construyes y devuelves. El esquema, la validación y `structured_content` son idénticos a los de la versión con `BaseModel` (salvo las descripciones, para las que `TypedDict` no tiene sitio).
Un `TypedDict` es un `dict` normal en tiempo de ejecución, así que eso es lo que construyes y devuelves. El esquema, la validación y `structured_content` siguen las mismas reglas que la versión con `BaseModel`: añade un docstring a la clase o `Annotated[..., Field(description=...)]` y se convierten en las descripciones, y una clave `NotRequired` que dejes fuera del dict se queda fuera de `structured_content`.
## Una dataclass {#a-dataclass}
@ -187,18 +187,19 @@ No lo notas mientras construyes el valor a mano: Pydantic ya se aseguró de que
La anotación promete `WeatherData`. La respuesta del servicio externo dejó de enviar `humidity`.
!!! check
Llama a `get_weather` y no le entrega al cliente en silencio un objeto medio vacío. La llamada falla,
y las primeras líneas del error nombran el campo:
Llama a `get_weather` y no le entrega al cliente en silencio un objeto medio vacío. La llamada falla:
el cliente recibe `is_error=True` con `Error executing tool get_weather`, así que el modelo sabe que la
llamada falló en lugar de leer con toda confianza un tiempo que no existe. El nombre del campo es para ti,
en el log del servidor con nivel `ERROR`:
```text
Error executing tool get_weather: 1 validation error for WeatherData
Tool 'get_weather' raised an unexpected exception
...
pydantic_core._pydantic_core.ValidationError: 1 validation error for WeatherData
humidity
Field required [type=missing, input_value={'temperature': 16.2, 'conditions': 'Overcast'}, input_type=dict]
```
Ese texto vuelve como resultado de la herramienta con `is_error=True`, así que el modelo sabe que la
llamada falló en lugar de leer con toda confianza un tiempo que no existe.
Por cierto, devolver un `dict` normal desde una herramienta `-> WeatherData` está bien. Es exactamente lo que produjo `json.loads`. La validación se aplica al valor, no al tipo de Python.
## Desactivarlo {#opting-out}
@ -213,6 +214,10 @@ Sin `output_schema`, sin envoltorio, sin validación. `structured_content` es `N
Lo contrario, `structured_output=True`, convierte la detección automática en un requisito: una herramienta cuyo tipo de retorno no pueda producir un esquema lanza una excepción al importar el módulo en lugar de recurrir al texto.
## Bloques de contenido y medios {#content-blocks-and-media}
Los bloques de contenido y los medios (`TextContent`, `EmbeddedResource`, `Image`, `Audio` y compañía, ya sea solos, como elementos de un `list`, `tuple` o `Sequence`, o como ramas de una unión) quedan excluidos sin que hagas nada: son para que los lea el modelo, así que la detección automática no deriva ningún esquema de ellos (**[Imágenes, audio e iconos](media.md)** se ocupa de `Image` y `Audio`). `structured_output=True` sigue forzando uno para las clases de bloques de contenido.
## Una clase sin anotaciones de tipo {#a-class-without-type-hints}
Hay una forma de acabar sin salida estructurada sin haberlo pedido: devolver una clase que **no tiene anotaciones en su cuerpo**.
@ -245,6 +250,6 @@ Hay una forma de acabar sin salida estructurada sin haberlo pedido: devolver una
* Los escalares, las listas, las tuplas y las uniones se envuelven en `{"result": ...}`. Los modelos, los `TypedDict`, las dataclasses, las clases con anotaciones y `dict[str, ...]` ya son objetos y se quedan como están.
* Cada resultado lleva `content` (texto, para el modelo) **y** `structured_content` (datos, para la aplicación).
* Lo que devuelves se valida contra el esquema. Una discrepancia es un error de herramienta, no un resultado corrupto.
* `structured_output=False` excluye una herramienta. Una clase sin anotaciones de tipo queda excluida en silencio; vigílalo.
* `structured_output=False` excluye una herramienta. Los bloques de contenido, `Image` y `Audio` quedan excluidos por defecto; una clase sin anotaciones de tipo queda excluida en silencio, así que vigílalo.
Ahora dominas todo lo que una herramienta puede responder. A continuación, la segunda primitiva: **[Recursos](resources.md)**.

View file

@ -1,6 +1,6 @@
---
translation:
sections: [e4cc390d56573409, 8566e2b68594e9ad, 2c97b9f888398951, 048e5471dfa71aea, 3076b1e16ad95950, edbedf2a16e71311, 3d8ef8da89fa87c1, f6c0e02e6ea5a363]
sections: [e4cc390d56573409, f30cf8103a6e918c, 2c97b9f888398951, 048e5471dfa71aea, 3076b1e16ad95950, edbedf2a16e71311, 3d8ef8da89fa87c1, f6c0e02e6ea5a363]
tool: 1
---
# Herramientas {#tools}
@ -39,6 +39,8 @@ A partir de esas anotaciones de tipo, el SDK genera un JSON Schema y lo envía a
Ambos argumentos están en `required` porque ninguno tiene valor por defecto. Lo arreglarás en un momento. (Las claves `title` son artefactos de Pydantic; las propiedades, sus tipos y `required` son el contrato.)
Tampoco hay una clave `$schema`: MCP trata un esquema que no la tiene como **JSON Schema 2020-12**, que es lo que genera Pydantic, así que no hay nada que elegir hasta que escribas esquemas a mano en el **[Server de bajo nivel](../advanced/low-level-server.md#the-dialect-is-json-schema-2020-12)**.
!!! tip
Aquí las anotaciones de tipo no son documentación. Son **el contrato**. Si un cliente envía `"limit": "ten"`,
el SDK lo rechaza antes de que tu función llegue a ejecutarse.

View file

@ -1,6 +1,6 @@
---
translation:
sections: [4a7033e1ed8ad602, 55dcbfff0c6271bf, 101ef9d14bf4ec46, 4b6c4a845438abc7, f98b46bafbee4acd]
sections: [4a7033e1ed8ad602, 55dcbfff0c6271bf, 317f4256a650cab6, 4b6c4a845438abc7, f98b46bafbee4acd]
tool: 1
---
# Plantillas de URI y seguridad de rutas {#uri-templates-and-path-safety}
@ -164,7 +164,7 @@ Las comprobaciones integradas detienen los casos comunes, pero no pueden conocer
de tu entorno aislado. Para acceder al sistema de archivos, usa `safe_join` para resolver la
ruta y verificar que se mantiene dentro de tu directorio base:
```python title="server.py" hl_lines="4 14"
```python title="server.py" hl_lines="5 15"
--8<-- "docs_src/uri_templates/tutorial002.py"
```
@ -204,10 +204,10 @@ Estas comprobaciones son un prefiltro heurístico; para el acceso al sistema de
`safe_join` sigue siendo el límite de contención.
!!! tip
Si tu handler no puede satisfacer la solicitud (el archivo no existe,
el id es desconocido), lanza una excepción. El SDK la convierte en una
respuesta de error. Consulta **[Manejo de errores](handling-errors.md)** para ver la diferencia entre un
error de protocolo y un error de herramienta.
Si tu handler no puede satisfacer la solicitud (el archivo no existe, el id es desconocido), lanza
`ResourceNotFoundError` como hace `read_manual` arriba. El cliente recibe `-32602` con tu mensaje
y la URI. Una excepción inesperada se convierte, en cambio, en un `-32603` genérico. Consulta
**[Manejo de errores](handling-errors.md#a-resource-that-doesnt-exist)**.
## Recursos en el Server de bajo nivel {#resources-on-the-low-level-server}

View file

@ -1,6 +1,6 @@
---
translation:
sections: [2efaecdef109a5c5, fcacd3e66b8635a4, 25323d737dcf0261, 4835ed1772f1d113, 137454d469c867f5, 6392596bd6df54f0, 41126fa9c4fe432f, 480b6d7897e30ab4, d83bb682e708dde0, ebbed3449c499db4, 323ef84f6b4bebde, 30fd31be74169d9a, 656943c6cb567218, c2dc3b1007d2e987, 7cf5386b997d04e9, 0b59feed8384456e, 0cba47bae78d04eb, 954dc21efdb532a3]
sections: [2efaecdef109a5c5, fcacd3e66b8635a4, 25323d737dcf0261, 8a6e351ec756904d, 137454d469c867f5, 6392596bd6df54f0, 41126fa9c4fe432f, 480b6d7897e30ab4, d83bb682e708dde0, ebbed3449c499db4, 323ef84f6b4bebde, 30fd31be74169d9a, 656943c6cb567218, c2dc3b1007d2e987, 7cf5386b997d04e9, 0b59feed8384456e, 0cba47bae78d04eb, e4355f4c7cf4fb2e]
tool: 1
---
# Solución de problemas {#troubleshooting}
@ -81,11 +81,11 @@ async def main() -> None:
`__aexit__` es la desconexión, y por eso no hay ningún `client.close()` que olvidar. **[Pruebas](get-started/testing.md)** se basa exactamente en este patrón.
## `Error executing tool <name>: <message>` y `Unknown tool: <name>` {#error-executing-tool-name-message-and-unknown-tool-name}
## `Error executing tool <name>: <message>`, `Error executing tool <name>` y `Unknown tool: <name>` {#error-executing-tool-name-message-error-executing-tool-name-and-unknown-tool-name}
Estás leyendo un **resultado**, no una excepción. `call_tool` no lanzó nada, y nunca lo hará para una herramienta que falla.
Llama a `forecast` con una ciudad que el servidor no conoce y la excepción que lanza vuelve con la solicitud marcada como *correcta*:
Llama a `forecast` con una ciudad que el servidor no conoce y el `ToolError` que lanza vuelve con la solicitud marcada como *correcta*:
```python
result.is_error # True
@ -97,6 +97,8 @@ result.structured_content # None
La solución está en tu cliente: **comprueba `result.is_error`**. Un `try/except` alrededor de `call_tool` no captura ninguno de estos casos, porque no hay nada que capturar. Es deliberado, y es lo más útil de esta página que puedes interiorizar: el *modelo* eligió la llamada, así que el modelo recibe el mensaje y la oportunidad de intentarlo de nuevo. **[Manejo de errores](servers/handling-errors.md)** tiene todos los detalles, incluida la vía de `MCPError` que *sí* lanza.
La forma escueta, `Error executing tool <name>` sin mensaje, significa que la herramienta **se cayó**: se le escapó una excepción que no había previsto (o su valor devuelto no pasó el esquema de salida), y el texto de esa excepción no se transmite. El traceback está en el **log del servidor** con nivel `ERROR`, como `Tool '<name>' raised an unexpected exception`.
## `TypeError: The @tool decorator was used incorrectly. Did you forget to call it? Use @tool() instead of @tool` {#typeerror-the-tool-decorator-was-used-incorrectly-did-you-forget-to-call-it-use-tool-instead-of-tool}
Escribiste `@mcp.tool` en lugar de `@mcp.tool()`. `tool()` es una *fábrica* de decoradores: sin los paréntesis, Python le pasa tu función a su parámetro `name=`.
@ -409,7 +411,7 @@ mcp = MCPServer("Weather", request_state_security=RequestStateSecurity(keys=[key
## Resumen {#recap}
* `ExceptionGroup: unhandled errors in a TaskGroup` nunca es el error. Lee la **última línea**; capturar `MCPError` *dentro* del bloque `async with Client(...)` evita el envoltorio por completo.
* `call_tool` no lanza nada para una herramienta que falla. `Error executing tool ...` y `Unknown tool: ...` son resultados: comprueba `result.is_error`.
* `call_tool` no lanza nada para una herramienta que falla. `Error executing tool ...` y `Unknown tool: ...` son resultados: comprueba `result.is_error`. Si no hay mensaje después del nombre de la herramienta, se cayó, y el traceback está en el log del servidor.
* `Client must be used within an async context manager` -> usa `async with`. `Use @tool() instead of @tool` -> añade los paréntesis.
* `Tool already exists:` en el log del servidor es la única señal de que dos herramientas con el mismo nombre se fundieron en una.
* Un 421, tres formas de escribirlo: `Server returned an error response` (el `Client` de python), `421 Misdirected Request` / `Invalid Host header` (todo lo demás), `Invalid Host header: <host>` (el log del servidor). Solución: `transport_security=TransportSecuritySettings(allowed_hosts=[...])`.

View file

@ -1,6 +1,6 @@
---
translation:
sections: [cfe01c0c5863dfa2, 11d93f1fa09eadf5, a7392996acf1ad8f, 875eb2889263424e]
sections: [cfe01c0c5863dfa2, 1c58c5cfcc37d455, a7392996acf1ad8f, 875eb2889263424e]
tool: 1
---
# Novedades de la v2 {#whats-new-in-v2}
@ -47,9 +47,9 @@ La v1 te entregaba tres capas anidadas: un gestor de contexto de transporte que
--8<-- "docs_src/client/tutorial001.py"
```
`Client` acepta un objeto servidor (en memoria, sin transporte: la historia de las pruebas), una URL (Streamable HTTP) o cualquier gestor de contexto de transporte como `stdio_client(...)`. Entrar en `async with` conecta y negocia la versión del protocolo, sea cual sea la generación que hable el servidor; `client.server_capabilities` y `client.protocol_version` simplemente están ahí después, y `client.server_info` también cuando el servidor se identifica (ahora es `Implementation | None`, porque la identidad en la generación 2026 es opcional). Los callbacks de muestreo y elicitación que registraste en la v1 siguen funcionando (sus cuerpos ven el mismo renombramiento de atributos a snake_case que todo lo demás en esta página), ahora también responden a las solicitudes dentro de resultados al estilo 2026 (más abajo) y se ejecutan concurrentemente en lugar de una a una. `ClientSession` sigue debajo para quien quiera la superficie de bajo nivel, y `client.session` te la entrega; también cambió (corre sobre el nuevo motor de despacho, y algunas de sus propias firmas cambiaron), así que lee la **[Guía de migración](migration.md#clientsession-now-runs-on-jsonrpcdispatcher-basesession-removed)** antes de bajar a ese nivel.
`Client` acepta un objeto servidor (en memoria, sin transporte: la historia de las pruebas), una URL (Streamable HTTP), un `StdioServerParameters` (un subproceso stdio) o cualquier otro gestor de contexto de transporte como `sse_client(...)`. Entrar en `async with` conecta y negocia la versión del protocolo, sea cual sea la generación que hable el servidor; `client.server_capabilities` y `client.protocol_version` simplemente están ahí después, y `client.server_info` también cuando el servidor se identifica (ahora es `Implementation | None`, porque la identidad en la generación 2026 es opcional). Los callbacks de muestreo y elicitación que registraste en la v1 siguen funcionando (sus cuerpos ven el mismo renombramiento de atributos a snake_case que todo lo demás en esta página), ahora también responden a las solicitudes dentro de resultados al estilo 2026 (más abajo) y se ejecutan concurrentemente en lugar de una a una. `ClientSession` sigue debajo para quien quiera la superficie de bajo nivel, y `client.session` te la entrega; también cambió (se ejecuta sobre el nuevo motor de despacho, y algunas de sus propias firmas cambiaron), así que lee la **[Guía de migración](migration.md#clientsession-now-runs-on-jsonrpcdispatcher-basesession-removed)** antes de bajar a ese nivel.
**[El Client](client/index.md)** lo presenta, **[Transportes del cliente](client/transports.md)** cubre las tres formas de conexión, **[Callbacks del cliente](client/callbacks.md)** cubre los callbacks en sí y **[Pruebas](get-started/testing.md)** muestra el patrón en memoria que sustituye al helper `create_connected_server_and_client_session()` de la v1.
**[El Client](client/index.md)** lo presenta, **[Transportes del cliente](client/transports.md)** cubre las cuatro formas de conexión, **[Callbacks del cliente](client/callbacks.md)** cubre los callbacks en sí y **[Pruebas](get-started/testing.md)** muestra el patrón en memoria que sustituye al helper `create_connected_server_and_client_session()` de la v1.
### El `Server` de bajo nivel se reconstruyó, no se renombró {#the-low-level-server-was-rebuilt-not-renamed}
@ -135,7 +135,7 @@ En esos tipos, cada atributo de Python es ahora snake_case: `result.is_error`, `
Los renombramientos se anuncian solos. Estos no:
* **Las funciones síncronas se ejecutan en un hilo de trabajo.** Una herramienta `def` (o recurso, prompt o resolutor) ya no bloquea el bucle de eventos; la contrapartida es que su cuerpo ya no se ejecuta *en* el hilo del bucle de eventos, lo que importa para código afín a un hilo. Los handlers `async def` no cambian. **[Guía de migración](migration.md#sync-handler-functions-now-run-on-a-worker-thread)**.
* **`MCPError` (el `McpError` de la v1) lanzado dentro de una herramienta es ahora un error de protocolo.** El modelo nunca lo ve. Cualquier otra excepción sigue convirtiéndose en un resultado `is_error=True` que el modelo puede leer y al que puede reaccionar. **[Manejo de errores](servers/handling-errors.md)** explica la división.
* **`MCPError` (el `McpError` de la v1) lanzado dentro de una herramienta es ahora un error de protocolo.** El modelo nunca lo ve. Cualquier otra excepción sigue convirtiéndose en un resultado `is_error=True`, pero solo el mensaje de un `ToolError` llega al modelo: cualquier otra excepción se lee ahora como `Error executing tool <name>`, con el traceback en el log de tu servidor. **[Manejo de errores](servers/handling-errors.md)** explica la división.
* **Los resultados se validan antes de salir.** Un `Tool` construido a mano cuyo `input_schema` sea `{}` ahora falla en `tools/list` (la especificación exige `"type": "object"`). Los servidores construidos sobre `@mcp.tool()` nunca ven esto; el SDK escribe sus esquemas.
* **Tu cliente valida lo que recibe.** `list_tools()` y `call_tool()` comprueban la respuesta del servidor contra la versión del protocolo negociada, así que un servidor no del todo válido que el análisis permisivo de la v1 toleraba ahora lanza `pydantic.ValidationError`. Si te conectas a servidores que no controlas, cuenta con ser tú quien los descubra; la **[Guía de migración](migration.md#client-validates-inbound-traffic-against-the-protocol-schema)** tiene los detalles.
* **Las plantillas de URI son ahora RFC 6570 de verdad.** `{+path}`, `{?query}` y compañía funcionan, la coincidencia es exacta en lugar de laxa por regex, y el path traversal en los valores extraídos se rechaza por defecto. Las plantillas más estrictas fallan al decorar, no en la primera solicitud. **[Plantillas de URI](servers/uri-templates.md)**.