docs: refresh translations, and translate pages in parallel (#3458)
This commit is contained in:
parent
08a3bc8eaf
commit
fd66270926
4 changed files with 4821 additions and 2277 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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)**.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue