docs: refresh translations, and translate pages in parallel (#3458)

This commit is contained in:
Max 2026-09-07 13:35:53 +01:00 • committed by GitHub
commit fd66270926
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 4821 additions and 2277 deletions

View file

@ -1,6 +1,6 @@
---
translation:
sections: [0355618e5f4d5fe4, 1821eaf50f2d0b64, 82e0b28ebd3abf5a, 8ac39614c094f2d0, dab6ff945501ab2a, bd5565c3b2d4f959, 96819ce3d63a0487]
sections: [0355618e5f4d5fe4, 0fefa31fb7c7b585, 5c53e7487c9c70cc, 8ac39614c094f2d0, dab6ff945501ab2a, bd5565c3b2d4f959, 96819ce3d63a0487]
tool: 1
---
# MCP Apps {#mcp-apps}
@ -25,7 +25,7 @@ y luego vuelve.
## Un reloj con cara visible {#a-clock-with-a-face}
```python title="server.py" hl_lines="19 22 30 32"
```python title="server.py" hl_lines="17 20 28 30"
--8<-- "docs_src/apps/tutorial001.py"
```
@ -50,21 +50,42 @@ dentro de tu HTML. Te da `ontoolresult`, `callServerTool`,
No todos los clientes muestran apps. La especificación es tajante sobre lo que eso significa para ti:
> Tools **MUST** return a meaningful `content` array even when UI is available.
> Las herramientas **DEBEN** devolver un array `content` significativo incluso cuando hay una UI disponible.
El modelo lee `content`; el iframe es para humanos. Un host capaz de mostrar UI sigue entregando
el resultado en texto al modelo, y un cliente solo de texto recibe *solo* eso. Así que el
patrón canónico es una herramienta, dos respuestas. Mira `get_time` de nuevo:
```python title="server.py" hl_lines="23-27"
```python title="server.py" hl_lines="21-25"
--8<-- "docs_src/apps/tutorial001.py"
```
`client_supports_apps(ctx)` es `True` solo cuando el cliente declaró la
extensión `io.modelcontextprotocol/ui` **y** incluyó `text/html;profile=mcp-app`
en su configuración `mimeTypes`. El campo es obligatorio, así que un cliente que lo omite
no cuenta. Eso es exactamente lo que declara `main()` en el mismo archivo: la
mitad cliente de la negociación, y vuelve la respuesta enriquecida.
no cuenta. Esta es la mitad cliente de la negociación:
```python title="client.py" hl_lines="8 12"
--8<-- "docs_src/apps/tutorial001_client.py"
```
Sirve `server.py` por HTTP y luego ejecuta el cliente desde una segunda terminal:
```console
uv run mcp run server.py --transport streamable-http
```
```console
python client.py
```
```text
2026-06-26T12:00:00Z
```
Volvió la respuesta enriquecida. Quita `extensions=[APPS_SUPPORT]` de la llamada a `Client`
y el mismo programa imprime `The time is 2026-06-26T12:00:00Z.` en su lugar, que es
todo lo que un cliente solo de texto llega a ver.
!!! warning
Nunca devuelvas un marcador de posición como `"[Rendered UI]"` como único contenido. Si el

View file

@ -1,6 +1,6 @@
---
translation:
sections: [05891e7cc1938a13, b3c01a6af28c51ee, 7ffc91f5e38bdfe0, 717d3f235a8333a7, f471a13b2fe5d737, ed6af2df4b656dff]
sections: [05891e7cc1938a13, b3c01a6af28c51ee, 6eba6ce094f4417e, 125f28588c85dd7b, ddd8969b698ca11c, ed6af2df4b656dff]
tool: 1
---
# Extensiones {#extensions}
@ -62,7 +62,7 @@ especificadas por el propio proyecto MCP.
La extensión útil más pequeña es una herramienta y un mapa de ajustes:
```python title="server.py" hl_lines="17 19-20 22-23 26"
```python title="server.py" hl_lines="16 18-19 21-22 25"
--8<-- "docs_src/extensions/tutorial003.py"
```
@ -74,18 +74,25 @@ La extensión útil más pequeña es una herramienta y un mapa de ajustes:
* La extensión nunca recibe el servidor. Declara sus aportaciones como datos;
`MCPServer` las consume. No hay un `self.server` que mutar.
Y `main()` es la prueba, un cliente en memoria directamente contra `mcp`:
Sírvela por HTTP, y un cliente es la prueba:
```python title="server.py" hl_lines="29-34"
--8<-- "docs_src/extensions/tutorial003.py"
```console
uv run mcp run server.py --transport streamable-http
```
```python title="client.py" hl_lines="7-11"
--8<-- "docs_src/extensions/tutorial003_client.py"
```
Cada `server.py` de esta página se sirve con ese comando, y cada `client.py`
se ejecuta a su lado con `python client.py` desde una segunda terminal.
### Servir tus propios métodos {#serving-your-own-methods}
Una extensión puede registrar **nuevos métodos de solicitud**: sus propios verbos, servidos junto a los
de la especificación:
```python title="server.py" hl_lines="16-22 31 40-48"
```python title="server.py" hl_lines="14-20 24 33-41"
--8<-- "docs_src/extensions/tutorial004.py"
```
@ -112,10 +119,10 @@ tiempo de ejecución:
### El lado del cliente {#the-client-side}
El `main()` del mismo archivo es toda la historia del cliente, sus dos mitades:
El cliente es su propio programa, y lleva las dos mitades de la historia del cliente:
```python title="server.py" hl_lines="54-58"
--8<-- "docs_src/extensions/tutorial004.py"
```python title="client.py" hl_lines="21-23 27-30"
--8<-- "docs_src/extensions/tutorial004_client.py"
```
* `Client(..., extensions=[advertise(EXTENSION_ID)])` declara la extensión. Las
@ -127,6 +134,9 @@ El `main()` del mismo archivo es toda la historia del cliente, sus dos mitades:
* Los métodos de proveedor bajan una capa hasta `client.session.send_request(...)`; `Client`
solo incorpora métodos de primera clase para los verbos de la especificación. `send_request`
acepta cualquier subclase de `Request`, así que la solicitud de proveedor pasa tal cual.
* `SearchRequest` y los dos modelos que lleva son el contrato de la extensión en el canal,
así que el cliente los declara por su cuenta. Una extensión publicada los distribuiría en
un paquete que ambos lados importan.
### Interceptar `tools/call` {#intercepting-toolscall}
@ -159,13 +169,20 @@ El hook envuelve `tools/call` y nada más. Para lo que afecta a cada mensaje, us
## Usar una extensión de cliente {#using-a-client-extension}
Una **extensión de cliente** es el mismo contrato desde el lado que consume: un paquete de
comportamiento del lado del cliente detrás de un único identificador. Pasa las instancias a
`Client(extensions=[...])` y llama a las herramientas con normalidad:
comportamiento del lado del cliente detrás de un único identificador. Aquí el servidor responde a `buy`
con un recibo para canjear en lugar de la mercancía, y solo para un cliente que declaró la
extensión:
```python title="client.py" hl_lines="66-68"
```python title="server.py" hl_lines="22-25"
--8<-- "docs_src/extensions/tutorial006.py"
```
En el cliente, pasa las instancias a `Client(extensions=[...])` y llama a las herramientas con normalidad:
```python title="client.py" hl_lines="33-35"
--8<-- "docs_src/extensions/tutorial006_client.py"
```
`call_tool("buy", ...)` devuelve un `CallToolResult` normal, como cualquier otra llamada. Lo que
cambió la extensión: el servidor ahora puede responder a `buy` con una **forma de resultado**
`receipt` en lugar de un resultado final, y `Receipts` la termina (aquí canjeando el
@ -184,7 +201,7 @@ según la capacidad, el cliente no hace nada, como en el cliente de búsqueda de
```python
from mcp.client import advertise
client = Client(mcp, extensions=[advertise("com.example/search")])
client = Client("http://localhost:8000/mcp", extensions=[advertise("com.example/search")])
```
## Escribir una extensión de cliente {#writing-a-client-extension}
@ -192,8 +209,8 @@ client = Client(mcp, extensions=[advertise("com.example/search")])
Crea una subclase de `ClientExtension` y sobrescribe solo lo que necesites. Tres tipos de
aportación, cada uno con un valor por defecto: `settings()`, `claims()` y `notifications()`.
```python title="client.py" hl_lines="17-18 43-44 46-47"
--8<-- "docs_src/extensions/tutorial006.py"
```python title="client.py" hl_lines="16-17 25-26 28-29"
--8<-- "docs_src/extensions/tutorial006_client.py"
```
* El identificador sigue la misma gramática que el del servidor y se valida cuando se
@ -231,14 +248,21 @@ forma reclamada que llega a un llamador del nivel de sesión lanza `UnexpectedCl
Los métodos de solicitud propios de una extensión no necesitan registro del lado del cliente. Un tipo
de solicitud de proveedor es una subclase de `mcp.types.Request` y pasa por `client.session.send_request`,
como en [Servir tus propios métodos](#serving-your-own-methods). Un añadido: cuando una
clave de los parámetros debe viajar en el header `Mcp-Name` (las especificaciones de extensión, como
tasks, lo exigen para sus verbos), el tipo de solicitud declara `name_param`:
como en [Servir tus propios métodos](#serving-your-own-methods). Toma un servidor cuya
extensión sirve un único verbo sobre un trabajo con nombre:
```python title="client.py" hl_lines="22-25 46-47"
```python title="server.py" hl_lines="12-13 30"
--8<-- "docs_src/extensions/tutorial007.py"
```
Un añadido en el cliente: cuando una clave de los parámetros debe viajar en el header `Mcp-Name`
(las especificaciones de extensión, como tasks, lo exigen para sus verbos), el tipo de solicitud
declara `name_param`:
```python title="client.py" hl_lines="20-23 28-29"
--8<-- "docs_src/extensions/tutorial007_client.py"
```
La sesión replica `params["jobId"]` en `Mcp-Name` en cada ruta de envío, y un
valor ausente falla de forma visible en lugar de omitir en silencio un header obligatorio.

View file

@ -1,6 +1,6 @@
---
translation:
sections: [2c79b6338e09b7ac, 7edc43b3fae11314, 1086e77ce561cd7f, a3f71823df5efc31, 9fc7109f72201cae, d50fe7faead8cf68, 7bf25983df655b66, 6330e1f4c6029683, 2f1749c8c133fa1c, 8db7116fc8ddd0ee, ebc33704fbd74262, cd0e9c933350390e]
sections: [2c79b6338e09b7ac, 9d5d10a5f0405d0a, 1086e77ce561cd7f, a3f71823df5efc31, 9fc7109f72201cae, d50fe7faead8cf68, 7bf25983df655b66, 6330e1f4c6029683, 2f1749c8c133fa1c, 8db7116fc8ddd0ee, ebc33704fbd74262, 0fde3bcea081ba3a]
tool: 1
---
# El Server de bajo nivel {#the-low-level-server}
@ -36,18 +36,22 @@ Cambiaron tres cosas, y son toda la API de bajo nivel:
### Pruébalo {#try-it}
Aquí no hay Inspector: `mcp dev` y `mcp run` solo aceptan un `MCPServer`. Al `Client` en memoria le da igual; acepta un `Server` de bajo nivel exactamente igual que acepta un `MCPServer`:
`mcp dev` y `mcp run` solo aceptan un `MCPServer`, así que este lo sirves por tu cuenta. La última línea de `server.py` construye una app ASGI común a partir de él, y uvicorn la ejecuta:
```python title="main.py"
```console
uvicorn server:app --port 8000
```
Apunta el Inspector, o cualquier cliente, a `http://localhost:8000/mcp`:
```python title="client.py"
import asyncio
from mcp import Client
from server import server
async def main() -> None:
async with Client(server) as client:
async with Client("http://localhost:8000/mcp") as client:
result = await client.call_tool("search_books", {"query": "dune", "limit": 5})
print(result.content)
@ -64,6 +68,8 @@ El mismo texto que produjo la versión con `@mcp.tool()`. Dos diferencias honest
* `result.structured_content` es `None`. El servidor de alto nivel envuelve un `-> str` en `{"result": ...}` por ti; aquí nadie construye lo que no construiste.
* `list_tools` devuelve el esquema que escribiste **tú**, carácter por carácter. La versión de alto nivel tenía `"title": "Query"` en cada propiedad y un `"title": "search_booksArguments"` en la raíz: artefactos de Pydantic. Aquí abajo, si se transmite, es porque lo pusiste ahí.
En una prueba te ahorras uvicorn y el puerto: `Client(server)` acepta un `Server` de bajo nivel en el mismo proceso exactamente igual que acepta un `MCPServer`, y **[Pruebas](../get-started/testing.md)** es ese patrón.
## Nada se comprueba por ti {#nothing-is-checked-for-you}
`MCPServer` rechaza un argumento incorrecto antes de que tu función llegue a ejecutarse, validando la llamada contra el esquema que generó (**[Herramientas](../servers/tools.md)**).
@ -215,4 +221,4 @@ Cada uno de estos es una idea para la que ya tienes el vocabulario; cada uno tie
* `add_request_handler(method, params_type, handler)` sirve cualquier método. `initialize` está reservado.
* Las capacidades que anuncia un `Server` se derivan de los handlers que registraste.
`Client(server)` trató a ambos servidores de forma idéntica porque *son* el mismo protocolo, que es justamente la idea. La siguiente capa hacia abajo no es una clase: es **[Middleware](middleware.md)**.
El cliente trató a ambos servidores de forma idéntica porque *son* el mismo protocolo, que es justamente la idea. La siguiente capa hacia abajo no es una clase: es **[Middleware](middleware.md)**.

View file

@ -1,6 +1,6 @@
---
translation:
sections: [a9aba7a026c7bd85, ed32bda7ba9ae33a, 7e64cc5646abb91f, 22a0129ee78b3c63, d875373c06d8d2f9]
sections: [a9aba7a026c7bd85, 83e2a08b9d46a398, 9fd8a0aa384b3257, 22a0129ee78b3c63, d875373c06d8d2f9]
tool: 1
---
# Paginación {#pagination}
@ -31,9 +31,13 @@ La paginación es para el servidor cuya lista de recursos es en realidad una bas
### Pruébalo {#try-it}
`Client(server)` se conecta a un `Server` de bajo nivel en memoria exactamente igual que se conecta a un `MCPServer`.
`mcp run` solo acepta un `MCPServer`, así que este lo sirves tú mismo. La última línea de `server.py` construye una app ASGI normal a partir del `Server`, y uvicorn la ejecuta:
Llama a `list_resources()` sin argumentos. Obtienes diez recursos, de `book-1` a `book-10`, y `next_cursor` es la cadena `"10"`.
```console
uvicorn server:app --port 8000
```
Apunta cualquier cliente (**[El cliente](../client/index.md)**, o el Inspector) a `http://localhost:8000/mcp` y llama a `list_resources()` sin argumentos. Obtienes diez recursos, de `book-1` a `book-10`, y `next_cursor` es la cadena `"10"`.
Devuélvelo con `list_resources(cursor="10")` y el primer recurso es `book-11`; el nuevo `next_cursor` es `"20"`.
@ -43,7 +47,7 @@ La décima página vuelve con `next_cursor` en `None`. Listo.
Cada método `list_*` de `Client` (`list_tools`, `list_resources`, `list_resource_templates`, `list_prompts`) acepta el argumento nombrado `cursor=`. Vaciar una lista paginada es un solo `while True`:
```python title="client.py" hl_lines="26-32"
```python title="client.py" hl_lines="9-15"
--8<-- "docs_src/pagination/tutorial002.py"
```
@ -51,7 +55,7 @@ Cada método `list_*` de `Client` (`list_tools`, `list_resources`, `list_resourc
* Extiende **antes** de mirar `next_cursor`: la última página también tiene recursos.
* `next_cursor is None` es la salida. Cualquier otra cosa vuelve directamente a `cursor=`, sin tocarla.
Ejecuta su `main()` e imprime `100 resources`: diez páginas de diez, unidas por un bucle que nunca supo que había diez páginas.
Con uvicorn todavía sirviendo `server.py`, ejecuta `python client.py` en una segunda terminal. Imprime `100 resources`: diez páginas de diez, unidas por un bucle que nunca supo que había diez páginas.
Es el mismo bucle que **[El cliente](../client/index.md)** muestra para cada verbo `list_*`, y no cuesta nada frente a un servidor que no pagina: `next_cursor` es `None` en la primera respuesta y el bucle se ejecuta una vez.