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