docs: refresh translations, and translate pages in parallel (#3458)

This commit is contained in:
Max 2026-09-07 13:35:53 +01:00 • committed by GitHub
commit fd66270926
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 4821 additions and 2277 deletions

View file

@ -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.

View file

@ -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)**.

View file

@ -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.

View file

@ -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

View file

@ -1,6 +1,6 @@
---
translation:
sections: [3d1663c18edc824c, d4fd37009a13f03d, af9f398a5a8b679a, 470c2dd144294d69, 8e45827e6d24e8c8, 91dfd0ce98ebb03c]
sections: [3d1663c18edc824c, 90956965ae6a1ca1, af9f398a5a8b679a, 5ce83b1f9d88da62, 0d9b5d13fffc94e5, 8e45827e6d24e8c8, 91dfd0ce98ebb03c]
tool: 1
---
# Atendendo clientes legados {#serving-legacy-clients}
@ -21,15 +21,25 @@ Então um cliente legado não é algo *para* o qual você constrói. É algo que
## Um handler, as duas eras {#one-handler-both-eras}
Aqui está uma ferramenta (tool) que precisa perguntar algo ao usuário, e clientes das duas eras chamando-a:
Aqui está uma ferramenta (tool) que precisa perguntar algo ao usuário:
```python title="server.py" hl_lines="24 37-38"
```python title="server.py" hl_lines="21"
--8<-- "docs_src/legacy_clients/tutorial001.py"
```
`reserve` precisa de uma coisa que o modelo não forneceu: quantas cópias. `Annotated[..., Resolve(ask_quantity)]` é como uma ferramenta declara isso (**[Dependências](../handlers/dependencies.md)** tem essa história completa). Nada em `reserve` cita uma versão, verifica uma capacidade ou ramifica.
Os dois clientes ficam abertos **ao mesmo tempo**, no mesmo objeto `mcp`. `mode="legacy"` executa o handshake `initialize`: exatamente a conexão que um cliente pré-2026 abre. O outro usa o padrão e cai em `2026-07-28`.
Sirva-a via HTTP, e aqui estão clientes das duas eras chamando-a:
```console
uv run mcp run server.py --transport streamable-http
```
```python title="client.py" hl_lines="14-15"
--8<-- "docs_src/legacy_clients/tutorial001_client.py"
```
Os dois clientes ficam abertos **ao mesmo tempo**, contra o mesmo servidor em execução. `mode="legacy"` executa o handshake `initialize`: exatamente a conexão que um cliente pré-2026 abre. O outro usa o padrão e cai em `2026-07-28`. Execute `python client.py` em um segundo terminal:
```text
2025-11-25 {'result': "Reserved 2 of 'Dune'."}
@ -61,6 +71,40 @@ Com um worker, isso é invisível. Com dois, é o problema inteiro: uma requisi
perdidos para um cliente que se reconecta à *mesma* sessão), não um armazenamento de sessões. Ele nunca torna uma
sessão alcançável a partir de outro processo.
## Tempo de vida e limites de sessão {#session-lifetime-and-limits}
Uma sessão legada não vive para sempre, e um processo não mantém um número ilimitado
delas. Duas configurações controlam isso. Ambas são argumentos nomeados em `run()`, `streamable_http_app()`
e `Server.streamable_http_app()`. Conexões modernas (`2026-07-28`) e `stateless_http=True`
não têm sessões, então nenhuma das duas configurações se aplica a elas.
| Configuração | Padrão | O que faz | O que o cliente vê | Para desligar |
|---|---|---|---|---|
| `session_idle_timeout` | `1800` (30 min) | Fecha uma sessão que não teve nada em andamento por esse tempo. | `404 Session not found`. Ele precisa fazer `initialize` de novo. | `None` |
| `max_sessions` | `10_000` | Recusa abrir uma sessão além dessa quantidade. As sessões existentes ficam intactas e nada é despejado. | `503 Too many open sessions` com o código JSON-RPC `-32603`. | `None` |
O que conta como "em andamento":
* Um stream `GET` aberto. Os clientes do SDK mantêm um aberto, então a sessão de um cliente conectado nunca
expira.
* Uma requisição que ainda está sendo respondida. Uma chamada de ferramenta que roda por mais tempo que o timeout não é
interrompida, e a contagem regressiva só começa quando ela termina.
* Nada mais. Entre requisições, o relógio corre. Qualquer requisição na sessão o reinicia,
`ping` inclusive. Depois que uma sessão expirou, nada a revive.
Um cliente que encerra sua sessão com `DELETE` a libera imediatamente. O mesmo vale para um cliente cuja
requisição de abertura foi recusada.
```python
mcp.run(transport="streamable-http", session_idle_timeout=None, max_sessions=50_000)
```
Os dois eventos aparecem no log do servidor. Uma expiração é `Session <id> idle timeout` em `INFO`. Uma
abertura recusada é `Refusing to open a new session: <n> sessions are already open` em `WARNING`.
Os limites são por processo. Com quatro workers, o teto é quatro vezes `max_sessions`, e cada
worker expira suas próprias sessões.
## A única chave: `stateless_http` {#the-one-knob-stateless_http}
Se stickiness é um custo que você se recusa a pagar, existe exatamente uma coisa que você pode mudar.
@ -86,7 +130,7 @@ Duas coisas sobre ele importam mais do que o que ele faz.
!!! check
Faça a coisa errada. `reserve` é exatamente a ferramenta que acabou de atender os dois clientes. Faça o deploy dela com
`stateless_http=True`, conecte os mesmos dois clientes via HTTP e chame-a de cada um.
`stateless_http=True`, conecte os mesmos dois clientes e chame-a de cada um.
O cliente moderno ainda recebe `Reserved 2 of 'Dune'.` A perna moderna não mudou.