docs: publish translated docs in twelve languages and the tool that maintains them (#3280)
This commit is contained in:
parent
14aa889992
commit
52ad0a8876
1 changed files with 89737 additions and 54 deletions
260
i18n/es/pages/advanced/extensions.md
Normal file
260
i18n/es/pages/advanced/extensions.md
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
---
|
||||
translation:
|
||||
sections: [05891e7cc1938a13, b3c01a6af28c51ee, 7ffc91f5e38bdfe0, 717d3f235a8333a7, f471a13b2fe5d737, ed6af2df4b656dff]
|
||||
tool: 1
|
||||
---
|
||||
# Extensiones {#extensions}
|
||||
|
||||
Una **extensión** es un paquete opcional de comportamiento MCP detrás de un único identificador.
|
||||
|
||||
En un servidor puede aportar herramientas, recursos y nuevos métodos de solicitud, y puede envolver
|
||||
`tools/call`. En un cliente puede reclamar formas de resultado adicionales de `tools/call` y observar
|
||||
notificaciones de proveedor. Cada lado se anuncia bajo su propio `capabilities.extensions`, y nada
|
||||
cambia para quien no lo haya pedido. Ese es el contrato ([SEP-2133](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2133)), y
|
||||
tiene una regla de oro: **las extensiones están desactivadas por defecto**.
|
||||
|
||||
## Usar una extensión {#using-an-extension}
|
||||
|
||||
Pasa las instancias al construir:
|
||||
|
||||
```python title="server.py"
|
||||
--8<-- "docs_src/extensions/tutorial001.py"
|
||||
```
|
||||
|
||||
Listo. El servidor ahora anuncia `io.modelcontextprotocol/ui` bajo
|
||||
`capabilities.extensions` y sirve todo lo que aporta la extensión.
|
||||
|
||||
`Apps` es la extensión de referencia incorporada y tiene su propia página: **[MCP Apps](apps.md)**.
|
||||
|
||||
!!! note
|
||||
Las extensiones se fijan al construir. No hay un `add_extension` que llamar después:
|
||||
el mapa de capacidades de un servidor no debería cambiar mientras haya clientes conectados a él.
|
||||
|
||||
El mapa de capacidades viaja en `server/discover`, que es una ruta de **2026-07-28**. Un
|
||||
handshake `initialize` heredado no tiene dónde ponerlo, así que un cliente heredado simplemente
|
||||
no ve la extensión. Diseña pensando en eso: una extensión *amplía* un servidor, no debe ser la
|
||||
única forma de usarlo.
|
||||
|
||||
## Escribir la tuya {#writing-your-own}
|
||||
|
||||
Crea una subclase de `Extension` y sobrescribe solo lo que necesites. Cada método tiene un valor por defecto.
|
||||
|
||||
### El identificador {#the-identifier}
|
||||
|
||||
```python
|
||||
--8<-- "docs_src/extensions/tutorial002.py"
|
||||
```
|
||||
|
||||
El identificador es una cadena `vendor-prefix/name` que sigue la gramática de claves `_meta`
|
||||
de la especificación: etiquetas separadas por puntos (cada una empieza con una letra y termina
|
||||
con una letra o un dígito), una barra y luego el nombre. Se valida **cuando se define la clase**,
|
||||
así que un error tipográfico no espera a que arranque un servidor:
|
||||
|
||||
```text
|
||||
TypeError: Stamps.identifier must be a `vendor-prefix/name` string
|
||||
(reverse-DNS prefix required), got 'stamps'
|
||||
```
|
||||
|
||||
Usa como prefijo un dominio que controles. `io.modelcontextprotocol/*` es para extensiones
|
||||
especificadas por el propio proyecto MCP.
|
||||
|
||||
### Aportar herramientas {#contributing-tools}
|
||||
|
||||
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"
|
||||
--8<-- "docs_src/extensions/tutorial003.py"
|
||||
```
|
||||
|
||||
* `tools()` devuelve objetos `ToolBinding`. El servidor registra cada uno exactamente como si
|
||||
hubieras llamado tú a `mcp.add_tool(...)`: la misma generación de esquema, la misma inyección
|
||||
de `Context`, todo igual.
|
||||
* `settings()` es el valor anunciado en `capabilities.extensions["com.example/stamps"]`.
|
||||
Devuelve `{}` (el valor por defecto) para anunciar la extensión sin 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`:
|
||||
|
||||
```python title="server.py" hl_lines="29-34"
|
||||
--8<-- "docs_src/extensions/tutorial003.py"
|
||||
```
|
||||
|
||||
### 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"
|
||||
--8<-- "docs_src/extensions/tutorial004.py"
|
||||
```
|
||||
|
||||
* `SearchParams` es una subclase de `RequestParams`, así que el sobre `_meta` de 2026 se analiza
|
||||
de forma uniforme y tu handler recibe parámetros validados, nunca un diccionario en bruto. Acota lo
|
||||
que controla el cliente: `Field(ge=1, le=100)` rechaza un `limit` absurdo antes de que
|
||||
tu código reserve nada para él.
|
||||
* `require_client_extension(ctx, EXTENSION_ID)` es el filtro: un cliente que no declaró
|
||||
la extensión recibe el error `-32021` (falta una capacidad de cliente requerida),
|
||||
con el payload legible por máquina `requiredCapabilities` que pide la especificación.
|
||||
* `protocol_versions=frozenset({"2026-07-28"})` fija el método a una única versión del protocolo.
|
||||
En cualquier otra versión el cliente recibe `METHOD_NOT_FOUND`, exactamente como si el método
|
||||
no existiera ahí. Para ese cliente, no existe.
|
||||
|
||||
Los métodos son **estrictamente aditivos**. El SDK lo hace cumplir al construir, no en
|
||||
tiempo de ejecución:
|
||||
|
||||
* Un `MethodBinding` para un método definido por la especificación (`tools/list`, `completion/complete`, ...)
|
||||
lanza `ValueError` cuando se construye el binding. Los verbos principales pertenecen al servidor.
|
||||
* Dos extensiones que vinculan el mismo método lanzan una excepción cuando se registra la segunda.
|
||||
Que gane la última escritura es como los plugins se corrompen entre sí; aquí no hacemos eso.
|
||||
* Un conjunto `protocol_versions` vacío también lanza una excepción: un método que nunca puede
|
||||
servirse es un bug, no una configuración.
|
||||
|
||||
### El lado del cliente {#the-client-side}
|
||||
|
||||
El `main()` del mismo archivo es toda la historia del cliente, sus dos mitades:
|
||||
|
||||
```python title="server.py" hl_lines="54-58"
|
||||
--8<-- "docs_src/extensions/tutorial004.py"
|
||||
```
|
||||
|
||||
* `Client(..., extensions=[advertise(EXTENSION_ID)])` declara la extensión. Las
|
||||
declaraciones se convierten en `ClientCapabilities.extensions`: en una conexión 2026-07-28
|
||||
el mapa viaja en el sobre `_meta` de cada solicitud, así que el servidor lo ve en
|
||||
**cada** solicitud; en una conexión heredada viaja en el handshake `initialize`.
|
||||
Al código del servidor le da igual cuál: `require_client_extension(ctx, ...)` y
|
||||
`ctx.session.check_client_capability(...)` leen la fuente correcta en ambas rutas.
|
||||
* 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.
|
||||
|
||||
### Interceptar `tools/call` {#intercepting-toolscall}
|
||||
|
||||
El único hook que intercepta. Sobrescribe `intercept_tool_call` para observar, cortocircuitar
|
||||
o vetar una llamada a herramienta:
|
||||
|
||||
```python title="server.py" hl_lines="17-24"
|
||||
--8<-- "docs_src/extensions/tutorial005.py"
|
||||
```
|
||||
|
||||
* `params` es el `CallToolRequestParams` validado: obtienes `params.name` y
|
||||
`params.arguments` sin tocar JSON en bruto. También es lo que decide qué llamada a
|
||||
herramienta se ejecuta: pasar un contexto reescrito a través de `call_next` cambia lo que
|
||||
el handler observa en `ctx`, no la invocación de la herramienta. Reescribir solicitudes a
|
||||
nivel del canal es cosa de [Middleware](middleware.md).
|
||||
* `call_next(ctx)` ejecuta el resto de la cadena y devuelve el resultado del handler.
|
||||
Devuélvelo sin cambios (observar), devuelve otra cosa (reemplazar) o lanza un
|
||||
`MCPError` (rechazar). Lo que devuelvas se serializa como cualquier resultado de
|
||||
handler, incluido el sello de identidad `serverInfo` de la generación 2026, así que un
|
||||
interceptor que cortocircuita nunca produce una respuesta anónima o fuera de esquema.
|
||||
* Con varias extensiones, los interceptores se anidan en orden de registro: la primera
|
||||
extensión en `extensions=[...]` es la más externa.
|
||||
* La implementación por defecto deja pasar todo, y un servidor cuyas extensiones nunca
|
||||
sobrescriben este hook mantiene intacto el handler `tools/call` sin más. No
|
||||
pagas por lo que no usas.
|
||||
|
||||
El hook envuelve `tools/call` y nada más. Para lo que afecta a cada mensaje, usa
|
||||
[Middleware](middleware.md). Para eso está.
|
||||
|
||||
## 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:
|
||||
|
||||
```python title="client.py" hl_lines="66-68"
|
||||
--8<-- "docs_src/extensions/tutorial006.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
|
||||
recibo con una llamada de seguimiento) antes de que `call_tool` devuelva. Nada del punto
|
||||
de llamada se mueve.
|
||||
|
||||
Quita la extensión y nada de esto existe: el filtro del servidor rechaza a un cliente
|
||||
que no la declaró (error -32021), y una forma reclamada procedente de un servidor que
|
||||
se salta el filtro falla la validación, exactamente como exige la especificación para un
|
||||
`resultType` no reconocido. Desactivada por defecto, en ambos extremos del canal.
|
||||
|
||||
Para anunciar un identificador **sin** comportamiento del lado del cliente (el servidor filtra
|
||||
según la capacidad, el cliente no hace nada, como en el cliente de búsqueda de arriba), usa
|
||||
`advertise()`:
|
||||
|
||||
```python
|
||||
from mcp.client import advertise
|
||||
|
||||
client = Client(mcp, extensions=[advertise("com.example/search")])
|
||||
```
|
||||
|
||||
## Escribir una extensión de cliente {#writing-a-client-extension}
|
||||
|
||||
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"
|
||||
```
|
||||
|
||||
* El identificador sigue la misma gramática que el del servidor y se valida cuando se
|
||||
define la clase.
|
||||
* `claims()` devuelve objetos `ResultClaim`: una etiqueta del canal, el modelo que la analiza y el
|
||||
resolutor que la termina. El modelo debe fijar la etiqueta con
|
||||
`result_type: Literal["receipt"]` y no debe ser subclase de los tipos de resultado principales
|
||||
del verbo; ambas cosas se hacen cumplir cuando se construye el claim. Los campos de proveedor como
|
||||
`receipt_token` viajan por el canal tal cual: una forma sustituida llega al cliente
|
||||
literalmente.
|
||||
* El resolutor recibe el modelo analizado y un `ClaimContext`; `ctx.session` es el
|
||||
mismo identificador público que `client.session`, así que los seguimientos son llamadas de
|
||||
sesión normales. Devuelve el `CallToolResult` normal del verbo.
|
||||
* `settings()` es el valor anunciado en `ClientCapabilities.extensions[identifier]`,
|
||||
leído una vez al construir el `Client`.
|
||||
|
||||
`notifications()` declara las notificaciones de servidor de proveedor que se van a observar:
|
||||
|
||||
```python
|
||||
def notifications(self) -> Sequence[NotificationBinding[Any]]:
|
||||
return [NotificationBinding(method="notifications/receipts", params_type=ReceiptEvent, handler=self.on_receipt)]
|
||||
```
|
||||
|
||||
El handler recibe los parámetros validados de uno en uno, en orden de despacho. Observa; no puede vetar
|
||||
ni responder.
|
||||
|
||||
Dos reglas discretas. Los claims solo están activos en conexiones 2026-07-28, y el anuncio de
|
||||
capacidades los sigue: en una conexión heredada los claims se disuelven y el identificador sale
|
||||
del anuncio con ellos, así que el cliente nunca anuncia una extensión cuyas formas
|
||||
rechazaría. Y cuando quieras la forma reclamada tú mismo en lugar del resolutor,
|
||||
llama a `client.session.call_tool(..., allow_claimed=True)`; sin esa bandera, una
|
||||
forma reclamada que llega a un llamador del nivel de sesión lanza `UnexpectedClaimedResult`.
|
||||
|
||||
### Verbos de extensión {#extension-verbs}
|
||||
|
||||
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`:
|
||||
|
||||
```python title="client.py" hl_lines="22-25 46-47"
|
||||
--8<-- "docs_src/extensions/tutorial007.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.
|
||||
|
||||
## Lo que una extensión no puede hacer {#what-an-extension-cannot-do}
|
||||
|
||||
La superficie de aportación es **cerrada** a propósito. En el servidor: ajustes, herramientas,
|
||||
recursos, métodos y un interceptor de `tools/call`. En el cliente: ajustes, claims de
|
||||
resultado y bindings de notificación. Una extensión no puede:
|
||||
|
||||
* **Meterse en el host.** Declara datos; no guarda ninguna referencia al servidor ni al cliente.
|
||||
* **Reemplazar el comportamiento principal.** Los métodos de la especificación y las etiquetas de
|
||||
resultado principales se rechazan al construir (el runner reserva `initialize` directamente); un
|
||||
binding de notificación eclipsado por el vocabulario principal se silencia con un aviso en su lugar.
|
||||
* **Registrarse tarde.** Una vez que `MCPServer(...)` o `Client(...)` devuelven, el conjunto
|
||||
de extensiones es el que es.
|
||||
|
||||
Si estás peleando contra estos muros, no estás escribiendo una extensión. Estás escribiendo
|
||||
un fork. Los muros son la funcionalidad: quien lee `extensions=[Apps(), Stamps()]`
|
||||
sabe *todo* lo que esas dos pueden haber tocado.
|
||||
Loading…
Reference in a new issue