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
6 changed files with 89737 additions and 54 deletions

View file

@ -0,0 +1,165 @@
---
translation:
sections: [0355618e5f4d5fe4, 1821eaf50f2d0b64, 82e0b28ebd3abf5a, 8ac39614c094f2d0, dab6ff945501ab2a, bd5565c3b2d4f959, 96819ce3d63a0487]
tool: 1
---
# MCP Apps {#mcp-apps}
Um **MCP App** é uma ferramenta (tool) com uma cara: junto com os dados, a ferramenta aponta para um
documento HTML que o host renderiza como uma superfície interativa.
Duas partes, sempre duas partes:
1. **Uma ferramenta** que faz o trabalho e retorna dados, como qualquer outra ferramenta.
2. **Um recurso `ui://`** contendo o HTML que o host mostra para ela.
A ferramenta carrega uma referência `_meta.ui.resourceUri` ao recurso. O host busca esse
recurso com `resources/read`, renderiza em um **iframe em sandbox** e envia o resultado
da ferramenta para dentro desse iframe via `postMessage`. Seu servidor nunca envia nem recebe
nenhuma mensagem `ui/*`: esse tráfego fica entre o host e o iframe. Você serve uma ferramenta
e um documento HTML; o host cuida do espetáculo.
O SDK entrega isso como a extensão embutida `Apps` (`io.modelcontextprotocol/ui`).
Se [Extensões](extensions.md) são novidade para você, dê uma olhada naquela página primeiro. Um minuto,
e depois volte aqui.
## Um relógio com uma cara {#a-clock-with-a-face}
```python title="server.py" hl_lines="19 22 30 32"
--8<-- "docs_src/apps/tutorial001.py"
```
Quatro movimentos:
* `Apps()`: uma única instância guarda suas ferramentas ligadas a UI e os recursos delas.
* `@apps.tool(resource_uri="ui://clock/app.html")`: uma ferramenta comum, mais o
carimbo `_meta.ui.resourceUri`. Tudo o que `@mcp.tool()` aceita (name, title,
description, ...) passa direto.
* `apps.add_html_resource("ui://clock/app.html", CLOCK_HTML)`: o recurso
correspondente, servido como `text/html;profile=mcp-app`. É exatamente esse MIME type que
diz ao host "isto é um app, renderize".
* `MCPServer("clock", extensions=[apps])`: você opta por participar. O servidor agora anuncia
`io.modelcontextprotocol/ui` em `capabilities.extensions`.
O HTML em si escuta o `postMessage` do host e mostra o resultado. Para apps
de verdade, use o SDK de navegador oficial [`@modelcontextprotocol/ext-apps`](https://github.com/modelcontextprotocol/ext-apps)
dentro do seu HTML. Ele dá a você `ontoolresult`, `callServerTool`,
`getHostContext` e `onhostcontextchanged` em vez de eventos de mensagem crus.
## Degradação elegante {#graceful-degradation}
Nem todo cliente renderiza apps. A especificação é direta sobre o que isso significa para você:
> As ferramentas **DEVEM** retornar um array `content` significativo mesmo quando há UI disponível.
O modelo lê `content`; o iframe é para humanos. Um host com suporte a UI ainda passa
o resultado em texto para o modelo, e um cliente só de texto recebe *apenas* isso. Então o
padrão canônico é uma ferramenta, duas respostas. Olhe `get_time` de novo:
```python title="server.py" hl_lines="23-27"
--8<-- "docs_src/apps/tutorial001.py"
```
`client_supports_apps(ctx)` é `True` somente quando o cliente declarou a
extensão `io.modelcontextprotocol/ui` **e** listou `text/html;profile=mcp-app`
nas suas configurações `mimeTypes`. O campo é obrigatório, então um cliente que o omite
não conta. É exatamente isso que `main()` no mesmo arquivo declara: a
metade cliente da negociação, e a resposta rica volta.
!!! warning
Nunca retorne um placeholder como `"[Rendered UI]"` como único conteúdo. Se o
texto de fallback é inútil, a ferramenta é inútil para todo cliente só de texto e para
o próprio modelo. Escreva a frase.
## Trancando o iframe {#locking-the-iframe-down}
O lado do recurso carrega os metadados de segurança: o que o iframe pode carregar, quais
permissões do navegador ele quer, como gostaria de ser enquadrado:
```python title="server.py" hl_lines="9 19-22"
--8<-- "docs_src/apps/tutorial002.py"
```
`csp` e `permissions` são **pedidos ao host**, não comportamento do servidor. O host
monta a Content-Security-Policy e a Permissions-Policy do iframe a partir deles, e
pode recusar. Faça detecção de funcionalidade no seu JS em vez de presumir que foi concedido.
`ResourceCsp`, campo por campo (nome em Python, chave no protocolo, o que o host faz com ele):
| Python | Protocolo (`_meta.ui.csp`) | Controla |
|---|---|---|
| `connect_domains` | `connectDomains` | `connect-src`: para onde `fetch`/XHR podem ir |
| `resource_domains` | `resourceDomains` | `img-src`, `style-src`, ...: assets estáticos |
| `frame_domains` | `frameDomains` | `frame-src`: iframes aninhados |
| `base_uri_domains` | `baseUriDomains` | `base-uri`: para onde `<base>` pode apontar |
`ResourcePermissions`: cada campo solicita uma permissão do navegador para o iframe.
| Python | Protocolo (`_meta.ui.permissions`) |
|---|---|
| `camera` | `camera` |
| `microphone` | `microphone` |
| `geolocation` | `geolocation` |
| `clipboard_write` | `clipboardWrite` |
!!! note
CSP e permissões vivem no **recurso**, nunca na ferramenta. Os metadados de ferramenta
da especificação não têm lugar para eles, e os hosts os ignoram ali. O SDK torna o
erro irrepresentável: `@apps.tool()` simplesmente não tem parâmetro `csp`.
### Visibilidade {#visibility}
`visibility=["app"]` em uma ferramenta diz "isto existe para o iframe, não para o modelo":
* `"model"`: o modelo pode chamá-la.
* `"app"`: o iframe pode chamá-la (via `callServerTool`).
* Omitido: ambos, que é o padrão.
Filtrar é trabalho do **host**. Seu servidor lista as ferramentas só de app em `tools/list`
como qualquer outra; o host as esconde do modelo. Não filtre no lado do servidor.
## As regras que o SDK impõe {#the-rules-the-sdk-enforces}
Todas estas falham na inicialização, não em produção:
* Um `resource_uri` ou URI de recurso que não seja `ui://...` é um `ValueError` no
momento da decoração/registro.
* Uma ferramenta ligada a uma URI **sem recurso registrado correspondente** é um `ValueError`
quando `MCPServer(extensions=[apps])` consome a extensão. Uma ferramenta que anuncia
um HTML que dá 404 em `resources/read` é uma configuração errada, então o servidor se recusa
a ser construído.
* `meta={"ui": ...}` em `@apps.tool()` é um `ValueError`. O decorator é dono de
`_meta["ui"]`; diga isso com `resource_uri=` e `visibility=`. Outras chaves em `meta=`
são mescladas normalmente ao lado.
Nem o SDK ext-apps em TypeScript nem o FastMCP pegam nenhum desses casos hoje; preferimos
que você descubra antes que um host descubra.
## Além do HTML inline {#beyond-inline-html}
`add_html_resource` cobre o caso comum: uma string de HTML. Para qualquer outra coisa,
HTML em disco ou conteúdo gerado, construa o recurso você mesmo e entregue:
```python title="server.py" hl_lines="12 18"
--8<-- "docs_src/apps/tutorial003.py"
```
`add_resource` preenche o MIME type `text/html;profile=mcp-app` quando o recurso
não define um explicitamente, e rejeita uma incompatibilidade explícita: um recurso `ui://`
sob qualquer outro MIME type é um que nenhum host vai renderizar.
!!! tip
Mirando um host pré-GA que ainda lê a chave plana depreciada
`_meta["ui/resourceUri"]`? Mescle você mesmo:
`@apps.tool(resource_uri="ui://x", meta={"ui/resourceUri": "ui://x"})`.
O objeto `ui` aninhado é o formato da especificação; a chave plana está de saída.
## Veja rodando {#see-it-run}
A história `apps` em `examples/stories/` é esta página como um par executável: um servidor
com uma ferramenta de relógio ligada a UI e um cliente que negocia Apps, lê o
`_meta.ui.resourceUri` da ferramenta, busca o HTML e chama a ferramenta.
```bash
uv run python -m stories.apps.client
```

View file

@ -0,0 +1,172 @@
---
translation:
sections: [05891e7cc1938a13, b3c01a6af28c51ee, 7ffc91f5e38bdfe0, 717d3f235a8333a7, f471a13b2fe5d737, ed6af2df4b656dff]
tool: 1
---
# Extensões {#extensions}
Uma **extensão** é um pacote opcional de comportamento MCP reunido sob um único identificador.
Em um servidor, ela pode contribuir com ferramentas (tools), recursos e novos métodos de requisição, e pode envolver `tools/call`. Em um cliente, ela pode reivindicar formatos extras de resultado de `tools/call` e observar notificações de fornecedores. Cada lado se anuncia no seu próprio `capabilities.extensions`, e nada muda para quem não pediu nada. Esse é o contrato ([SEP-2133](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2133)), e ele tem uma regra de ouro: **extensões vêm desligadas por padrão**.
## Usando uma extensão {#using-an-extension}
Passe as instâncias na construção:
```python title="server.py"
--8<-- "docs_src/extensions/tutorial001.py"
```
Pronto. O servidor agora anuncia `io.modelcontextprotocol/ui` em `capabilities.extensions` e serve tudo o que a extensão contribui.
`Apps` é a extensão de referência embutida, e ela tem uma página própria: **[MCP Apps](apps.md)**.
!!! note
As extensões são fixadas na construção. Não existe um `add_extension` para chamar depois: o mapa de capacidades de um servidor não deve mudar enquanto há clientes conectados a ele.
O mapa de capacidades viaja em `server/discover`, que é um caminho da **2026-07-28**. Um handshake `initialize` legado não tem onde colocá-lo, então um cliente legado simplesmente não enxerga a extensão. Projete pensando nisso: uma extensão *amplia* um servidor, ela não pode ser a única forma de usá-lo.
## Escrevendo a sua {#writing-your-own}
Herde de `Extension` e sobrescreva apenas o que precisar. Todo método tem um padrão.
### O identificador {#the-identifier}
```python
--8<-- "docs_src/extensions/tutorial002.py"
```
O identificador é uma string `vendor-prefix/name` que segue a gramática de chaves `_meta` da especificação: rótulos separados por ponto (cada um começa com uma letra e termina com uma letra ou dígito), uma barra e então o nome. Ele é validado **quando a classe é definida**, então um erro de digitação não espera o servidor subir:
```text
TypeError: Stamps.identifier must be a `vendor-prefix/name` string
(reverse-DNS prefix required), got 'stamps'
```
Use como prefixo um domínio que você controla. `io.modelcontextprotocol/*` é reservado para extensões especificadas pelo próprio projeto MCP.
### Contribuindo com ferramentas {#contributing-tools}
A menor extensão útil é uma ferramenta e um mapa de configurações:
```python title="server.py" hl_lines="17 19-20 22-23 26"
--8<-- "docs_src/extensions/tutorial003.py"
```
* `tools()` retorna `ToolBinding`s. O servidor registra cada uma exatamente como se você tivesse chamado `mcp.add_tool(...)` por conta própria: mesma geração de schema, mesma injeção de `Context`, tudo igual.
* `settings()` é o valor anunciado em `capabilities.extensions["com.example/stamps"]`. Retorne `{}` (o padrão) para anunciar a extensão sem configurações.
* A extensão nunca recebe o servidor. Ela declara contribuições como dados; o `MCPServer` as consome. Não existe um `self.server` para modificar.
E `main()` é a prova, um cliente em memória direto contra `mcp`:
```python title="server.py" hl_lines="29-34"
--8<-- "docs_src/extensions/tutorial003.py"
```
### Servindo seus próprios métodos {#serving-your-own-methods}
Uma extensão pode registrar **novos métodos de requisição**: seus próprios verbos, servidos ao lado dos da especificação:
```python title="server.py" hl_lines="16-22 31 40-48"
--8<-- "docs_src/extensions/tutorial004.py"
```
* `SearchParams` herda de `RequestParams`, então o envelope `_meta` de 2026 é analisado de forma uniforme e seu handler recebe parâmetros validados, nunca um dict cru. Limite o que o cliente controla: `Field(ge=1, le=100)` rejeita um `limit` absurdo antes que seu código aloque qualquer coisa para ele.
* `require_client_extension(ctx, EXTENSION_ID)` é a barreira: um cliente que não declarou a extensão recebe o erro `-32021` (capacidade obrigatória do cliente ausente), com o payload `requiredCapabilities` legível por máquina que a especificação pede.
* `protocol_versions=frozenset({"2026-07-28"})` fixa o método em uma única versão de protocolo. Em qualquer outra versão o cliente recebe `METHOD_NOT_FOUND`, exatamente como se o método não existisse ali. Para esse cliente, não existe.
Os métodos são **estritamente aditivos**. O SDK impõe isso na construção, não em tempo de execução:
* Um `MethodBinding` para um método definido pela especificação (`tools/list`, `completion/complete`, ...) lança `ValueError` quando o binding é construído. Os verbos centrais pertencem ao servidor.
* Duas extensões vinculando o mesmo método lançam quando a segunda se registra. A última escrita vencer é como plugins corrompem uns aos outros; não fazemos isso.
* Um conjunto `protocol_versions` vazio também lança: um método que nunca pode ser servido é um bug, não uma configuração.
### O lado do cliente {#the-client-side}
O `main()` do mesmo arquivo é a história inteira do cliente, com as duas metades:
```python title="server.py" hl_lines="54-58"
--8<-- "docs_src/extensions/tutorial004.py"
```
* `Client(..., extensions=[advertise(EXTENSION_ID)])` declara a extensão. As declarações viram `ClientCapabilities.extensions`: em uma conexão 2026-07-28 o mapa viaja no envelope `_meta` de cada requisição, então o servidor o vê em **toda** requisição; em uma conexão legada ele vai no handshake `initialize`. O código do servidor não se importa com qual: `require_client_extension(ctx, ...)` e `ctx.session.check_client_capability(...)` leem a fonte certa nos dois caminhos.
* Métodos de fornecedor descem uma camada para `client.session.send_request(...)`; `Client` só ganha métodos de primeira classe para verbos da especificação. `send_request` aceita qualquer subclasse de `Request`, então a requisição do fornecedor passa como está.
### Interceptando `tools/call` {#intercepting-toolscall}
O único hook interceptador. Sobrescreva `intercept_tool_call` para observar, curto-circuitar ou vetar uma chamada de ferramenta:
```python title="server.py" hl_lines="17-24"
--8<-- "docs_src/extensions/tutorial005.py"
```
* `params` é o `CallToolRequestParams` validado: você recebe `params.name` e `params.arguments` sem tocar em JSON cru. É também o que decide qual chamada de ferramenta é executada: passar um contexto reescrito por `call_next` muda o que o handler observa em `ctx`, não a invocação da ferramenta. Reescrita de requisição no nível do protocolo pertence ao [Middleware](middleware.md).
* `call_next(ctx)` executa o resto da cadeia e retorna o resultado do handler. Retorne-o sem alterações (observar), retorne outra coisa (substituir) ou lance um `MCPError` (recusar). O que você retornar é serializado como qualquer resultado de handler, incluindo o carimbo de identidade `serverInfo` da era 2026, então um interceptador que curto-circuita nunca produz uma resposta anônima ou fora do schema.
* Com várias extensões, os interceptadores se aninham na ordem de registro: a primeira extensão em `extensions=[...]` é a mais externa.
* A implementação padrão é um repasse direto, e um servidor cujas extensões nunca sobrescrevem esse hook mantém o handler puro de `tools/call` intocado. Você não paga pelo que não usa.
O hook envolve `tools/call` e nada mais. Para preocupações que valem para toda mensagem, use o [Middleware](middleware.md). É para isso que ele serve.
## Usando uma extensão de cliente {#using-a-client-extension}
Uma **extensão de cliente** é o mesmo contrato visto do lado consumidor: um pacote de comportamento do lado do cliente reunido sob um único identificador. Passe as instâncias para `Client(extensions=[...])` e chame as ferramentas normalmente:
```python title="client.py" hl_lines="66-68"
--8<-- "docs_src/extensions/tutorial006.py"
```
`call_tool("buy", ...)` retorna um `CallToolResult` comum, como toda outra chamada. O que a extensão mudou: o servidor agora pode responder a `buy` com um **formato de resultado** `receipt` em vez de um resultado final, e `Receipts` o finaliza (aqui, resgatando o recibo com uma chamada seguinte) antes de `call_tool` retornar. Nada muda no ponto da chamada.
Tire a extensão e nada disso existe: a barreira do servidor recusa um cliente que não a declarou (erro -32021), e um formato reivindicado vindo de um servidor que pula a barreira falha na validação, exatamente como a especificação exige para um `resultType` não reconhecido. Desligado por padrão, nas duas pontas da conexão.
Para anunciar um identificador **sem** nenhum comportamento do lado do cliente (o servidor faz a barreira pela capacidade, o cliente não faz nada, como no cliente de busca acima), use `advertise()`:
```python
from mcp.client import advertise
client = Client(mcp, extensions=[advertise("com.example/search")])
```
## Escrevendo uma extensão de cliente {#writing-a-client-extension}
Herde de `ClientExtension` e sobrescreva apenas o que precisar. Três tipos de contribuição, cada um com um padrão: `settings()`, `claims()` e `notifications()`.
```python title="client.py" hl_lines="17-18 43-44 46-47"
--8<-- "docs_src/extensions/tutorial006.py"
```
* O identificador segue a mesma gramática do servidor, validada quando a classe é definida.
* `claims()` retorna `ResultClaim`s: uma tag de protocolo, o modelo que a analisa e o resolvedor que a finaliza. O modelo precisa fixar a tag com `result_type: Literal["receipt"]` e não pode herdar dos tipos de resultado centrais do verbo; as duas coisas são impostas quando a claim é construída. Campos de fornecedor como `receipt_token` viajam pela conexão como estão: um formato substituído chega ao cliente literalmente.
* O resolvedor recebe o modelo analisado e um `ClaimContext`; `ctx.session` é o mesmo handle público que `client.session`, então as chamadas seguintes são chamadas comuns de sessão. Ele retorna o `CallToolResult` normal do verbo.
* `settings()` é o valor anunciado em `ClientCapabilities.extensions[identifier]`, lido uma vez na construção do `Client`.
`notifications()` declara notificações de servidor de fornecedor a observar:
```python
def notifications(self) -> Sequence[NotificationBinding[Any]]:
return [NotificationBinding(method="notifications/receipts", params_type=ReceiptEvent, handler=self.on_receipt)]
```
O handler recebe parâmetros validados um de cada vez, na ordem de despacho. Ele observa; não pode vetar nem responder.
Duas regras discretas. As claims ficam ativas apenas em conexões 2026-07-28, e o anúncio de capacidade as acompanha: em uma conexão legada as claims se dissolvem e o identificador sai do anúncio junto com elas, então o cliente nunca anuncia uma extensão cujos formatos ele rejeitaria. E quando você mesmo quer o formato reivindicado em vez do resolvedor, chame `client.session.call_tool(..., allow_claimed=True)`; sem essa flag, um formato reivindicado que chega a um chamador no nível da sessão lança `UnexpectedClaimedResult`.
### Verbos de extensão {#extension-verbs}
Os métodos de requisição próprios de uma extensão não precisam de registro no lado do cliente. Um tipo de requisição de fornecedor herda de `mcp.types.Request` e passa por `client.session.send_request`, como em [Servindo seus próprios métodos](#serving-your-own-methods). Um acréscimo: quando uma chave de params precisa viajar no header `Mcp-Name` (especificações de extensão como tasks exigem isso para seus verbos), o tipo de requisição declara `name_param`:
```python title="client.py" hl_lines="22-25 46-47"
--8<-- "docs_src/extensions/tutorial007.py"
```
A sessão espelha `params["jobId"]` em `Mcp-Name` em todo caminho de envio, e um valor ausente falha de forma explícita em vez de omitir silenciosamente um header obrigatório.
## O que uma extensão não pode fazer {#what-an-extension-cannot-do}
A superfície de contribuição é **fechada** de propósito. No servidor: configurações, ferramentas, recursos, métodos, um interceptador de `tools/call`. No cliente: configurações, claims de resultado, bindings de notificação. Uma extensão não pode:
* **Alcançar o host.** Ela declara dados; não guarda nenhuma referência ao servidor nem ao cliente.
* **Substituir comportamento central.** Métodos da especificação e tags de resultado centrais são rejeitados na construção (`initialize` é reservado pelo runner sem exceção); já um binding de notificação encoberto pelo vocabulário central fica em silêncio com um aviso.
* **Registrar-se depois.** Depois que `MCPServer(...)` ou `Client(...)` retorna, o conjunto de extensões é o que é.
Se você está brigando com essas paredes, não está escrevendo uma extensão. Está escrevendo um fork. As paredes são a funcionalidade: um usuário que lê `extensions=[Apps(), Stamps()]` sabe *tudo* o que essas duas podem ter tocado.

View file

@ -0,0 +1,34 @@
---
translation:
sections: [ca6988b7503cd2d3]
tool: 1
---
# Avançado {#advanced}
Tudo o que um servidor ou cliente comum precisa tem seu lugar por assunto nas seções acima.
Esta seção reúne as saídas de emergência a que você recorre quando a camada de conveniência
do `MCPServer` atrapalha:
* **[O Server de baixo nível](low-level-server.md)**: a classe sobre a qual o `MCPServer` é construído.
Schemas escritos à mão, handlers `on_*`, nada verificado para você e métodos JSON-RPC
personalizados criados por você.
* **[Paginação](pagination.md)** e **[Middleware](middleware.md)**: duas coisas que você
*só* consegue fazer no `Server` de baixo nível.
* **[Extensões](extensions.md)** e **[MCP Apps](apps.md)**: a superfície de
extensão do protocolo. Componha pacotes de extensão em um servidor ou escreva os seus.
Algumas coisas que você poderia, com razão, procurar aqui ficam onde você de fato
as usaria:
* **Autorização** está em **[Executando seu servidor](../run/index.md)**, porque você
protege um servidor onde faz o deploy dele.
* **OAuth**, **asserção de identidade**, conexão a **vários servidores** e o
**cache** de respostas estão todos em **[Clientes](../client/index.md)**.
* **Requisições com várias idas e voltas** e **Assinaturas** estão em
**[Dentro do seu handler](../handlers/index.md)**, porque ambas são coisas que um
handler *faz*.
* **Templates de URI** está em **[Servidores](../servers/index.md)**, ao lado de Recursos.
* **[Versões do protocolo](../protocol-versions.md)** e
**[Funcionalidades descontinuadas](../deprecated.md)** têm, cada uma, sua própria página de nível superior.
Se você não tem certeza de que precisa desta seção, não precisa.

View file

@ -0,0 +1,207 @@
---
translation:
sections: [2c79b6338e09b7ac, 7edc43b3fae11314, 1086e77ce561cd7f, a3f71823df5efc31, 9fc7109f72201cae, 7bf25983df655b66, 6330e1f4c6029683, 2f1749c8c133fa1c, b3530fcf4d11fd56, ebc33704fbd74262, cd0e9c933350390e]
tool: 1
---
# O Server de baixo nível {#the-low-level-server}
`@mcp.tool()` é uma camada. Por baixo dela existe uma segunda classe de servidor, `Server`, que fala MCP cru: você entrega os objetos do protocolo e ela os coloca no fio, sem alterar nada.
O `MCPServer` é construído em cima dela. Você desce um nível quando a camada de conveniência atrapalha:
* Você precisa emitir um schema **exato** (carregado de um arquivo, gerado a partir de um banco de dados), não um derivado de uma assinatura Python.
* Você precisa de controle total do resultado: `_meta`, `is_error`, cada chave de `structured_content`.
* Você precisa tratar um método que o MCP não define.
Para todo o resto, fique no `MCPServer`.
## A mesma ferramenta, à mão {#the-same-tool-by-hand}
Esta é a ferramenta (tool) `search_books` que **[Ferramentas](../servers/tools.md)** escreve em nove linhas de `@mcp.tool()`, com o açúcar removido:
```python title="server.py" hl_lines="22 26 32"
--8<-- "docs_src/lowlevel/tutorial001.py"
```
Três coisas mudaram, e elas são a API de baixo nível inteira:
* **Os handlers são parâmetros do construtor.** `on_list_tools=` e `on_call_tool=` entram em `Server(...)`. Não há decoradores aqui embaixo, e todo handler tem o mesmo formato: `async (ctx, params) -> result`.
* **Você escreve o schema de entrada.** `Tool.input_schema` é um `dict` JSON Schema comum. Ninguém o deriva de anotações de tipo, porque não há anotações de tipo de onde derivar.
* **Você monta o resultado.** `CallToolResult(content=[TextContent(...)])`, à mão. Nada é encapsulado, convertido ou inferido de uma anotação de retorno.
`params` é a requisição já parseada: `CallToolRequestParams` dá `.name` e `.arguments`. `ctx` é um `ServerRequestContext`: `ctx.session` para falar de volta com o cliente, `ctx.lifespan_context`, `ctx.request_id` e `ctx.meta`, o `_meta` de entrada da requisição.
!!! info
Se você já usou FastAPI, já conhece essa relação. O `MCPServer` é a camada de decoradores e anotações de tipo; o `Server` é o Starlette por baixo. Eles não são rivais: o `MCPServer` constrói um `Server` e registra nele handlers exatamente como esses.
### Experimente {#try-it}
Não existe Inspector para este aqui: `mcp dev` e `mcp run` só aceitam um `MCPServer`. O `Client` em memória não se importa; ele recebe um `Server` de baixo nível exatamente como recebe um `MCPServer`:
```python title="main.py"
import asyncio
from mcp import Client
from server import server
async def main() -> None:
async with Client(server) as client:
result = await client.call_tool("search_books", {"query": "dune", "limit": 5})
print(result.content)
asyncio.run(main())
```
```text
[TextContent(type='text', text="Found 3 books matching 'dune' (showing up to 5).", annotations=None, meta=None)]
```
O mesmo texto que a versão com `@mcp.tool()` produziu. Duas diferenças honestas:
* `result.structured_content` é `None`. O servidor de alto nível encapsula um `-> str` em `{"result": ...}` para você; aqui ninguém monta o que você não montou.
* `list_tools` retorna o schema que **você** digitou, caractere por caractere. A versão de alto nível tinha `"title": "Query"` em cada propriedade e um `"title": "search_booksArguments"` na raiz: artefatos do Pydantic. Aqui embaixo, se está no fio, foi você quem colocou lá.
## Nada é verificado por você {#nothing-is-checked-for-you}
O `MCPServer` rejeita um argumento ruim antes mesmo de a sua função executar, validando a chamada contra o schema que ele gerou (**[Ferramentas](../servers/tools.md)**).
O `Server` não faz isso. O seu `input_schema` é *anunciado* ao cliente; ele nunca é *aplicado* a `params.arguments`.
!!! check
Chame `search_books` sem `limit` e o seu `args["limit"]` levanta `KeyError`. O cliente vê:
```text
MCPError: Internal server error
```
Um erro JSON-RPC, código `-32603`, com uma mensagem deliberadamente genérica: o SDK não vaza o seu traceback para um chamador remoto. O modelo nunca descobre o que fez de errado, então não consegue tentar de novo. (Em um teste, `raise_exceptions=True` expõe a exceção real; veja **[Testes](../get-started/testing.md)**.)
Isso se generaliza. Uma exceção levantada de um handler de baixo nível é **sempre** um erro de protocolo, nunca um resultado de ferramenta com `is_error=True`. Se você quer que o modelo leia a falha e se recupere, valide `params.arguments` você mesmo e retorne `CallToolResult(content=[TextContent(...)], is_error=True)`. Os dois tipos de falha são o assunto de **[Tratando erros](../servers/handling-errors.md)**.
## Duas ferramentas, um handler {#two-tools-one-handler}
`on_call_tool` é o único ponto de entrada para todas as ferramentas do servidor. Você roteia por `params.name`:
```python title="server.py" hl_lines="38-43"
--8<-- "docs_src/lowlevel/tutorial002.py"
```
* `list_tools` anuncia as duas. `call_tool` despacha pelo nome.
* O ramo `else` importa: o `Server` encaminha sem reclamar um `tools/call` para um nome que você nunca listou direto para o seu handler. Levantar uma exceção ali transforma a chamada no mesmo `-32603` de cima.
## Saída estruturada, à mão {#structured-output-by-hand}
Declare `output_schema` na `Tool` e coloque `structured_content` no resultado. Os dois são seus:
```python title="server.py" hl_lines="19-23 36"
--8<-- "docs_src/lowlevel/tutorial003.py"
```
Chame e o resultado carrega as duas representações:
```json
{
"content": [{"type": "text", "text": "Found 3 books matching 'dune'."}],
"structuredContent": {"matches": 3, "query": "dune"},
"isError": false,
"resultType": "complete",
"_meta": {"io.modelcontextprotocol/serverInfo": {"name": "Bookshop", "version": "2.0.0"}}
}
```
O bloco `_meta` é o carimbo de identidade do servidor: o SDK o adiciona a todo resultado da era 2026, com a `version` vinda do construtor (um servidor que não define nenhuma reporta uma string vazia). Um servidor que não deve se identificar pode remover a chave com um middleware, que é dono dos resultados que retorna.
O servidor nunca compara os dois campos. O `Client` deste SDK compara: retorne um `structured_content` que não satisfaz o `output_schema` que você declarou e `call_tool` levanta um `RuntimeError` que começa com `Invalid structured content returned by tool search_books` e segue citando a falha do `jsonschema`. Prometer um schema é barato; cumprir a promessa é com você. A escada inteira de tipos de retorno e schemas está em **[Saída estruturada](../servers/structured-output.md)**.
## `_meta`: para a aplicação, não para o modelo {#\_meta-for-the-application-not-the-model}
`content` é a parte da resposta que o modelo lê. `structured_content` é a mesma resposta como dados tipados. `_meta` é o terceiro canal: dados que viajam junto com o resultado para a **aplicação cliente**, sem fazer parte da resposta de forma alguma.
Use para IDs de registro, IDs de trace, qualquer coisa de que a sua UI precisa e o seu prompt não:
```python title="server.py" hl_lines="37"
--8<-- "docs_src/lowlevel/tutorial004.py"
```
* Você o constrói como `_meta=`, o nome no fio. O cliente o lê de volta como `result.meta`.
* Use namespace nas suas chaves (`bookshop/record_ids`). As chaves `io.modelcontextprotocol/*` são reservadas pelo protocolo.
!!! warning
`_meta` é uma convenção entre você e a aplicação cliente, não uma garantia sobre o que chega
ao modelo. O host decide o que renderiza. Nunca coloque um segredo em nenhuma parte de um resultado de ferramenta.
## As capacidades seguem os seus handlers {#capabilities-follow-your-handlers}
Um `Server` anuncia exatamente as famílias de métodos para as quais você deu handlers. O `Bookshop` acima passa `on_list_tools` e `on_call_tool` e nada mais, então um cliente que se conecta a ele vê:
```json
{"tools": {"listChanged": false}}
```
Sem `resources`, sem `prompts`: não há nada que os sustente. Passe `on_list_prompts` e `prompts` aparece; passe `on_completion` e `completions` aparece.
O `MCPServer` sempre anuncia ferramentas, recursos e prompts, tenha você registrado algum ou não, porque os seus managers sempre existem. Aqui embaixo a declaração *é* a chamada ao construtor.
## O genérico do lifespan {#the-lifespan-generic}
O `Server` é genérico no tipo que o seu lifespan produz. Anote uma vez e o objeto fica tipado em todo lugar onde aparece:
```python title="server.py" hl_lines="24-26 44-45 50"
--8<-- "docs_src/lowlevel/tutorial005.py"
```
* O lifespan é um `Callable[[Server[Catalog]], AbstractAsyncContextManager[Catalog]]`; `@asynccontextmanager` em um gerador `async` dá exatamente isso.
* O que quer que ele produza com `yield` vira `ctx.lifespan_context`, e como os handlers são anotados com `ServerRequestContext[Catalog]`, `.search(...)` tem autocompletar e passa na checagem de tipos.
* Ele é aberto uma vez quando o servidor inicia e fechado uma vez quando para. Inicialização, encerramento e a versão do `MCPServer` da mesma ideia estão em **[Lifespan](../handlers/lifespan.md)**.
Sem um `lifespan=`, `ctx.lifespan_context` é um `dict` vazio.
## Um método só seu {#a-method-of-your-own}
O construtor cobre os métodos que o MCP define. `add_request_handler` cobre todo o resto:
```python title="server.py" hl_lines="35-36 39-40 43-44 48"
--8<-- "docs_src/lowlevel/tutorial006.py"
```
* O primeiro argumento é a string do método. Notificações têm um irmão gêmeo, `add_notification_handler`.
* `params_type` é o modelo contra o qual os `params` recebidos são validados **antes** de o seu handler executar, então métodos personalizados *recebem* a validação que as ferramentas não recebem. Faça subclasse de `RequestParams` para que o campo `_meta` seja parseado como o de qualquer outro método.
* O handler retorna um `BaseModel`, um `dict` ou `None`. O SDK serializa isso no resultado JSON-RPC.
Uma ressalva honesta: o `Client` de alto nível só tem verbos para os métodos que o MCP define, então não existe `client.reindex()`. Um método de fornecedor é para um par que já sabe que ele existe: um cliente que você também distribui, ou outro serviço seu falando JSON-RPC.
Um método que você não pode reivindicar:
```text
ValueError: 'initialize' is handled by the server runner and cannot be overridden;
use Server.middleware to observe or wrap initialization
```
O handshake pertence ao runner. `server/discover`, `ping` e todos os outros embutidos são seus para substituir.
!!! tip
`Server.middleware`, mencionado naquele erro, envolve **toda** mensagem de entrada, inclusive `initialize`. Se o que você quer é observar ou reescrever o tráfego em vez de responder a um método novo, comece por **[Middleware](middleware.md)**.
## Os outros handlers {#the-other-handlers}
Cada um destes é uma ideia para a qual você já tem o vocabulário; cada um tem sua própria página.
* `on_call_tool`, `on_get_prompt` e `on_read_resource` podem retornar um `InputRequiredResult` em vez do resultado normal para pausar a chamada e pedir entrada ao cliente; veja **[Requisições de múltiplas idas e voltas](../handlers/multi-round-trip.md)**. Fiel a este nível, nada é instalado para você: enquanto o `MCPServer` sela o `requestState` por padrão, aqui o `request_state` que você define atravessa o fio exatamente como foi escrito até você optar com `server.middleware.append(RequestStateBoundary(RequestStateSecurity(keys=[...]), default_audience=server.name))`: uma linha (os dois nomes são importados de `mcp.server.request_state`) para a mesma selagem e verificação que o `MCPServer` faz (**[Protegendo o `requestState`](../handlers/multi-round-trip.md#protecting-requeststate)**).
* `on_list_resources`, `on_read_resource`, `on_list_prompts`, `on_get_prompt`, `on_completion` têm o mesmo formato `(ctx, params) -> result` para as outras primitivas.
* `on_subscriptions_listen` serve o stream `subscriptions/listen` de 2026-07-28. Passe um `ListenHandler` construído sobre um `SubscriptionBus` e publique eventos no bus a partir dos seus outros handlers; veja **[Assinaturas](../handlers/subscriptions.md)** para a composição completa.
* `server.streamable_http_app()` retorna o mesmo app Starlette que o do `MCPServer`; faça o deploy dele do jeito que **[Executando o seu servidor](../run/index.md)** faz o deploy de qualquer outro app ASGI. Não existe `server.run(transport=...)` aqui embaixo: `server.run(read_stream, write_stream, server.create_initialization_options())` conduz uma conexão sobre um par de streams, e essa única linha é a história completa.
## Recapitulando {#recap}
* O `Server` de baixo nível recebe os seus handlers como **parâmetros do construtor** `on_*`; todo handler é `async (ctx, params) -> result`.
* Você escreve o dict `input_schema` e você monta o `CallToolResult`. Nada é derivado, encapsulado ou validado por você.
* Uma exceção em um handler é um erro de protocolo `-32603`. Um erro de ferramenta que o modelo consegue ler é um `CallToolResult` com `is_error=True` que **você** retorna.
* O `_meta` no resultado é endereçado à aplicação cliente, não ao modelo.
* `Server[T]` é genérico no que o seu lifespan produz; `ctx.lifespan_context` é um `T` tipado.
* `add_request_handler(method, params_type, handler)` serve qualquer método. `initialize` é reservado.
* As capacidades que um `Server` anuncia são derivadas de quais handlers você registrou.
`Client(server)` tratou os dois servidores de forma idêntica porque eles *são* o mesmo protocolo, e essa é justamente a ideia. A próxima camada abaixo nem é uma classe: é **[Middleware](middleware.md)**.

View file

@ -0,0 +1,123 @@
---
translation:
sections: [6048b4f308edbb8c, 068bda0f21ee9c1b, c3e565b61acd75c5, c62422b159c6ed09, 47204fab253cc45c]
tool: 1
---
# Middleware {#middleware}
Um **middleware** é uma única função async que envolve toda mensagem que o seu servidor recebe.
Você o escreve como `async (ctx, call_next)` e o adiciona ao fim de `server.middleware`. A API inteira é essa.
!!! warning
A lista de middlewares está marcada como **provisória** no código-fonte: a assinatura e a
semântica podem mudar em uma versão minor 2.x. Use-a para *observar* (tempo, logs, tracing) e
para *recusar* mensagens; não faça dela o alicerce sobre o qual o seu servidor se apoia.
`MCPServer` recebe a lista na construção (`MCPServer(name, middleware=[...])`) e a expõe como
`mcp.middleware`; o `Server` de baixo nível expõe a mesma lista como `server.middleware`. O exemplo
abaixo usa o `Server` de baixo nível; se `Server(name, on_call_tool=...)` é novidade para você, leia
**[O Server de baixo nível](low-level-server.md)** primeiro.
## Um middleware de medição de tempo {#a-timing-middleware}
Um servidor, uma ferramenta, um middleware que registra no log quanto tempo cada mensagem levou:
```python title="server.py" hl_lines="39-45 49"
--8<-- "docs_src/middleware/tutorial001.py"
```
* `ctx` é o mesmo `ServerRequestContext` que os seus handlers recebem. `ctx.method` é a string
bruta do método; `ctx.params` são os params brutos, **antes** de qualquer validação.
* `call_next(ctx)` executa o restante da cadeia: a validação, a busca do handler, o seu handler.
Retorne o que ele retornou e a resposta fica intacta.
* O `try`/`finally` é proposital: um handler que lança exceção ainda é cronometrado, porque a falha
chega ao seu middleware como a exceção que sai de `call_next`.
* `server.middleware.append(...)` faz o registro. A lista executa do mais externo para o mais
interno, então `middleware[0]` é o que fica mais perto do fio.
### Experimente {#try-it}
Conecte um cliente, liste as ferramentas, chame uma. O seu log tem **três** linhas:
```text
server/discover took 18.3 ms
tools/list took 0.1 ms
tools/call took 0.1 ms
```
Você fez duas chamadas e recebeu três linhas. A primeira é `server/discover`: a requisição que o
cliente enviou para estabelecer a conexão, antes de você pedir qualquer coisa.
É justamente esse o ponto. O middleware envolve **toda** mensagem de entrada:
* O estabelecimento da conexão: `server/discover`, ou `initialize` e `notifications/initialized`
em uma sessão legada.
* Toda requisição e toda notificação. Para uma notificação, `ctx.request_id is None`,
`call_next(ctx)` retorna `None` e o que quer que você retorne é descartado.
* Até um método para o qual o servidor não tem handler: `call_next` lança o
`MCPError(-32601, "Method not found")` *através* do seu middleware a caminho do cliente.
## O que você pode fazer dentro de um {#what-you-can-do-inside-one}
Em ordem crescente do quanto você deveria hesitar:
* **Observar.** Cronometre, conte, registre no log. O exemplo acima.
* **Recusar.** Lance um `MCPError` *em vez de* chamar `call_next(ctx)` e essa única mensagem é
respondida com um erro JSON-RPC. A conexão continua de pé; a próxima mensagem passa. É assim
que um servidor controla o acesso a `subscriptions/listen` por chamador:
**[Decidindo quem pode observar](../handlers/subscriptions.md#deciding-who-may-watch)**, na
página de Assinaturas, percorre o passo a passo.
* **Reescrever.** `ctx` é uma dataclass: `await call_next(dataclasses.replace(ctx, params=...))`
entrega ao restante da cadeia params diferentes dos que o cliente enviou. Nunca faça isso com
`initialize`: o resultado que o cliente recebe de volta é construído a partir dos seus params
reescritos, mas o servidor grava o estado da conexão a partir dos params originais do fio. Os
dois lados podem terminar o handshake discordando sobre o que negociaram.
* **Responder.** Retorne um resultado sem chamar `call_next(ctx)` e ele vai para o cliente como a
sua resposta. `call_next` entrega a você a forma final do fio, e o pipeline nunca altera o que
você retorna, então o envelope inteiro é seu: em uma conexão da era 2026 isso inclui o carimbo
`_meta` de `serverInfo`, que o SDK adiciona aos resultados dos handlers, mas não aos seus.
!!! check
`initialize` é uma das coisas que o middleware envolve, e é o *único* gancho que você tem
para ele. Tente assumi-lo com `add_request_handler` e o SDK recusa:
```text
ValueError: 'initialize' is handled by the server runner and cannot be overridden;
use Server.middleware to observe or wrap initialization
```
!!! warning
`initialize` é tratado inline: o servidor não lê mais nenhuma mensagem de entrada até a sua
cadeia de middlewares retornar. Aguardar uma requisição do servidor para o cliente
(`ctx.session.send_request(...)`, uma elicitação (elicitation)) enquanto trata `initialize`,
portanto, **trava a conexão em deadlock**: a resposta que você está esperando nunca poderá ser
lida. Notificações do tipo fire-and-forget não têm problema.
## O único middleware que já vem ligado por padrão {#the-one-middleware-that-ships-on-by-default}
O SDK traz exatamente um middleware, e ele já está na lista do seu servidor: o que emite um span
do OpenTelemetry para cada mensagem. Você não o adiciona, e na maior parte do tempo nem pensa
nele. Ele é um no-op até você instalar um exportador, e tem a própria página:
**[OpenTelemetry](../run/opentelemetry.md)**.
!!! info
Se você já escreveu middleware ASGI, já conhece esse formato. O `(scope, receive, send)`
do Starlette virou `(ctx, call_next)`, e ele executa *depois* do transporte, sobre a mensagem
decodificada em vez da requisição HTTP bruta. Os dois se compõem: o middleware do Starlette
em `streamable_http_app()` enxerga HTTP; este enxerga MCP.
## Recapitulando {#recap}
* Um middleware é `async (ctx, call_next) -> result`, passado como `MCPServer(middleware=[...])` (ou
adicionado a `mcp.middleware`) e adicionado a `server.middleware` no `Server` de baixo nível.
* Ele envolve **toda** mensagem de entrada (`server/discover`, `initialize`, requisições,
notificações, métodos desconhecidos) e executa do mais externo para o mais interno.
* `ctx.request_id is None` é como você distingue uma notificação de uma requisição.
* Lance uma exceção em vez de chamar `call_next` para recusar uma mensagem; a conexão sobrevive.
* O tracing do OpenTelemetry do próprio SDK também é um middleware, já na lista. Veja
**[OpenTelemetry](../run/opentelemetry.md)**.
* Toda essa superfície é provisória. Observe com ela; não construa em cima dela.
Isso é tudo o que envolve uma requisição. **[Autorização](../run/authorization.md)** é o que decide
se a requisição chega a ser executada.

View file

@ -0,0 +1,85 @@
---
translation:
sections: [a9aba7a026c7bd85, ed32bda7ba9ae33a, 7e64cc5646abb91f, 22a0129ee78b3c63, d875373c06d8d2f9]
tool: 1
---
# Paginação {#pagination}
A maioria dos servidores nunca precisa disso.
O `MCPServer` responde a toda requisição `list_*` com tudo o que tem, em uma única página, `next_cursor=None`. Para algumas dezenas de ferramentas, recursos ou prompts, essa é a resposta certa e não há nada para configurar.
A paginação é para o servidor cuja lista de recursos é, na verdade, um banco de dados: milhares de linhas que ele se recusa a serializar em uma única resposta. A resposta do protocolo é um **cursor**: o servidor retorna uma página mais um token opaco, e o cliente envia esse token de volta para obter a próxima página.
O `@mcp.resource()` não tem nenhum gancho para isso. Para paginar, você mesmo escreve o handler de listagem, no **[Server de baixo nível](low-level-server.md)**.
## Um servidor que pagina {#a-server-that-pages}
```python title="server.py" hl_lines="12 15-16"
--8<-- "docs_src/pagination/tutorial001.py"
```
* Em um `Server` de baixo nível, os handlers são argumentos do construtor, não decoradores. O `on_list_resources` responde a toda requisição `resources/list`; a ligação inteira é essa.
* Todo handler paginado é tipado como `params: PaginatedRequestParams | None`, e o exemplo aceita os dois. Em uma conexão, porém, o SDK nunca entrega `None` para você (uma requisição sem o membro `params` chega ao handler como o modelo com seus valores padrão), então o sinal que importa é `params.cursor is None`: **comece do início**.
* Você decide o que um cursor *é*. Aqui é um offset representado como string. Um timestamp, uma chave primária, um blob em base64: qualquer coisa que você consiga gerar na saída e reconhecer na volta.
* `next_cursor=None` é como você diz "essa foi a última página". Não há contagem, nem total, nem `has_more`. O `None` é o sinal inteiro.
!!! tip
Um `PAGE_SIZE` de 10 deixa o exemplo legível. Escolha o seu por endpoint: uma lista de
recursos de uma linha comporta uma página de 500; uma lista de templates de prompt pesados, não.
O cliente não tem voz nisso, e é assim de propósito.
### Experimente {#try-it}
`Client(server)` se conecta a um `Server` de baixo nível em memória exatamente como se conecta a um `MCPServer`.
Chame `list_resources()` sem argumentos. Você recebe dez recursos, de `book-1` a `book-10`, e `next_cursor` é a string `"10"`.
Devolva-a com `list_resources(cursor="10")` e o primeiro recurso é `book-11`, o novo `next_cursor` é `"20"`.
A décima página volta com `next_cursor` definido como `None`. Pronto.
## O loop do cliente {#the-client-loop}
Todo método `list_*` do `Client` (`list_tools`, `list_resources`, `list_resource_templates`, `list_prompts`) aceita a palavra-chave `cursor=`. Esgotar uma lista paginada é um único `while True`:
```python title="client.py" hl_lines="26-32"
--8<-- "docs_src/pagination/tutorial002.py"
```
* `cursor` começa como `None`, então a primeira requisição não carrega nenhum cursor.
* Acumule **antes** de olhar para `next_cursor`: a última página também tem recursos.
* `next_cursor is None` é a saída. Qualquer outra coisa volta direto para `cursor=`, intocada.
Execute o `main()` dele e ele imprime `100 resources`: dez páginas de dez, costuradas por um loop que nunca soube que havia dez páginas.
Este é o mesmo loop que **[O cliente](../client/index.md)** mostra para todo verbo `list_*`, e ele não custa nada contra um servidor que não pagina: `next_cursor` é `None` na primeira resposta e o loop roda uma vez.
## As três regras {#the-three-rules}
**Cursores são opacos.** Um cliente nunca deve interpretar, construir ou adivinhar um. A única fonte legítima de um cursor é o `next_cursor` da página anterior, literalmente.
**O servidor escolhe o tamanho da página.** Não existe `limit=` no protocolo. Se você precisa de um tamanho de página diferente, altere o servidor.
**Um cliente que ignora a paginação continua funcionando.** Ele chama `list_resources()` uma vez, recebe os dez primeiros e nunca percebe o `next_cursor` que jogou fora. Nada quebra; ele só vê menos.
!!! check
Opaco quer dizer opaco. Invente um cursor (`list_resources(cursor="page-2")`) e não há
nada que o protocolo possa fazer por você. Este servidor tenta `int("page-2")`, o handler lança uma exceção,
e o que volta para o cliente é:
```text
MCPError(-32603, 'Internal server error', None)
```
Um cursor que você não recebeu do servidor é um bug, não um pedido de funcionalidade.
## Recapitulando {#recap}
* O `MCPServer` retorna tudo em uma página. A paginação é opcional, e você opta por ela no `Server` de baixo nível.
* `on_list_resources` (e `on_list_tools`, `on_list_prompts`, `on_list_resource_templates`) recebe `PaginatedRequestParams | None`; `params.cursor` é `None` na primeira página.
* Você retorna uma página mais `next_cursor`: qualquer string que você vá reconhecer depois, ou `None` quando não sobrar nada.
* O loop do cliente: passe `cursor=`, acumule, repita até `next_cursor is None`.
* Cursores são opacos, o servidor é dono do tamanho da página, e um cliente que não pagina ainda recebe a primeira página.
O restante da API do `Server` escrita à mão (`on_call_tool`, dicts `input_schema`, `_meta`) está em **[O Server de baixo nível](low-level-server.md)**.