docs: refresh translations, and translate pages in parallel (#3458)
This commit is contained in:
parent
08a3bc8eaf
commit
fd66270926
23 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 @@ e depois volte aqui.
|
|||
|
||||
## Um relógio com uma cara {#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"
|
||||
```
|
||||
|
||||
|
|
@ -56,15 +56,36 @@ O modelo lê `content`; o iframe é para humanos. Um host com suporte a UI ainda
|
|||
o resultado em texto para o modelo, e um cliente só de texto recebe *apenas* isso. Então o
|
||||
padrão canônico é uma ferramenta, duas respostas. Olhe `get_time` de novo:
|
||||
|
||||
```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)` é `True` somente quando o cliente declarou a
|
||||
extensão `io.modelcontextprotocol/ui` **e** listou `text/html;profile=mcp-app`
|
||||
nas suas configurações `mimeTypes`. O campo é obrigatório, então um cliente que o omite
|
||||
não conta. É exatamente isso que `main()` no mesmo arquivo declara: a
|
||||
metade cliente da negociação, e a resposta rica volta.
|
||||
não conta. Aqui está a metade cliente da negociação:
|
||||
|
||||
```python title="client.py" hl_lines="8 12"
|
||||
--8<-- "docs_src/apps/tutorial001_client.py"
|
||||
```
|
||||
|
||||
Sirva `server.py` por HTTP e depois execute o cliente em um segundo terminal:
|
||||
|
||||
```console
|
||||
uv run mcp run server.py --transport streamable-http
|
||||
```
|
||||
|
||||
```console
|
||||
python client.py
|
||||
```
|
||||
|
||||
```text
|
||||
2026-06-26T12:00:00Z
|
||||
```
|
||||
|
||||
A resposta rica voltou. Tire `extensions=[APPS_SUPPORT]` da chamada a `Client`
|
||||
e o mesmo programa imprime `The time is 2026-06-26T12:00:00Z.` no lugar, que é
|
||||
tudo o que um cliente só de texto chega a ver.
|
||||
|
||||
!!! warning
|
||||
Nunca retorne um placeholder como `"[Rendered UI]"` como único conteúdo. Se o
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [05891e7cc1938a13, b3c01a6af28c51ee, 7ffc91f5e38bdfe0, 717d3f235a8333a7, f471a13b2fe5d737, ed6af2df4b656dff]
|
||||
sections: [05891e7cc1938a13, b3c01a6af28c51ee, 6eba6ce094f4417e, 125f28588c85dd7b, ddd8969b698ca11c, ed6af2df4b656dff]
|
||||
tool: 1
|
||||
---
|
||||
# Extensões {#extensions}
|
||||
|
|
@ -49,7 +49,7 @@ Use como prefixo um domínio que você controla. `io.modelcontextprotocol/*` é
|
|||
|
||||
A menor extensão útil é uma ferramenta e um mapa de configurações:
|
||||
|
||||
```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"
|
||||
```
|
||||
|
||||
|
|
@ -57,17 +57,23 @@ A menor extensão útil é uma ferramenta e um mapa de configurações:
|
|||
* `settings()` é o valor anunciado em `capabilities.extensions["com.example/stamps"]`. Retorne `{}` (o padrão) para anunciar a extensão sem configurações.
|
||||
* A extensão nunca recebe o servidor. Ela declara contribuições como dados; o `MCPServer` as consome. Não existe um `self.server` para modificar.
|
||||
|
||||
E `main()` é a prova, um cliente em memória direto contra `mcp`:
|
||||
Sirva-a por HTTP, e um cliente é a prova:
|
||||
|
||||
```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"
|
||||
```
|
||||
|
||||
Todo `server.py` desta página é servido com esse comando, e todo `client.py` roda ao lado dele com `python client.py` em um segundo terminal.
|
||||
|
||||
### Servindo seus próprios métodos {#serving-your-own-methods}
|
||||
|
||||
Uma extensão pode registrar **novos métodos de requisição**: seus próprios verbos, servidos ao lado dos da especificação:
|
||||
|
||||
```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"
|
||||
```
|
||||
|
||||
|
|
@ -83,14 +89,15 @@ Os métodos são **estritamente aditivos**. O SDK impõe isso na construção, n
|
|||
|
||||
### O lado do cliente {#the-client-side}
|
||||
|
||||
O `main()` do mesmo arquivo é a história inteira do cliente, com as duas metades:
|
||||
O cliente é um programa à parte, e carrega as duas metades da história do 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 a extensão. As declarações viram `ClientCapabilities.extensions`: em uma conexão 2026-07-28 o mapa viaja no envelope `_meta` de cada requisição, então o servidor o vê em **toda** requisição; em uma conexão legada ele vai no handshake `initialize`. O código do servidor não se importa com qual: `require_client_extension(ctx, ...)` e `ctx.session.check_client_capability(...)` leem a fonte certa nos dois caminhos.
|
||||
* Métodos de fornecedor descem uma camada para `client.session.send_request(...)`; `Client` só ganha métodos de primeira classe para verbos da especificação. `send_request` aceita qualquer subclasse de `Request`, então a requisição do fornecedor passa como está.
|
||||
* `SearchRequest` e os dois modelos que ela carrega são o contrato de protocolo da extensão, então o cliente os declara por conta própria. Uma extensão publicada os distribuiria em um pacote que os dois lados importam.
|
||||
|
||||
### Interceptando `tools/call` {#intercepting-toolscall}
|
||||
|
||||
|
|
@ -109,12 +116,18 @@ O hook envolve `tools/call` e nada mais. Para preocupações que valem para toda
|
|||
|
||||
## Usando uma extensão de cliente {#using-a-client-extension}
|
||||
|
||||
Uma **extensão de cliente** é o mesmo contrato visto do lado consumidor: um pacote de comportamento do lado do cliente reunido sob um único identificador. Passe as instâncias para `Client(extensions=[...])` e chame as ferramentas normalmente:
|
||||
Uma **extensão de cliente** é o mesmo contrato visto do lado consumidor: um pacote de comportamento do lado do cliente reunido sob um único identificador. Aqui o servidor responde a `buy` com um recibo a resgatar em vez da mercadoria, e só para um cliente que declarou a extensão:
|
||||
|
||||
```python title="client.py" hl_lines="66-68"
|
||||
```python title="server.py" hl_lines="22-25"
|
||||
--8<-- "docs_src/extensions/tutorial006.py"
|
||||
```
|
||||
|
||||
No cliente, passe as instâncias para `Client(extensions=[...])` e chame as ferramentas normalmente:
|
||||
|
||||
```python title="client.py" hl_lines="33-35"
|
||||
--8<-- "docs_src/extensions/tutorial006_client.py"
|
||||
```
|
||||
|
||||
`call_tool("buy", ...)` retorna um `CallToolResult` comum, como toda outra chamada. O que a extensão mudou: o servidor agora pode responder a `buy` com um **formato de resultado** `receipt` em vez de um resultado final, e `Receipts` o finaliza (aqui, resgatando o recibo com uma chamada seguinte) antes de `call_tool` retornar. Nada muda no ponto da chamada.
|
||||
|
||||
Tire a extensão e nada disso existe: a barreira do servidor recusa um cliente que não a declarou (erro -32021), e um formato reivindicado vindo de um servidor que pula a barreira falha na validação, exatamente como a especificação exige para um `resultType` não reconhecido. Desligado por padrão, nas duas pontas da conexão.
|
||||
|
|
@ -124,15 +137,15 @@ Para anunciar um identificador **sem** nenhum comportamento do lado do cliente (
|
|||
```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")])
|
||||
```
|
||||
|
||||
## Escrevendo uma extensão de cliente {#writing-a-client-extension}
|
||||
|
||||
Herde de `ClientExtension` e sobrescreva apenas o que precisar. Três tipos de contribuição, cada um com um padrão: `settings()`, `claims()` e `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"
|
||||
```
|
||||
|
||||
* O identificador segue a mesma gramática do servidor, validada quando a classe é definida.
|
||||
|
|
@ -153,12 +166,18 @@ Duas regras discretas. As claims ficam ativas apenas em conexões 2026-07-28, e
|
|||
|
||||
### Verbos de extensão {#extension-verbs}
|
||||
|
||||
Os métodos de requisição próprios de uma extensão não precisam de registro no lado do cliente. Um tipo de requisição de fornecedor herda de `mcp.types.Request` e passa por `client.session.send_request`, como em [Servindo seus próprios métodos](#serving-your-own-methods). Um acréscimo: quando uma chave de params precisa viajar no header `Mcp-Name` (especificações de extensão como tasks exigem isso para seus verbos), o tipo de requisição declara `name_param`:
|
||||
Os métodos de requisição próprios de uma extensão não precisam de registro no lado do cliente. Um tipo de requisição de fornecedor herda de `mcp.types.Request` e passa por `client.session.send_request`, como em [Servindo seus próprios métodos](#serving-your-own-methods). Considere um servidor cuja extensão serve um único verbo sobre um job nomeado:
|
||||
|
||||
```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"
|
||||
```
|
||||
|
||||
Um acréscimo no cliente: quando uma chave de params precisa viajar no header `Mcp-Name` (especificações de extensão como tasks exigem isso para seus verbos), o tipo de requisição declara `name_param`:
|
||||
|
||||
```python title="client.py" hl_lines="20-23 28-29"
|
||||
--8<-- "docs_src/extensions/tutorial007_client.py"
|
||||
```
|
||||
|
||||
A sessão espelha `params["jobId"]` em `Mcp-Name` em todo caminho de envio, e um valor ausente falha de forma explícita em vez de omitir silenciosamente um header obrigatório.
|
||||
|
||||
## O que uma extensão não pode fazer {#what-an-extension-cannot-do}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
---
|
||||
# O Server de baixo nível {#the-low-level-server}
|
||||
|
|
@ -36,18 +36,22 @@ Três coisas mudaram, e elas são a API de baixo nível inteira:
|
|||
|
||||
### Experimente {#try-it}
|
||||
|
||||
Não existe Inspector para este aqui: `mcp dev` e `mcp run` só aceitam um `MCPServer`. O `Client` em memória não se importa; ele recebe um `Server` de baixo nível exatamente como recebe um `MCPServer`:
|
||||
`mcp dev` e `mcp run` só aceitam um `MCPServer`, então este aqui você serve por conta própria. A última linha de `server.py` monta um app ASGI comum a partir dele, e o uvicorn o executa:
|
||||
|
||||
```python title="main.py"
|
||||
```console
|
||||
uvicorn server:app --port 8000
|
||||
```
|
||||
|
||||
Aponte o Inspector, ou qualquer cliente, para `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 @@ O mesmo texto que a versão com `@mcp.tool()` produziu. Duas diferenças honesta
|
|||
* `result.structured_content` é `None`. O servidor de alto nível encapsula um `-> str` em `{"result": ...}` para você; aqui ninguém monta o que você não montou.
|
||||
* `list_tools` retorna o schema que **você** digitou, caractere por caractere. A versão de alto nível tinha `"title": "Query"` em cada propriedade e um `"title": "search_booksArguments"` na raiz: artefatos do Pydantic. Aqui embaixo, se está no fio, foi você quem colocou lá.
|
||||
|
||||
Em um teste você dispensa o uvicorn e a porta: `Client(server)` recebe um `Server` de baixo nível no mesmo processo exatamente como recebe um `MCPServer`, e **[Testes](../get-started/testing.md)** é esse padrão.
|
||||
|
||||
## Nada é verificado por você {#nothing-is-checked-for-you}
|
||||
|
||||
O `MCPServer` rejeita um argumento ruim antes mesmo de a sua função executar, validando a chamada contra o schema que ele gerou (**[Ferramentas](../servers/tools.md)**).
|
||||
|
|
@ -208,11 +214,11 @@ Cada um destes é uma ideia para a qual você já tem o vocabulário; cada um te
|
|||
## Recapitulando {#recap}
|
||||
|
||||
* O `Server` de baixo nível recebe os seus handlers como **parâmetros do construtor** `on_*`; todo handler é `async (ctx, params) -> result`.
|
||||
* Você escreve o dict `input_schema` e você monta o `CallToolResult`. Nada é derivado, encapsulado ou validado por você.
|
||||
* Você escreve o dict `input_schema` e você monta o `CallToolResult`. Nada é derivado, encapsulado ou validado para você.
|
||||
* Uma exceção em um handler é um erro de protocolo `-32603`. Um erro de ferramenta que o modelo consegue ler é um `CallToolResult` com `is_error=True` que **você** retorna.
|
||||
* O `_meta` no resultado é endereçado à aplicação cliente, não ao modelo.
|
||||
* `Server[T]` é genérico no que o seu lifespan produz; `ctx.lifespan_context` é um `T` tipado.
|
||||
* `add_request_handler(method, params_type, handler)` serve qualquer método. `initialize` é reservado.
|
||||
* As capacidades que um `Server` anuncia são derivadas de quais handlers você registrou.
|
||||
|
||||
`Client(server)` tratou os dois servidores de forma idêntica porque eles *são* o mesmo protocolo, e essa é justamente a ideia. A próxima camada abaixo nem é uma classe: é **[Middleware](middleware.md)**.
|
||||
O cliente tratou os dois servidores de forma idêntica porque eles *são* o mesmo protocolo, e essa é justamente a ideia. A próxima camada abaixo nem é uma classe: é **[Middleware](middleware.md)**.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [a9aba7a026c7bd85, ed32bda7ba9ae33a, 7e64cc5646abb91f, 22a0129ee78b3c63, d875373c06d8d2f9]
|
||||
sections: [a9aba7a026c7bd85, 83e2a08b9d46a398, 9fd8a0aa384b3257, 22a0129ee78b3c63, d875373c06d8d2f9]
|
||||
tool: 1
|
||||
---
|
||||
# Paginação {#pagination}
|
||||
|
|
@ -31,9 +31,13 @@ O `@mcp.resource()` não tem nenhum gancho para isso. Para paginar, você mesmo
|
|||
|
||||
### Experimente {#try-it}
|
||||
|
||||
`Client(server)` se conecta a um `Server` de baixo nível em memória exatamente como se conecta a um `MCPServer`.
|
||||
O `mcp run` só aceita um `MCPServer`, então este aqui você mesmo serve. A última linha de `server.py` monta um app ASGI comum a partir do `Server`, e o uvicorn executa esse app:
|
||||
|
||||
Chame `list_resources()` sem argumentos. Você recebe dez recursos, de `book-1` a `book-10`, e `next_cursor` é a string `"10"`.
|
||||
```console
|
||||
uvicorn server:app --port 8000
|
||||
```
|
||||
|
||||
Aponte qualquer cliente (**[O cliente](../client/index.md)**, ou o Inspector) para `http://localhost:8000/mcp` e chame `list_resources()` sem argumentos. Você recebe dez recursos, de `book-1` a `book-10`, e `next_cursor` é a string `"10"`.
|
||||
|
||||
Devolva-a com `list_resources(cursor="10")` e o primeiro recurso é `book-11`, o novo `next_cursor` é `"20"`.
|
||||
|
||||
|
|
@ -43,7 +47,7 @@ A décima página volta com `next_cursor` definido como `None`. Pronto.
|
|||
|
||||
Todo método `list_*` do `Client` (`list_tools`, `list_resources`, `list_resource_templates`, `list_prompts`) aceita a palavra-chave `cursor=`. Esgotar uma lista paginada é um único `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 @@ Todo método `list_*` do `Client` (`list_tools`, `list_resources`, `list_resourc
|
|||
* Acumule **antes** de olhar para `next_cursor`: a última página também tem recursos.
|
||||
* `next_cursor is None` é a saída. Qualquer outra coisa volta direto para `cursor=`, intocada.
|
||||
|
||||
Execute o `main()` dele e ele imprime `100 resources`: dez páginas de dez, costuradas por um loop que nunca soube que havia dez páginas.
|
||||
Com o uvicorn ainda servindo `server.py`, execute `python client.py` em um segundo terminal. Ele imprime `100 resources`: dez páginas de dez, costuradas por um loop que nunca soube que havia dez páginas.
|
||||
|
||||
Este é o mesmo loop que **[O cliente](../client/index.md)** mostra para todo verbo `list_*`, e ele não custa nada contra um servidor que não pagina: `next_cursor` é `None` na primeira resposta e o loop roda uma vez.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [9e7b9a1710e5aeba, b74ca4c1d2ddddee, fa8714e61bf90c5a, 04db67a886b7271c, 857690fb8f876800]
|
||||
sections: [9e7b9a1710e5aeba, 66a2e9acc9101d54, d3d25aa802f5145a, 04db67a886b7271c, 857690fb8f876800]
|
||||
tool: 1
|
||||
---
|
||||
# Dicas de cache {#caching-hints}
|
||||
|
|
@ -30,7 +30,7 @@ Por padrão, todo resultado diz `ttlMs: 0, cacheScope: "private"`: obsoleto imed
|
|||
|
||||
No `Server` de baixo nível, os handlers montam seus resultados à mão, e `ttl_ms` / `cache_scope` são apenas campos nos modelos de resultado. Um handler que os define explicitamente sempre vence o mapa do construtor, campo a campo:
|
||||
|
||||
```python title="server.py" hl_lines="10 16"
|
||||
```python title="server.py" hl_lines="11 17"
|
||||
--8<-- "docs_src/caching/tutorial002.py"
|
||||
```
|
||||
|
||||
|
|
@ -44,10 +44,24 @@ Uma ressalva sobre listas paginadas: o protocolo exige o **mesmo `cacheScope` em
|
|||
|
||||
Numa sessão 2026-07-28, o `Client` respeita as dicas por você: ele tem um cache de respostas embutido, ligado por padrão. Um resultado que chega carregando um `ttlMs` é armazenado, e uma chamada idêntica dentro desse TTL é servida do cache sem viagem de ida e volta. Um resultado que não carrega *nenhuma* dica não é armazenado: resultados sem dica recebem `CacheConfig.default_ttl_ms`, cujo padrão é `0` (obsoleto imediatamente), então um servidor que não declara nada vê exatamente o mesmo tráfego chamada a chamada de sempre.
|
||||
|
||||
```python title="client.py" hl_lines="33 35 38"
|
||||
Para ver isso acontecer, sirva o `server.py` da seção anterior com o uvicorn (a última linha dele monta o app ASGI). O handler imprime uma linha toda vez que executa de verdade:
|
||||
|
||||
```console
|
||||
uvicorn server:app --port 8000
|
||||
```
|
||||
|
||||
```python title="client.py" hl_lines="20 23 28"
|
||||
--8<-- "docs_src/caching/tutorial003.py"
|
||||
```
|
||||
|
||||
Execute `python client.py` em um segundo terminal. Ele imprime as dicas que o primeiro resultado carregava, o `ttlMs` do handler ao lado do `cacheScope` do mapa:
|
||||
|
||||
```text
|
||||
1000 public
|
||||
```
|
||||
|
||||
O terminal do servidor conta o resto da história: entre os logs de requisição do uvicorn, `tools/list served` aparece três vezes.
|
||||
|
||||
Quatro chamadas, três buscas. A segunda chamada encontrou uma entrada fresca e nunca chegou ao servidor; avançar o relógio (injetado) além do TTL fez a terceira buscar de novo; a quarta disse `cache_mode="refresh"`. Esse argumento nomeado existe nos cinco verbos com cache (`list_tools`, `list_prompts`, `list_resources`, `list_resource_templates`, `read_resource`):
|
||||
|
||||
* `"use"` (o padrão) serve uma entrada fresca se houver uma, e armazena a busca se não houver.
|
||||
|
|
@ -56,7 +70,7 @@ Quatro chamadas, três buscas. A segunda chamada encontrou uma entrada fresca e
|
|||
|
||||
Uma regra fica acima de `"use"`: **chamadas que carregam `meta` sempre chegam ao servidor.** Uma requisição com `meta` definido (um token de progresso, campos de rastreamento) espera uma requisição no fio, então sob `cache_mode="use"` ela é tratada como `"refresh"`: a leitura do cache é pulada, e o resultado buscado ainda substitui a entrada em cache. `"bypass"` e um `"refresh"` explícito se comportam como sempre.
|
||||
|
||||
Para desligar o cache por completo, construa com `Client(server, cache=None)`: toda chamada volta a ser uma viagem de ida e volta, e `cache_mode`, embora ainda aceito, não faz nada.
|
||||
Para desligar o cache por completo, passe `cache=None` ao construir o `Client`: toda chamada volta a ser uma viagem de ida e volta, e `cache_mode`, embora ainda aceito, não faz nada.
|
||||
|
||||
O escopo também é respeitado automaticamente: entradas `"private"` são indexadas pela *partição* do cache (abaixo), enquanto as `"public"` podem optar por um compartilhamento mais amplo. E **notificações vencem o TTL** para as entradas exatas que nomeiam: uma notificação `list_changed` remove a listagem em cache correspondente, e `resources/updated` remove a leitura em cache armazenada sob exatamente a sua URI, por mais frescas que estivessem. Numa conexão 2026-07-28 essas notificações chegam num stream `subscriptions/listen` que você abre com `client.listen(...)`, e a remoção se completa antes de o seu observador ver o evento; **[Assinaturas](subscriptions.md)** é essa página.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [adf3c545b5be46b6, 916cd3ab1c03f461, e9be7a8d0eb0a456, 565890a636288ecf, 6af7e49db9129ec3, 06b0238c174186af, 90c6043be435fcb0]
|
||||
sections: [adf3c545b5be46b6, 916cd3ab1c03f461, 32ef568335dd95a7, 565890a636288ecf, 6af7e49db9129ec3, 06b0238c174186af, 0abc5ea5cb7ff6b3]
|
||||
tool: 1
|
||||
---
|
||||
# Callbacks do cliente {#client-callbacks}
|
||||
|
|
@ -63,7 +63,7 @@ Um `tools/call` seu, um `elicitation/create` de volta do servidor, respondido pe
|
|||
O `mode="legacy"` na chamada `Client(...)` está fazendo trabalho de verdade. Por padrão, `Client(...)` negocia o caminho
|
||||
moderno do protocolo, e esse caminho não tem canal de retorno (back-channel) para requisições do servidor ao cliente: `ctx.elicit`
|
||||
falha antes mesmo de o seu callback rodar. Não é o transporte que decide isso; é o protocolo
|
||||
negociado, tanto em memória quanto por uma URL. Fixe `mode="legacy"` sempre que o seu cliente tiver
|
||||
negociado. Fixe `mode="legacy"` sempre que o seu cliente tiver
|
||||
que responder a uma; todos os testes por trás desta página fazem isso. **[Versões do protocolo](../protocol-versions.md)** tem a história completa.
|
||||
|
||||
Em uma sessão 2026-07-28 o callback não está morto, ele é alimentado de outro jeito: quando uma ferramenta retorna um
|
||||
|
|
@ -151,4 +151,4 @@ Mais dois. Nenhum deles declara nada.
|
|||
* `sampling_callback` e `list_roots_callback` funcionam do mesmo jeito, mas atendem funcionalidades descontinuadas; servidores modernos usam requisições de múltiplas idas e voltas no lugar.
|
||||
* `logging_callback` e `message_handler` recebem notificações. Eles não declaram nada.
|
||||
|
||||
O primeiro argumento de `Client(...)` é um objeto de transporte. **[Transportes do cliente](transports.md)** cobre todos os tipos.
|
||||
O primeiro argumento de `Client(...)` escolhe o transporte. **[Transportes do cliente](transports.md)** cobre todos os tipos.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [ebef1e7a0df854f4, 8355cfaf1f76c9d5, 8e79141fc2985342, 46bdb07c7537e8a5, 80ce41579825a6fa, 5f0fa90494de8f65, 83d10514eaa62fa5, 9190555aa39a5d28, 84a4c9d8bf14dddb, 927d71cf40b58c30]
|
||||
sections: [ebef1e7a0df854f4, 7e16449f66e7dfd6, eeb0682f7d2a1079, 5713f0196a34e6e7, 0e844597859e4248, 3a97d9195ddcc92e, 1da08c483e59c141, 84702cc6e0a1fd42, 8dee7a31c86ffc5c, 83a5bce168ef23d7]
|
||||
tool: 1
|
||||
---
|
||||
# O cliente {#the-client}
|
||||
|
|
@ -11,13 +11,23 @@ Um **`Client`** é como um programa Python conversa com um servidor MCP.
|
|||
|
||||
## Seu primeiro cliente {#your-first-client}
|
||||
|
||||
```python title="client.py" hl_lines="14-18"
|
||||
Um cliente precisa de um servidor com quem conversar. Esta Bookshop é o servidor a que todo trecho desta página se conecta. Salve-o como `server.py` e deixe-o rodando via HTTP:
|
||||
|
||||
```python title="server.py"
|
||||
--8<-- "docs_src/client/tutorial001.py"
|
||||
```
|
||||
|
||||
O servidor no topo só está ali para você ter algo a que se conectar. O cliente são as cinco linhas destacadas.
|
||||
```console
|
||||
uv run mcp run server.py --transport streamable-http
|
||||
```
|
||||
|
||||
* `Client(mcp)` recebe o **próprio objeto servidor**. Esse é o transporte em memória: sem subprocesso, sem porta, sem HTTP. É assim que todo exemplo nesta página, e todo teste que você escrever, se conecta.
|
||||
Isso o serve em `http://localhost:8000/mcp`. O cliente é um programa à parte. Salve-o como `client.py` e execute `python client.py` em um segundo terminal:
|
||||
|
||||
```python title="client.py" hl_lines="7-11"
|
||||
--8<-- "docs_src/client/tutorial001_client.py"
|
||||
```
|
||||
|
||||
* `Client("http://localhost:8000/mcp")` recebe uma **URL**, então se conecta via Streamable HTTP ao servidor que você acabou de iniciar.
|
||||
* `async with` é o **ciclo de vida**. Entrar nele conecta e negocia; sair dele desconecta. Não há um par `connect()` / `close()`, e um `Client` não pode ser reutilizado depois que o bloco termina.
|
||||
* Dentro do bloco, os fatos da conexão já estão ali como propriedades comuns.
|
||||
|
||||
|
|
@ -25,10 +35,10 @@ O servidor no topo só está ali para você ter algo a que se conectar. O client
|
|||
|
||||
`Client` recebe um argumento posicional e resolve o transporte a partir do tipo dele:
|
||||
|
||||
* Uma instância de `MCPServer` (ou do `Server` de baixo nível): conectada **no mesmo processo**.
|
||||
* Uma string de URL (`Client("http://localhost:8000/mcp")`): Streamable HTTP, o caminho de produção.
|
||||
* Um `StdioServerParameters`: o comando a iniciar como **subprocesso**, com o qual se conversa pelo stdin e stdout dele.
|
||||
* Uma string de URL (`Client("http://localhost:8000/mcp")`): Streamable HTTP, o transporte atrás do qual você faz o deploy.
|
||||
* Um `StdioServerParameters`: o comando a iniciar como **subprocesso** local, com o qual se conversa pelo stdin e stdout dele.
|
||||
* Um **transporte**: qualquer coisa com que você possa fazer `async with ... as (read, write)`, como `streamable_http_client(url, http_client=...)` em volta do seu próprio cliente HTTP.
|
||||
* Uma instância de `MCPServer` (ou do `Server` de baixo nível): conectada **no mesmo processo**, sem subprocesso e sem porta. Essa é para testes, e **[Testes](../get-started/testing.md)** se apoia nela.
|
||||
|
||||
Todo o resto desta página é idêntico entre os quatro. Cabeçalhos, subprocessos, timeouts e o protocolo `Transport` têm sua própria página: **[Transportes do cliente](transports.md)**.
|
||||
|
||||
|
|
@ -49,11 +59,11 @@ Você nunca escolheu uma versão do protocolo. Por padrão, o `Client` sonda o s
|
|||
|
||||
## Listando ferramentas {#listing-tools}
|
||||
|
||||
```python title="client.py" hl_lines="15-20"
|
||||
```python title="client.py" hl_lines="8-13"
|
||||
--8<-- "docs_src/client/tutorial002.py"
|
||||
```
|
||||
|
||||
`list_tools()` retorna um `ListToolsResult`; as ferramentas estão em `.tools`. Cada uma é a definição completa que um host entregaria a um modelo:
|
||||
`list_tools()` retorna um `ListToolsResult`; as ferramentas estão em `.tools`. Cada uma é a definição completa que um host entregaria a um modelo. Eis a primeira:
|
||||
|
||||
```python
|
||||
tool.name # 'search_books'
|
||||
|
|
@ -77,6 +87,8 @@ e `tool.input_schema` é o JSON Schema que o servidor derivou das anotações de
|
|||
|
||||
Esse schema é tudo o que uma UI precisa para renderizar um formulário de argumentos, e tudo o que um modelo precisa para produzir argumentos válidos.
|
||||
|
||||
A segunda ferramenta, `lookup_book`, foi registrada sem um `title=`, então o `tool.title` dela é `None`.
|
||||
|
||||
!!! tip
|
||||
`title` é opcional, então uma UI que mostra ferramentas a um humano tem que escolher: o `title` se houver um,
|
||||
o `name` se não. `from mcp.shared.metadata_utils import get_display_name` faz exatamente isso,
|
||||
|
|
@ -86,7 +98,7 @@ Esse schema é tudo o que uma UI precisa para renderizar um formulário de argum
|
|||
|
||||
`call_tool(name, arguments)` executa a ferramenta e devolve um `CallToolResult`.
|
||||
|
||||
```python title="client.py" hl_lines="27-34"
|
||||
```python title="client.py" hl_lines="9-16"
|
||||
--8<-- "docs_src/client/tutorial003.py"
|
||||
```
|
||||
|
||||
|
|
@ -142,7 +154,7 @@ Uma ferramenta que lança uma exceção **não** lança no seu cliente. Ela volt
|
|||
|
||||
Os verbos de recurso vêm em pares: duas formas de listar, uma forma de ler.
|
||||
|
||||
```python title="client.py" hl_lines="22-31"
|
||||
```python title="client.py" hl_lines="9-18"
|
||||
--8<-- "docs_src/client/tutorial004.py"
|
||||
```
|
||||
|
||||
|
|
@ -156,7 +168,7 @@ Um cliente também pode ser avisado quando um recurso muda. Em conexões da era
|
|||
|
||||
## Prompts {#prompts}
|
||||
|
||||
```python title="client.py" hl_lines="15-20"
|
||||
```python title="client.py" hl_lines="8-13"
|
||||
--8<-- "docs_src/client/tutorial005.py"
|
||||
```
|
||||
|
||||
|
|
@ -181,7 +193,7 @@ Um host entrega essas mensagens direto ao modelo. A funcionalidade inteira é es
|
|||
|
||||
Um servidor com um handler de completion pode autocompletar argumentos de prompts e de templates de recurso enquanto o usuário digita.
|
||||
|
||||
```python title="client.py" hl_lines="27-31"
|
||||
```python title="client.py" hl_lines="9-13"
|
||||
--8<-- "docs_src/client/tutorial006.py"
|
||||
```
|
||||
|
||||
|
|
@ -194,21 +206,21 @@ A resposta está em `result.completion.values`. Digite `"p"` e o servidor volta
|
|||
|
||||
Todo método `list_*` aceita um argumento nomeado `cursor=` e todo resultado carrega um `next_cursor`. Quando `next_cursor` é `None`, você tem tudo.
|
||||
|
||||
```python title="client.py" hl_lines="22-30"
|
||||
```python title="client.py" hl_lines="7-15"
|
||||
--8<-- "docs_src/client/tutorial007.py"
|
||||
```
|
||||
|
||||
Esse loop está correto contra qualquer servidor. O `MCPServer` retorna tudo em uma página só, então `next_cursor` é `None` e o loop roda uma vez, e é por isso que a maioria do código nunca o escreve. Servidores que paginam de verdade, e as regras que os cursores obedecem, estão em **[Paginação](../advanced/pagination.md)**.
|
||||
`list_all_tools` está correta contra qualquer servidor. O `MCPServer` retorna tudo em uma página só, então `next_cursor` é `None` e o loop roda uma vez, e é por isso que a maioria do código nunca o escreve. Servidores que paginam de verdade, e as regras que os cursores obedecem, estão em **[Paginação](../advanced/pagination.md)**.
|
||||
|
||||
## Em testes {#in-tests}
|
||||
|
||||
`Client(mcp)`, sem processo e sem porta, já é um harness de teste para o seu servidor.
|
||||
Todo `client.py` desta página alcançou o `server.py` via HTTP. Em um teste você pula a rede e entrega ao `Client` o próprio objeto servidor: `from server import mcp`, depois `Client(mcp)`. Sem processo, sem porta, e todo método acima funciona igual.
|
||||
|
||||
Existe uma flag do construtor feita para isso: `Client(mcp, raise_exceptions=True)`. Ela só tem efeito em conexões em memória, e **[Testes](../get-started/testing.md)** é a página que a explica e constrói todo o padrão em torno dela.
|
||||
Existe uma flag do construtor feita para isso: `Client(mcp, raise_exceptions=True)`. Ela só tem efeito em conexões no mesmo processo, e **[Testes](../get-started/testing.md)** é a página que a explica e constrói todo o padrão em torno dela.
|
||||
|
||||
## Recapitulando {#recap}
|
||||
|
||||
* `Client(x)` conecta em memória a um objeto servidor, via Streamable HTTP a uma string de URL, e por qualquer outra coisa via um transporte.
|
||||
* `Client(x)` conecta via Streamable HTTP a uma string de URL, inicia um subprocesso para um `StdioServerParameters`, entra direto em um transporte e, em testes, recebe o próprio objeto servidor.
|
||||
* `async with` é o ciclo de vida inteiro. Dentro dele, `server_capabilities` e `protocol_version` já estão preenchidos; `server_info` e `instructions` também, quando o servidor os fornece.
|
||||
* `list_tools()` dá a você o `name`, `title`, `description` e `input_schema` de cada ferramenta.
|
||||
* `call_tool()` retorna `content` para o modelo, `structured_content` para o seu código e `is_error`. Uma ferramenta que lança exceção é um resultado, não uma exceção.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [c6899d3892bd9fa0, 79372cff3cc48a88, 63878d29e87c3e73, 13175843d3588af4, e7e2b9fd516f77de, 758f06399b513c1f, a05d7278487d610b]
|
||||
sections: [c6899d3892bd9fa0, 79372cff3cc48a88, c2dae1ebe2ebd543, 13175843d3588af4, df06056fb16b3846, 758f06399b513c1f, a05d7278487d610b]
|
||||
tool: 1
|
||||
---
|
||||
# Clientes OAuth {#oauth-clients}
|
||||
|
|
@ -81,18 +81,20 @@ Veja `main()`. O provider vai no **cliente httpx2**, o cliente httpx2 vai em `st
|
|||
|
||||
Na primeira vez que `Client` envia uma requisição, o servidor responde `401`. O provider assume:
|
||||
|
||||
1. **Descoberta.** Ele lê o cabeçalho `WWW-Authenticate`, busca os Protected Resource Metadata do servidor em `/.well-known/oauth-protected-resource`, descobre qual servidor de autorização protege este recurso e busca os metadados *desse* servidor.
|
||||
1. **Descoberta.** Ele lê o cabeçalho `WWW-Authenticate`, busca os Protected Resource Metadata do servidor em `/.well-known/oauth-protected-resource`, descobre qual servidor de autorização protege este recurso e busca os metadados *desse* servidor. (A um servidor mais antigo, que não publica metadados de recurso, o provider pede os metadados do servidor de autorização na própria origem dele.) De um jeito ou de outro, os metadados precisam nomear, como `issuer`, o servidor para o qual foram buscados; qualquer outra coisa é recusada.
|
||||
2. **Registro.** Nada no armazenamento? Ele registra você dinamicamente com o seu `OAuthClientMetadata` e armazena o resultado.
|
||||
3. **Autorização.** Ele gera o par PKCE e um `state`, monta a URL de autorização, faz await no seu `redirect_handler` e depois faz await no seu `callback_handler` para obter o code.
|
||||
4. **Troca.** Ele troca o code por um `OAuthToken`, armazena e reenvia a sua requisição original com `Authorization: Bearer ...`.
|
||||
|
||||
Depois disso ele fica quieto. Os tokens saem do armazenamento, um token de acesso expirado é renovado com o refresh token, e só quando nada disso funciona ele executa o fluxo de novo.
|
||||
|
||||
Uma regra de transporte vale para todas essas requisições: assim como a requisição MCP dentro da qual elas acontecem, elas só seguem um redirecionamento quando ele fica na mesma origem e mantém o método (um 307/308 de barra final, digamos), e tratam qualquer outro redirecionamento como se aquela URL não respondesse.
|
||||
|
||||
Você não escreveu nada disso. Restam dois argumentos nomeados (`client_metadata_url` e `validate_resource_url`), e este arquivo não precisa de nenhum dos dois. `client_metadata_url` é o que vale a pena conhecer; ele ganha uma seção própria abaixo.
|
||||
|
||||
### Experimente {#try-it}
|
||||
|
||||
A maioria dos exemplos nesta documentação você consegue conferir com um `Client(server)` em memória. Este não: o ponto central do fluxo é um `401` HTTP, e não há HTTP entre um cliente em memória e o seu servidor.
|
||||
O `Client(server)` em memória que os seus testes usam não ajuda aqui: o ponto central do fluxo é um `401` HTTP, e não há HTTP entre um cliente em memória e o seu servidor.
|
||||
|
||||
O repositório traz a versão ao vivo. `examples/servers/simple-auth/` executa um servidor de autorização independente e um servidor MCP protegido; `examples/clients/simple-auth-client/` é o cliente desta página crescido até virar uma pequena CLI. O README dele tem os dois comandos: inicie os servidores, execute o cliente contra eles e veja as quatro etapas passarem.
|
||||
|
||||
|
|
@ -110,13 +112,14 @@ Um job noturno, uma etapa de CI, outro serviço. Não há navegador nem ninguém
|
|||
|
||||
`ClientCredentialsOAuthProvider` é o mesmo `httpx2.Auth`, sem o humano:
|
||||
|
||||
```python title="client.py" hl_lines="4 27-33"
|
||||
```python title="client.py" hl_lines="4 27-34"
|
||||
--8<-- "docs_src/oauth_clients/tutorial002.py"
|
||||
```
|
||||
|
||||
O que mudou:
|
||||
|
||||
* Sem `OAuthClientMetadata`, sem handlers. Você passa `client_id` e `client_secret`; o provider monta um registro `client_credentials` mínimo em torno deles e pula o registro dinâmico por completo.
|
||||
* `issuer` nomeia o servidor de autorização que emitiu essas credenciais; use o valor `issuer` que o documento `/.well-known/oauth-authorization-server` dele retorna. A descoberta ainda acontece como acima, mas as requisições de token só são montadas a partir dos metadados *desse* issuer; se o servidor MCP apontar para qualquer outro lugar, o fluxo para com um `OAuthFlowError`. Deixá-lo de fora está obsoleto e ele passa a ser obrigatório na 3.0 (veja **[Funcionalidades obsoletas](../deprecated.md#deprecated-sdk-helpers)**); até lá, o provider emite um aviso e usa o servidor de autorização que a descoberta encontrar.
|
||||
* `scope` é uma string separada por espaços, o formato OAuth usado na comunicação.
|
||||
* Tudo a partir daí é idêntico: o mesmo `TokenStorage`, o mesmo `httpx2.AsyncClient(auth=...)`, o mesmo `streamable_http_client`.
|
||||
|
||||
|
|
@ -129,7 +132,7 @@ Por padrão, o secret viaja como HTTP Basic auth na requisição de token (`clie
|
|||
Mais um provider mora em `mcp.client.auth.extensions.client_credentials`:
|
||||
**`PrivateKeyJWTOAuthProvider`**, para clientes que se autenticam com um JWT em vez de um
|
||||
segredo compartilhado (`private_key_jwt`, a variante de par de chaves e workload identity). Ele segue
|
||||
o mesmo padrão: construa um, coloque em `auth=`. O mesmo módulo traz
|
||||
o mesmo padrão: construa um (ele aceita o mesmo `issuer` opcional), coloque em `auth=`. O mesmo módulo traz
|
||||
`SignedJWTParameters` e `static_assertion_provider`, dois helpers que montam a assertion dele.
|
||||
|
||||
Há mais uma situação sem humano: o cliente pertence a uma empresa cujo provedor de identidade, e não o usuário, decide quais servidores MCP ele pode alcançar. Esse é um grant diferente, com seu próprio modelo de confiança e sua própria página, **[Asserção de identidade](identity-assertion.md)**.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [09c857a25a9dc37a, 43bc6a76a243a50e, 0a716022a88768df, 4b7f78042bfcfff7, c112662e61b03315, 58974ba1f489a8b4, d18adbdbb835ea73]
|
||||
sections: [09c857a25a9dc37a, 43bc6a76a243a50e, 0a716022a88768df, 4b7f78042bfcfff7, c112662e61b03315, 58974ba1f489a8b4, ed4d17e894864056]
|
||||
tool: 1
|
||||
---
|
||||
# Grupos de sessões {#session-groups}
|
||||
|
|
@ -78,7 +78,7 @@ Se você já tem em mãos uma `ClientSession` conectada (`Client.session` é uma
|
|||
## Recapitulando {#recap}
|
||||
|
||||
* `ClientSessionGroup` mantém várias conexões de servidor e reúne as ferramentas, recursos e prompts delas em um `dict` para cada tipo.
|
||||
* `connect_to_server(params)` por servidor. Ele recebe parâmetros de transporte, nunca o objeto de servidor ou a URL que um `Client` recebe.
|
||||
* `connect_to_server(params)` por servidor. Ele recebe parâmetros de transporte, nunca a URL ou o `Transport` que um `Client` recebe.
|
||||
* `group.call_tool(name, arguments)` roteia para o servidor dono por você.
|
||||
* Os nomes precisam ser únicos no grupo inteiro; dois servidores com uma ferramenta `search` não conseguem coexistir por conta própria.
|
||||
* `component_name_hook=` reescreve cada nome registrado. A chave do dict muda, o nome na rede não.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [9cac816674181eb0, 0700f337babcd4dd, 2bde0dd58cdf00f5, 40b4916d82eaf1d4, 3d0832f39b0d7059, dfa4446556badef0, 5bd93be2ab2ecb9c]
|
||||
sections: [9cac816674181eb0, 7c157764133fea1f, 40b4916d82eaf1d4, 10d151f2cc75317f, 3d0832f39b0d7059, 92742ba36533633d, 0aeca6145e7bd302]
|
||||
tool: 1
|
||||
---
|
||||
# Transportes do cliente {#client-transports}
|
||||
|
|
@ -11,30 +11,15 @@ Você nunca configura um transporte separadamente. `Client` recebe um único arg
|
|||
|
||||
O lado do *servidor* de cada um (o que `mcp.run()` faz e o que você coloca no deploy) está em **[Executando seu servidor](../run/index.md)**.
|
||||
|
||||
## Em memória {#in-memory}
|
||||
|
||||
Passe o próprio objeto do servidor:
|
||||
|
||||
```python title="client.py" hl_lines="14"
|
||||
--8<-- "docs_src/client_transports/tutorial001.py"
|
||||
```
|
||||
|
||||
Sem subprocesso, sem porta, sem bytes trafegando na rede. O cliente e o servidor são dois objetos no mesmo processo, e a chamada ainda passa pela camada real do protocolo: `search_books` é listada, validada e invocada exatamente como seria sobre HTTP.
|
||||
|
||||
Isso faz dele duas coisas ao mesmo tempo:
|
||||
|
||||
* **Uma estrutura de testes.** Todo exemplo desta documentação é exercitado dessa forma, e a página **[Testes](../get-started/testing.md)** constrói o padrão inteiro em torno disso.
|
||||
* **Uma API de embutimento.** Uma aplicação que constrói o servidor não precisa de um salto pela rede para chamar as ferramentas dele.
|
||||
|
||||
## Streamable HTTP {#streamable-http}
|
||||
|
||||
Passe uma string de URL e você tem **Streamable HTTP**, o transporte atrás do qual você faz o deploy:
|
||||
Passe uma string de URL e você tem **Streamable HTTP**, o transporte atrás do qual você faz o deploy e o primeiro a que você deve recorrer:
|
||||
|
||||
```python title="client.py" hl_lines="5"
|
||||
--8<-- "docs_src/client_transports/tutorial002.py"
|
||||
```
|
||||
|
||||
Esse é o cliente de produção inteiro. `Client` envolve a URL em `streamable_http_client(...)` para você, sobre um `httpx2.AsyncClient` configurado do jeito que o MCP precisa: `follow_redirects=True`, um timeout de 30 segundos para connect/write/pool e um timeout de leitura de 300 segundos, porque o servidor pode manter um stream de resposta aberto.
|
||||
Esse é o cliente de produção inteiro. `Client` envolve a URL em `streamable_http_client(...)` para você, sobre um `httpx2.AsyncClient` configurado do jeito que o MCP precisa: um timeout de 30 segundos para connect/write/pool e um timeout de leitura de 300 segundos, porque o servidor pode manter um stream de resposta aberto.
|
||||
|
||||
!!! check
|
||||
Um `Client` que você construiu **não** está conectado. A construção só escolhe o transporte;
|
||||
|
|
@ -50,7 +35,7 @@ Esse é o cliente de produção inteiro. `Client` envolve a URL em `streamable_h
|
|||
|
||||
No momento em que você precisar de um header `Authorization`, um cookie, um proxy, mTLS ou um timeout diferente, construa o `httpx2.AsyncClient` você mesmo e entregue-o a `streamable_http_client`:
|
||||
|
||||
```python title="client.py" hl_lines="8-14"
|
||||
```python title="client.py" hl_lines="8-13"
|
||||
--8<-- "docs_src/client_transports/tutorial003.py"
|
||||
```
|
||||
|
||||
|
|
@ -80,9 +65,30 @@ sem um repositório de CAs do sistema utilizável (alguns contêineres mínimos)
|
|||
!!! info
|
||||
`httpx2` mantém a API conhecida do `httpx`, então se você conhece `httpx` já sabe como fazer auth,
|
||||
proxies, event hooks, retentativas e limites de conexão aqui. O SDK não acrescenta nada por cima nem
|
||||
tira nada. É também onde o OAuth se encaixa:
|
||||
tira nada, exceto o [tratamento de redirecionamentos](#redirects). É também onde o OAuth se encaixa:
|
||||
`httpx2.AsyncClient(auth=OAuthClientProvider(...))`. Esse fluxo inteiro está em **[Clientes OAuth](oauth-clients.md)**.
|
||||
|
||||
### Redirecionamentos {#redirects}
|
||||
|
||||
O transporte se conecta à URL que você forneceu, e somente a essa origem.
|
||||
|
||||
* Um redirecionamento `307`/`308` que permanece no mesmo esquema, host e porta é seguido, e `http://` → `https://` no mesmo host também. Isso cobre o redirecionamento habitual de barra final `/mcp` → `/mcp/`.
|
||||
* Um redirecionamento para qualquer outro lugar **não** é seguido. A chamada falha com:
|
||||
|
||||
```text
|
||||
MCPError: Redirect to https://other.example.com/mcp not followed; use that URL as the endpoint if it is the intended server
|
||||
```
|
||||
|
||||
Se essa URL é o servidor que você queria, coloque-a na sua configuração. Se não é, o servidor ou um proxy na frente dele está mal configurado.
|
||||
|
||||
Isso vale para qualquer `httpx2.AsyncClient` que você passe: a configuração `follow_redirects` dele não é consultada para requisições MCP, em nenhuma das direções. Os provedores OAuth do SDK aplicam a mesma regra às próprias requisições.
|
||||
|
||||
!!! tip
|
||||
`Redirect to http://… not followed: it would downgrade this HTTPS endpoint to plain HTTP` significa que o
|
||||
servidor está atrás de um proxy com terminação TLS que ele desconhece e está emitindo redirecionamentos `http://`.
|
||||
Isso se corrige no servidor (**[Deploy e escala](../run/deploy.md#behind-a-tls-terminating-proxy)**)
|
||||
ou usando a URL exata `https://…/` que a mensagem sugere.
|
||||
|
||||
## stdio {#stdio}
|
||||
|
||||
Um servidor **stdio** é um subprocesso. O cliente o inicia, escreve JSON-RPC no stdin dele e lê JSON-RPC do stdout dele. É assim que um host de desktop executa um servidor na sua máquina: um host *é* este código mais uma interface, e **[Conecte a um host real](../get-started/real-host.md)** é a mesma relação vista do lado do host, como um arquivo de configuração.
|
||||
|
|
@ -105,6 +111,18 @@ O stderr do processo filho vai para o seu. Para mandá-lo para outro lugar, cons
|
|||
Um servidor que precise de uma chave de API não vai encontrá-la ali. Passe-a explicitamente com `env=`; essas
|
||||
variáveis são mescladas por cima da allow-list. É isso que `BOOKSHOP_API_KEY` está fazendo acima.
|
||||
|
||||
## Em memória {#in-memory}
|
||||
|
||||
Em um teste não há nada para colocar no deploy nem nada para iniciar. Passe o próprio objeto do servidor:
|
||||
|
||||
```python hl_lines="14"
|
||||
--8<-- "docs_src/client_transports/tutorial001.py"
|
||||
```
|
||||
|
||||
Sem subprocesso, sem porta, sem bytes trafegando na rede. O cliente e o servidor são dois objetos no mesmo processo, e a chamada ainda passa pela camada real do protocolo: `search_books` é listada, validada e invocada exatamente como seria sobre HTTP. A página **[Testes](../get-started/testing.md)** constrói o padrão inteiro em torno disso.
|
||||
|
||||
A mesma forma serve também como API de embutimento: uma aplicação que constrói o servidor por conta própria pode chamar as ferramentas dele sem um salto pela rede.
|
||||
|
||||
## SSE {#sse}
|
||||
|
||||
`sse_client(url)`, de `mcp.client.sse`, é o transporte HTTP que o Streamable HTTP substituiu. Envolva-o da mesma forma, `Client(sse_client("http://localhost:8000/sse"))`, para conversar com um servidor que ainda o fala, e não construa nada novo em cima dele.
|
||||
|
|
@ -113,15 +131,16 @@ O stderr do processo filho vai para o seu. Para mandá-lo para outro lugar, cons
|
|||
|
||||
Para o `Client`, tudo acima é a mesma coisa.
|
||||
|
||||
Um **transporte** é qualquer gerenciador de contexto assíncrono que produz um par `(read, write)` de streams de mensagens: formalmente, o protocolo `Transport` em `mcp.client`. `Client` resolve seu argumento pelo tipo: um objeto de servidor conecta no próprio processo, uma `str` vira `streamable_http_client(url)`, um `StdioServerParameters` vira `stdio_client(params)` e qualquer outra coisa é aberta diretamente como transporte. É por causa dessa última regra que `stdio_client(...)`, `streamable_http_client(...)` e `sse_client(...)` se encaixam todos no mesmo lugar, e que você pode escrever o seu próprio.
|
||||
Um **transporte** é qualquer gerenciador de contexto assíncrono que produz um par `(read, write)` de streams de mensagens: formalmente, o protocolo `Transport` em `mcp.client`. `Client` resolve seu argumento pelo tipo: uma `str` vira `streamable_http_client(url)`, um `StdioServerParameters` vira `stdio_client(params)`, um objeto de servidor conecta no próprio processo e qualquer outra coisa é aberta diretamente como transporte. É por causa dessa última regra que `stdio_client(...)`, `streamable_http_client(...)` e `sse_client(...)` se encaixam todos no mesmo lugar, e que você pode escrever o seu próprio.
|
||||
|
||||
## Recapitulando {#recap}
|
||||
|
||||
* `Client(mcp)` (o objeto do servidor) conecta em memória. Use para testes e para embutir.
|
||||
* `Client("http://.../mcp")` (uma URL) conecta por Streamable HTTP, o transporte de produção.
|
||||
* Headers, auth, proxies e timeouts pertencem a um `httpx2.AsyncClient` que você passa a `streamable_http_client(url, http_client=...)`. Não existe o argumento `headers=`.
|
||||
* Redirecionamentos só são seguidos dentro da própria origem da URL (um `307`/`308` de barra final), mais `http`→`https` no mesmo host. Qualquer outra coisa falha com `Redirect to … not followed`; configure a URL final.
|
||||
* stdio é `Client(StdioServerParameters(...))`. Envolva-o em `stdio_client(...)` você mesmo apenas para redirecionar o stderr do processo filho.
|
||||
* O subprocesso recebe um ambiente em allow-list, não o seu; `env=` acrescenta a ele.
|
||||
* `Client(mcp)` (o objeto do servidor) conecta em memória. Use em testes, ou para embutir um servidor na aplicação que o construiu.
|
||||
* Um transporte é qualquer coisa com que você possa fazer `async with x as (read, write)`. `Client` entrega direto a esse protocolo tudo que não for um objeto de servidor, uma URL ou um `StdioServerParameters`.
|
||||
* Construir um `Client` escolhe o transporte. `async with` o abre.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
---
|
||||
translation:
|
||||
sections: [490237e61c3a7a44, 01262a123ad9501d, 429db5b574a2ac08, e2d0d273fbd2d74b, 64ab0331e868f3d4, 6c8878ce2d1f6d56, 4068f23e371bf0b3, eaef75b8725bc931]
|
||||
sections: [4c1dea378b2b1bf7, 01262a123ad9501d, 429db5b574a2ac08, e2d0d273fbd2d74b, 64ab0331e868f3d4, 6c8878ce2d1f6d56, c3d1099701156881, e185a1b8e53669f6]
|
||||
tool: 1
|
||||
---
|
||||
# Funcionalidades descontinuadas {#deprecated-features}
|
||||
|
||||
A especificação 2026-07-28 aposenta cinco coisas. O SDK ainda implementa cada uma delas, e cada uma agora carrega um **aviso de descontinuação**. Um helper do SDK está descontinuado por conta própria e aparece listado [no final](#deprecated-sdk-helpers).
|
||||
A especificação 2026-07-28 aposenta cinco coisas. O SDK ainda implementa cada uma delas, e cada uma agora carrega um **aviso de descontinuação**. Algumas descontinuações no nível do SDK valem por conta própria e aparecem listadas [no final](#deprecated-sdk-helpers).
|
||||
|
||||
A tabela abaixo nomeia cada funcionalidade descontinuada, o motivo de ela estar saindo e o substituto sobre o qual construir.
|
||||
|
||||
|
|
@ -137,11 +137,13 @@ A API inteira é essa. Não há uma chave por método, e você não quer uma: o
|
|||
|
||||
## Helpers descontinuados do SDK {#deprecated-sdk-helpers}
|
||||
|
||||
Estas não são mudanças de especificação, apenas detalhes internos do SDK com um substituto melhor. Avisam com o mesmo `MCPDeprecationWarning` e serão removidos na 3.0.
|
||||
Estas não são mudanças de especificação, apenas usos do SDK com um substituto melhor. Avisam com o mesmo `MCPDeprecationWarning`, e a 3.0 remove a forma antiga.
|
||||
|
||||
| Descontinuado | O que fazer no lugar |
|
||||
|---|---|
|
||||
| `FuncMetadata.call_fn_with_arg_validation()` | `FuncMetadata.validate_arguments()` e depois `FuncMetadata.call_fn()`. Só código que conduz `FuncMetadata` diretamente (uma subclasse personalizada de `Tool`, digamos) chegou a chamá-lo. |
|
||||
| `AuthSettings(resource_server_url=...)` sem `validate_token_resource=` | Defina-o: `True` faz o servidor recusar bearer tokens que o seu verificador não informa como emitidos para `resource_server_url`; `False` diz que o seu verificador confere a audiência do token por conta própria (veja **[Autorização](run/authorization.md#a-token-verifier)**). Sem definir, ele se comporta como `False`; a 3.0 torna `True` o padrão sempre que `resource_server_url` estiver definido. |
|
||||
| `ClientCredentialsOAuthProvider(...)` ou `PrivateKeyJWTOAuthProvider(...)` sem `issuer=` | Passe `issuer=` nomeando o servidor de autorização que emitiu as credenciais (veja **[Escrevendo clientes OAuth](client/oauth-clients.md#machine-to-machine)**). Sem ele, é o servidor MCP que decide qual servidor de autorização as recebe; a 3.0 torna o parâmetro obrigatório. |
|
||||
|
||||
## Recapitulando {#recap}
|
||||
|
||||
|
|
@ -150,7 +152,7 @@ Estas não são mudanças de especificação, apenas detalhes internos do SDK co
|
|||
* Descontinuado é consultivo: sem mudanças no protocolo de transmissão, tudo continua funcionando em sessões pré-2026, e você recebe um `MCPDeprecationWarning` visível (um `UserWarning`, então está ligado por padrão).
|
||||
* Amostragem e roots precisam, além disso, de um canal de retorno que uma sessão 2026-07-28 não tem. Em uma conexão moderna elas avisam e depois lançam uma exceção.
|
||||
* `warnings.filterwarnings("ignore", category=MCPDeprecationWarning)` silencia a categoria inteira; `"error::mcp.MCPDeprecationWarning"` no pytest a transforma em falha de teste.
|
||||
* Um helper do SDK, `FuncMetadata.call_fn_with_arg_validation()`, está descontinuado separadamente para remoção na 3.0.
|
||||
* As [descontinuações no nível do SDK](#deprecated-sdk-helpers) seguem a mesma regra: avisam agora, e a 3.0 remove a forma antiga.
|
||||
* Código novo não deve ser construído sobre nenhuma delas.
|
||||
|
||||
Todas as outras páginas desta documentação ensinam a API atual.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [0d6c05bcbf836bf3, 59a7b14eeefc68c1, 7114d8d6daba203f, e8bbb56a98ba7bc9, 5138010f6159901c, f78da7c7c363d4c6, 220a939cab348686]
|
||||
sections: [0d6c05bcbf836bf3, 9a78b5f6b44b18ab, 7114d8d6daba203f, e8bbb56a98ba7bc9, bfd2fd1153e71dac, 1615a994ef071fdd, 65c599fae991f245]
|
||||
tool: 1
|
||||
---
|
||||
# Primeiros passos {#first-steps}
|
||||
|
|
@ -17,7 +17,7 @@ Três palavras que você vai ver em todas as páginas daqui em diante:
|
|||
* Um **cliente** vive dentro do host e fala MCP. O host executa um cliente para cada servidor ao qual está conectado.
|
||||
* Um **servidor** é o que você constrói com este SDK. Ele expõe coisas aos clientes. Nunca fala diretamente com o modelo.
|
||||
|
||||
Você escreve o servidor. Os hosts são produto de terceiros. O SDK também traz um `Client`. Você vai usá-lo para testar seus servidores, e ele aparece mais adiante nesta página.
|
||||
Você escreve o servidor. Os hosts são produto de terceiros. O SDK também traz um `Client`, a mesma classe que um host usaria para alcançar um servidor por URL ou iniciá-lo como subprocesso. Ele aparece mais adiante nesta página, e é também com ele que você vai testar seus servidores.
|
||||
|
||||
## As três primitivas {#the-three-primitives}
|
||||
|
||||
|
|
@ -83,22 +83,20 @@ Você viu três abas no Inspector. Como ele sabia que eram três?
|
|||
|
||||
Quando um cliente se conecta, o servidor declara suas **capacidades**: quais famílias de requisições ele vai responder. O cliente usa essa declaração para decidir o que faz sentido pedir. Você nunca escreveu isso; o `MCPServer` declara por você.
|
||||
|
||||
Veja você mesmo. O `Client` do SDK aceita o objeto do servidor diretamente e se conecta a ele **em memória** (sem subprocesso, sem porta):
|
||||
Veja você mesmo. Deixe o `server.py` rodando via HTTP em um terminal:
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
```console
|
||||
uv run mcp run server.py --transport streamable-http
|
||||
```
|
||||
|
||||
from mcp import Client
|
||||
e aponte um cliente para ele a partir de outro:
|
||||
|
||||
from server import mcp
|
||||
```python title="client.py" hl_lines="7-8"
|
||||
--8<-- "docs_src/first_steps/tutorial001_client.py"
|
||||
```
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
async with Client(mcp) as client:
|
||||
print(client.server_capabilities.model_dump(exclude_none=True))
|
||||
|
||||
|
||||
asyncio.run(main())
|
||||
```console
|
||||
python client.py
|
||||
```
|
||||
|
||||
```text
|
||||
|
|
@ -118,8 +116,9 @@ O `MCPServer` serve as três primitivas, então as três são sempre declaradas.
|
|||
Repare no que não aparece ali. `completions` (autocompletar de argumentos para templates de recurso e prompts) precisa de um handler escrito por você; este servidor não tem nenhum, então a capacidade fica de fora e um cliente bem-comportado nem pede. Essa é a regra para tudo que é opcional: registre a coisa e a capacidade aparece; **[Completions](../servers/completions.md)** comprova isso.
|
||||
|
||||
!!! info
|
||||
`Client(mcp)` é o mesmo cliente em memória com que todos os exemplos desta documentação são
|
||||
testados, e é assim que você vai testar os seus. Ele ganha uma página inteira: **[Testes](testing.md)**.
|
||||
Esse `client.py` é um cliente MCP completo, e **[O cliente](../client/index.md)** é a página dele.
|
||||
Em um teste, você pula o terminal e a porta e entrega ao `Client` o próprio objeto do servidor,
|
||||
`Client(mcp)`. Isso também ganha uma página inteira: **[Testes](testing.md)**.
|
||||
|
||||
## O que você não escreveu {#what-you-did-not-write}
|
||||
|
||||
|
|
@ -128,7 +127,7 @@ Olhe de novo esta página. Você escreveu três funções Python pequenas. Você
|
|||
* Um JSON Schema. `a: int, b: int` *é* o schema de `add`.
|
||||
* Um handler de requisição. `tools/list`, `resources/read`, `prompts/get`: o SDK atende todos por você.
|
||||
* Uma declaração de capacidades. O `MCPServer` fez isso por você.
|
||||
* Uma linha de protocolo. A negociação de versão, o enquadramento JSON-RPC, a troca de capacidades: tudo isso aconteceu dentro de `mcp dev` e `Client(mcp)`, e você nunca viu.
|
||||
* Uma linha de protocolo. A negociação de versão, o enquadramento JSON-RPC, a troca de capacidades: tudo isso aconteceu dentro de `mcp dev` e `client.py`, e você nunca viu.
|
||||
|
||||
Essa proporção é a razão de ser do SDK.
|
||||
|
||||
|
|
@ -139,6 +138,6 @@ Essa proporção é a razão de ser do SDK.
|
|||
* Um decorador por primitiva: `@mcp.tool()`, `@mcp.resource(uri)`, `@mcp.prompt()`. Nome, descrição e schema vêm da função.
|
||||
* Uma URI com um `{param}` cria um **template** de recurso, listado separadamente dos recursos concretos.
|
||||
* As **capacidades** do servidor já vêm declaradas para você, e um cliente só pede o que o servidor declara.
|
||||
* `Client(mcp)` se conecta ao objeto do servidor em memória: seu ambiente de testes desde o primeiro dia.
|
||||
* `Client("http://localhost:8000/mcp")` conversa com seu servidor em execução. Entregue a ele o objeto do servidor no lugar, `Client(mcp)`, e ele vira seu ambiente de testes desde o primeiro dia.
|
||||
|
||||
A seguir vem **[Conecte a um host real](real-host.md)**: este servidor dentro do Claude Desktop ou de uma IDE, de verdade. Depois, **[Testes](testing.md)**: uma página, um cliente em memória, e você nunca mais fica adivinhando se funciona. Depois disso, cada primitiva ganha sua própria página, começando pela que o modelo comanda: **[Ferramentas](../servers/tools.md)**.
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
---
|
||||
translation:
|
||||
sections: ['4926721070127497', c52a1de2b6b32f40, 8e792bf8c7489ec6, 627195f7159e24ef]
|
||||
sections: [5d13c2f0ba42c0d2, c52a1de2b6b32f40, 8e792bf8c7489ec6, 38552ea228b0a04f]
|
||||
tool: 1
|
||||
---
|
||||
# Testes {#testing}
|
||||
|
||||
O SDK Python traz uma classe `Client` com um **transporte em memória**: passe a ela o objeto do seu servidor e ela se conecta diretamente a ele.
|
||||
A classe `Client` do SDK, a mesma que se conecta a uma URL ou inicia um subprocesso, também se conecta **em memória**: passe a ela o objeto do seu servidor e ela conversa diretamente com ele.
|
||||
|
||||
Sem subprocesso. Sem porta. Sem transporte nenhum. É a mesma ideia do `TestClient` do FastAPI.
|
||||
Sem subprocesso. Sem porta. Nada trafegando na rede. É a mesma ideia do `TestClient` do FastAPI.
|
||||
|
||||
## Uso básico {#basic-usage}
|
||||
|
||||
|
|
@ -97,7 +97,7 @@ em vez da sanitizada.
|
|||
|
||||
Deixe-a ligada nos testes. Em código de produção, ela não significa nada.
|
||||
|
||||
## No mesmo processo por padrão {#in-process-by-default}
|
||||
## Neutro quanto à era por padrão {#era-neutral-by-default}
|
||||
|
||||
!!! note
|
||||
`Client(mcp)` se conecta no mesmo processo e é **neutro quanto à era** por padrão: ele sonda o servidor e
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [74011e683045eea9, 9b64cc175c18b6a9, 4b41be4824030397, e3b1502da786ec33, 71e41161f143c6a9, 9ec2c1eeb8c36378, 8dd027377d46448b, f81491125dcbfe8b]
|
||||
sections: [74011e683045eea9, 9b64cc175c18b6a9, 4b41be4824030397, e3b1502da786ec33, 71e41161f143c6a9, 9ec2c1eeb8c36378, b47667184ca5b516, f81491125dcbfe8b]
|
||||
tool: 1
|
||||
---
|
||||
# Requisições com múltiplas idas e voltas {#multi-round-trip-requests}
|
||||
|
|
@ -164,7 +164,7 @@ O `Server` de baixo nível é o nível sem pilhas inclusas: diferente do `MCPSer
|
|||
|
||||
## Um resultado de 2026-07-28 {#a-2026-07-28-result}
|
||||
|
||||
`InputRequiredResult` só existe na versão de protocolo **2026-07-28**. O `Client(server)` em memória a negocia por você; pela rede, `mode="auto"` a descobre. Depois de conectar, `client.protocol_version` diz o que você obteve.
|
||||
`InputRequiredResult` só existe na versão de protocolo **2026-07-28**. O `mode="auto"` padrão do `Client` a descobre em qualquer conexão. Depois de conectar, `client.protocol_version` diz o que você obteve.
|
||||
|
||||
!!! warning
|
||||
Uma sessão pré-2026 não tem onde colocar um `InputRequiredResult`. Retorne um do seu handler em uma
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [5315262fe26b33e1, 9d8e98840f1b78f0, 0284b215e85366c4, 8534d8dbb4053a70, 2966fac6fe697007]
|
||||
sections: [5315262fe26b33e1, 9d8e98840f1b78f0, 52d6009a07e770ea, 8534d8dbb4053a70, 2966fac6fe697007]
|
||||
tool: 1
|
||||
---
|
||||
# Progresso {#progress}
|
||||
|
|
@ -29,19 +29,17 @@ Três argumentos, e você decide o que eles significam:
|
|||
|
||||
O cliente opta por receber **por chamada**, passando `progress_callback=` para `call_tool`:
|
||||
|
||||
```python title="client.py" hl_lines="7 16"
|
||||
```python title="client.py" hl_lines="5 14"
|
||||
import anyio
|
||||
from mcp import Client
|
||||
|
||||
from server import mcp
|
||||
|
||||
|
||||
async def show(progress: float, total: float | None, message: str | None) -> None:
|
||||
print(f"{message} ({progress}/{total})")
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
async with Client(mcp) as client:
|
||||
async with Client("http://localhost:8000/mcp") as client:
|
||||
result = await client.call_tool(
|
||||
"import_catalog",
|
||||
{"urls": ["https://example.com/a.json", "https://example.com/b.json"]},
|
||||
|
|
@ -56,28 +54,31 @@ anyio.run(main)
|
|||
O callback é uma função `async` que recebe exatamente o que o servidor informou: `progress`, `total`, `message`.
|
||||
|
||||
!!! info
|
||||
`Client(mcp)` conecta direto ao objeto do servidor, em memória, o mesmo cliente sobre o qual a página **[Testes](../get-started/testing.md)**
|
||||
é construída. `progress_callback` é o mesmo parâmetro seja qual for o transporte que o `Client`
|
||||
usa; o *timing* que você está prestes a ver é o da conexão em memória. Ela executa seu callback
|
||||
inline, então todo informe chega antes de `call_tool` retornar. Em um transporte real, as
|
||||
notificações disputam corrida com o resultado, e um callback lento ainda pode estar executando depois que `call_tool`
|
||||
retornou.
|
||||
`progress_callback` é o mesmo parâmetro seja lá o que você tenha passado ao `Client`: uma URL como aqui, um
|
||||
`StdioServerParameters`, ou o objeto do servidor em um teste. Mas fique atento ao timing em um transporte
|
||||
real. Cada notificação é entregue por conta própria, ao lado da resposta, então um callback lento
|
||||
ainda pode estar executando depois que `call_tool` retornou. Só a conexão de teste em processo
|
||||
executa o callback inline e garante que todo informe chegue antes.
|
||||
|
||||
### Experimente {#try-it}
|
||||
|
||||
Coloque `client.py` ao lado de `server.py` e execute:
|
||||
Sirva `server.py` por HTTP e depois execute o cliente em um segundo terminal:
|
||||
|
||||
```console
|
||||
uv run mcp run server.py --transport streamable-http
|
||||
```
|
||||
|
||||
```console
|
||||
python client.py
|
||||
```
|
||||
|
||||
```text
|
||||
Imported https://example.com/a.json (1/2)
|
||||
Imported https://example.com/b.json (2/2)
|
||||
Imported https://example.com/a.json (1.0/2.0)
|
||||
Imported https://example.com/b.json (2.0/2.0)
|
||||
{'result': 'Imported 2 records.'}
|
||||
```
|
||||
|
||||
Cada `await ctx.report_progress(...)` no servidor virou uma chamada a `show` no cliente, em ordem, e as duas linhas foram impressas **antes** de `call_tool` retornar. O progresso não vem embutido no resultado; ele é transmitido enquanto a ferramenta ainda está trabalhando.
|
||||
Cada `await ctx.report_progress(...)` no servidor virou uma chamada a `show` no cliente, em ordem. O progresso não vem embutido no resultado. Ele é transmitido enquanto a ferramenta ainda está trabalhando.
|
||||
|
||||
!!! warning
|
||||
`progress_callback` pertence à **chamada**, não ao `Client`. Não há argumento de construtor
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [478fd619e5f90ef8, aef094a00e44e248, bab8cbf3449fa7e9, df1809b15a58335b, 5f9d8c2336ed0239, f54974398e43ddef, b24443dd78584870]
|
||||
sections: [d98e337bf28845e0, dd642acbba36f615, f547b3e76da19c94, 149513378588da5c, 4e149ed992046709, f54974398e43ddef, b24443dd78584870]
|
||||
tool: 1
|
||||
---
|
||||
# Versões do protocolo {#protocol-versions}
|
||||
|
|
@ -11,9 +11,17 @@ Os servidores lançados antes de 2026-07-28 abrem toda conexão com o **handshak
|
|||
|
||||
Você quase nunca precisa se preocupar com isso, porque o `Client` negocia por você. Esta página trata do único argumento do construtor que controla isso, `mode=`, e das três situações em que você o altera.
|
||||
|
||||
Cada trecho de código desta página é um `client.py` que conversa com o `server.py` da Bookshop de **[O cliente](client/index.md)**. Inicie esse servidor em um terminal:
|
||||
|
||||
```console
|
||||
uv run mcp run server.py --transport streamable-http
|
||||
```
|
||||
|
||||
Depois execute cada trecho em um segundo terminal com `python client.py`.
|
||||
|
||||
## `mode="auto"` {#modeauto}
|
||||
|
||||
```python title="client.py" hl_lines="14-15"
|
||||
```python title="client.py" hl_lines="7-8"
|
||||
--8<-- "docs_src/protocol_versions/tutorial001.py"
|
||||
```
|
||||
|
||||
|
|
@ -31,13 +39,14 @@ De um jeito ou de outro você sai conectado, e `client.protocol_version` diz qua
|
|||
A funcionalidade inteira é essa. Um `Client`, qualquer era de servidor, sem ramificações no seu código.
|
||||
|
||||
!!! info
|
||||
O `MCPServer` responde a `server/discover` em todos os transportes — em memória, stdio, streamable
|
||||
HTTP — então, contra o seu próprio servidor, `auto` sempre chega em `2026-07-28`. O fallback só
|
||||
dispara contra um servidor real anterior a 2026, que é exatamente quando você quer que ele dispare.
|
||||
O `MCPServer` responde a `server/discover` em todos os transportes — Streamable HTTP, stdio e a
|
||||
conexão no mesmo processo que os seus testes usam —, então, contra o seu próprio servidor, `auto`
|
||||
sempre chega em `2026-07-28`. O fallback só dispara contra um servidor real anterior a 2026, que é
|
||||
exatamente quando você quer que ele dispare.
|
||||
|
||||
## `mode="legacy"` {#modelegacy}
|
||||
|
||||
```python title="client.py" hl_lines="14"
|
||||
```python title="client.py" hl_lines="7"
|
||||
--8<-- "docs_src/protocol_versions/tutorial002.py"
|
||||
```
|
||||
|
||||
|
|
@ -61,7 +70,7 @@ Em 2026-07-28 ele não existe mais. O servidor *retorna* suas perguntas e você
|
|||
|
||||
`mode` também aceita uma string de versão moderna do protocolo. Hoje esse conjunto é exatamente `["2026-07-28"]`.
|
||||
|
||||
```python title="client.py" hl_lines="14"
|
||||
```python title="client.py" hl_lines="7"
|
||||
--8<-- "docs_src/protocol_versions/tutorial003.py"
|
||||
```
|
||||
|
||||
|
|
@ -94,7 +103,7 @@ A sondagem é barata, mas ainda é uma ida e volta que você paga a cada reconex
|
|||
|
||||
Então guarde-a. Depois de uma conexão `auto`, `client.session.discover_result` contém o `DiscoverResult` exato que o servidor enviou: seu `supported_versions`, seu `capabilities`, seu `instructions` e a identidade que o servidor carimbou no `_meta` do resultado. Passe-o de volta como `prior_discover=` na próxima vez:
|
||||
|
||||
```python title="client.py" hl_lines="15 17"
|
||||
```python title="client.py" hl_lines="8 10"
|
||||
--8<-- "docs_src/protocol_versions/tutorial004.py"
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [1062ef792791488a, 4be2b831547184a9, 374b049e770385f2, b72f6947089e6de0, b172c9db7831bb31, 70b9ece244ca1b0c, cba78e052898c3f6, f06bdb541cb0b469, fb82d526320b7cc3]
|
||||
sections: [1062ef792791488a, 4be2b831547184a9, 374b049e770385f2, b72f6947089e6de0, b172c9db7831bb31, 10394c6f16601638, cba78e052898c3f6, f06bdb541cb0b469, 6dc898ccc5a903f9]
|
||||
tool: 1
|
||||
---
|
||||
# Adicione a um app existente {#add-to-an-existing-app}
|
||||
|
|
@ -99,7 +99,7 @@ Aquele `/mcp` no final é o `streamable_http_path`. Defina-o como `"/"` e o pref
|
|||
--8<-- "docs_src/asgi/tutorial004.py"
|
||||
```
|
||||
|
||||
Agora os clientes se conectam a `/notes`, não a `/notes/mcp`.
|
||||
Agora os clientes se conectam a `/notes/`, não a `/notes/mcp`.
|
||||
|
||||
## CORS para clientes no navegador {#cors-for-browser-clients}
|
||||
|
||||
|
|
@ -142,4 +142,4 @@ Um cliente que roda no navegador precisa de duas permissões suas: para **enviar
|
|||
* Clientes no navegador precisam de CORS: `allow_headers` para os headers de requisição `Mcp-*`, `expose_headers=["Mcp-Session-Id"]` para a resposta.
|
||||
* `@mcp.custom_route()` adiciona endpoints HTTP comuns, sem autenticação, ao lado de `/mcp`.
|
||||
|
||||
Com o servidor acessível em uma URL real, **[O cliente](../client/index.md)** se conecta a ele com essa URL em vez de um objeto servidor.
|
||||
Com o servidor acessível em uma URL real, **[O cliente](../client/index.md)** se conecta a ele com essa URL.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [d62c13457fc4a534, 80e73abaca6e0652, d1dc4c54cd00ec9c, 14ad3bc7904036bb, 5225f127bc1b9c77, fe1626fdd5aad1da, 4556cb7ea1a04a31]
|
||||
sections: [d62c13457fc4a534, 80e73abaca6e0652, 128a492d18295f64, 14ad3bc7904036bb, 54a6697833fcc1d9, fe1626fdd5aad1da, 811d083c1da8bcf6]
|
||||
tool: 1
|
||||
---
|
||||
# Autorização {#authorization}
|
||||
|
|
@ -23,12 +23,12 @@ O triângulo inteiro é esse. Tudo nesta página é o item do meio.
|
|||
|
||||
O SDK não tem opinião sobre como é um token válido. Você diz a ele, implementando **`TokenVerifier`**:
|
||||
|
||||
```python title="server.py" hl_lines="12-14 19-24"
|
||||
```python title="server.py" hl_lines="14-16 21-27"
|
||||
--8<-- "docs_src/authorization/tutorial001.py"
|
||||
```
|
||||
|
||||
* `TokenVerifier` é um protocolo com um único método assíncrono. `verify_token` recebe o token bruto do header `Authorization` e retorna um **`AccessToken`** se ele for válido, `None` se não for. Não há mais nada a implementar.
|
||||
* Este aqui procura o token em uma tabela. Um de verdade verifica a assinatura de um JWT ou chama o endpoint de introspecção de tokens do servidor de autorização. Esse código é seu; o SDK apenas o chama.
|
||||
* Este aqui procura o token em uma tabela; cada entrada registra o recurso para o qual ele foi emitido. Um de verdade verifica a assinatura de um JWT ou chama o endpoint de introspecção de tokens do servidor de autorização, e informa para quem o token foi emitido (seu `aud`) em `AccessToken.resource`. Esse código é seu; o SDK apenas o chama.
|
||||
* `token_verifier=` e `auth=` sempre andam juntos. Passe um sem o outro e `MCPServer(...)` levanta um `ValueError` antes mesmo de atender uma requisição.
|
||||
|
||||
`AuthSettings` é a face pública do seu resource server:
|
||||
|
|
@ -36,6 +36,10 @@ O SDK não tem opinião sobre como é um token válido. Você diz a ele, impleme
|
|||
* `issuer_url`: o servidor de autorização que emite seus tokens.
|
||||
* `resource_server_url`: a URL pública deste endpoint MCP. Ela indica *a qual* recurso um token se destina, e é onde fica o documento de descoberta.
|
||||
* `required_scopes`: todo token deve conter todos eles.
|
||||
* `validate_token_resource`: recusa qualquer token cujo `AccessToken.resource` não seja `resource_server_url`. Deixá-lo sem definir enquanto `resource_server_url` está definido emite um aviso (`MCPDeprecationWarning`) e se comporta como `False`; a versão 3.0 torna `True` o padrão para resource servers.
|
||||
* Ative-o quando seu servidor de autorização vincula os tokens ao `resource` que o cliente pediu, o que clientes MCP sempre enviam. Mantenha `resource_server_url` como a URL exata à qual os clientes se conectam.
|
||||
* Deixe-o desligado quando seu servidor de autorização usa seus próprios identificadores de audiência (um identificador de API do Auth0, um ID de aplicação do Entra) e, em vez disso, verifique `aud` no seu verificador, retornando `None` para um token que não é para este servidor.
|
||||
* Se `aud` for uma lista, coloque em `resource` a entrada que é igual a `resource_server_url`.
|
||||
|
||||
!!! tip
|
||||
`examples/servers/simple-auth/` no repositório do SDK tem um `IntrospectionTokenVerifier` que chama
|
||||
|
|
@ -91,7 +95,7 @@ Esse documento é como um cliente que nunca ouviu falar do seu servidor encontra
|
|||
|
||||
Dentro de qualquer handler, **`get_access_token()`** é o `AccessToken` que seu verificador retornou para a requisição atual:
|
||||
|
||||
```python title="server.py" hl_lines="4 32-35"
|
||||
```python title="server.py" hl_lines="4 35-38"
|
||||
--8<-- "docs_src/authorization/tutorial002.py"
|
||||
```
|
||||
|
||||
|
|
@ -125,6 +129,6 @@ Um servidor de autorização também pode aceitar a asserção assinada de um pr
|
|||
* `token_verifier=` e `auth=AuthSettings(issuer_url=..., resource_server_url=..., required_scopes=[...])` sempre andam juntos.
|
||||
* O SDK publica o Protected Resource Metadata da [RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728) em `/.well-known/oauth-protected-resource/...` e responde a requisições não autenticadas com um 401 cujo header `WWW-Authenticate` aponta para ele. A história da descoberta é toda essa.
|
||||
* `get_access_token()` em qualquer handler diz quem está chamando.
|
||||
* Autorização é assunto do HTTP. O `stdio` e o cliente em memória nunca a veem.
|
||||
* Autorização é assunto do HTTP. O `stdio` e o cliente de teste em memória nunca a veem.
|
||||
|
||||
A metade do cliente (descobrir seu servidor de autorização e buscar o token para você) está em **[Clientes OAuth](../client/oauth-clients.md)**. E um cliente que *afirma* uma identidade em vez de pedir uma ao usuário está em **[Asserção de identidade](../client/identity-assertion.md)**.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [28221886b198784f, f88ea1f1614f3a1d, ce926d686730b6d0, 3be24f8ad8bb5ab9, 3fad24032b2224ff, f25a7f860e579ecb, e758745df6fb7b0a]
|
||||
sections: [28221886b198784f, f88ea1f1614f3a1d, 2e76f5cb9df15042, ce926d686730b6d0, 3be24f8ad8bb5ab9, 3fad24032b2224ff, f25a7f860e579ecb, 697b01d95080880d]
|
||||
tool: 1
|
||||
---
|
||||
# Deploy e escala {#deploy-scale}
|
||||
|
|
@ -47,6 +47,22 @@ Depois do deploy atrás de um hostname de verdade, esse mesmo padrão rejeita **
|
|||
que recusa toda conexão é uma allowlist de Host até que se prove o contrário.
|
||||
**[Solução de problemas](../troubleshooting.md)** também começa por aqui.
|
||||
|
||||
## Atrás de um proxy que termina TLS {#behind-a-tls-terminating-proxy}
|
||||
|
||||
Se o TLS termina em um proxy (um ingress, um balanceador de carga, Caddy, nginx) e o uvicorn serve HTTP puro atrás dele, diga ao uvicorn para confiar nos headers `X-Forwarded-*` do proxy:
|
||||
|
||||
```console
|
||||
uvicorn server:app --proxy-headers --forwarded-allow-ips='<proxy address>'
|
||||
```
|
||||
|
||||
Sem isso, o app acredita que está sendo servido por `http://`, e qualquer redirecionamento que ele emita (o habitual é `/mcp` → `/mcp/`) aponta para `http://…`. O cliente Python se recusa a seguir um endpoint HTTPS para HTTP puro e avisa:
|
||||
|
||||
```text
|
||||
MCPError: Redirect to http://mcp.example.com/mcp/ not followed: it would downgrade this HTTPS endpoint to plain HTTP.
|
||||
```
|
||||
|
||||
O paliativo do lado do cliente é configurar a URL exata que o servidor serve (`https://mcp.example.com/mcp/`, com a barra) para que nenhum redirecionamento aconteça. A correção é a flag acima. `FORWARDED_ALLOW_IPS` é a grafia como variável de ambiente; `*` confia em todo salto, o que só está certo quando nada além do proxy consegue alcançar o uvicorn.
|
||||
|
||||
## Workers, e quem precisa de afinidade {#workers-and-who-has-to-be-sticky}
|
||||
|
||||
Quando o hostname responder, coloque mais de um worker atrás dele. Não há botão no SDK para isso; você escala um app Starlette do jeito que escala qualquer app ASGI, entregando o objeto a algo que saiba fazer fork:
|
||||
|
|
@ -170,6 +186,7 @@ Um `MCPServer` é uma implementação de protocolo, não um servidor de aplicaç
|
|||
## Recapitulando {#recap}
|
||||
|
||||
* Por padrão, o app responde apenas a requisições endereçadas ao localhost. `transport_security=TransportSecuritySettings(allowed_hosts=[...], allowed_origins=[...])` é o portão para ir ao ar: até você passar isso, toda requisição atrás de um hostname de verdade é um `421` e o motivo só está no log do servidor.
|
||||
* Atrás de um proxy que termina TLS, execute o uvicorn com `--proxy-headers --forwarded-allow-ips=...`, senão os redirecionamentos dele apontam para `http://` e o cliente os recusa.
|
||||
* No 2026-07-28 não há sessão e nada em que um balanceador de carga possa ter afinidade. `stateless_http=True` é um botão só para o legado porque uma requisição moderna é roteada e respondida antes de essa flag ser lida.
|
||||
* A chave padrão do `requestState` é `os.urandom(32)`, cunhada por processo. Uma nova tentativa de múltiplas idas e voltas que chega a um worker diferente falha com `-32602` *"Invalid or expired requestState"*.
|
||||
* A correção é `RequestStateSecurity(keys=[...])` **e** o mesmo nome de servidor em toda instância. O nome é a claim de audiência padrão do token. Mesmas chaves, mesmo nome.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [fea8d769ff9edeba, ce8e2ad42f29ef71, 0d705efb19cf99c2, 2fd7cf825e6d2b2c, 9adc400e8c88e854, 318893ad8e2e9924, 6b63ab96b34476c0]
|
||||
sections: [fea8d769ff9edeba, ce8e2ad42f29ef71, 0d705efb19cf99c2, 9fd2357154a5b7e7, 9adc400e8c88e854, 318893ad8e2e9924, 6b63ab96b34476c0]
|
||||
tool: 1
|
||||
---
|
||||
# Executando seu servidor {#running-your-server}
|
||||
|
|
@ -75,6 +75,11 @@ Cada transporte tem seus próprios argumentos nomeados, todos em `run()`:
|
|||
* `max_request_body_size`: maior corpo de requisição aceito, em bytes. O padrão é 4 MiB; requisições maiores
|
||||
recebem HTTP 413 antes do parsing ou da criação da sessão. Aumente apenas quando mensagens MCP legítimas
|
||||
ultrapassarem esse tamanho.
|
||||
* `session_idle_timeout`: segundos que uma sessão legada pode ficar sem nada em andamento antes de o
|
||||
servidor fechá-la. Padrão 1800. `None` desativa. Veja
|
||||
[Tempo de vida e limites de sessão](legacy-clients.md#session-lifetime-and-limits).
|
||||
* `max_sessions`: quantas sessões legadas um processo mantém ao mesmo tempo. Padrão 10 000. `None`
|
||||
remove o limite. Tratado na mesma seção.
|
||||
* `event_store`, `retry_interval`, `transport_security`: retomada e proteção contra DNS rebinding. Podem esperar até você fazer o deploy em algum lugar que não seja o localhost; **[Deploy e escala](deploy.md)** cobre `transport_security`.
|
||||
|
||||
!!! warning
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue