docs: publish translated docs in twelve languages and the tool that maintains them (#3280)
This commit is contained in:
parent
14aa889992
commit
52ad0a8876
5 changed files with 89737 additions and 54 deletions
144
i18n/es/pages/get-started/first-steps.md
Normal file
144
i18n/es/pages/get-started/first-steps.md
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
---
|
||||
translation:
|
||||
sections: [0d6c05bcbf836bf3, 59a7b14eeefc68c1, 7114d8d6daba203f, e8bbb56a98ba7bc9, 5138010f6159901c, f78da7c7c363d4c6, 220a939cab348686]
|
||||
tool: 1
|
||||
---
|
||||
# Primeros pasos {#first-steps}
|
||||
|
||||
La **[página de inicio](../index.md)** va rápido: escribes un servidor, lo ejecutas, llamas a una herramienta.
|
||||
|
||||
Esta página va despacio, con las tres cosas que un servidor puede exponer y un nombre para cada pieza por el camino.
|
||||
|
||||
## Host, cliente y servidor {#host-client-and-server}
|
||||
|
||||
Tres palabras que verás en cada página a partir de aquí:
|
||||
|
||||
* Un **host** es la aplicación LLM: Claude, un IDE, un entorno de ejecución de agentes. Es aquello con lo que habla el usuario.
|
||||
* Un **cliente** vive dentro del host y habla MCP. El host ejecuta un cliente por cada servidor al que está conectado.
|
||||
* Un **servidor** es lo que construyes con este SDK. Expone cosas a los clientes. Nunca habla directamente con el modelo.
|
||||
|
||||
Tú escribes el servidor. Los hosts son el producto de otra persona. El SDK también te da un `Client`. Lo usarás para probar tus servidores, y aparece más adelante en esta página.
|
||||
|
||||
## Las tres primitivas {#the-three-primitives}
|
||||
|
||||
Un servidor expone exactamente tres tipos de cosas. Lo que las distingue es **quién decide usarlas**:
|
||||
|
||||
| Primitiva | Quién la controla | Qué es | Ejemplo |
|
||||
|------------------|-------------------|------------------------------------------------------------------------|-------------------------------------------------|
|
||||
| **Herramientas** | El modelo | Una función que el modelo llama para realizar una acción | Una llamada a una API, una escritura en base de datos |
|
||||
| **Recursos** | La aplicación | Datos que el host carga en el contexto del modelo | El contenido de un archivo, una respuesta de una API |
|
||||
| **Prompts** | El usuario | Una plantilla de mensajes reutilizable que el usuario invoca por nombre | Un comando de barra, una entrada de menú |
|
||||
|
||||
"Quién la controla" es precisamente la razón de la división. Una herramienta se ejecuta porque el **modelo** decidió llamarla. Un recurso se adjunta porque la **aplicación** decidió que el modelo lo necesitaba. Un prompt se ejecuta porque el **usuario** lo eligió.
|
||||
|
||||
!!! info
|
||||
Si has construido una API web ya tienes casi toda la intuición: un **recurso** es un `GET`
|
||||
(carga datos y no cambia nada) y una **herramienta** es un `POST` (hace trabajo y puede tener
|
||||
efectos secundarios). Un **prompt** no tiene equivalente HTTP; se parece más a una consulta
|
||||
guardada que el usuario ejecuta por nombre.
|
||||
|
||||
## Un servidor, las tres {#one-server-all-three}
|
||||
|
||||
```python title="server.py" hl_lines="6 12 18"
|
||||
--8<-- "docs_src/first_steps/tutorial001.py"
|
||||
```
|
||||
|
||||
Tres funciones normales, tres decoradores. Cada decorador es el registro completo:
|
||||
|
||||
* `@mcp.tool()` convierte `add` en una **herramienta**.
|
||||
* `@mcp.resource("greeting://{name}")` convierte `greeting` en una **plantilla de recurso**: el `{name}` de la URI es el parámetro de la función.
|
||||
* `@mcp.prompt()` convierte `summarize` en un **prompt**. La cadena que devuelve se convierte en un mensaje de usuario.
|
||||
|
||||
Todo lo demás (el nombre, la descripción, el esquema de argumentos) el SDK lo lee de la propia función: su nombre, su docstring, sus anotaciones de tipo. Nunca declaraste nada de eso por separado.
|
||||
|
||||
!!! tip
|
||||
Las dos mitades del SDK tienen dos rutas de importación: `from mcp import Client` y
|
||||
`from mcp.server import MCPServer`. No existe `from mcp import MCPServer`.
|
||||
|
||||
### Pruébalo {#try-it}
|
||||
|
||||
Ejecútalo con el MCP Inspector:
|
||||
|
||||
```console
|
||||
uv run mcp dev server.py
|
||||
```
|
||||
|
||||
Abre la URL que imprime. El Inspector tiene una pestaña por primitiva; recórrelas en orden.
|
||||
|
||||
**Tools.** Una entrada: `add`, descrita como *Add two numbers.* El formulario tiene un campo entero obligatorio para `a` y otro para `b`. Rellénalos, llámala, y el resultado es `3`. El Inspector construyó ese formulario a partir de `a: int, b: int`. Lo mismo hace cualquier otro cliente.
|
||||
|
||||
**Resources.** La lista *Resources* está vacía. `greeting` está en **Resource Templates**, porque `greeting://{name}` tiene un parámetro: no hay un recurso concreto que listar hasta que alguien indique un `name`. Dale `World` y léelo:
|
||||
|
||||
```text
|
||||
Hello, World!
|
||||
```
|
||||
|
||||
**Prompts.** Una entrada: `summarize`, con un único argumento obligatorio `text`. Obtenlo con algo de texto y recibes un mensaje con `role: user` y tu cadena ya generada como contenido. Eso es todo lo que es un prompt: una función que construye mensajes.
|
||||
|
||||
El Inspector ejecutó tu servidor sobre **stdio**, uno de los transportes que puede hablar un servidor MCP. Todavía no eliges uno; **[Ejecutar tu servidor](../run/index.md)** es la página para eso.
|
||||
|
||||
## Capacidades {#capabilities}
|
||||
|
||||
Viste tres pestañas en el Inspector. ¿Cómo supo que había tres?
|
||||
|
||||
Cuando un cliente se conecta, el servidor declara sus **capacidades**: qué familias de solicitudes va a responder. El cliente usa esa declaración para decidir qué vale la pena pedir siquiera. Nunca la escribiste; `MCPServer` la declara por ti.
|
||||
|
||||
Míralo tú mismo. El `Client` del SDK acepta el objeto servidor directamente y se conecta a él **en memoria** (sin subproceso, sin puerto):
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
|
||||
from mcp import Client
|
||||
|
||||
from server import mcp
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
async with Client(mcp) as client:
|
||||
print(client.server_capabilities.model_dump(exclude_none=True))
|
||||
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
```text
|
||||
{'prompts': {'list_changed': True}, 'resources': {'subscribe': True, 'list_changed': True}, 'tools': {'list_changed': True}}
|
||||
```
|
||||
|
||||
Ese diccionario son las **capacidades** declaradas de tu servidor. Es lo primero que aprende cada cliente que se conecta:
|
||||
|
||||
| Capacidad | El cliente ya puede llamar a |
|
||||
|-------------|----------------------------------------------------------------|
|
||||
| `tools` | `tools/list`, `tools/call` |
|
||||
| `resources` | `resources/list`, `resources/templates/list`, `resources/read` |
|
||||
| `prompts` | `prompts/list`, `prompts/get` |
|
||||
|
||||
`MCPServer` sirve las tres primitivas, así que las tres se declaran siempre.
|
||||
|
||||
Fíjate en lo que no está. `completions` (autocompletado de argumentos para plantillas de recurso y prompts) necesita un handler que escribes tú, este servidor no tiene uno, así que la capacidad está ausente y un cliente bien hecho no la pedirá. Esa es la regla para todo lo opcional: registra la cosa y la capacidad aparece; **[Autocompletado](../servers/completions.md)** lo demuestra.
|
||||
|
||||
!!! info
|
||||
`Client(mcp)` es el mismo cliente en memoria con el que se prueba cada ejemplo de esta
|
||||
documentación, y es como probarás los tuyos. Tiene una página entera: **[Pruebas](testing.md)**.
|
||||
|
||||
## Lo que no escribiste {#what-you-did-not-write}
|
||||
|
||||
Repasa esta página. Escribiste tres pequeñas funciones de Python. **No** escribiste:
|
||||
|
||||
* Un JSON Schema. `a: int, b: int` *es* el esquema de `add`.
|
||||
* Un handler de solicitudes. `tools/list`, `resources/read`, `prompts/get`: todos servidos por ti.
|
||||
* Una declaración de capacidades. `MCPServer` la hizo por ti.
|
||||
* Una línea de protocolo. La negociación de versión, el encuadre JSON-RPC, el intercambio de capacidades: todo ocurrió dentro de `mcp dev` y `Client(mcp)`, y nunca lo viste.
|
||||
|
||||
Esa proporción es la razón de ser del SDK.
|
||||
|
||||
## Resumen {#recap}
|
||||
|
||||
* Un **host** es la app LLM, un **cliente** es su mitad que habla MCP, un **servidor** es lo que construyes.
|
||||
* Las herramientas las controla el **modelo**, los recursos los controla la **aplicación**, los prompts los controla el **usuario**.
|
||||
* Un decorador por primitiva: `@mcp.tool()`, `@mcp.resource(uri)`, `@mcp.prompt()`. Nombre, descripción y esquema salen de la función.
|
||||
* Una URI con un `{param}` crea una **plantilla** de recurso, que se lista aparte de los recursos concretos.
|
||||
* Las **capacidades** del servidor se declaran por ti, y un cliente solo pide lo que un servidor declara.
|
||||
* `Client(mcp)` se conecta al objeto servidor en memoria: tu entorno de pruebas desde el primer día.
|
||||
|
||||
Lo siguiente es **[Conectar a un host real](real-host.md)**: este servidor dentro de Claude Desktop o un IDE, de verdad. Después, **[Pruebas](testing.md)**: una página, un cliente en memoria, y nunca más adivinas si funciona. Tras eso, cada primitiva tiene su propia página, empezando por la que maneja el modelo: **[Herramientas](../servers/tools.md)**.
|
||||
53
i18n/es/pages/get-started/index.md
Normal file
53
i18n/es/pages/get-started/index.md
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
---
|
||||
translation:
|
||||
sections: [ed4a756b4c53c585, 97e2fb315b7fe398, 4d04f1c6f4bf6c1d, 577d73078fc62baf]
|
||||
tool: 1
|
||||
---
|
||||
# Empieza aquí {#get-started}
|
||||
|
||||
¿Eres nuevo en MCP o nuevo en este SDK? Empieza aquí. Estas páginas te llevan desde cero hasta un servidor funcional y probado: [instala el SDK](installation.md), construye tu [primer servidor](first-steps.md), [conéctalo a un host real](real-host.md) y [pruébalo](testing.md) con un cliente en memoria.
|
||||
|
||||
## Ejecuta el código {#run-the-code}
|
||||
|
||||
Todos los bloques de código se pueden copiar y usar directamente: son archivos completos que funcionan.
|
||||
|
||||
Para seguir los pasos, pega un bloque en un `server.py` y ábrelo en el MCP Inspector:
|
||||
|
||||
```console
|
||||
uv run mcp dev server.py
|
||||
```
|
||||
|
||||
Se **RECOMIENDA ENCARECIDAMENTE** que escribas (o copies) el código, lo edites y lo ejecutes localmente. Usarlo en tu propio editor es lo que de verdad te muestra la idea: lo poco que escribes, el autocompletado, las comprobaciones de tipos que detectan errores antes de ejecutar nada.
|
||||
|
||||
## No vas a adivinar {#you-will-not-be-guessing}
|
||||
|
||||
Cada ejemplo de esta documentación es un archivo completo dentro de [`docs_src/`](https://github.com/modelcontextprotocol/python-sdk/tree/main/docs_src) en el propio repositorio del SDK, y cada uno de ellos lo ejercita la suite de pruebas del SDK mediante un **cliente en memoria**:
|
||||
|
||||
```python
|
||||
import pytest
|
||||
from mcp import Client
|
||||
|
||||
from server import mcp
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_add() -> None:
|
||||
async with Client(mcp) as client:
|
||||
result = await client.call_tool("add", {"a": 1, "b": 2})
|
||||
assert result.structured_content == {"result": 3}
|
||||
```
|
||||
|
||||
Sin subproceso, sin puerto, sin transporte. `Client(mcp)` se conecta directamente al objeto del servidor.
|
||||
|
||||
Si un cambio en el SDK rompe un ejemplo de una de estas páginas, la CI se pone en rojo antes que la página. El código que lees aquí es el código que se ejecuta.
|
||||
|
||||
Lo usarás tú mismo en [Pruebas](testing.md); así es también como pruebas tus propios servidores.
|
||||
|
||||
## Adónde ir después {#where-to-go-next}
|
||||
|
||||
Una vez que tengas un servidor en marcha, el resto de esta documentación es una referencia, no un curso. Cada página es independiente, así que ve directo a lo que necesitas:
|
||||
|
||||
* Lo que expone un servidor (herramientas, recursos, prompts) está en **[Servidores](../servers/index.md)**.
|
||||
* Lo que tienes disponible dentro de las funciones que registras está en **[Dentro de tu handler](../handlers/index.md)**.
|
||||
* Ponerlo delante de los clientes (stdio, HTTP, tu app FastAPI existente) está en **[Ejecutar el servidor](../run/index.md)**.
|
||||
* Construir el otro lado, una aplicación que *usa* servidores MCP, está en **[Clientes](../client/index.md)**.
|
||||
47
i18n/es/pages/get-started/installation.md
Normal file
47
i18n/es/pages/get-started/installation.md
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
---
|
||||
translation:
|
||||
sections: [6e2f9bab94d5ed36, 8cf653388f69e28b, 6fd9ea2f65de0df6]
|
||||
tool: 1
|
||||
---
|
||||
# Instalación {#installation}
|
||||
|
||||
El SDK de Python está en PyPI como [`mcp`](https://pypi.org/project/mcp/). Requiere **Python 3.10+**.
|
||||
|
||||
Esta documentación describe **v2**, la línea de versiones estable actual:
|
||||
|
||||
=== "uv"
|
||||
|
||||
```bash
|
||||
uv add "mcp[cli]"
|
||||
```
|
||||
|
||||
=== "pip"
|
||||
|
||||
```bash
|
||||
pip install "mcp[cli]"
|
||||
```
|
||||
|
||||
!!! note "¿Vienes de v1?"
|
||||
v2 es una versión mayor con cambios incompatibles; la **[Guía de migración](../migration.md)**
|
||||
los cubre todos. Si tu *paquete* depende de `mcp` y aún no está listo para migrar, mantén un
|
||||
límite superior `<2` (por ejemplo `mcp>=1.28,<2`) para que una resolución sin versión fijada se quede en la línea 1.x.
|
||||
|
||||
## Qué se instala {#what-gets-installed}
|
||||
|
||||
No necesitas saber nada de esto para usar el SDK, pero si te preguntas para qué sirve cada dependencia:
|
||||
|
||||
* `mcp-types`: todos los tipos del protocolo (solicitudes, resultados, bloques de contenido) como paquete propio, versionado a la par del SDK. El código que depende de `mcp` lo importa a través del alias `mcp.types` (cada `from mcp.types import ...` de esta documentación); importa `mcp_types` directamente solo en un proyecto que instale `mcp-types` sin el SDK.
|
||||
* [`anyio`](https://anyio.readthedocs.io/): el entorno de ejecución asíncrono. Todo el SDK está escrito sobre anyio, así que funciona tanto con `asyncio` como con `trio`.
|
||||
* [`pydantic`](https://docs.pydantic.dev/): la base de todos los modelos de `mcp.types`, además de toda la generación y validación de esquemas.
|
||||
* [`httpx2`](https://pypi.org/project/httpx2/): el cliente HTTP detrás de los transportes de *cliente* Streamable HTTP y SSE, con compatibilidad integrada con server-sent events.
|
||||
* [`starlette`](https://www.starlette.io/), [`uvicorn`](https://www.uvicorn.org/), [`sse-starlette`](https://pypi.org/project/sse-starlette/) y [`python-multipart`](https://pypi.org/project/python-multipart/): los transportes HTTP de *servidor*.
|
||||
* [`jsonschema`](https://pypi.org/project/jsonschema/): valida la salida estructurada de una herramienta contra su esquema de salida declarado.
|
||||
* [`pyjwt[crypto]`](https://pyjwt.readthedocs.io/): manejo de tokens OAuth para la autorización.
|
||||
* [`opentelemetry-api`](https://opentelemetry-python.readthedocs.io/): solo la API ligera, de modo que el middleware de trazas del SDK no cuesta nada a menos que instales por tu cuenta un SDK y un exportador de OpenTelemetry.
|
||||
* [`typing-extensions`](https://typing-extensions.readthedocs.io/) y [`typing-inspection`](https://pypi.org/project/typing-inspection/): funcionalidades modernas de tipado en Python 3.10.
|
||||
* [`pywin32`](https://pypi.org/project/pywin32/): solo en Windows, se usa para la gestión de subprocesos `stdio`.
|
||||
|
||||
## Extras opcionales {#optional-extras}
|
||||
|
||||
* `mcp[cli]` añade [`typer`](https://typer.tiangolo.com/) y [`python-dotenv`](https://pypi.org/project/python-dotenv/) para la herramienta de línea de comandos `mcp` (`mcp dev`, `mcp run`, `mcp install`). La querrás durante el desarrollo; puede que no la necesites en un servidor desplegado.
|
||||
* `mcp[rich]` añade [`rich`](https://rich.readthedocs.io/) para unos logs del servidor más legibles.
|
||||
182
i18n/es/pages/get-started/real-host.md
Normal file
182
i18n/es/pages/get-started/real-host.md
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
---
|
||||
translation:
|
||||
sections: [3c4f2f06b4e978b6, 22520eecae3d1961, f4e1709db18d635a, 2eb57992049671d9, 1ba83e9af37cc1b4, 4822586344b08d9e, 1c93afef72478992, b6b448f9eddd51dc, fe55370fd931815b]
|
||||
tool: 1
|
||||
---
|
||||
# Conectarse a un host real {#connect-to-a-real-host}
|
||||
|
||||
Un **host** es la aplicación dentro de la que acaba tu servidor: Claude Desktop, Claude Code, un IDE. El host es con lo que habla el usuario. Dentro de él, un **cliente** MCP lanza tu servidor como un proceso hijo y se comunica con él a través del stdin y el stdout de ese proceso.
|
||||
|
||||
Esto significa que conectarse a un host es un único acto: le indicas **el comando que arranca tu servidor**. Todo lo que hay en esta página (dos comandos de CLI, tres archivos JSON) es un lugar distinto donde poner ese mismo comando.
|
||||
|
||||
## Un servidor, todos los hosts {#one-server-every-host}
|
||||
|
||||
```python title="server.py" hl_lines="3 33-34"
|
||||
--8<-- "docs_src/real_host/tutorial001.py"
|
||||
```
|
||||
|
||||
Dos herramientas y un recurso, un solo archivo. Tres cosas de ese archivo importan para todos los hosts de abajo:
|
||||
|
||||
* `mcp.run()` sin argumentos arranca un servidor **stdio**: se bloquea, lee los mensajes del protocolo por stdin y los escribe por stdout. Ese es el transporte que hablan todos los hosts de esta página. El host arranca tu archivo como proceso hijo y es dueño de esas dos tuberías, y por eso conectarse siempre se reduce a "aquí tienes el comando". Nunca eliges un puerto, y nada escucha en ninguno.
|
||||
* `run()` está bajo `if __name__ == "__main__":`. Todo lo de abajo **importa** este archivo en lugar de ejecutarlo, así que un `run()` sin protección arrancaría un servidor en cuanto cualquier cosa cargara el módulo.
|
||||
* El objeto servidor es una variable global del módulo llamada `mcp`. Ese es el nombre que busca `mcp run` (`server` y `app` también funcionan). Si lo llamas de otra forma, lo nombras explícitamente: `mcp run server.py:bookshop`.
|
||||
|
||||
Esa es la última línea de Python de esta página. De aquí en adelante todo es configuración del host.
|
||||
|
||||
## El comando de arranque {#the-launch-command}
|
||||
|
||||
Todos los hosts de abajo reciben el mismo comando:
|
||||
|
||||
```bash
|
||||
uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py
|
||||
```
|
||||
|
||||
Un solo comando para todos porque `uv run --with` resuelve el SDK en un entorno nuevo al momento: funciona desde cualquier directorio y no necesita ni proyecto ni entorno virtual que activar. Aquí eso importa más que en ningún otro sitio, porque un host lanza tu servidor desde *su* directorio de trabajo con un entorno casi vacío, no desde tu shell.
|
||||
|
||||
También es el comando que `mcp install` escribe por ti en la configuración de Claude Desktop (abajo), así que lo que escribes a mano y lo que genera la herramienta coinciden, salvo por la versión exacta que fija la herramienta.
|
||||
|
||||
!!! tip "Si un host no encuentra `uv`"
|
||||
Un host lanza tu servidor con un `PATH` mínimo, y puede que `uv` no esté en él. Sustituye el
|
||||
`uv` a secas por la ruta absoluta que da `which uv` (macOS/Linux) o `where uv` (Windows). Eso es
|
||||
exactamente lo que escribe `mcp install`.
|
||||
|
||||
!!! note "Esta página es la historia local"
|
||||
Todo lo de aquí ejecuta tu servidor en la máquina donde está el host: el host lanza tu
|
||||
archivo, por stdio. Eso es justo lo correcto para una herramienta personal o de una sola
|
||||
máquina. Para dar un servidor a personas que *no* tienen tu archivo, repartes una **URL**, no un
|
||||
comando: el mismo objeto `mcp` servido por Streamable HTTP. **[Ejecutar tu servidor](../run/index.md)**
|
||||
es esa decisión en una sola tabla, y **[Desplegar y escalar](../run/deploy.md)** es el camino desde
|
||||
ahí hasta un nombre de host real.
|
||||
|
||||
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)**
|
||||
se conecta a él en memoria, sin ningún proceso.
|
||||
|
||||
## Claude Desktop {#claude-desktop}
|
||||
|
||||
El único host que el SDK puede configurar por ti:
|
||||
|
||||
```bash
|
||||
uv run mcp install server.py
|
||||
```
|
||||
|
||||
Eso es todo. `mcp install` importa el archivo para leer el nombre del servidor, encuentra el archivo de configuración de Claude Desktop y escribe en él el comando de arranque. De paso convierte tu ruta en absoluta, así que no tienes que hacerlo.
|
||||
|
||||
No hay nada misterioso. Esta es la entrada que escribe:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"Bookshop": {
|
||||
"command": "/absolute/path/to/uv",
|
||||
"args": [
|
||||
"run",
|
||||
"--frozen",
|
||||
"--with",
|
||||
"mcp[cli]==2.0.0",
|
||||
"mcp",
|
||||
"run",
|
||||
"/absolute/path/to/server.py"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Es el comando de arranque de la sección anterior con tres añadidos: la ruta absoluta a `uv`, `--frozen` para que `uv` nunca reescriba un archivo de bloqueo que tenga cerca por casualidad, y una versión fijada exactamente a la de `mcp` que tienes instalada. Va a parar a `claude_desktop_config.json`, que vive en:
|
||||
|
||||
* **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
||||
* **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
|
||||
|
||||
Puedes escribir ese archivo a mano. `mcp install` existe para que no cometas el error clásico (una ruta relativa) al hacerlo.
|
||||
|
||||
Cierra Claude Desktop por completo (no solo su ventana) y vuelve a abrirlo.
|
||||
|
||||
!!! warning
|
||||
`mcp install` falla con `Claude app not found` si el *directorio* de configuración de Claude Desktop
|
||||
todavía no existe. Instala Claude Desktop y ejecútalo una vez: eso es lo que crea el directorio.
|
||||
|
||||
!!! tip
|
||||
Claude Desktop arranca tu servidor en su propio proceso, así que las variables de entorno de tu
|
||||
shell no están ahí. `uv run mcp install server.py -v API_KEY=abc123` (o `-f .env`) las registra en el
|
||||
campo `env` de la entrada. `--name` sobrescribe el nombre de la entrada; por defecto es el `name` del servidor.
|
||||
|
||||
## Claude Code {#claude-code}
|
||||
|
||||
No hay ningún archivo que editar. Registra el servidor con la CLI `claude`; todo lo que va después de `--` es el comando de arranque.
|
||||
|
||||
```bash
|
||||
claude mcp add bookshop -- uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py
|
||||
```
|
||||
|
||||
Ejecuta `/mcp` dentro de una sesión de Claude Code para confirmar que `bookshop` está conectado y sus herramientas aparecen listadas.
|
||||
|
||||
## Cursor {#cursor}
|
||||
|
||||
Crea `.cursor/mcp.json` en la raíz de tu proyecto.
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"bookshop": {
|
||||
"command": "uv",
|
||||
"args": ["run", "--with", "mcp[cli]", "mcp", "run", "/absolute/path/to/server.py"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
El mismo `command` más `args`, bajo la misma clave `mcpServers` que usa Claude Desktop. El servidor aparece en los ajustes de MCP de Cursor con ambas herramientas listadas.
|
||||
|
||||
## VS Code {#vs-code}
|
||||
|
||||
Crea `.vscode/mcp.json` en la raíz de tu proyecto.
|
||||
|
||||
```json
|
||||
{
|
||||
"servers": {
|
||||
"bookshop": {
|
||||
"type": "stdio",
|
||||
"command": "uv",
|
||||
"args": ["run", "--with", "mcp[cli]", "mcp", "run", "/absolute/path/to/server.py"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Dos diferencias con el archivo de Cursor, y son las únicas dos: la clave contenedora es `servers`, no `mcpServers`, y cada entrada declara su `type`. Confirma el aviso de confianza y luego **MCP: List Servers** en la paleta de comandos muestra `bookshop` en ejecución.
|
||||
|
||||
!!! note
|
||||
Necesitas VS Code 1.99 o posterior con la extensión **GitHub Copilot** con sesión iniciada (basta con
|
||||
Copilot Free), y Copilot Chat debe estar en modo **Agent**, porque ningún otro modo llama a herramientas.
|
||||
|
||||
## No aparece {#it-doesnt-show-up}
|
||||
|
||||
Antes de tocar ninguna configuración de host, ejecuta tú mismo el comando de arranque:
|
||||
|
||||
```bash
|
||||
uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py
|
||||
```
|
||||
|
||||
No imprime nada y no termina. Ese silencio es correcto: un servidor stdio está esperando a que un host hable primero por stdin (`Ctrl-C` para detenerlo). Un traceback o una salida inmediata es el error real, y ahora puedes leerlo en vez de adivinarlo a través de un host.
|
||||
|
||||
Una vez que ese comando se queda esperando, lo que queda es casi siempre una de estas tres cosas:
|
||||
|
||||
* **Una ruta relativa.** El host lanza tu servidor desde *su* directorio de trabajo, no desde aquel en el que lo registraste. `server.py` donde hace falta `/absolute/path/to/server.py` es el fallo más común de todos. Si el host tampoco encuentra `uv`, esa ruta también tiene que ser absoluta.
|
||||
* **El host sigue ejecutando su configuración anterior.** Los hosts leen su configuración al arrancar. Claude Desktop en particular hay que *cerrarlo por completo* (no solo cerrar su ventana) y volver a abrirlo para que un cambio en `claude_desktop_config.json` surta efecto.
|
||||
* **Algo llegó a stdout fuera de la ventana desviada.** En stdio, stdout *es* el protocolo. El SDK desvía a stderr la salida extraviada que se vacía mientras sirve, pero la salida vaciada a stdout antes de eso (un script contenedor que hace echo, un `print()` en tiempo de importación en un proceso sin búfer), o un `print()` en búfer que se drena al salir el intérprete, le entrega al host un mensaje corrupto y este corta la conexión. Registra con la configuración por defecto de `logging`, cuyo handler de stderr vacía cada registro; los handlers personalizados también deben evitar stdout. **[Registro](../handlers/logging.md)** tiene todos los detalles.
|
||||
|
||||
Claude Desktop guarda un log por servidor: `mcp-server-<NAME>.log` es el stderr de tu servidor, junto a `mcp.log` para las conexiones, bajo `~/Library/Logs/Claude` en macOS y `%APPDATA%\Claude\logs` en Windows.
|
||||
|
||||
Para cualquier cosa más allá de esas tres, **[Solución de problemas](../troubleshooting.md)** es la página.
|
||||
|
||||
## Resumen {#recap}
|
||||
|
||||
* Un **host** (Claude Desktop, un IDE) ejecuta un cliente MCP que lanza tu servidor como proceso hijo por stdio. Conectarse significa darle un comando de arranque.
|
||||
* Ese comando es `uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py`: ningún entorno virtual que activar, funciona desde cualquier directorio.
|
||||
* **Claude Desktop** es el único host que `mcp install` configura por ti. Escribe ese mismo comando (más la ruta absoluta a `uv`, `--frozen` y una versión fijada exactamente a la que tienes instalada) en `claude_desktop_config.json`, así que nunca tienes que hacerlo tú.
|
||||
* **Claude Code** es `claude mcp add bookshop -- <launch command>`. **Cursor** es `.cursor/mcp.json` bajo `mcpServers`. **VS Code** es `.vscode/mcp.json` bajo `servers`, cada entrada con un `type`.
|
||||
* Rutas absolutas en todas partes, reinicia el host tras editar su configuración y nunca dejes que nada salvo el SDK escriba en stdout.
|
||||
|
||||
Todos los hosts de esta página se conectaron al mismo archivo, con el mismo comando. Lo que ese archivo puede *exponer* es el resto de esta documentación: **[Herramientas](../servers/tools.md)**, **[Recursos](../servers/resources.md)** y todos los transportes aparte de stdio en **[Ejecutar tu servidor](../run/index.md)**.
|
||||
115
i18n/es/pages/get-started/testing.md
Normal file
115
i18n/es/pages/get-started/testing.md
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
---
|
||||
translation:
|
||||
sections: ['4926721070127497', c52a1de2b6b32f40, 2e410b412c25f314, 627195f7159e24ef]
|
||||
tool: 1
|
||||
---
|
||||
# Pruebas {#testing}
|
||||
|
||||
El SDK de Python incluye una clase `Client` con un **transporte en memoria**: le pasas tu objeto servidor y se conecta a él directamente.
|
||||
|
||||
Sin subproceso. Sin puerto. Sin transporte alguno. Es la misma idea que el `TestClient` de FastAPI.
|
||||
|
||||
## Uso básico {#basic-usage}
|
||||
|
||||
Supongamos que tienes un servidor sencillo con una sola herramienta:
|
||||
|
||||
```python title="server.py"
|
||||
--8<-- "docs_src/testing/tutorial001.py"
|
||||
```
|
||||
|
||||
Para ejecutar la prueba de abajo necesitarás dos dependencias adicionales (de desarrollo):
|
||||
|
||||
=== "uv"
|
||||
|
||||
```bash
|
||||
uv add --dev pytest inline-snapshot
|
||||
```
|
||||
|
||||
=== "pip"
|
||||
|
||||
```bash
|
||||
pip install pytest inline-snapshot
|
||||
```
|
||||
|
||||
!!! info
|
||||
Esta documentación supone que ya conoces [`pytest`](https://docs.pytest.org/en/stable/).
|
||||
|
||||
[`inline-snapshot`](https://15r10nk.github.io/inline-snapshot/latest/) es lo que usa la prueba
|
||||
de abajo para comprobar el objeto de resultado completo en una sola línea. Registra la salida de
|
||||
una prueba como el literal `snapshot(...)` que ves. Si prefieres no usarlo, quita la importación
|
||||
y comprueba los campos que te interesan (`result.content[0].text == "3"`) como en cualquier otra prueba.
|
||||
|
||||
Ahora la prueba:
|
||||
|
||||
```python title="test_server.py"
|
||||
import pytest
|
||||
from inline_snapshot import snapshot
|
||||
from mcp import Client
|
||||
from mcp.types import CallToolResult, TextContent
|
||||
|
||||
from server import mcp
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def anyio_backend(): # (1)!
|
||||
return "asyncio"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def client(): # (2)!
|
||||
async with Client(mcp, raise_exceptions=True) as c:
|
||||
yield c
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_call_add_tool(client: Client):
|
||||
result = await client.call_tool("add", {"a": 1, "b": 2})
|
||||
# Drop the server identity stamp in `_meta`; it is not what this test is about.
|
||||
result.meta = None
|
||||
assert result == snapshot(
|
||||
CallToolResult(
|
||||
content=[TextContent(type="text", text="3")],
|
||||
structured_content={"result": 3},
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
1. Si usas `trio`, devuelve `"trio"` en su lugar. Consulta la [documentación de anyio](https://anyio.readthedocs.io/en/stable/testing.html#specifying-the-backends-to-run-on) para los detalles.
|
||||
2. El fixture entrega un cliente conectado. Cada prueba que recibe `client` obtiene una conexión en memoria nueva al mismo servidor.
|
||||
|
||||
¡Listo! Ahora puedes ampliar tus pruebas para cubrir más escenarios.
|
||||
|
||||
## ¿Por qué `raise_exceptions=True`? {#why-raise_exceptionstrue}
|
||||
|
||||
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)**.
|
||||
|
||||
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
|
||||
que el cliente lo vea. Nunca deberías filtrar los detalles de un fallo inesperado a un llamador
|
||||
remoto. En una prueba eso es exactamente lo que *no* quieres, y es lo que cambia
|
||||
`raise_exceptions=True`: tu prueba ve el mensaje real en lugar del depurado.
|
||||
|
||||
Déjalo activado en las pruebas. No tiene ningún sentido en código de producción.
|
||||
|
||||
## En proceso por defecto {#in-process-by-default}
|
||||
|
||||
!!! note
|
||||
`Client(mcp)` se conecta en proceso y es **neutral respecto a la generación** por defecto: sondea
|
||||
el servidor y elige la ruta de protocolo adecuada. Fija `mode="legacy"` si tu prueba ejercita
|
||||
comportamientos específicos de las conexiones heredadas (envío de muestreo (sampling) o
|
||||
elicitación (elicitation), `message_handler`), y quita `raise_exceptions=True` en ese caso: una
|
||||
conexión heredada nunca depura los errores en primer lugar, y el indicador relanza el fallo
|
||||
dentro de la tarea del servidor en lugar de en tu prueba.
|
||||
|
||||
Esa única línea es también la razón por la que esta documentación puede prometerte que sus ejemplos
|
||||
funcionan: cada archivo de ejemplo lo ejercita la propia suite de pruebas del SDK, casi todos a
|
||||
través de exactamente este cliente. Estás usando la misma herramienta que el SDK usa consigo mismo.
|
||||
|
||||
Tienes un servidor que funciona y está probado. Ponerlo dentro de una aplicación real (Claude
|
||||
Desktop, un IDE) es **[Conectar a un host real](real-host.md)**; todas las demás formas de servirlo
|
||||
están en **[Ejecutar tu servidor](../run/index.md)**.
|
||||
Loading…
Reference in a new issue