docs: publish translated docs in twelve languages and the tool that maintains them (#3280)

This commit is contained in:
Max 2026-08-14 17:07:34 +01:00 • committed by GitHub
commit 52ad0a8876
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 89737 additions and 54 deletions

View file

@ -0,0 +1,134 @@
---
translation:
sections: [b50152f05c81e786, b302059b22fb7cb4, 85682a1bf561243a, 53fc48838eb6837a, b24190e0842786ec, 85f93e150fc9b240]
tool: 1
---
# O Context {#the-context}
Os argumentos de uma ferramenta (tool) vêm do modelo. Todo o resto (a requisição que você está atendendo, o servidor em que você vive, um jeito de falar de volta com o cliente) vem de um único objeto: o **`Context`**.
Você não o constrói nem o configura. Você pede por ele.
## Peça por ele {#ask-for-it}
Adicione a qualquer ferramenta um parâmetro anotado com `Context`:
```python title="server.py" hl_lines="2 8"
--8<-- "docs_src/context/tutorial001.py"
```
* O SDK constrói um `Context` novo para cada requisição e o passa para a função.
* O **nome do parâmetro não importa**. `ctx`, `context`, `c`: o SDK o encontra pela anotação.
* Recursos e prompts também podem declarar um, do mesmo jeito.
* `ctx.request_id` é o id da requisição que sua função está atendendo neste momento.
!!! info
Se você já usou FastAPI, já conhece essa jogada: declare um parâmetro com o tipo do próprio
framework (`Request` lá, `Context` aqui) e o framework o fornece. Nada para registrar, nada para
configurar: a anotação de tipo é o mecanismo inteiro.
### Invisível para o modelo {#invisible-to-the-model}
Esta é a parte para internalizar. Aqui está o schema de entrada que `tools/list` informa para `search_books`:
```json
{
"type": "object",
"properties": {
"query": {"title": "Query", "type": "string"}
},
"required": ["query"],
"title": "search_booksArguments"
}
```
Uma única propriedade. `ctx` não é um argumento: ele nunca aparece no schema, o modelo nunca fica sabendo dele e nenhum cliente consegue preenchê-lo. É um contrato entre você e o SDK, invisível no protocolo.
### Experimente {#try-it}
Execute o servidor com o MCP Inspector:
```console
uv run mcp dev server.py
```
O formulário de `search_books` tem um único campo, `query`. Chame-a com `dune`:
```text
[request 3] Found 3 books matching 'dune'.
```
O número é o da requisição da vez. Chame a ferramenta de novo e ele muda: cada requisição recebe seu próprio `Context`.
## O que ele oferece {#what-it-gives-you}
O objeto injetado é pequeno. Além de `request_id`:
* `await ctx.read_resource(uri)`: lê um dos recursos do **próprio** servidor, de dentro de uma ferramenta. É a próxima seção.
* `await ctx.report_progress(progress, total, message)`: envia o progresso de volta a quem chamou, durante uma chamada demorada. **[Progresso](progress.md)** tem a história completa.
* `await ctx.elicit(message, schema)` e `await ctx.elicit_url(...)`: pausam a ferramenta e fazem uma pergunta ao usuário. Isso é **[Elicitação](elicitation.md)** (elicitation).
* `ctx.session`: o lado do servidor na conversa com este cliente. As notificações que você envia ao cliente ficam aqui; a última seção usa isso.
* `ctx.headers`: os cabeçalhos da requisição que o transporte carregou, ou `None` no stdio. Leia um cabeçalho customizado com `(ctx.headers or {}).get("x-...")`. Cabeçalhos são entrada fornecida pelo cliente - servem para um locale ou uma feature flag, nunca para uma identidade.
* `ctx.request_context`: o registro bruto de cada requisição. O campo que você vai querer é `lifespan_context`, o objeto que seu código de inicialização entregou no yield (veja **[Lifespan](lifespan.md)**).
Logging está fora dessa lista de propósito. Um servidor registra logs com o módulo `logging` do Python, como qualquer outro programa Python. **[Logging](logging.md)** é a página curta que explica o porquê.
!!! tip
A injeção só acontece na função que você registrou. Uma função auxiliar que sua ferramenta chama
não recebe um `Context` próprio; passe `ctx` adiante como um argumento comum. Não existe um
"contexto atual" implícito para buscar de algum outro lugar.
## Leia seus próprios recursos {#read-your-own-resources}
Os recursos de um servidor não são só para os clientes. Uma ferramenta também pode lê-los:
```python title="server.py" hl_lines="16"
--8<-- "docs_src/context/tutorial002.py"
```
`ctx.read_resource` resolve a URI pelo mesmo registro que atende `resources/read`, então uma ferramenta recebe o que um cliente receberia: um iterável de `ReadResourceContents`, um por bloco de conteúdo. Para esta URI existe um só:
```python
contents.content # 'fiction, non-fiction, poetry'
contents.mime_type # 'text/plain'
```
* `content` é exatamente o que `genres()` retornou. Uma única fonte da verdade: o cliente navega pelo recurso, suas ferramentas o consomem, ninguém copia a string.
* O único parâmetro de `describe_catalog` é o `Context`, então seu schema de entrada **não tem nenhuma propriedade**. O modelo a chama com `{}`.
## Avise o cliente de que a lista mudou {#tell-the-client-the-list-changed}
O que um servidor oferece não é fixo no momento do import. Registre uma ferramenta em tempo de execução e depois avise o cliente:
```python title="server.py" hl_lines="15-16"
--8<-- "docs_src/context/tutorial003.py"
```
* `mcp.add_tool(recommend_book)` registra uma função comum como ferramenta: nome, descrição e schema derivados exatamente como `@mcp.tool()` faria.
* `await ctx.session.send_tool_list_changed()` envia `notifications/tools/list_changed`. Um cliente que a recebe chama `tools/list` de novo e vê `recommend_book`.
Os irmãos são `send_resource_list_changed()`, `send_prompt_list_changed()` e `send_resource_updated(uri)`, este último para uma mudança em um recurso específico.
Em uma conexão 2026-07-28, os clientes só recebem notificações de mudança em um stream `subscriptions/listen` que eles mesmos abriram, então os métodos `send_*` acima não alcançam esses streams. Os métodos de publicação do `Context` entregam a todos os streams assinantes de uma vez só: `await ctx.notify_tools_changed()`, `await ctx.notify_prompts_changed()`, `await ctx.notify_resources_changed()` e `await ctx.notify_resource_updated(uri)`. **[Assinaturas](subscriptions.md)** tem a história completa, incluindo como escalar horizontalmente entre réplicas.
!!! check
Antes de alguém executar `enable_recommendations`, a ferramenta que você está prometendo não
existe. Chame-a mesmo assim e o resultado é um erro que o modelo consegue ler:
```text
Unknown tool: recommend_book
```
Execute `enable_recommendations` e a mesmíssima chamada dá certo. A lista de ferramentas é
dinâmica de verdade: `tools/list` reflete o que quer que esteja registrado *neste exato momento*.
## Recapitulando {#recap}
* Anote um parâmetro com `Context` (em uma ferramenta, um recurso ou um prompt) e o SDK o injeta. O nome fica por sua conta.
* Ele é invisível para o modelo: o schema de entrada sempre contém apenas seus argumentos de verdade.
* `ctx.request_id` identifica a requisição; `ctx.request_context.lifespan_context` é o que sua inicialização entregou no yield.
* `await ctx.read_resource(uri)` permite que uma ferramenta leia os recursos do próprio servidor.
* `ctx.session` é o canal de volta para o cliente: `send_tool_list_changed()` e seus irmãos dizem a ele para buscar de novo uma lista que você mudou.
* Relatar progresso e a elicitação também começam no `Context`; cada um tem sua própria página.
Parâmetros que o modelo nunca vê, preenchidos pelas suas próprias funções, são as **[Dependências](dependencies.md)**.

View file

@ -0,0 +1,163 @@
---
translation:
sections: [b0389403e98d25ad, e2cf58b43b285e86, a363e1a38e1a5971, 6cfac078feb18013, b4535bd61df337e6, e97ed44207f929fd]
tool: 1
---
# Dependências {#dependencies}
Os argumentos de uma ferramenta (tool) vêm do modelo. Alguns valores nunca deveriam vir dele: um preço consultado nos seus registros, uma confirmação que só uma pessoa pode dar, qualquer coisa que o modelo poderia errar se inventasse.
**Dependências** são parâmetros preenchidos por funções suas. Você anota o parâmetro, indica a função, e o SDK a chama antes de a ferramenta rodar.
## Declare uma {#declare-one}
Envolva o tipo do parâmetro em `Annotated[...]` e adicione `Resolve(fn)`:
```python title="server.py" hl_lines="18-19 23"
--8<-- "docs_src/dependencies/tutorial001.py"
```
* `check_stock` é um **resolvedor**: uma função comum que o SDK executa antes de `reserve_book` e cujo valor de retorno vira o argumento `stock`.
* O parâmetro `title` dele é o próprio argumento `title` da ferramenta, associado **pelo nome**. O resolvedor vê exatamente o valor validado que o corpo da ferramenta vai ver.
* O corpo da ferramenta já parte de um `Stock` que existe. Nada de código de consulta na ferramenta, nada de preâmbulo do tipo "e se estiver faltando".
!!! info
Se você já usou FastAPI, isto é o `Depends`. Mesma ideia, mesmo motivo: a função declara o que
precisa, o framework fornece, e a ligação toda fica na anotação de tipo.
### Invisível para o modelo {#invisible-to-the-model}
Este é o schema de entrada que `tools/list` informa para `reserve_book`:
```json
{
"type": "object",
"properties": {
"title": {"title": "Title", "type": "string"}
},
"required": ["title"],
"title": "reserve_bookArguments"
}
```
Uma única propriedade. Assim como o `Context` em **[O Context](context.md)**, um parâmetro resolvido é um contrato entre você e o SDK: `stock` não está no schema, o modelo nunca fica sabendo dele, e um cliente que mande um valor de `stock` mesmo assim é ignorado. O valor do resolvedor é o único que a sua ferramenta pode receber.
É essa última parte que importa. Um parâmetro que o modelo não pode fornecer é um parâmetro que o modelo não pode errar.
### Experimente {#try-it}
Execute o servidor com o MCP Inspector:
```console
uv run mcp dev server.py
```
O formulário de `reserve_book` tem um único campo `title`. Nem sinal de `stock` nele. Chame a ferramenta com `Dune`:
```text
Reserved 'Dune' (6 copies left).
```
O corpo da ferramenta não consultou nada: `check_stock` rodou primeiro, e o `Stock` que ele retornou chegou como argumento. Experimente `Neuromancer` e o mesmo resolvedor entrega um zero à ferramenta.
!!! tip
Você poderia simplesmente chamar `check_stock(title)` no corpo da ferramenta. Declare como dependência quando o
valor merecer mais que uma chamada a uma função auxiliar: toda ferramenta que precisa do estoque declara o mesmo parâmetro,
e o SDK executa o resolvedor no máximo uma vez por chamada, não importa quantas o declarem. As próximas
seções acrescentam o resto: resolvedores que dependem uns dos outros e resolvedores que perguntam ao usuário.
## Dependências de dependências {#dependencies-of-dependencies}
Um resolvedor pode declarar as próprias dependências, com a mesma anotação:
```python title="server.py" hl_lines="22 29-30"
--8<-- "docs_src/dependencies/tutorial002.py"
```
* `estimate_delivery` depende de `check_stock`. O SDK executa o grafo em ordem: primeiro o estoque, depois a estimativa, depois a ferramenta.
* Tanto `stock` quanto `delivery` precisam, no fim das contas, de `check_stock`, mas ele roda **uma vez por chamada**. Uma consulta ao estoque, dois consumidores.
* Não há nada para registrar. O grafo *são* as anotações.
!!! check
Não aceite o "uma vez por chamada" de olhos fechados. Coloque um `print` em `check_stock` e chame `order_book` pelo
Inspector: uma linha por chamada. Dois consumidores, uma consulta.
O SDK analisa o grafo quando a ferramenta é registrada, não quando é chamada. Um parâmetro que ele não consegue classificar - que não é um `Context`, nem um `Resolve(...)`, nem o nome de um argumento da ferramenta - e um ciclo de resolvedores levantam, os dois, `InvalidSignature` na inicialização. O servidor falha antes mesmo de qualquer cliente se conectar, com o parâmetro ou resolvedor culpado nomeado no erro.
Os parâmetros de um resolvedor se resolvem exatamente como os de uma ferramenta: outro `Resolve(...)`, os próprios argumentos da ferramenta pelo nome, ou o `Context` - `ctx.headers`, o objeto do lifespan, tudo isso.
!!! warning
Nos transportes HTTP o `Context` inclui `ctx.headers`. Cabeçalhos são **entrada fornecida pelo cliente**,
como qualquer argumento de ferramenta: servem para um locale ou uma feature flag, nunca para uma identidade. A identidade
de quem chama vem da sua camada de autorização (**[Autorização](../run/authorization.md)**), não de um cabeçalho que qualquer um pode definir.
!!! tip
*Uma vez por chamada* significa exatamente isso: o próximo `tools/call` executa `check_stock` de novo. Um recurso
que deve viver mais que uma requisição - um pool de banco de dados, um cliente HTTP - tem seu lugar no **[Lifespan](lifespan.md)**, e
um resolvedor chega até ele por `ctx.request_context.lifespan_context`.
## Pergunte quando for preciso {#ask-when-you-must}
Um resolvedor não precisa saber a resposta. Ele pode retornar `Elicit(message, Model)` e o SDK pergunta ao usuário - o mecanismo de **[Elicitação](elicitation.md)** (elicitation), executado para você:
```python title="server.py" hl_lines="26-32 39"
--8<-- "docs_src/dependencies/tutorial003.py"
```
* Em estoque: `confirm_backorder` retorna um `Backorder` diretamente. **Sem pergunta, sem ida e volta.** O usuário só é interrompido quando a resposta dele faz diferença.
* Sem estoque: o SDK envia a elicitação, valida a resposta contra `Backorder` e a injeta. O seu resolvedor nunca encosta no protocolo.
* A ferramenta lê `backorder.confirm` como qualquer outro argumento. Responder **não** ainda é uma resposta: a elicitação é aceita com `confirm=False`, a ferramenta roda, e nenhum pedido é feito. Perguntar virou pré-condição, e não código de infraestrutura no corpo da ferramenta.
E se o usuário simplesmente não responder - recusar a pergunta ou cancelá-la?
!!! check
Execute `order_book` para `Neuromancer` e recuse a pergunta. Com a anotação escrita como
`Annotated[Backorder, Resolve(...)]` o corpo da ferramenta nunca roda; a chamada falha com um resultado
de erro que o modelo consegue ler:
```text
Error executing tool order_book: Resolver for parameter 'backorder' could not resolve: elicitation was decline
```
Esse é o padrão certo para uma pré-condição: sem resposta, sem pedido. Quando a recusa é um desfecho que a sua ferramenta quer tratar - pular a encomenda, mas ainda assim sugerir outro título - anote `ElicitationResult[Backorder]` no lugar, e a ferramenta recebe o desfecho completo de aceitar/recusar/cancelar para decidir o que fazer. **[Elicitação](elicitation.md)** mostra essa forma e todo o resto sobre perguntar: as regras de schema, as três respostas, o lado do cliente na conversa.
!!! info
O framework escolhe o transporte da pergunta a partir da versão de protocolo negociada; o código
acima é idêntico nas duas. Em **2026-07-28** e posteriores a pergunta viaja dentro de um
`tools/call` com múltiplas idas e voltas - o servidor a retorna, o `elicitation_callback` do cliente
a responde, e o `Client` refaz a chamada para você (**[Requisições com múltiplas idas e voltas](multi-round-trip.md)**). Em
**2025-11-25** e anteriores ela é uma requisição síncrona de elicitação no meio da chamada. Cada pergunta é
feita exatamente uma vez por chamada - uma garantia sobre a pergunta, não sobre o resolvedor. Na
forma com múltiplas idas e voltas, qualquer resolvedor pode rodar de novo sempre que a chamada é retomada depois de uma pergunta,
então o código antes de um `return Elicit(...)` roda em cada uma dessas rodadas; a resposta registrada então
satisfaz a pergunta repetida sem consultar o usuário outra vez. Uma resposta registrada só
é consultada quando o resolvedor pergunta; um resolvedor que responde *sem* perguntar, como
`check_stock`, sempre fornece o próprio valor calculado. Como cada resposta é associada de volta à
sua pergunta, um resolvedor que faz elicitação precisa derivar a pergunta de forma determinística a partir dos
argumentos da ferramenta e das respostas anteriores. Um valor gerado por chamada (um id de `default_factory`, um
timestamp) é derivado de novo a cada rodada e não pode aparecer em uma pergunta à qual a resposta deva
ficar vinculada. Uma pergunta montada com dados voláteis assim faz toda resposta registrada parecer obsoleta,
então o servidor a refaz a cada rodada até o limite de rodadas do cliente encerrar a chamada.
## Pergunte ao cliente, não ao usuário {#ask-the-client-not-the-user}
A elicitação é uma das três perguntas que um resolvedor pode fazer, e o fluxo com múltiplas idas e voltas não permite nenhuma outra. As outras duas vão para o **cliente**, e não para o usuário: retorne `Sample(...)` para executar uma chamada de LLM por meio do cliente (uma requisição `sampling/createMessage`), ou `ListRoots()` para buscar os roots (diretórios raiz) atuais do cliente. Nenhuma das duas tem desfecho de aceitar/recusar; o consumidor anota diretamente o tipo do resultado, `CreateMessageResult` (`CreateMessageResultWithTools` quando a requisição carrega `tools` ou `tool_choice`) ou `ListRootsResult`:
```python title="server.py" hl_lines="10-15 21"
--8<-- "docs_src/dependencies/tutorial004.py"
```
* O framework roteia essas perguntas exatamente como `Elicit`: dentro do `tools/call` com múltiplas idas e voltas em **2026-07-28**, pela requisição independente servidor->cliente em **2025-11-25**. Uma capacidade não declarada recusa a chamada com um erro de protocolo `-32021` (`sampling`, `roots`, `elicitation` em modo formulário; `sampling.tools` quando a requisição carrega `tools` ou `tool_choice`).
* Tudo o que a caixa de informação acima diz sobre perguntas vale sem mudanças: uma requisição `Sample` é associada ao seu resultado registrado pela sua representação exata, então monte-a de forma determinística a partir dos argumentos da ferramenta e das respostas anteriores; assim o cliente paga pela chamada de LLM uma vez por chamada de ferramenta, não uma vez por rodada. O resultado registrado viaja no `request_state` pelo resto da chamada, então uma resposta de LLM muito grande deixa cada ida e volta restante mais pesada.
* As *funcionalidades* independentes de amostragem (sampling) e roots são descontinuadas em 2026-07-28 (SEP-2577). Servidores novos que precisam do modelo do cliente perguntam por este canal; servidores que não precisam devem se integrar diretamente a um provedor de LLM. Valores de `include_context` diferentes de `"none"` também estão descontinuados; evite-os.
## Recapitulando {#recap}
* `Annotated[T, Resolve(fn)]` em um parâmetro de ferramenta: o SDK executa `fn` e injeta o valor de retorno.
* Um parâmetro resolvido é invisível para o modelo e não pode ser fornecido por um cliente. Valores que o modelo não pode inventar - preços, identidades, permissões - entram aqui.
* Os parâmetros de um resolvedor são resolvidos do mesmo jeito: o `Context`, outro `Resolve(...)`, ou um argumento da ferramenta pelo nome. O grafo executa cada resolvedor no máximo uma vez por rodada, não importa quantos consumidores ele tenha; cada pergunta é feita exatamente uma vez, e qualquer resolvedor pode rodar de novo quando uma chamada é retomada depois de uma pergunta.
* Grafos ruins falham no registro com `InvalidSignature`, não no meio da chamada.
* Retorne `Elicit(message, Model)` para perguntar ao usuário, só quando for preciso. Anotações com o tipo puro abortam na recusa; `ElicitationResult[T]` deixa a ferramenta tratar cada desfecho.
* Retorne `Sample(...)` ou `ListRoots()` para pedir ao cliente uma resposta de LLM ou a lista de roots; o resultado é injetado diretamente.
O estado que o seu servidor monta uma vez na inicialização, e como um handler chega até ele, é assunto da página **[Lifespan](lifespan.md)**.

View file

@ -0,0 +1,192 @@
---
translation:
sections: [335ca2a0b266f003, d1ad562d3fe87bc0, 0bb1396c86daeba4, d1cb1235bb9ee267, 833179c09d239c83, e5d6dec2d2e655e8]
tool: 1
---
# Elicitação {#elicitation}
Uma ferramenta (tool) no meio do trabalho, à qual falta uma resposta, não precisa falhar.
A **elicitação** (elicitation) permite que ela pergunte. No meio de uma chamada de ferramenta, o usuário recebe uma pergunta, e a resposta dele volta para dentro da mesma chamada de função.
Existem dois modos:
* **Modo formulário**: você precisa de um valor (uma confirmação, uma data, uma quantidade). Você descreve os campos, o cliente renderiza o formulário.
* **Modo URL**: você precisa que o usuário vá a outro lugar (uma tela de consentimento OAuth, uma página de pagamento). Nada do que ele fizer lá passa pelo protocolo.
E existem duas formas de perguntar. A opção a preferir é um **resolvedor**: você pendura a pergunta em um parâmetro e o SDK pergunta - em qualquer conexão, seja qual for a era de protocolo que o cliente fale. A forma direta, `await ctx.elicit(...)`, é uma requisição do *servidor* para o *cliente*, um canal que só existe para um cliente em uma conexão legada (versão da especificação 2025-11-25 ou anterior). As duas estão nesta página; comece pelo resolvedor.
## Pergunte com um resolvedor {#ask-with-a-resolver}
Uma pergunta que condiciona a ferramenta inteira - *tem certeza? qual das três contas encontradas?* - pode ser tirada do corpo da ferramenta e colocada em um **resolvedor**, e o framework faz a pergunta por você.
Um parâmetro anotado com `Annotated[T, Resolve(fn)]` é preenchido executando `fn` antes do corpo da ferramenta. O resolvedor retorna o valor diretamente quando já o conhece, ou retorna `Elicit(...)` para que o framework pergunte:
```python title="server.py" hl_lines="24-30 35-36"
--8<-- "docs_src/elicitation/tutorial004.py"
```
* `confirm_delete` lê pelo nome o argumento `path` da própria ferramenta, lista a pasta e **só faz a elicitação quando precisa** - uma pasta vazia resolve para `Confirm(ok=True)` sem nenhuma ida e volta ao cliente.
* `delete_folder` anota `ElicitationResult[Confirm]`, então o framework injeta o resultado completo e a ferramenta trata cada caso com `match`: aceitou e confirmou, aceitou mas quer manter (`ok=False`), recusou, cancelou.
* O parâmetro `confirm` nunca aparece no schema de entrada da ferramenta - o cliente fornece `path`, o resolvedor fornece `confirm`.
Quando a ferramenta não precisa ramificar, anote o modelo diretamente (`Annotated[Confirm, Resolve(confirm_delete)]`): ela recebe o modelo quando o usuário aceita, e a chamada é abortada com um erro quando ele recusa ou cancela.
Um resolvedor funciona em **toda** conexão. Para um cliente em uma conexão legada, o SDK envia a pergunta diretamente a ele; em uma conexão **2026-07-28**, o SDK *retorna* a pergunta a partir da chamada, e a próxima tentativa do cliente traz a resposta. Seu resolvedor nunca percebe a diferença; o que acontece por baixo dos panos está em **[Requisições com múltiplas idas e voltas](multi-round-trip.md)**.
Perguntar é só uma das coisas que um resolvedor pode fazer. O mecanismo geral - dependências que calculam sem perguntar, dependências de dependências, o que o modelo pode e não pode fornecer - é a página **[Dependências](dependencies.md)**.
## Pergunte de dentro da ferramenta {#ask-from-inside-the-tool}
Uma ferramenta também pode parar no meio do próprio corpo e perguntar.
!!! warning
`ctx.elicit()` e `ctx.elicit_url()` são requisições do *servidor* para o *cliente* - um
canal que só existe para um cliente em uma conexão legada (versão da especificação
**2025-11-25** ou anterior). Em uma conexão **2026-07-28** não existem requisições
iniciadas pelo servidor, então essas chamadas falham. Um resolvedor funciona nas duas.
**[Versões do protocolo](../protocol-versions.md)** tem a história completa.
`await ctx.elicit()` recebe uma mensagem e um modelo Pydantic:
```python title="server.py" hl_lines="9-11 20-23 25"
--8<-- "docs_src/elicitation/tutorial001.py"
```
* É o parâmetro **`Context`** que dá acesso a `ctx.elicit`; qualquer ferramenta pode receber um. Esse objeto tem uma página própria: **[O Context](context.md)**.
* `AlternativeDate` é o **schema** da resposta que você quer.
* A ferramenta é `async def`. Tem que ser: ela para no meio e espera por uma pessoa.
* Em qualquer outra data, a ferramenta retorna na hora. Ela só pergunta quando precisa.
* A data que o usuário aceita passa de novo pela própria `book_table`. Uma resposta é uma entrada como qualquer outra: uma alternativa que também está lotada gera uma nova pergunta, em vez de ser confirmada às cegas.
### O que o cliente recebe {#what-the-client-receives}
O cliente recebe sua mensagem e, junto com ela, um JSON Schema gerado a partir do modelo:
```json
{
"properties": {
"accept_alternative": {
"description": "Try another date?",
"title": "Accept Alternative",
"type": "boolean"
},
"date": {
"default": "2025-12-26",
"description": "Alternative date (YYYY-MM-DD)",
"title": "Date",
"type": "string"
}
},
"required": ["accept_alternative"],
"title": "AlternativeDate",
"type": "object"
}
```
Esse schema é o formulário. `Field(description=...)` é o rótulo; um valor padrão pré-preenche a entrada e torna o campo opcional. É o mesmo mecanismo de Pydantic para JSON Schema que **[Ferramentas](../servers/tools.md)** descreve para os argumentos de uma ferramenta.
!!! warning
Um schema de elicitação não é tão expressivo quanto o schema de entrada de uma ferramenta.
Só campos planos e primitivos: `str`, `int`, `float`, `bool` ou um `Literal` de strings
(que vira um `enum`). Coloque um modelo dentro do modelo e `ctx.elicit` lança uma exceção
antes de qualquer coisa ser enviada ao cliente:
```text
TypeError: Elicitation schema field 'address' rendered as {'$ref': '#/$defs/Address'}, which is not a valid PrimitiveSchemaDefinition
```
Você está interrompendo uma pessoa no meio de uma tarefa. Se a resposta precisa de
aninhamento, ela deveria ter sido um argumento da ferramenta.
### As três respostas {#the-three-answers}
`result.action` diz o que o usuário fez, e existem exatamente três possibilidades:
* `"accept"`: ele enviou o formulário. `result.data` é uma instância de `AlternativeDate`, já validada.
* `"decline"`: ele disse não.
* `"cancel"`: ele dispensou a pergunta sem escolher.
`result.data` só existe em `"accept"`, e é por isso que o exemplo verifica `result.action` primeiro. Seu verificador de tipos garante essa ordem: depois de `result.action == "accept"`, `result.data` é um `AlternativeDate`; antes disso, `.data` simplesmente não existe.
Uma recusa não é um erro. A ferramenta decide o que recusar significa (aqui, nenhuma reserva) e responde ao modelo normalmente.
!!! tip
A resposta é validada contra seu modelo antes que seu código a veja. Um cliente que envia
`"maybe"` para um `bool` não corrompe sua reserva: a chamada falha com um erro de
incompatibilidade de schema, e seu `if` nem chega a executar.
## Envie o usuário para uma URL {#send-the-user-to-a-url}
Algumas coisas não devem passar pelo modelo nem pelo cliente: credenciais, números de cartão, consentimento OAuth. Para essas, você não pede dados; pede ao usuário que vá a algum lugar:
```python title="server.py" hl_lines="10-14 23"
--8<-- "docs_src/elicitation/tutorial002.py"
```
* `ctx.elicit_url()` recebe a mensagem, a **URL** a visitar e um `elicitation_id` que você escolhe: qualquer string que identifique esta elicitação dentro do seu servidor.
* O resultado tem uma ação e mais nada. `"accept"` significa que o usuário concordou em abrir a URL, **não** que ele terminou o que está do outro lado.
* O pagamento acontece fora de banda, entre o navegador do usuário e seu provedor de pagamento. Nenhum conteúdo jamais volta pelo MCP.
Observe a segunda ferramenta. Quando seu servidor fica sabendo que o fluxo fora de banda terminou (um webhook, um polling; aqui está modelado como uma segunda ferramenta), `ctx.session.send_elicit_complete(...)` envia `notifications/elicitation/complete` com o mesmo `elicitation_id`. É assim que o cliente sabe que pode parar de exibir *"aguardando pagamento..."*. Sem isso, o cliente só pode adivinhar.
## O lado do cliente {#the-client-side}
Servidores perguntam. Clientes respondem passando um **`elicitation_callback`** para `Client(...)`:
```python title="client.py" hl_lines="6-7 18"
--8<-- "docs_src/elicitation/tutorial003.py"
```
* Um único callback trata os dois modos. `params` é uma união de `ElicitRequestFormParams` e `ElicitRequestURLParams`; o `isinstance` faz a ramificação.
* Para uma URL, você mostra `params.url` ao usuário e retorna a ação que ele escolheu. Nunca nenhum `content`.
* Para um formulário, uma aplicação real renderiza `params.requested_schema` e retorna a entrada do usuário como `content`. Este aqui sempre diz sim com uma resposta pronta, que é exatamente o callback que você quer em um teste.
* Passar o callback também é a **declaração de capacidade**: é assim que o servidor fica sabendo que pode perguntar a este cliente. As outras coisas que um cliente pode responder para um servidor estão em **[Callbacks do cliente](../client/callbacks.md)**.
!!! info
A elicitação é uma requisição do *servidor* para o *cliente*, e requisições assim só
existem em uma sessão com handshake clássico; por isso este cliente passa `mode="legacy"`.
Em uma conexão **2026-07-28**, uma ferramenta pergunta *retornando* a pergunta a partir
da chamada; esse fluxo está em **[Requisições com múltiplas idas e voltas](multi-round-trip.md)**.
### Experimente {#try-it}
Inicie em Streamable HTTP o `server.py` do modo formulário com `ctx.elicit` (aquele da `book_table`) (**[Executando seu servidor](../run/index.md)** tem o comando de uma linha), depois execute a `main()` do cliente e peça à `book_table` o dia de Natal.
O callback imprime a pergunta que recebeu:
```text
No tables for 2 on 2025-12-25. Would you like to try another date?
```
Ele responde com `{"accept_alternative": True, "date": "2025-12-27"}`, e a ferramenta, que ficou esperando dentro de `await ctx.elicit(...)` esse tempo todo, conclui a reserva:
```text
Booked a table for 2 on 2025-12-27.
```
Agora troque para o `server.py` do modo URL e aponte a mesma `main()` para `pay_deposit`: o mesmo callback segue pelo outro ramo, imprime o link de pagamento, e a ferramenta volta com *"Complete the payment in your browser."* Uma ida e volta, no meio da chamada, nos dois sentidos.
!!! check
Agora remova `elicitation_callback=` do `Client` e chame `book_table` para o dia de Natal
outra vez. A chamada inteira falha com um erro de protocolo:
```text
Elicitation not supported
```
Um cliente que não registrou nenhum callback nunca declarou a capacidade `elicitation`,
então não há a quem perguntar. Sua ferramenta não recebeu um `"decline"`; recebeu uma
exceção. Projete pensando nisso: toda elicitação precisa de uma resposta sensata para
"e se eu não puder perguntar?".
## Recapitulando {#recap}
* Um parâmetro anotado com `Annotated[T, Resolve(fn)]` é preenchido por um resolvedor, que retorna `Elicit(...)` quando precisa perguntar. Funciona em toda conexão.
* O schema é um modelo Pydantic plano: só campos primitivos, validados na volta.
* `result.action` é `"accept"`, `"decline"` ou `"cancel"`; `result.data` só existe quando o usuário aceita.
* `await ctx.elicit(message, schema=Model)` pergunta de dentro do corpo da ferramenta, e `await ctx.elicit_url(message, url, elicitation_id)` serve para tudo o que não deve passar pelo modelo (`ctx.session.send_elicit_complete(elicitation_id)` avisa que a parte fora de banda terminou). As duas são requisições do servidor para o cliente: precisam do cliente em uma conexão legada.
* O cliente responde com um único `elicitation_callback`, ramificando pelo tipo dos params; registrá-lo é o que declara a capacidade.
* Em uma conexão 2026-07-28, o servidor retorna a pergunta em vez de empurrá-la; o mesmo callback é alimentado por **[Requisições com múltiplas idas e voltas](multi-round-trip.md)**.
Tudo o que fica por baixo desse retorno (o loop de novas tentativas, a proteção do `requestState`, conduzir o fluxo por conta própria) está em **[Requisições com múltiplas idas e voltas](multi-round-trip.md)**.

View file

@ -0,0 +1,38 @@
---
translation:
sections: [424930166c4bc6f3]
tool: 1
---
# Dentro do seu handler {#inside-your-handler}
Os argumentos de um handler vêm do cliente. Todo o *resto* que ele pode ler, e
tudo o que ele pode fazer enquanto executa, está aqui.
O que ele pode ler:
* **[O Context](context.md)** é o único parâmetro extra que qualquer handler
pode pedir: a requisição em andamento, seus cabeçalhos, sua sessão e os
verbos de progresso e de notificação de mudanças.
* **[Dependências](dependencies.md)** são parâmetros que o modelo nunca vê,
preenchidos pelas suas próprias funções com `Resolve`.
* **[Lifespan](lifespan.md)** trata do estado que seu servidor monta uma única
vez na inicialização e de como um handler chega até ele por meio do
`Context`.
O que ele pode fazer enquanto executa:
* Pedir mais informações ao usuário com **[Elicitação](elicitation.md)**
(elicitation) e com **[Requisições de múltiplas idas e voltas](multi-round-trip.md)**,
o padrão de 2026-07-28 que a transporta.
* Pedir ao cliente uma completion de LLM ou as pastas do seu workspace com
**[Amostragem (sampling) e roots](sampling-and-roots.md)**, obsoletos, mas
ainda atendidos.
* Informar o **[Progresso](progress.md)** de algo demorado.
* Escrever logs (na saída de erro padrão, para quem opera o servidor) com
**[Logging](logging.md)**.
* Avisar os clientes assinantes de que algo mudou com
**[Assinaturas](subscriptions.md)**.
Se você ainda não registrou um handler, comece por
**[Ferramentas](../servers/tools.md)**. Todas as páginas aqui pressupõem que
você já tem um.

View file

@ -0,0 +1,107 @@
---
translation:
sections: [f3ca8ac5f90f2dfa, 85a1ef3588ba0736, 563346d4d5804933, 9e3528340d0bab53]
tool: 1
---
# Lifespan {#lifespan}
A maioria dos servidores reais mantém alguma coisa durante a vida inteira: um pool de banco de dados, um cliente HTTP, um modelo carregado.
Você não quer construir isso a cada chamada, e quer fechar tudo de forma limpa. É para isso que serve o **lifespan** (ciclo de vida do servidor).
## Um lifespan tipado {#a-typed-lifespan}
Um lifespan é um `@asynccontextmanager` que recebe o servidor e faz `yield` de **um único objeto**. Seja qual for o objeto que você entregar, ele fica disponível para todos os handlers enquanto o servidor estiver rodando.
```python title="server.py" hl_lines="25-31 34 38 40"
--8<-- "docs_src/lifespan/tutorial001.py"
```
Leia de baixo para cima:
* `app_lifespan` conecta o `Database` **antes** do `yield` e o desconecta **depois**, dentro de um `finally`. Isso é a inicialização e o encerramento.
* Ele entrega um `AppContext`, uma dataclass comum que agrupa o que você configurou. Um campo hoje, dez amanhã.
* `MCPServer("Bookshop", lifespan=app_lifespan)` é toda a ligação necessária.
* Dentro da ferramenta (tool), o objeto entregue é `ctx.request_context.lifespan_context`.
O lifespan executa **uma vez**. O servidor entra nele ao iniciar (antes da primeira requisição) e sai dele ao parar. Todas as requisições nesse intervalo compartilham o mesmo `AppContext`.
!!! info
Se você já escreveu um `lifespan` do FastAPI, já conhece isso. Mesmo decorador, mesmo `yield`, mesmo `finally`.
### O que o modelo vê {#what-the-model-sees}
Nada de novo. `ctx` é um parâmetro **Context**, então o SDK o injeta e ele nunca chega ao schema de entrada:
```json
{
"type": "object",
"properties": {
"genre": {"title": "Genre", "type": "string"}
},
"required": ["genre"],
"title": "count_booksArguments"
}
```
`genre` é o único argumento que o modelo pode passar. O lifespan é assunto do seu servidor.
Funções `@mcp.resource()` e `@mcp.prompt()` também podem receber um parâmetro `ctx`, escrito como um `Context` puro por um motivo que a próxima seção explica. Tudo o que `ctx` carrega está em **[O Context](context.md)**.
### É tipado de verdade {#it-really-is-typed}
Olhe de novo a anotação: `ctx: Context[AppContext]`.
Esse único parâmetro de tipo é o motivo pelo qual `ctx.request_context.lifespan_context` **é** um `AppContext` para o seu verificador de tipos. `.db` autocompleta; `.dbb` é um erro antes mesmo de você executar o servidor.
Escreva um `Context` puro no lugar e `lifespan_context` passa a ser tipado como `dict[str, Any]`: o verificador de tipos não tem como saber o que o seu lifespan entregou. O objeto continua lá em tempo de execução; o que você perdeu foi a ajuda.
!!! warning
`Context[AppContext]` é uma grafia **só para ferramentas**. Coloque-a em uma função
`@mcp.resource()` ou `@mcp.prompt()` e toda chamada a esse handler falha. O cliente recebe um
erro de volta, e o log do servidor mostra o porquê:
```text
Context is not available outside of a request
```
Em recursos e prompts, escreva o `ctx: Context` puro. O objeto que o seu lifespan entregou
continua sendo `ctx.request_context.lifespan_context` em tempo de execução; você abre mão do
parâmetro de tipo, não do objeto.
!!! tip
Sempre existe um lifespan. Se você não passar um, o padrão do SDK entrega um `dict` vazio,
então `ctx.request_context.lifespan_context` é `{}`, nunca `None`. Esse padrão também é o
motivo de um `Context` puro tipá-lo como `dict[str, Any]`.
## Veja acontecer {#watch-it-happen}
"A inicialização roda antes da primeira requisição" é o tipo de afirmação em que você não deveria ter que acreditar sem ver.
Reduza o servidor só ao ciclo de vida: dê ao `Database` uma flag `connected`, inverta-a em `connect()` e `disconnect()`, e adicione uma ferramenta que informe o valor dela.
```python title="server.py" hl_lines="11 14 17 25 44"
--8<-- "docs_src/lifespan/tutorial002.py"
```
`database` fica no nível do módulo por um único motivo: para que você possa observá-lo de *fora* do servidor.
!!! check
Três momentos, três valores:
* Antes de o servidor iniciar, `database.connected` é `False`. Importar o módulo não conectou nada.
* Enquanto ele está rodando, chame `database_status` e o resultado é `"connected"`.
* Pare o servidor e o bloco `finally` executa: `database.connected` é `False` de novo.
O trabalho aconteceu exatamente onde você o colocou: em volta do `yield`, não na importação e nem a cada requisição.
## Recapitulando {#recap}
* `lifespan=` aceita um `@asynccontextmanager` que recebe o servidor e faz `yield` de um único objeto.
* O código antes do `yield` é a inicialização. O `finally` depois dele é o encerramento.
* Ele executa uma vez, em torno da vida inteira do servidor, não a cada requisição.
* O que você entregar no `yield` é `ctx.request_context.lifespan_context` em toda ferramenta, recurso e prompt.
* `ctx: Context[AppContext]` deixa esse acesso totalmente tipado em ferramentas. Recursos e prompts recebem o `Context` puro.
* Não passar `lifespan=` significa um `dict` vazio, nunca `None`.
Um handler que para no meio da chamada para perguntar ao usuário algo que só ele sabe é **[Elicitação (elicitation)](elicitation.md)**.

View file

@ -0,0 +1,86 @@
---
translation:
sections: [c93a3e1aefd77955, 7851abd5ec54393b, f49d1ca2f330f9cd, c03764bd9dfeef7b, 4a0391691a674ae4, 2df5cd279eabf9f5]
tool: 1
---
# Logging {#logging}
Faça log de uma ferramenta (tool) do mesmo jeito que faz de qualquer outra função Python: com a biblioteca padrão.
O MCP tem uma **capacidade de logging** no nível do protocolo: um servidor poderia enviar suas mensagens de log para o cliente como notificações, por meio de métodos do objeto `Context`. A revisão 2026-07-28 da especificação **torna essa capacidade obsoleta e não a substitui**, por isso esta documentação não a ensina. A lista completa do que foi marcado como obsoleto e do que fazer no lugar está em **[Funcionalidades obsoletas](../deprecated.md)**.
O que você faz no lugar é o que faz em qualquer outro programa Python: a biblioteca padrão.
## Uma ferramenta que faz log {#a-tool-that-logs}
```python title="server.py" hl_lines="1 5 13"
--8<-- "docs_src/logging/tutorial001.py"
```
* `logging.getLogger(__name__)` dá a você um logger com o nome do seu módulo. Crie-o uma vez, no topo.
* Dentro da ferramenta você chama `logger.info(...)` como em qualquer outra função. Nada para injetar, nada para fazer `await`, nada específico do MCP.
!!! check
Chame a ferramenta e olhe o resultado inteiro:
```python
result.content # [TextContent(text="Found 3 books matching 'dune'.")]
result.structured_content # {'result': "Found 3 books matching 'dune'."}
```
A linha de log não aparece em lugar nenhum. O logging é para **você**, a pessoa que opera o servidor. O modelo
nunca o vê. Se o modelo precisa ler alguma coisa, faça `return` dela.
## Para onde vai {#where-it-goes}
Para um servidor **stdio**, essa pergunta importa mais do que o normal. O host iniciou seu servidor como um subprocesso e está lendo mensagens MCP do **stdout** dele. O erro padrão (stderr) é seu.
A biblioteca padrão já faz a coisa certa: a saída de log vai para `sys.stderr` por padrão. Suas linhas de `logger.info(...)` caem no terminal (ou onde quer que o host colete o stderr do subprocesso), e o fluxo do protocolo fica limpo.
!!! tip
Não use `print()` em um servidor stdio. `print` escreve no **stdout**, e o stdout pertence ao protocolo.
Enquanto serve, o SDK desvia para o stderr o stdout que de fato recebe *flush*, então ele não consegue corromper a
comunicação, mas um `print()` em um processo com buffer em bloco costuma ficar sem flush no buffer de `sys.stdout`
até o interpretador esvaziá-lo na saída, direto no fluxo do protocolo. Mesmo quando é desviada,
a linha cai crua no meio da saída de log, sem nível, sem nome de logger e sem jeito de filtrá-la.
`logger.debug("got here")` dá o mesmo trabalho de uma linha e vai para o lugar certo.
## O nível {#the-level}
Você não precisa chamar `logging.basicConfig()` por conta própria. Construir um `MCPServer` já fez isso, com um handler apontado para o erro padrão, no nível que você passa em `log_level=`, então `MCPServer("Bookshop", log_level="DEBUG")` é tudo o que precisa para ver suas linhas de `logger.debug(...)`.
O padrão é `"INFO"`.
`logging.basicConfig()` nunca substitui handlers que já existem. Se você configurar o logging por conta própria antes de criar o servidor, sua configuração vence.
## Experimente {#try-it}
Execute o servidor com o MCP Inspector:
```console
uv run mcp dev server.py
```
Chame `search_books` na aba **Tools**. O Inspector mostra o resultado: apenas o valor de retorno. A linha
```text
Searching for 'dune'
```
foi para o erro padrão: o terminal, não a comunicação com o cliente.
!!! info
Se o que você quer de verdade é *tracing* (cada requisição, quanto tempo levou, se falhou), você
não quer linhas de log, quer spans. Seu servidor já os emite: o SDK faz tracing de cada
mensagem com OpenTelemetry por padrão. Veja **[OpenTelemetry](../run/opentelemetry.md)**.
## Recapitulando {#recap}
* A capacidade de logging do protocolo MCP foi tornada obsoleta pela especificação 2026-07-28 e não foi substituída. Não construa em cima dela.
* `logger = logging.getLogger(__name__)` no nível do módulo, `logger.info(...)` na ferramenta. O padrão inteiro é esse.
* A saída de log nunca chega ao modelo. Só o valor que você faz `return` chega.
* O erro padrão é seu; o stdout pertence ao protocolo. O SDK desvia para o stderr o stdout perdido que recebe flush enquanto serve, mas um `print()` sem flush ainda pode vazar para a comunicação na saída, e as linhas desviadas chegam sem rótulo; use `logging`, cujo handler faz flush de cada registro.
* `MCPServer(..., log_level="DEBUG")` define o nível, e uma configuração de logging que você tenha feito antes fica intacta.
Avisar os clientes conectados de que algo no seu servidor mudou (a lista de ferramentas, um recurso) é assunto de **[Assinaturas](subscriptions.md)**.

View file

@ -0,0 +1,191 @@
---
translation:
sections: [74011e683045eea9, 9b64cc175c18b6a9, 4b41be4824030397, e3b1502da786ec33, 71e41161f143c6a9, 9ec2c1eeb8c36378, 8dd027377d46448b, f81491125dcbfe8b]
tool: 1
---
# Requisições com múltiplas idas e voltas {#multi-round-trip-requests}
Às vezes uma ferramenta (tool) não consegue terminar em uma única ida e volta. Ela precisa de algo que só o usuário tem: uma escolha, uma confirmação, uma credencial.
Antes de 2026-07-28 o servidor conseguia isso chamando **de volta**: abria sua própria requisição para o cliente (uma elicitação (elicitation), uma chamada de amostragem (sampling)) no meio do tratamento da requisição original. A especificação 2026-07-28 aposenta esse canal de retorno (back-channel).
Em vez disso, o servidor **retorna**.
## Retorne, não chame de volta {#return-dont-call-back}
O servidor responde a `tools/call` com um **`InputRequiredResult`** no lugar de um `CallToolResult`. Dois dos seus campos fazem o trabalho:
* **`input_requests`**: o que o servidor ainda precisa, como um dict cujas chaves são nomes que o próprio servidor escolheu. Cada valor é um `ElicitRequest`, um `CreateMessageRequest` ou um `ListRootsRequest`.
* **`request_state`**: um token opaco. O cliente o devolve ao pé da letra na nova tentativa. Seu servidor é o único que o lê.
O cliente atende cada requisição e então chama a **mesma ferramenta de novo**, levando suas respostas em `input_responses` e o token em `request_state`. Agora o servidor tem o que faltava e retorna um `CallToolResult` normal.
O protocolo inteiro é esse. Cada trecho é uma requisição comum do cliente para o servidor. Nada jamais flui no sentido contrário.
## O lado do servidor {#the-server-side}
Em `@mcp.tool()` você raramente monta isso à mão: declare uma dependência que pergunta ao usuário (`Elicit`), faz amostragem no LLM do cliente (`Sample`) ou lista os roots dele (`ListRoots`) e o SDK retorna o `InputRequiredResult` por você; essa forma está na página **[Dependências](dependencies.md)**. As duas formas não se misturam: uma chamada tem um único canal `input_responses`/`request_state`, então uma ferramenta que usa parâmetros `Resolve(...)` não pode também retornar `InputRequiredResult` do seu corpo. Um retorno `InputRequiredResult` declarado é rejeitado no registro (`InvalidSignature`), e um não declarado faz a chamada falhar em tempo de execução. A forma manual é o `Server` de **baixo nível**, cujo handler `on_call_tool` pode retornar qualquer um dos dois tipos de resultado:
```python title="server.py" hl_lines="43-46"
--8<-- "docs_src/mrtr/tutorial001.py"
```
* `on_call_tool` tem o tipo `-> CallToolResult | InputRequiredResult`. Retornar o segundo é a API inteira do lado do servidor.
* Na primeira chamada `params.input_responses` é `None`, então a guarda dispara e o handler pergunta em vez de responder.
* Na nova tentativa, o `ElicitResult` que o cliente enviou está sob a **mesma chave** (`"region"`) que o servidor usou em `input_requests`.
Todo o resto naquele arquivo (o `input_schema` explícito, o `CallToolResult` montado à mão) é o `Server` de baixo nível comum, coberto em **[O Server de baixo nível](../advanced/low-level-server.md)**. Esta página só acrescenta o segundo tipo de retorno.
## Além das ferramentas {#beyond-tools}
`tools/call` não é especial: em 2026-07-28 um servidor pode responder a `prompts/get` e `resources/read` do mesmo jeito. No `MCPServer`, uma função `@mcp.prompt()` — ou uma função de **template** `@mcp.resource()` — retorna ela mesma o `InputRequiredResult` e lê as respostas da nova tentativa no contexto:
```python title="server.py" hl_lines="20 22 24"
--8<-- "docs_src/mrtr/tutorial004.py"
```
* A primeira rodada retorna o `InputRequiredResult`. Na nova tentativa, `ctx.input_responses` traz as respostas sob as mesmas chaves e a função retorna seu resultado comum — mensagens de prompt aqui, conteúdo de recurso para um recurso de template.
* Um `request_state` que você define é selado antes de passar pela rede e verificado no eco, como todo o resto no servidor; **[Protegendo o `requestState`](#protecting-requeststate)** mais abaixo cobre o que o selo oferece e quando você precisa configurar chaves.
* Uma função `@mcp.tool()` pode retornar o resultado diretamente do mesmo jeito, quando a forma por dependência não serve.
* Funções `@mcp.resource()` estáticas não participam: elas não recebem `Context`, então nunca poderiam ler a nova tentativa. Só recursos de template podem perguntar.
* As regras de era mais abaixo valem sem mudança: retornar um `InputRequiredResult` em uma sessão pré-2026 é o mesmo `-32603` que o aviso descreve.
## O lado do cliente {#the-client-side}
O `Client` executa o loop por você.
Registre os callbacks que o servidor pode pedir (`elicitation_callback`, `sampling_callback`, `list_roots_callback`) e chame a ferramenta. Quando chega um `InputRequiredResult`, o `Client` despacha cada entrada de `input_requests` para o callback correspondente, tenta de novo com as respostas e o `request_state` ecoado, e segue em frente até que um `CallToolResult` volte:
```python title="client.py" hl_lines="11 12"
--8<-- "docs_src/mrtr/tutorial003.py"
```
* Esse `elicitation_callback` é o mesmo que o `elicitation/create` do canal de retorno de um servidor pré-2026 teria acionado. O mesmo vale para `sampling_callback` em relação a `sampling/createMessage` e para `list_roots_callback` em relação a `roots/list`: em 2026-07-28 os RPCs avulsos servidor->cliente deixaram de existir, mas os mesmíssimos payloads `ElicitRequest` / `CreateMessageRequest` / `ListRootsRequest` viajam dentro de `input_requests` e são despachados para os mesmos três callbacks. Um único conjunto de callbacks atende às duas eras.
* `call_tool` retorna um `CallToolResult` simples. As rodadas intermediárias são invisíveis para quem chama.
* `get_prompt` e `read_resource` conduzem o mesmo loop.
!!! check
Deixe o callback de fora e o loop falha na primeira rodada: o callback substituto do SDK
responde a toda elicitação com um erro, e `call_tool` lança `MCPError` com a mensagem
*"Elicitation not supported"*.
O loop tem limite. `Client(..., input_required_max_rounds=10)` é o teto padrão; um servidor que continua retornando `InputRequiredResult` além dele faz `call_tool` lançar. Se uma rodada traz apenas `request_state` e nenhum `input_requests`, o `Client` dorme brevemente (50 ms, dobrando até um teto de 250 ms) antes de tentar de novo, de modo que um servidor que está só dizendo *"ainda não terminei"* não sofre busy-polling.
### Conduzindo o loop você mesmo {#driving-the-loop-yourself}
O loop automático basta para um cliente de processo único. Assuma o loop você mesmo quando:
* Seu cliente é **distribuído**: o processo que exibe a pergunta ao usuário não é o processo que chamou `call_tool`, então um worker diferente emite a nova tentativa. `request_state` é o token persistível que você carrega através dessa fronteira, pelo seu próprio armazenamento, e `input_responses` é o que o outro lado envia de volta junto com ele.
* Você quer **inspecionar** cada rodada: registrar em log ou auditar cada entrada de `input_requests`, recusar certos tipos de requisição ou aplicar seu próprio backoff entre os trechos.
* Você quer um limite de **relógio** em vez de um limite por contagem de rodadas: envolva seu próprio loop em `anyio.fail_after(...)` em vez de depender de `input_required_max_rounds`.
Desça para a sessão subjacente, onde `allow_input_required=True` entrega a união diretamente:
```python title="client.py" hl_lines="12 13 19"
--8<-- "docs_src/mrtr/tutorial002.py"
```
* `client.session.call_tool(..., allow_input_required=True)` amplia o tipo de retorno para `CallToolResult | InputRequiredResult`. O `isinstance` é o que o estreita de volta.
* `request_state` agora está nas suas mãos. Anote-o entre os trechos e a conversa pode ser retomada a partir de um processo novo.
* Para cada entrada em `input_requests` você coloca um `InputResponse` sob a **mesma chave** em `input_responses`. `fulfil` é onde entra sua UI; esta aqui fixa a resposta no código.
* Mesmo nome de ferramenta, mesmos `arguments`, em todo trecho. A nova tentativa é a chamada original realizada de novo, não um método novo.
## Protegendo o `requestState` {#protecting-requeststate}
Tudo acima trata `request_state` como um eco, e na rede é só isso mesmo. Mas o cliente o guarda entre os trechos (anotá-lo entre processos é exatamente o que a seção anterior aprovou), então o que volta é **entrada fornecida pelo cliente**: pode estar modificada, expirada ou ter sido retirada de uma chamada completamente diferente. A especificação exige que os servidores protejam a integridade desse estado e rejeitem a rodada quando a verificação falhar, sempre que o estado puder influenciar autorização, acesso a recursos ou lógica de negócio.
O `MCPServer` o protege por padrão. Todo servidor sela o `requestState` de saída e verifica todo eco — estado de resolvedor e estado montado à mão do mesmo jeito — sob uma chave gerada na inicialização do processo. Você não configura nada, escreve texto puro e lê texto puro; pela rede só passa um token opaco e criptografado.
A chave padrão vive e morre com o processo, e essa é a única coisa que você precisa saber antes de fazer o deploy além de um único processo:
```python
from mcp.server.mcpserver import MCPServer, RequestStateSecurity
# Multi-instance or restart-surviving: one or more shared secret keys (>= 32 bytes each).
mcp = MCPServer("fleet", request_state_security=RequestStateSecurity(keys=[key]))
```
* **O padrão (sem configuração)** serve para um único processo: stdio, ou exatamente um worker HTTP. Uma nova tentativa que cai em um worker diferente, em uma instância diferente atrás de um balanceador de carga ou no mesmo servidor depois de um reinício está selada sob uma chave que aquele processo não tem — o cliente recebe a rejeição fixa mostrada abaixo e precisa recomeçar o fluxo.
* **`keys=[...]`** é obrigatório sempre que uma nova tentativa pode chegar a uma **instância diferente** (`uvicorn` com múltiplos workers, HTTP com balanceamento de carga) ou precisa sobreviver a reinícios: cada instância verifica o que qualquer irmã emitiu. Mesma engrenagem, seu segredo em vez de um gerado.
* Para sua própria criptografia, como um KMS ou um serviço de tokens existente, passe `RequestStateSecurity(codec=...)` em vez de `keys`; **[Traga sua própria criptografia](#bring-your-own-crypto)** mais abaixo cobre o contrato.
### O que o selo carrega {#what-the-seal-carries}
Padrão ou configurado, o `requestState` na rede é um token criptografado e autenticado. Seu código nunca o vê: handlers e resolvedores escrevem texto puro e leem texto puro (`ctx.request_state`); o SDK sela na saída e verifica na entrada. Além da integridade, cada token fica vinculado a:
* **Uma janela de tempo.** Cada rodada sela de novo com uma expiração nova, então `RequestStateSecurity(ttl=...)` (padrão de 600 segundos) limita o tempo de reflexão por rodada, não o fluxo inteiro.
* **O principal autenticado.** Quando a requisição carrega um token de acesso OAuth que o SDK validou, o estado fica vinculado ao cliente, ao emissor e ao sujeito do token: estado emitido para um usuário falha sob outro, mesmo quando os dois compartilham um único cliente OAuth. Um verificador que não fornece sujeito degrada o vínculo para apenas a identidade do cliente, que com IDs de cliente baseados em URL é compartilhada por todos os usuários daquele software cliente. Quando a autenticação é encerrada fora do SDK (um proxy na frente), ou o transporte não é autenticado, não há principal a vincular e essa verificação fica inerte, a menos que `RequestStateSecurity(bind_principal=...)` forneça um a partir do seu próprio sinal de identidade. Quaisquer que sejam os componentes que seu verificador de tokens forneça, ele precisa fornecê-los de forma consistente: um verificador que inclui o sujeito em algumas requisições e o omite em outras muda o principal no meio do fluxo, e as rodadas em andamento são rejeitadas.
* **A requisição de origem.** O método, o nome da ferramenta ou do prompt (ou a URI do recurso) e um digest dos argumentos. Um token reproduzido contra uma ferramenta diferente, argumentos diferentes ou um método diferente falha.
* **A pergunta exata que foi feita.** Toda resposta de resolvedor fica presa à pergunta renderizada que foi mostrada ao cliente, tanto na rodada em que ela chega pela primeira vez quanto quando uma resposta gravada é reutilizada depois. Faça um novo deploy com uma mensagem reformulada ou um schema alterado e o servidor pergunta de novo em vez de consumir uma resposta obsoleta. A mesma amarração também corta no outro sentido: derive as mensagens dos argumentos da ferramenta, não de dados que variam a cada chamada. Uma mensagem montada a partir de um timestamp ou de uma cotação ao vivo renderiza diferente a cada rodada, então toda resposta gravada parece obsoleta e o servidor pergunta de novo até que o limite de rodadas do cliente encerre a chamada.
Tudo isso é trabalho do SDK, não seu, e nem do codec se você trouxer o seu.
### Rotacionando chaves {#rotating-keys}
`keys[0]` sela estado novo; toda chave da lista verifica. A rotação sem downtime tem três fases, cada uma totalmente distribuída antes da próxima:
```python
RequestStateSecurity(keys=[OLD, NEW]) # 1: every instance learns to verify NEW; OLD still mints
RequestStateSecurity(keys=[NEW, OLD]) # 2: NEW mints; in-flight OLD state keeps verifying
RequestStateSecurity(keys=[NEW]) # 3: one ttl after phase 2 is fully out, retire OLD
```
Nunca promova o emissor primeiro: emitir sob uma chave que alguma instância ainda não sabe verificar derruba rodadas em andamento no meio da distribuição.
As chaves têm escopo de um único serviço. O envelope selado também carrega o nome do servidor como uma declaração de audiência, então um token emitido por um serviço diferente que por acaso compartilha um segredo é rejeitado mesmo assim. A declaração é tão distintiva quanto o nome, então um servidor que recebe uma política explícita precisa ter um nome de verdade ou definir `RequestStateSecurity(audience=...)` — um sem nome lança na construção. `audience=` também atende topologias multisserviço deliberadas em que um serviço precisa aceitar estado que outro emitiu. (O padrão sem configuração está isento: sua chave nunca sai do processo, então a declaração de audiência não tem nada a acrescentar.)
### Traga sua própria criptografia {#bring-your-own-crypto}
`RequestStateSecurity(codec=...)` aceita qualquer coisa com `seal(bytes) -> str` e `unseal(str) -> bytes` que lance `InvalidRequestState` para qualquer token que não tenha emitido. O formato clássico é a criptografia de envelope contra um KMS, em que você desembrulha uma chave de dados uma vez na inicialização e mantém a criptografia por token local:
```python title="server.py" hl_lines="12 26-27 34-35 38"
--8<-- "docs_src/mrtr/tutorial005.py"
```
TTL, vínculo de principal e vínculo de requisição **não** são trabalho do codec: o SDK os grava no payload antes de `seal` e os verifica de novo depois de `unseal`, para todo codec. As únicas obrigações de um codec são integridade (adulterado significa lançar) e, idealmente, confidencialidade.
### Quando a verificação falha {#when-verification-fails}
Toda falha de entrada, seja token adulterado, expirado, reproduzido contra uma requisição ou principal diferente, ou selado sob uma chave que este servidor não conhece, recebe a mesma resposta:
```json
{"code": -32602, "message": "Invalid or expired requestState"}
```
Uma única mensagem fixa para toda causa, de modo que a rede nunca revela qual verificação falhou; o motivo real vai para o log do servidor. Todo `requestState` de entrada em `tools/call`, `prompts/get` e `resources/read` é verificado, inclusive um que chega para um handler que nunca emite estado. A rejeição mais comum na prática não é um atacante — é a chave padrão local ao processo encontrando uma nova tentativa de antes de um reinício ou de outra instância; o cliente recomeça o fluxo, e `keys=[...]` é a correção quando isso importa.
### Estado montado à mão {#hand-built-state}
Um `request_state` que você mesmo define (retornando `InputRequiredResult` de uma função de ferramenta, prompt ou template de recurso) é selado e verificado pela mesma engrenagem do estado de resolvedor, sem nenhuma mudança de código: escreva texto puro, leia texto puro, e todo vínculo acima se aplica.
A única coisa que o SDK não consegue amarrar por você, mesmo configurado, é a identidade da pergunta: ele não sabe a qual das *suas* perguntas pertence uma resposta no seu estado. Se você armazena respostas indexadas por pergunta, inclua seu próprio identificador de pergunta no estado e confira-o na nova tentativa.
O `Server` de baixo nível é o nível sem pilhas inclusas: diferente do `MCPServer`, nada é selado até que você mesmo acrescente a fronteira, e seu `request_state` atravessa a rede exatamente como foi escrito até você fazer isso. O opt-in de uma linha aparece em **[O Server de baixo nível](../advanced/low-level-server.md#the-other-handlers)**.
## 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.
!!! warning
Uma sessão pré-2026 não tem onde colocar um `InputRequiredResult`. Retorne um do seu handler em uma
conexão `mode="legacy"` e o executor não consegue serializá-lo na versão negociada; o
cliente recebe de volta um erro `-32603` *"Handler returned an invalid result"*. Um servidor que atende
às duas eras precisa conferir `ctx.protocol_version` antes de recorrer a ele.
!!! info
A **elicitação em modo URL** usa exatamente esse mecanismo em uma conexão 2026. A entrada em
`input_requests` é um `ElicitRequest` cujos params são `ElicitRequestURLParams`; o usuário
termina o fluxo fora de banda e seu cliente tenta a chamada de novo. Mesmo loop, nenhuma API nova. A
metade do servidor de alto nível está em **[Elicitação](elicitation.md)**.
## Recapitulando {#recap}
* Em 2026-07-28 um servidor que precisa de entrada no meio de uma chamada **retorna** um `InputRequiredResult`. Ele nunca abre uma requisição para o cliente.
* `input_requests` é o que ele precisa. `request_state` é um token opaco de retomada que só o servidor lê.
* O `Client` executa o loop de novas tentativas por você: registre `elicitation_callback` / `sampling_callback` / `list_roots_callback` e `call_tool` retorna um `CallToolResult` simples. `input_required_max_rounds` (padrão 10) o limita.
* Para inspecionar ou persistir rodadas, use `client.session.call_tool(..., allow_input_required=True)` e assuma você mesmo o loop `while isinstance(result, InputRequiredResult)`.
* Em `@mcp.tool()`, uma dependência que pergunta ao usuário produz esse resultado por você (**[Dependências](dependencies.md)**); o `Server` de **baixo nível** é a forma manual.
* Prompts e recursos também participam: uma função `@mcp.prompt()` ou `@mcp.resource()` de template retorna ela mesma o `InputRequiredResult` e lê `ctx.input_responses` na nova tentativa.
* O `requestState` volta como entrada fornecida pelo cliente, então o `MCPServer` o sela por padrão — estado de resolvedor e estado montado à mão do mesmo jeito — sob uma chave local ao processo; deploys com múltiplas instâncias passam `RequestStateSecurity(keys=[...])` (ou um codec personalizado) para que cada instância possa verificar o que uma irmã emitiu. O selo vincula todo token a uma janela de tempo, à requisição de origem e ao principal autenticado quando a requisição carrega autenticação que o SDK validou ou `bind_principal=` fornece seu próprio sinal de identidade (**[Protegendo o `requestState`](#protecting-requeststate)**).
Este é o mecanismo que substitui a amostragem iniciada pelo servidor e o resto do canal de retorno no estilo push; veja **[Funcionalidades descontinuadas](../deprecated.md)**.

View file

@ -0,0 +1,122 @@
---
translation:
sections: [5315262fe26b33e1, 9d8e98840f1b78f0, 0284b215e85366c4, 8534d8dbb4053a70, 2966fac6fe697007]
tool: 1
---
# Progresso {#progress}
Uma ferramenta (tool) que leva trinta segundos e não diz nada durante trinta segundos parece quebrada.
**Notificações de progresso** resolvem isso. A ferramenta informa em que ponto está; o cliente decide o que desenhar com isso: uma barra, um spinner, uma linha de log.
## Informe a partir da ferramenta {#report-it-from-the-tool}
Receba um parâmetro **`Context`** e chame `report_progress`:
```python title="server.py" hl_lines="8 11"
--8<-- "docs_src/progress/tutorial001.py"
```
Três argumentos, e você decide o que eles significam:
* `progress`: até onde você chegou. A especificação exige que ele **aumente** a cada informe; nunca repita um valor nem volte atrás.
* `total`: quanto há no total, se você souber. Opcional.
* `message`: uma linha legível por humanos sobre *este* passo. Opcional.
`ctx` é injetado por causa da sua anotação de tipo e o modelo nunca o vê: o schema de entrada de `import_catalog` tem uma única propriedade, `urls`. A página **[O Context](context.md)** trata inteiramente desse objeto; progresso é uma das coisas que ele oferece a você.
## Escute a partir do cliente {#listen-for-it-from-the-client}
O cliente opta por receber **por chamada**, passando `progress_callback=` para `call_tool`:
```python title="client.py" hl_lines="7 16"
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:
result = await client.call_tool(
"import_catalog",
{"urls": ["https://example.com/a.json", "https://example.com/b.json"]},
progress_callback=show,
)
print(result.structured_content)
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.
### Experimente {#try-it}
Coloque `client.py` ao lado de `server.py` e execute:
```console
python client.py
```
```text
Imported https://example.com/a.json (1/2)
Imported https://example.com/b.json (2/2)
{'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.
!!! warning
`progress_callback` pertence à **chamada**, não ao `Client`. Não há argumento de construtor
para ele, porque chamadas diferentes querem callbacks diferentes: uma move uma barra de download, a
seguinte, uma linha de log.
!!! check
Agora apague `progress_callback=show` e execute de novo:
```text
{'result': 'Imported 2 records.'}
```
Nenhum erro, nenhum aviso, mesmo resultado. `report_progress` é um **no-op quando quem chamou não pediu
progresso**, então você informa incondicionalmente e nunca precisa se perguntar se alguém está
escutando.
## Quando você não sabe o total {#when-you-dont-know-the-total}
`total` serve para quando você conhece o denominador. Muitas vezes você não conhece: está esvaziando um feed, percorrendo um cursor, baixando algo sem cabeçalho de tamanho.
Deixe-o de fora:
```python title="server.py" hl_lines="20"
--8<-- "docs_src/progress/tutorial002.py"
```
O callback recebe `total=None`. Um cliente ainda consegue mostrar *atividade* ("3 importados até agora...") mas não consegue mostrar uma porcentagem. Não invente um total para ter uma barra mais bonita.
!!! tip
`progress` não precisa contar nada em particular. Bytes, linhas, páginas: escolha a unidade que o
usuário reconheceria, e só prometa um `total` que você consiga cumprir.
## Recapitulando {#recap}
* `await ctx.report_progress(progress, total=None, message=None)` a partir de qualquer ferramenta que receba um `Context`.
* O cliente passa `progress_callback=` para `call_tool`: por chamada, nunca no `Client`.
* O callback é `async (progress, total, message) -> None` e dispara enquanto a ferramenta ainda está executando.
* Sem callback na chamada, `report_progress` não faz nada. Informe incondicionalmente.
* Omita `total` quando não o souber; o callback recebe `None`.
Progresso é o que uma ferramenta em execução mostra ao *usuário*. As linhas que ela registra em log para *você*, a pessoa que opera o servidor, são um canal diferente: **[Logging](logging.md)**.

View file

@ -0,0 +1,51 @@
---
translation:
sections: [5c82b20cbd65ded0, 9dc22632be79a533, 1fb8f452e990c456, 42666ab914ff0cb1, c4e0cb3667fd5ff9]
tool: 1
---
# Amostragem e roots {#sampling-and-roots}
Um handler pode pedir mais duas coisas ao cliente conectado: uma completion do próprio modelo do cliente (**amostragem**, sampling), e as pastas de workspace do cliente (**roots**).
As duas continuam funcionando, em todas as versões do protocolo que o SDK fala. Mas leia o aviso antes de projetar algo em cima delas:
!!! warning "Descontinuado pela especificação 2026-07-28"
Amostragem e roots estão descontinuados a partir de `2026-07-28` ([SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/2577)). Eles continuam totalmente funcionais e permanecem na especificação por pelo menos doze meses antes de se tornarem elegíveis para remoção, mas novas implementações não devem se apoiar neles. As migrações sugeridas: integre diretamente com a API do seu provedor de LLM em vez de usar amostragem, e passe diretórios via parâmetros de ferramenta, URIs de recurso ou configuração do servidor em vez de roots. A lista de todo o SDK está em **[Funcionalidades descontinuadas](../deprecated.md)**.
## Amostragem: pegue emprestado o modelo do cliente {#sampling-borrow-the-clients-model}
Um resolvedor retorna `Sample(...)` e a ferramenta recebe a completion, pelo mesmo mecanismo de dependência que executa `Elicit` em **[Dependências](dependencies.md)**:
```python title="server.py" hl_lines="10-15 19"
--8<-- "docs_src/sampling_and_roots/tutorial001.py"
```
* `Sample(messages, max_tokens=...)` espelha os parâmetros de `sampling/createMessage`. O valor injetado é o `CreateMessageResult` do cliente; passe `tools` ou `tool_choice` e ele vira um `CreateMessageResultWithTools`.
* O cliente precisa ter declarado a capacidade `sampling` (`sampling.tools` se você passar `tools` ou `tool_choice`). Se não declarou, a chamada falha com um erro de protocolo `-32021` em vez de enviar uma requisição que o cliente não consegue tratar. Uma sessão pré-2026 sem canal de retorno (back-channel) falha com o erro habitual de ausência de canal de retorno, já que não há por onde enviar.
* Em `2026-07-28` a requisição é entregue dentro do fluxo de múltiplas idas e voltas (**[Requisições com múltiplas idas e voltas](multi-round-trip.md)**); em `2025-11-25` ela é uma requisição independente para o cliente. O código é o mesmo nos dois casos, mas atenção à regra das múltiplas idas e voltas: a requisição precisa ser gerada de forma idêntica em todas as rodadas de retry, então construa-a apenas a partir dos argumentos da ferramenta e de outros dados estáveis.
* Deixe `include_context` quieto: valores diferentes de `"none"` também estão descontinuados (SEP-2596) e exigem uma capacidade que quase nenhum cliente declara.
## Roots: onde isso deve ir? {#roots-where-should-this-go}
Roots são as pastas sobre as quais o cliente diz que o servidor pode operar. São uma orientação informativa, não um mecanismo de controle de acesso. Um resolvedor retorna `ListRoots()`:
```python title="server.py" hl_lines="10-11 15"
--8<-- "docs_src/sampling_and_roots/tutorial002.py"
```
* O `ListRootsResult` injetado traz uma lista de `Root`s: uma URI `file://` e um nome de exibição opcional.
* A barreira é a mesma da amostragem: sem uma capacidade `roots` declarada, a chamada falha com `-32021` em vez de enviar a requisição.
Do outro lado da conexão, o cliente responde às duas requisições com os callbacks que já tem: `sampling_callback` e `list_roots_callback`, tratados em **[Callbacks do cliente](../client/callbacks.md)**.
## Em conexões da era 2025 {#on-2025-era-connections}
`ctx.session.create_message(...)` e `ctx.session.list_roots()` ainda existem para código que controla a sessão diretamente. Eles só funcionam onde existe um canal de retorno (conexões da era 2025, não stateless), e chamá-los dispara um aviso de descontinuação. Os marcadores de resolvedor acima são a forma suportada: eles escolhem a entrega conforme a versão negociada e não emitem aviso.
## Recapitulando {#recap}
* Retorne `Sample(...)` ou `ListRoots()` de um resolvedor; a ferramenta recebe o `CreateMessageResult` ou o `ListRootsResult` como qualquer outra dependência.
* O cliente precisa declarar a capacidade correspondente, ou a chamada falha com `-32021` em vez de uma requisição ser enviada.
* As duas funcionalidades estão descontinuadas em `2026-07-28`: totalmente funcionais por enquanto, erradas para novos projetos. Prefira APIs de provedor à amostragem e parâmetros explícitos aos roots.
Para informar o andamento de uma ferramenta lenta: **[Progresso](progress.md)**.

View file

@ -0,0 +1,152 @@
---
translation:
sections: [60a9de8a0bdaa531, 317bbe7e4355cdcc, a61d660c8029e04a, 8f7e82fcb88df8a9, b165db51249ff8ed, 266f56fb798068a4, 7c0e57030b622139, df18d7c2417a9883]
tool: 1
---
# Assinaturas {#subscriptions}
O catálogo de um servidor não é fixo. Ferramentas aparecem em tempo de execução, e o conteúdo por trás da URI de um recurso muda.
As **assinaturas** (subscriptions) são como um cliente fica sabendo disso. O cliente envia uma única requisição `subscriptions/listen`, e a resposta a essa requisição *é* o stream: ela fica aberta e carrega as notificações de mudança que o cliente pediu.
## Publique a partir da ferramenta {#publish-it-from-the-tool}
A sua parte é uma linha: publicar a mudança.
```python title="server.py" hl_lines="20 32"
--8<-- "docs_src/subscriptions/tutorial001.py"
```
* `await ctx.notify_resource_updated("board://sprint")` chega a todo stream aberto que assinou essa URI. A mais ninguém.
* `await ctx.notify_tools_changed()` chega a todo stream que pediu mudanças na lista de ferramentas. Um cliente que recebe isso chama `tools/list` de novo e agora vê `sprint_report`.
* Os irmãos são `notify_prompts_changed()` e `notify_resources_changed()`.
* Sem assinantes, sem trabalho. Publicar em um servidor ocioso é um no-op, então você nunca verifica se há alguém ouvindo. Você declara o que mudou.
O `MCPServer` serve `subscriptions/listen` para você. As obrigações do protocolo na conexão (o acknowledgment como primeiro frame, a filtragem por stream, o id da assinatura em cada frame) são trabalho do SDK.
!!! check
Na conexão, um stream cujo filtro nomeou `board://sprint` fica assim depois que `complete_task` executa:
```json
{"method": "notifications/subscriptions/acknowledged",
"params": {"notifications": {"resourceSubscriptions": ["board://sprint"]}, "_meta": {"io.modelcontextprotocol/subscriptionId": "listen-1"}}}
{"method": "notifications/resources/updated",
"params": {"uri": "board://sprint", "_meta": {"io.modelcontextprotocol/subscriptionId": "listen-1"}}}
```
Repare no que a atualização *não* carrega: o quadro. Cada frame carrega o id JSON-RPC da requisição listen em `_meta`, e esse id é o id da assinatura. Quem o gera é o cliente: o `Client` em Python usa strings como `"listen-1"`; outros clientes podem usar inteiros.
## Só o que foi pedido {#only-what-was-asked-for}
O filtro é um contrato. Um stream que pediu mudanças na lista de ferramentas e uma URI de recurso recebe esses dois tipos e nada mais. Publique uma mudança de prompt e esse stream fica em silêncio.
O `MCPServer` compara URIs de recurso como strings exatas, então um stream que nomeou `board://sprint` não ouve nada sobre `board://sprint/tasks/1`. A especificação permite que um servidor reporte uma mudança em um sub-recurso de uma URI assinada; o `MCPServer` nunca faz isso, mas os clientes são construídos para esperar por isso.
Duas coisas que o stream *não* é:
* **Não é um log de replay.** Um stream que caiu já era, e eventos publicados enquanto ninguém estava conectado não ficam em fila. Os clientes refazem o listen e buscam de novo.
* **Não é o caminho de 2025.** Clientes que chamaram `resources/subscribe` são atendidos por `ctx.session.send_resource_updated(uri)`. Os métodos `notify_*` chegam apenas a streams de `subscriptions/listen`.
## Decidindo quem pode observar {#deciding-who-may-watch}
Por padrão, todo tipo e toda URI pedidos são atendidos: qualquer chamador pode observar qualquer URI que você publica. Nada consulta o seu handler de leitura, porque ninguém está lendo — um chamador que o seu handler de `files://{name}` recusaria ainda pode abrir um stream em `files://payroll.csv` e saber que o arquivo mudou, e quando. Ele nunca descobre o conteúdo, e não consegue sondar o que existe, porque uma URI desconhecida também é atendida e simplesmente nunca dispara. Estreito, mas real, então bloqueie isso antes de publicar URIs por usuário a partir de um servidor multi-tenant.
O bloqueio é um middleware. Ele vê a requisição `subscriptions/listen` antes de o SDK fazer o acknowledgment e recusa quando o chamador pede qualquer coisa que não pode ler:
```python title="server.py" hl_lines="19-26 29"
--8<-- "docs_src/subscriptions/tutorial006.py"
```
* `ctx.params` é a requisição crua, então o próprio middleware a valida em `SubscriptionsListenRequestParams` e lê o filtro que o cliente pediu.
* A recusa é um `MCPError` lançado antes de `call_next(ctx)`: o cliente recebe esse erro e nenhum stream, e a conexão segue em frente. Mantenha a mensagem uniforme, sem nomear nenhuma URI, para que uma recusa nunca confirme quais URIs são protegidas.
* Um único `can_access(user, uri)` responde às duas perguntas. O handler do recurso o consulta em `resources/read`; o middleware o consulta em `subscriptions/listen`. Troque a tabela por um banco de dados ou pelo seu sistema de RBAC e os dois continuam em sintonia.
* A decisão vale por toda a vida do stream. Não há nova verificação por evento, então se o acesso de um chamador pode expirar no meio do stream (um token que vence), encerre a conexão desse chamador quando isso acontecer.
O contrato completo do middleware, incluindo o que mais ele envolve e por que está marcado como provisório, está em **[Middleware](../advanced/middleware.md)**.
## A ponta do cliente {#the-client-end}
Aqui está um cliente do outro lado desse stream, acompanhando o quadro:
```python title="client.py" hl_lines="15"
--8<-- "docs_src/subscriptions/tutorial003.py"
```
Entrar em `client.listen(...)` envia a requisição e espera pelo seu acknowledgment, então o stream já está ativo quando o bloco começa, e cada evento tipado é um sinal para buscar de novo, nunca um payload. Esse é o contrato inteiro em uma tela. Todo o resto sobre a ponta do cliente mora na sua própria página: observar ao lado de um fluxo principal, fim de streams e refazer o listen. Veja **[Assinaturas](../client/subscriptions.md)** em *Clientes*.
## Escalando além de um processo {#scaling-past-one-process}
As publicações viajam do seu handler até os streams abertos por um `SubscriptionBus`. O padrão é em memória: um processo, todos os streams dentro dele. Essa é a resposta certa até você rodar réplicas atrás de um balanceador de carga, porque aí o stream de um cliente fica preso a uma réplica, e uma publicação em outra réplica precisa chegar até ele.
Essa costura é sua para implementar: dois métodos sobre o seu backend de pub/sub.
```python
from collections.abc import Callable
from redis.asyncio import Redis
from mcp.server.mcpserver import MCPServer
from mcp.server.subscriptions import ServerEvent # SubscriptionBus is a Protocol: no base class
class RedisSubscriptionBus:
def __init__(self, redis: Redis) -> None:
self._redis = redis
self._listeners: dict[object, Callable[[ServerEvent], None]] = {}
async def publish(self, event: ServerEvent) -> None:
await self._redis.publish("mcp-events", encode(event)) # to every replica
def subscribe(self, listener: Callable[[ServerEvent], None]) -> Callable[[], None]:
token = object()
self._listeners[token] = listener
def unsubscribe() -> None:
self._listeners.pop(token, None)
return unsubscribe
mcp = MCPServer("Sprint Board", subscriptions=RedisSubscriptionBus(redis))
```
`encode` é seu, assim como a task leitora em cada réplica que decodifica as mensagens que chegam e chama cada listener registrado. Os listeners são síncronos, não podem lançar exceções e rodam no loop de eventos do servidor.
O bus carrega valores `ServerEvent` tipados, quatro dataclasses pequenas, nunca JSON-RPC. Carimbo, filtragem e ciclos de vida dos streams ficam no SDK, então uma implementação de bus não consegue quebrar o protocolo. Ela só consegue mover eventos entre processos.
Para publicar de fora de uma requisição, construa o bus você mesmo para ficar com a referência. O `MCPServer` monta um internamente quando você não passa nada, e não o expõe.
```python
from mcp.server.subscriptions import InMemorySubscriptionBus, ToolsListChanged
bus = InMemorySubscriptionBus()
mcp = MCPServer("Sprint Board", subscriptions=bus)
async def tools_reloaded() -> None:
await bus.publish(ToolsListChanged()) # from a lifespan task, a webhook, anywhere
```
## A composição de baixo nível {#the-low-level-composition}
Lá embaixo, no `Server` de baixo nível, nada vem pré-conectado, e as mesmas peças se montam em três linhas:
```python title="server.py" hl_lines="8-9 47"
--8<-- "docs_src/subscriptions/tutorial002.py"
```
* O bus é seu, então você publica nele diretamente: `await bus.publish(ResourceUpdated(uri=...))`. Coloque-o onde os seus handlers consigam alcançá-lo: escopo de módulo aqui, o lifespan em um app maior.
* `ListenHandler(bus)` é o mesmo handler que o `MCPServer` registra, e `on_subscriptions_listen=` é um slot de handler comum. Coloque o seu próprio callable nesse slot para ter uma semântica diferente, e as obrigações da especificação passam para você: fazer o acknowledgment primeiro, carimbar cada frame com o id da assinatura, não entregar nada fora do filtro.
* `ListenHandler.close()` encerra cada stream aberto de forma graciosa. Cada um recebe o resultado da requisição listen como seu frame final, que é o jeito da especificação de dizer que o servidor encerrou a assinatura de propósito. Ele retorna antes de esses streams terminarem de descarregar, então dê um instante a eles antes de derrubar o transporte. Sem ele, os streams terminam quando o cliente desconecta.
## Recapitulando {#recap}
* Um cliente opta por participar com uma única requisição `subscriptions/listen`, e a resposta é o stream. Servir isso já vem embutido.
* Você publica com `ctx.notify_*`, e o SDK cuida do carimbo, da filtragem e do ciclo de vida.
* Eventos são sinais, não payloads. As duas pontas buscam de novo.
* A ponta do cliente é `async with client.listen(...)`: **[Assinaturas](../client/subscriptions.md)** em *Clientes* conta essa história.
* No `Server` de baixo nível você monta as mesmas peças por conta própria: um bus, `ListenHandler(bus)`, o slot `on_subscriptions_listen`.
* Escalar horizontalmente significa implementar `SubscriptionBus`, dois métodos, e passá-lo como `MCPServer(subscriptions=...)`.
Rodar o servidor que serve tudo isso, atrás de uma réplica ou de vinte, é **[Deploy e escala](../run/deploy.md)**.