docs: publish translated docs in twelve languages and the tool that maintains them (#3280)
This commit is contained in:
parent
14aa889992
commit
52ad0a8876
9 changed files with 89737 additions and 54 deletions
130
i18n/pt/pages/servers/completions.md
Normal file
130
i18n/pt/pages/servers/completions.md
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
---
|
||||
translation:
|
||||
sections: [72f9c964769076dd, 9a2c14e10935b515, 235299eb78ab12d7, 8aee1e78c8237fb8, 9bd86acd4112138f, 55343cb7f250dc7b]
|
||||
tool: 1
|
||||
---
|
||||
# Completions {#completions}
|
||||
|
||||
Um cliente que monta uma UI em cima do seu servidor quer autocompletar os valores dos argumentos enquanto o usuário digita: nomes de linguagens, nomes de repositórios, caminhos de arquivo.
|
||||
|
||||
É com as **completions** que o seu servidor fornece essas sugestões.
|
||||
|
||||
## Algo que valha a pena completar {#something-worth-completing}
|
||||
|
||||
As completions se aplicam a exatamente duas coisas: os argumentos de um **prompt** e os parâmetros de um **template de recurso**. Então comece com um servidor que tenha um de cada:
|
||||
|
||||
```python title="server.py" hl_lines="6 12"
|
||||
--8<-- "docs_src/completions/tutorial001.py"
|
||||
```
|
||||
|
||||
Ainda não há nada de completions aqui.
|
||||
|
||||
* `review_code` recebe um `language`. O usuário não deveria precisar adivinhar quais grafias você aceita.
|
||||
* `github_repo` recebe um `owner` e um `repo`. Campos de texto livre para os dois resultam em um formulário ruim.
|
||||
|
||||
## O handler de completion {#the-completion-handler}
|
||||
|
||||
Adicione **uma** função decorada com `@mcp.completion()`:
|
||||
|
||||
```python title="server.py" hl_lines="21-29"
|
||||
--8<-- "docs_src/completions/tutorial002.py"
|
||||
```
|
||||
|
||||
* Existe um handler por servidor. Toda requisição de completion chega aqui, e você ramifica de acordo com o que está sendo completado.
|
||||
* Ele precisa ser `async def`: o SDK faz o await dele.
|
||||
* Ele recebe três argumentos:
|
||||
* `ref`: *qual* prompt ou template de recurso, como um `PromptReference` ou um `ResourceTemplateReference`. É com `isinstance` que você distingue um do outro.
|
||||
* `argument`: `argument.name` é o argumento que está sendo completado, `argument.value` é o que o usuário digitou até agora.
|
||||
* `context`: os argumentos já resolvidos. Ignore-o por enquanto.
|
||||
* Você retorna um `Completion(values=[...])`, ou `None` quando não tem nada a oferecer.
|
||||
|
||||
!!! tip
|
||||
`argument.value` é o prefixo que o usuário digitou. O SDK **não** filtra para você: o que
|
||||
você colocar em `values` é o que a UI mostra. O `startswith` é você quem escreve.
|
||||
|
||||
### Experimente {#try-it}
|
||||
|
||||
Use o `Client` em memória de **[Testes](../get-started/testing.md)** para exercitá-lo. Chame
|
||||
`client.complete()` com `ref=PromptReference(name="review_code")` e
|
||||
`argument={"name": "language", "value": "py"}`:
|
||||
|
||||
```python
|
||||
result.completion.values # ['python']
|
||||
```
|
||||
|
||||
* `ref` é o mesmo tipo de referência que o seu handler recebe.
|
||||
* `argument` é um dict simples com exatamente duas chaves, `name` e `value`.
|
||||
|
||||
Envie um `value` vazio e você recebe a lista inteira de volta. `lang.startswith("")` é verdadeiro para toda linguagem:
|
||||
|
||||
```python
|
||||
result.completion.values # ['go', 'javascript', 'python', 'rust', 'typescript']
|
||||
```
|
||||
|
||||
Pergunte sobre `code` (um argumento que o seu handler não reconhece) e ele retorna `None`, que o SDK transforma em uma lista vazia:
|
||||
|
||||
```python
|
||||
result.completion.values # []
|
||||
```
|
||||
|
||||
`None` significa *"sem sugestões"*, nunca um erro. A UI recorre a uma caixa de texto simples.
|
||||
|
||||
## Uma capacidade que você nunca declarou {#a-capability-you-never-declared}
|
||||
|
||||
Registrar o handler é a declaração. Conecte um cliente e veja:
|
||||
|
||||
```python
|
||||
client.server_capabilities.completions # CompletionsCapability()
|
||||
```
|
||||
|
||||
Você não listou `completions` em lugar nenhum. O SDK viu o handler e declarou a capacidade por você. Toda capacidade *opcional* funciona assim: o handler é a declaração. (As três primitivas não são opcionais: o `MCPServer` sempre as declara, com ou sem handlers.)
|
||||
|
||||
!!! check
|
||||
Volte ao primeiro `server.py` (aquele sem handler) e pergunte mesmo assim. A chamada falha
|
||||
com um erro JSON-RPC:
|
||||
|
||||
```text
|
||||
Method not found
|
||||
```
|
||||
|
||||
E `client.server_capabilities.completions` é `None`. É para isso que a capacidade existe: um
|
||||
cliente bem-comportado a confere e nunca envia uma requisição que você não tem como responder.
|
||||
|
||||
## Argumentos dependentes {#dependent-arguments}
|
||||
|
||||
`github://repos/{owner}/{repo}` tem dois parâmetros, e os valores úteis para `repo` dependem de qual `owner` foi escolhido antes.
|
||||
|
||||
É para isso que serve o `context`. Ele carrega os argumentos que o usuário **já resolveu**:
|
||||
|
||||
```python title="server.py" hl_lines="8-11 34-38"
|
||||
--8<-- "docs_src/completions/tutorial003.py"
|
||||
```
|
||||
|
||||
* O novo ramo é acionado para o parâmetro `repo` do template.
|
||||
* `context.arguments` é um `dict[str, str] | None` com os valores escolhidos até agora (aqui, `owner`).
|
||||
* Sem `owner` ainda, não há sugestões que façam sentido, então o handler retorna `None`.
|
||||
|
||||
O cliente envia esses valores resolvidos com `context_arguments=`. Desta vez, `ref` é um
|
||||
`ResourceTemplateReference(uri="github://repos/{owner}/{repo}")`. Peça `repo` com um
|
||||
`value` vazio e passe `context_arguments={"owner": "modelcontextprotocol"}`:
|
||||
|
||||
```python
|
||||
result.completion.values # ['python-sdk', 'typescript-sdk', 'inspector']
|
||||
```
|
||||
|
||||
Tire o `context_arguments=` e a mesma chamada retorna `[]`. O handler não tem como saber quais repositórios oferecer antes de saber quem é o owner.
|
||||
|
||||
!!! info
|
||||
`Completion` também aceita `total=` e `has_more=`. Defina-os quando `values` for uma fatia de uma
|
||||
lista maior, para que a UI possa mostrar *"e mais 200"*. A maioria dos handlers nunca precisa deles.
|
||||
|
||||
## Recapitulando {#recap}
|
||||
|
||||
* Completions são sugestões para **argumentos de prompt** e **parâmetros de template de recurso**. Nada mais.
|
||||
* `@mcp.completion()` registra o único handler. Ele é `async def (ref, argument, context) -> Completion | None`.
|
||||
* Ramifique com base em `isinstance(ref, ...)` e em `argument.name`. Filtre por `argument.value` você mesmo.
|
||||
* `None` vira uma lista vazia. Nunca é um erro.
|
||||
* `context.arguments` guarda os valores já resolvidos; o cliente os fornece como `context_arguments=`.
|
||||
* A capacidade `completions` aparece no momento em que você registra o handler. Sem ele, a requisição dá `Method not found`.
|
||||
|
||||
As sugestões ajudam enquanto o usuário ainda está *preenchendo* um prompt ou template; para fazer uma pergunta a ele no *meio* de uma chamada de ferramenta, o que você quer é a **[Elicitação](../handlers/elicitation.md)** (elicitation). Tudo o que uma ferramenta pode retornar além de texto está em **[Imagens, áudio e ícones](media.md)**.
|
||||
139
i18n/pt/pages/servers/handling-errors.md
Normal file
139
i18n/pt/pages/servers/handling-errors.md
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
---
|
||||
translation:
|
||||
sections: [e33d441f12d50535, 7099694c603e0f5f, c1df4cf9673433e6, c9cd294541422e6e, 6cec073617bfd037, efa92b8f99e908c8, 6a22a29e27fb4601]
|
||||
tool: 1
|
||||
---
|
||||
# Tratando erros {#handling-errors}
|
||||
|
||||
Uma ferramenta (tool) pode falhar de duas maneiras, e o SDK trata cada uma de forma bem diferente.
|
||||
|
||||
Lance uma exceção comum e é o **modelo** que a vê. Lance `MCPError` e é o **protocolo** que a vê.
|
||||
|
||||
Esta página é sobre essa escolha.
|
||||
|
||||
## Um erro que o modelo consegue corrigir {#an-error-the-model-can-fix}
|
||||
|
||||
Pegue uma ferramenta que faz uma consulta e deixe a consulta não encontrar nada:
|
||||
|
||||
```python title="server.py" hl_lines="11-12"
|
||||
--8<-- "docs_src/handling_errors/tutorial001.py"
|
||||
```
|
||||
|
||||
Não há nada de MCP nessas duas linhas. `get_author` lança um `ValueError` comum, como qualquer função Python faria.
|
||||
|
||||
Chame a ferramenta com um título que não está no catálogo e veja o resultado:
|
||||
|
||||
```python
|
||||
result.is_error # True
|
||||
result.content # [TextContent(text="Error executing tool get_author: No book titled 'Nothing' in the catalog.")]
|
||||
result.structured_content # None
|
||||
```
|
||||
|
||||
* A requisição **foi bem-sucedida**. Há um resultado; nada foi lançado no lado de quem chamou.
|
||||
* `is_error` é `True`, e a mensagem da sua exceção (prefixada com o nome da ferramenta) está em `content`, exatamente onde o modelo lê.
|
||||
* `structured_content` é `None`. Uma chamada que falhou não tem valor de retorno para estruturar.
|
||||
|
||||
Isso é um **erro de ferramenta**, e é o padrão para *qualquer* exceção que a sua ferramenta lançar. Também é, quase sempre, o que você quer.
|
||||
|
||||
Quem chama a sua ferramenta é o modelo. Foi ele que escolheu os argumentos. Então um erro de ferramenta é um turno na conversa: o modelo lê *"No book titled 'Nothing' in the catalog."*, percebe que chutou o título errado e chama de novo com um melhor. Você escreveu um `raise` e ganhou um agente que se corrige sozinho.
|
||||
|
||||
!!! tip
|
||||
Nunca faça `return` de uma mensagem de erro em uma ferramenta. Uma string retornada tem `is_error=False`, então, para o
|
||||
modelo (e para toda interface de cliente), parece que a ferramenta funcionou e que aquela string era a resposta.
|
||||
Use `raise`. A flag é o sinal.
|
||||
|
||||
## Um erro que o modelo não consegue corrigir {#an-error-the-model-cannot-fix}
|
||||
|
||||
Agora troque `ValueError` por `MCPError`.
|
||||
|
||||
```python title="server.py" hl_lines="1 3 14"
|
||||
--8<-- "docs_src/handling_errors/tutorial002.py"
|
||||
```
|
||||
|
||||
`MCPError` é o **erro de protocolo** do SDK. É a única exceção que o wrapper da ferramenta *não* captura: ela se propaga, e a requisição `tools/call` inteira falha com um erro JSON-RPC em vez de um resultado.
|
||||
|
||||
```json
|
||||
{
|
||||
"code": -32602,
|
||||
"message": "No book titled 'Nothing' in the catalog."
|
||||
}
|
||||
```
|
||||
|
||||
* **Não há resultado**. Sem `content`, sem `is_error`: nada para o modelo ler.
|
||||
* Quem recebe o erro é a aplicação **host**, do mesmo jeito que receberia se a ferramenta nem existisse.
|
||||
* `code`, `message` e `data` chegam intactos. `INVALID_PARAMS` é `-32602`; `mcp.types` exporta esse e os outros códigos de erro JSON-RPC (`INVALID_REQUEST`, `INTERNAL_ERROR`, ...) como constantes, para que você nunca precise digitar um número mágico.
|
||||
|
||||
!!! check
|
||||
Mesma consulta, mesma falha, mas agora a chamada *lança* a exceção no lado do cliente em vez de retornar:
|
||||
|
||||
```text
|
||||
mcp.shared.exceptions.MCPError: No book titled 'Nothing' in the catalog.
|
||||
```
|
||||
|
||||
A primeira versão entregou ao modelo uma frase à qual ele podia reagir. Esta não entrega nada.
|
||||
Para `get_author` isso é estritamente pior, e é esse o ponto da próxima seção.
|
||||
|
||||
## Qual delas lançar {#which-one-to-raise}
|
||||
|
||||
Os dois caminhos respondem a duas perguntas diferentes.
|
||||
|
||||
* **Lance qualquer exceção** para uma falha de *execução*: aquilo que a sua ferramenta tentou fazer não funcionou. Foi o modelo que escolheu a chamada, então é o modelo que deve ver a consequência e ter a chance de se recuperar. Um título escrito errado, uma API upstream que deu timeout, uma linha que não existe: tudo erro de ferramenta.
|
||||
* **Lance `MCPError`** quando a *própria requisição* deve ser rejeitada: o cliente não tem uma capacidade da qual a sua ferramenta depende, o servidor não está em condições de atender ninguém, quem chamou pulou uma etapa obrigatória. Nenhuma nova tentativa do modelo corrige nada disso, então não há nada a ganhar entregando a mensagem a ele.
|
||||
|
||||
Uma pergunta decide: **um modelo mais esperto teria evitado isso?** Sim -> exceção comum. Não -> `MCPError`.
|
||||
|
||||
Por esse critério, a segunda versão de `get_author` fez a escolha errada: um título melhor resolve, então o modelo merecia ver a mensagem. Ela está ali para mostrar o mecanismo, não para recomendá-lo.
|
||||
|
||||
!!! info
|
||||
`MCPError` fica em `from mcp import MCPError` e recebe `code`, `message` e um payload
|
||||
`data` opcional. O que você colocar neles é o que o cliente recebe: o SDK repassa um
|
||||
`MCPError` lançado tal e qual, em vez de sanitizá-lo.
|
||||
|
||||
## Um recurso que não existe {#a-resource-that-doesnt-exist}
|
||||
|
||||
Recursos fazem a mesma distinção, e vêm com uma exceção nomeada para o caso mais comum.
|
||||
|
||||
```python title="server.py" hl_lines="2 13"
|
||||
--8<-- "docs_src/handling_errors/tutorial003.py"
|
||||
```
|
||||
|
||||
`books://{title}` é um **template**. Ele casa com *qualquer* título, então "a URI está bem formada" e "o livro existe" são duas perguntas diferentes, e só a sua função consegue responder à segunda.
|
||||
|
||||
Quando não consegue, lance `ResourceNotFoundError`. O SDK a transforma no erro de protocolo que a especificação atribui a um recurso ausente: `-32602` com a URI requisitada em `data`, para que o cliente saiba *qual* leitura falhou.
|
||||
|
||||
```json
|
||||
{
|
||||
"code": -32602,
|
||||
"message": "No book titled 'Nothing' in the catalog.",
|
||||
"data": {"uri": "books://Nothing"}
|
||||
}
|
||||
```
|
||||
|
||||
Repare que aqui não existe um meio-resultado com `is_error=True`. A leitura de um recurso ou retorna conteúdo ou falha: recursos só têm o caminho do protocolo. Templates e todo o resto sobre recursos ficam em **[Recursos](resources.md)**.
|
||||
|
||||
## Erros que você nunca lança {#errors-you-never-raise}
|
||||
|
||||
Um argumento inválido nunca chega à sua função.
|
||||
|
||||
Mande para `get_author` um `title` que não seja uma string e o SDK o rejeita com base no schema de entrada **antes** de chamar você, como o mesmo tipo de erro de ferramenta com `is_error=True` que o modelo consegue ler e corrigir. **[Ferramentas](tools.md)** mostra a mesma rejeição com uma restrição `Field(le=50)`.
|
||||
|
||||
Isso significa uma classe inteira de instruções `raise` que você não escreve: não revalide as suas próprias anotações de tipo.
|
||||
|
||||
!!! info
|
||||
Tudo nesta página é o que um **cliente** vê, e o `Client` em memória com o qual você vai escrever
|
||||
seus testes vê exatamente a mesma coisa. Nem `raise_exceptions=True` transforma um erro de ferramenta
|
||||
de volta em traceback: no momento em que essa flag poderia agir, a sua exceção já virou o
|
||||
resultado com `is_error=True`. Faça o assert no resultado. **[Testes](../get-started/testing.md)** cobre o padrão.
|
||||
|
||||
## Recapitulando {#recap}
|
||||
|
||||
* Lance **qualquer exceção** em uma ferramenta -> a chamada retorna `is_error=True` com a sua mensagem em `content`. O modelo lê e pode tentar de novo. Esse é o padrão.
|
||||
* Lance **`MCPError`** -> a própria chamada falha com um erro JSON-RPC. O modelo não vê nada; quem lida com isso é o host. `code`, `message` e `data` sobrevivem intactos.
|
||||
* A pergunta que decide: *um modelo mais esperto teria evitado isso?* Sim -> exceção. Não -> `MCPError`.
|
||||
* `ResourceNotFoundError` em um handler de recurso -> o `-32602` do protocolo, com a URI em `data`.
|
||||
* Argumentos inválidos são rejeitados com base no schema antes de a sua função executar; você não dá `raise` para eles.
|
||||
* `from mcp import MCPError`; as constantes de código de erro vêm de `mcp.types`.
|
||||
|
||||
Erros tratados. Isso é tudo o que um servidor *expõe*. O que cada handler pode ler, e fazer de volta ao cliente enquanto executa, é a próxima seção: **[Dentro do seu handler](../handlers/index.md)**.
|
||||
|
||||
O texto exato dos erros do SDK que você tem mais chance de encontrar, o que cada um significa e a correção de um passo só para cada um estão em **[Solução de problemas](../troubleshooting.md)**.
|
||||
35
i18n/pt/pages/servers/index.md
Normal file
35
i18n/pt/pages/servers/index.md
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
---
|
||||
translation:
|
||||
sections: [09defc170a0da89d]
|
||||
tool: 1
|
||||
---
|
||||
# Servidores {#servers}
|
||||
|
||||
Um `MCPServer` expõe três primitivas a um cliente conectado. O que as
|
||||
distingue é quem decide usá-las:
|
||||
|
||||
* Uma **[ferramenta](tools.md)** (tool) é uma ação que o *modelo* escolhe e chama. Esta é
|
||||
a página que a maioria das pessoas procura primeiro, e
|
||||
**[Saída estruturada](structured-output.md)** é a referência que a acompanha:
|
||||
tudo sobre o formato do que uma ferramenta retorna.
|
||||
* Um **[recurso](resources.md)** é um dado somente leitura que a *aplicação*
|
||||
escolhe ler. **[Templates de URI](uri-templates.md)** é a referência que o
|
||||
acompanha: a sintaxe completa de endereçamento e as regras de segurança de caminhos.
|
||||
* Um **[prompt](prompts.md)** é um template de mensagem que uma *pessoa* invoca pelo
|
||||
nome, a partir de um menu ou de um comando de barra.
|
||||
|
||||
Em torno das três primitivas, o restante do que um servidor declara:
|
||||
|
||||
* **[Autocompletar](completions.md)** (completions) é o preenchimento automático, feito no
|
||||
servidor, dos argumentos de prompts e de templates de recurso.
|
||||
* **[Imagens, áudio e ícones](media.md)** cobre tudo o que uma ferramenta pode
|
||||
retornar além de texto, e os ícones que um cliente mostra ao lado do seu servidor.
|
||||
* **[Tratamento de erros](handling-errors.md)** explica a diferença entre um
|
||||
erro do qual o modelo consegue se recuperar e um que ele nunca deve ver.
|
||||
|
||||
Cada página aqui é independente; vá direto para a que você precisa. Se você ainda não
|
||||
construiu um servidor, comece antes por **[Primeiros passos](../get-started/first-steps.md)**.
|
||||
|
||||
O que acontece *dentro* das funções que você registra (o `Context`, a injeção de dependência,
|
||||
pedir mais informações ao usuário no meio de uma chamada) é o assunto da próxima seção,
|
||||
**[Dentro do seu handler](../handlers/index.md)**.
|
||||
122
i18n/pt/pages/servers/media.md
Normal file
122
i18n/pt/pages/servers/media.md
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
---
|
||||
translation:
|
||||
sections: [496394d24d221bf1, 4ceb4591180dc6c3, 0fd63e4682d02e0c, 969ede0bd3686a16, 043f526230dd243d, 6ee3e9bcfd24047a]
|
||||
tool: 1
|
||||
---
|
||||
# Mídia {#media}
|
||||
|
||||
Texto não é a única coisa que uma ferramenta pode retornar.
|
||||
|
||||
O SDK traz dois helpers para resultados binários (**`Image`** e **`Audio`**) e um tipo **`Icon`** para dar uma cara ao seu servidor, às ferramentas, aos recursos e aos prompts na interface do cliente.
|
||||
|
||||
## Retornando uma imagem {#returning-an-image}
|
||||
|
||||
Anote o tipo de retorno como `Image`, aponte para um arquivo e retorne:
|
||||
|
||||
```python title="server.py" hl_lines="8 12 14"
|
||||
--8<-- "docs_src/media/tutorial001.py"
|
||||
```
|
||||
|
||||
* `Image` recebe exatamente um entre `path` (um arquivo a ser lido) ou `data` (bytes brutos).
|
||||
* O tipo MIME que o cliente vê é inferido a partir do sufixo: `logo.png` é anunciado como `image/png`.
|
||||
* Não há nada aqui específico de logos. Qualquer PNG ao lado de `server.py` funciona: um gráfico que seu código renderizou, um diagrama, uma foto.
|
||||
|
||||
`Image` é uma conveniência do SDK, não um tipo do protocolo. Na rede, o seu valor de retorno vira um bloco **`ImageContent`** (os bytes do arquivo codificados em base64, mais o tipo MIME):
|
||||
|
||||
```python
|
||||
result.content # [ImageContent(type="image", data="iVBORw0KGgoAAAANSUhEUg...", mime_type="image/png")]
|
||||
result.structured_content # None
|
||||
```
|
||||
|
||||
Repare em duas coisas:
|
||||
|
||||
* `data` é base64. Você nunca tocou nos bytes; o SDK leu o arquivo e fez a codificação.
|
||||
* `structured_content` é `None`. Uma `Image` é conteúdo para o modelo olhar, não dados para a aplicação interpretar: não há schema de saída. (Compare com **[Saída estruturada](structured-output.md)**, onde a anotação de retorno *é* o schema.)
|
||||
|
||||
!!! info
|
||||
`ImageContent` e `AudioContent` ficam em `mcp.types`, bem ao lado do `TextContent`
|
||||
em que um resultado `str` simples se transforma (**[Ferramentas](tools.md)**). O resultado de uma ferramenta é uma lista de blocos de conteúdo; `Image` e `Audio` são
|
||||
o caminho mais curto para produzir os dois tipos binários.
|
||||
|
||||
### Experimente {#try-it}
|
||||
|
||||
Coloque qualquer PNG ao lado de `server.py`, dê a ele o nome `logo.png` e execute:
|
||||
|
||||
```console
|
||||
uv run mcp dev server.py
|
||||
```
|
||||
|
||||
Abra a aba **Tools** e chame `logo`. O resultado não é uma string: é um bloco de conteúdo `image`, e o Inspector renderiza sua imagem. Tudo o que aconteceu entre o arquivo no disco e os pixels na tela foi obra do SDK.
|
||||
|
||||
## Retornando áudio {#returning-audio}
|
||||
|
||||
`Audio` segue o mesmo molde. Mantenha `logo.png` onde estava e coloque qualquer WAV ao lado dele como `chime.wav`:
|
||||
|
||||
```python title="server.py" hl_lines="18-21"
|
||||
--8<-- "docs_src/media/tutorial002.py"
|
||||
```
|
||||
|
||||
O resultado é um bloco **`AudioContent`**:
|
||||
|
||||
```python
|
||||
result.content # [AudioContent(type="audio", data="UklGR...", mime_type="audio/wav")]
|
||||
result.structured_content # None
|
||||
```
|
||||
|
||||
Funciona do mesmo jeito: entra um arquivo em disco, saem base64 e um tipo MIME, nenhum schema de saída.
|
||||
|
||||
## Bytes ou um arquivo {#bytes-or-a-file}
|
||||
|
||||
Os dois helpers também aceitam `data=` (bytes brutos) em vez de `path=`. Esse é o modo para bytes que nunca vieram de um arquivo próprio — uma coluna de banco de dados, uma resposta HTTP, algo que o Pillow acabou de desenhar:
|
||||
|
||||
```python title="server.py" hl_lines="14 15"
|
||||
--8<-- "docs_src/media/tutorial003.py"
|
||||
```
|
||||
|
||||
Com `path=` não há nada a declarar: o arquivo é lido quando o resultado é montado, e o tipo MIME é inferido a partir do sufixo:
|
||||
|
||||
* `Image`: `.png`, `.jpg`, `.jpeg`, `.gif`, `.webp`.
|
||||
* `Audio`: `.wav`, `.mp3`, `.ogg`, `.flac`, `.aac`, `.m4a`.
|
||||
|
||||
Um sufixo não reconhecido cai no padrão `application/octet-stream`.
|
||||
|
||||
!!! check
|
||||
Com `data=` não há nome de arquivo, então não há de onde inferir nada. Esqueça o `format=` e
|
||||
o SDK recorre a um padrão: `image/png` para imagens, `audio/wav` para áudio. Monte um
|
||||
`Audio` a partir de bytes MP3 desse jeito e o cliente recebe `mime_type="audio/wav"` e,
|
||||
confiando nisso, falha ao decodificar. Quando você passar `data=`, passe `format=`.
|
||||
|
||||
## Ícones {#icons}
|
||||
|
||||
Um `Icon` é metadado, não conteúdo. Ele não carrega a imagem; aponta para uma por meio de uma URI, e um cliente pode buscá-la e mostrá-la ao lado do nome do seu servidor, de uma ferramenta, de um recurso ou de um prompt.
|
||||
|
||||
```python title="server.py" hl_lines="4-5 7 10 16"
|
||||
--8<-- "docs_src/media/tutorial004.py"
|
||||
```
|
||||
|
||||
* `src` é uma URI que o cliente consegue resolver: `https:`, ou uma URI `data:` se você quiser o ícone embutido, sem uma busca extra.
|
||||
* `mime_type` e `sizes` (`"48x48"`, ou `"any"` para um formato escalável) permitem que o cliente escolha o certo quando você oferece vários.
|
||||
* `theme="light"` ou `theme="dark"` marca um ícone para um único esquema de cores.
|
||||
|
||||
`MCPServer(...)`, `@mcp.tool()`, `@mcp.resource()` e `@mcp.prompt()` aceitam o mesmo argumento nomeado `icons=[...]`.
|
||||
|
||||
### Onde um cliente os vê {#where-a-client-sees-them}
|
||||
|
||||
Os ícones viajam junto com aquilo que decoram. Os do servidor chegam quando o cliente se conecta, em `client.server_info` (opcional em conexões da era 2026, então restrinja o tipo primeiro):
|
||||
|
||||
```python
|
||||
assert client.server_info is not None # python-sdk servers identify themselves by default
|
||||
client.server_info.icons # [Icon(src="https://example.com/brand-kit.png", mime_type="image/png", sizes=["48x48"])]
|
||||
```
|
||||
|
||||
Os ícones de uma ferramenta ficam no objeto `Tool` de `tools/list`; os de um recurso, no `Resource` de `resources/list`; os de um prompt, no `Prompt` de `prompts/list`. O campo sempre se chama `icons`.
|
||||
|
||||
## Recapitulando {#recap}
|
||||
|
||||
* Retorne uma `Image` ou um `Audio` de uma ferramenta e o cliente recebe um bloco `ImageContent` / `AudioContent`: seus bytes codificados em base64, com um tipo MIME.
|
||||
* Monte um a partir de um `path=` e deixe o sufixo decidir o tipo MIME, ou a partir de `data=` em memória mais um `format=` explícito.
|
||||
* Resultados de mídia não trazem `structured_content` nem schema de saída.
|
||||
* Um `Icon` é um ponteiro: uma URI `src` mais `mime_type`, `sizes` e `theme` opcionais.
|
||||
* `icons=[...]` funciona no servidor, em ferramentas, em recursos e em prompts, e os clientes os encontram nos objetos correspondentes.
|
||||
|
||||
Isso é tudo o que uma ferramenta pode colocar *dentro* de um resultado. O que acontece quando uma ferramenta *falha* (e quem deve ficar sabendo) está em **[Tratando erros](handling-errors.md)**.
|
||||
155
i18n/pt/pages/servers/prompts.md
Normal file
155
i18n/pt/pages/servers/prompts.md
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
---
|
||||
translation:
|
||||
sections: [d65c098f37f5b6c3, dd0c2724d6f2877e, 6835bb3570c6714c, ffe823cb0fedd488, f33651add1b59094]
|
||||
tool: 1
|
||||
---
|
||||
# Prompts {#prompts}
|
||||
|
||||
Um **prompt** é um template de mensagem que o usuário escolhe.
|
||||
|
||||
Ferramentas são para o modelo. Um prompt é o oposto: o usuário escolhe um em um menu do seu cliente (um comando de barra, um botão), preenche os argumentos, e as mensagens renderizadas entram na conversa como se ele mesmo as tivesse digitado.
|
||||
|
||||
Para declarar um, coloque `@mcp.prompt()` em uma função que retorna o texto.
|
||||
|
||||
## Seu primeiro prompt {#your-first-prompt}
|
||||
|
||||
```python title="server.py" hl_lines="6-9"
|
||||
--8<-- "docs_src/prompts/tutorial001.py"
|
||||
```
|
||||
|
||||
O SDK lê as mesmas três coisas que lê de uma ferramenta:
|
||||
|
||||
* O **nome** é o nome da função: `review_code`.
|
||||
* A **descrição** que o cliente exibe é a docstring: `Review a piece of code.`
|
||||
* Os **argumentos** vêm dos parâmetros. `code` não tem valor padrão, então é obrigatório.
|
||||
|
||||
É isso que um cliente recebe de volta de `prompts/list`:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "review_code",
|
||||
"description": "Review a piece of code.",
|
||||
"arguments": [
|
||||
{"name": "code", "required": true}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Não há JSON Schema aqui. Os argumentos de um prompt são uma lista plana de **strings nomeadas**: um formulário que uma pessoa preenche, não um payload que um modelo constrói.
|
||||
|
||||
### Renderizando {#rendering-it}
|
||||
|
||||
O cliente renderiza o template com `prompts/get`, passando os argumentos. Sua função executa e a `str` que você retorna vira **uma mensagem de usuário**:
|
||||
|
||||
```json
|
||||
{
|
||||
"description": "Review a piece of code.",
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": {
|
||||
"type": "text",
|
||||
"text": "Please review this code:\n\ndef add(a, b): return a + b"
|
||||
}
|
||||
}
|
||||
],
|
||||
"resultType": "complete"
|
||||
}
|
||||
```
|
||||
|
||||
Essa é a vida inteira de um prompt: listado pelo nome, renderizado sob demanda, colocado no chat.
|
||||
|
||||
!!! check
|
||||
`required` é verificado antes que sua função execute. Renderize `review_code` sem `code` e a
|
||||
própria requisição falha com um erro JSON-RPC (código `-32603`):
|
||||
|
||||
```text
|
||||
mcp.shared.exceptions.MCPError: Internal server error
|
||||
```
|
||||
|
||||
Não há um resultado de erro no estilo das ferramentas para devolver a um modelo, porque não há
|
||||
nenhum modelo envolvido: a chamada levanta uma exceção. O motivo (`Missing required arguments: {'code'}`) vai parar no log do seu servidor.
|
||||
|
||||
### Experimente {#try-it}
|
||||
|
||||
Execute o servidor com o MCP Inspector:
|
||||
|
||||
```console
|
||||
uv run mcp dev server.py
|
||||
```
|
||||
|
||||
Abra a aba **Prompts** e selecione `review_code`. O Inspector desenha um formulário com um único campo obrigatório, `code`. Preencha, renderize e você recebe de volta exatamente a mensagem de usuário acima.
|
||||
|
||||
## Mais de uma mensagem {#more-than-one-message}
|
||||
|
||||
Uma revisão de código é uma mensagem só. Uma sessão de depuração é uma conversa, e um prompt pode iniciar a coisa toda.
|
||||
|
||||
Retorne uma lista de mensagens em vez de uma `str`:
|
||||
|
||||
```python title="server.py" hl_lines="2 13-20"
|
||||
--8<-- "docs_src/prompts/tutorial002.py"
|
||||
```
|
||||
|
||||
* `UserMessage` e `AssistantMessage` vêm de `mcp.server.mcpserver.prompts.base`. Passe uma `str` para elas e elas a embrulham em `TextContent` para você. O papel (role) é o nome da classe.
|
||||
* `Message` é a base comum delas. Use-a como anotação de retorno.
|
||||
|
||||
Renderizar `debug_error` agora produz três mensagens, nesta ordem:
|
||||
|
||||
```json
|
||||
{
|
||||
"description": "Start a debugging conversation.",
|
||||
"messages": [
|
||||
{"role": "user", "content": {"type": "text", "text": "I'm seeing this error:"}},
|
||||
{"role": "user", "content": {"type": "text", "text": "TypeError: 'int' object is not iterable"}},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": {"type": "text", "text": "I'll help debug that. What have you tried so far?"}
|
||||
}
|
||||
],
|
||||
"resultType": "complete"
|
||||
}
|
||||
```
|
||||
|
||||
Repare na última. Pré-preencher um turno de `assistant` é como você direciona a *próxima* resposta do modelo sem fazer o usuário digitar esse direcionamento por conta própria.
|
||||
|
||||
## Títulos e descrições dos argumentos {#titles-and-argument-descriptions}
|
||||
|
||||
`review_code` é um nome de função, não um rótulo. Dê ao cliente algo melhor para colocar no botão e descreva cada argumento para que o formulário se explique sozinho:
|
||||
|
||||
```python title="server.py" hl_lines="10-13"
|
||||
--8<-- "docs_src/prompts/tutorial003.py"
|
||||
```
|
||||
|
||||
* `title="Code review"` é o nome legível por humanos, exatamente como o `title` de uma ferramenta.
|
||||
* `Annotated[str, Field(description=...)]` é o mesmo padrão que **[Ferramentas](tools.md)** usa para descrever os parâmetros de uma ferramenta. Aqui a descrição vai parar no argumento, e não em um schema.
|
||||
* `language` tem um valor padrão, então deixa de ser obrigatório.
|
||||
|
||||
A entrada em `prompts/list` agora traz tudo de que um cliente precisa para desenhar um bom formulário:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "review_code",
|
||||
"title": "Code review",
|
||||
"description": "Review a piece of code.",
|
||||
"arguments": [
|
||||
{"name": "code", "description": "The code to review.", "required": true},
|
||||
{"name": "language", "description": "The language the code is written in.", "required": false}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
!!! info
|
||||
Se você leu **[Ferramentas](tools.md)**, já sabe tudo o que está nesta página. O mesmo decorador, a mesma
|
||||
docstring como descrição, o mesmo `Annotated`/`Field`. As únicas coisas que mudam são quem
|
||||
dispara (o usuário) e para onde vai o resultado (para a conversa).
|
||||
|
||||
## Recapitulando {#recap}
|
||||
|
||||
* `@mcp.prompt()` em uma função faz dela um prompt. O nome vem da função, a descrição vem da docstring.
|
||||
* Prompts são **controlados pelo usuário**: o cliente os lista, o usuário escolhe um e preenche os argumentos.
|
||||
* Os argumentos são uma lista plana de strings nomeadas (sem schema). Um parâmetro com valor padrão é opcional.
|
||||
* Retorne uma `str` e ela vira uma mensagem de usuário. Retorne uma lista de `UserMessage` / `AssistantMessage` para iniciar uma conversa de vários turnos.
|
||||
* `title=` e `Field(description=...)` são o que um cliente coloca na interface dele.
|
||||
* Um argumento obrigatório ausente faz a requisição inteira falhar. Não existe um resultado de erro por prompt.
|
||||
|
||||
O autocomplete do lado do servidor para os argumentos de um prompt (ou de um template de recurso) é assunto de **[Completions](completions.md)**.
|
||||
146
i18n/pt/pages/servers/resources.md
Normal file
146
i18n/pt/pages/servers/resources.md
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
---
|
||||
translation:
|
||||
sections: [09df998c2a799f78, 0cf131146d16d4f9, 4e6b91e3f8025346, 8fe4eef576db17ed, 0d0d1ed43e3d0a53]
|
||||
tool: 1
|
||||
---
|
||||
# Recursos {#resources}
|
||||
|
||||
Um **recurso** (resource) é um dado que você expõe para a aplicação ler.
|
||||
|
||||
A divisão é essa. Uma ferramenta é algo que o **modelo** decide chamar. Um recurso é algo que a **aplicação** decide carregar (um arquivo de configuração, um registro, um documento) e colocar diante do modelo como contexto.
|
||||
|
||||
Você declara um colocando `@mcp.resource(uri)` em uma função Python comum.
|
||||
|
||||
## Seu primeiro recurso {#your-first-resource}
|
||||
|
||||
```python title="server.py" hl_lines="6-8"
|
||||
--8<-- "docs_src/resources/tutorial001.py"
|
||||
```
|
||||
|
||||
Tem o mesmo formato de uma ferramenta, com uma coisa a mais: a **URI**. Recursos têm endereço, não nome. Um cliente pede `config://app`, nunca `get_config`.
|
||||
|
||||
O SDK ainda lê o restante a partir da função:
|
||||
|
||||
* O **nome** é o nome da função: `get_config`.
|
||||
* A **descrição** que o cliente vê é a docstring.
|
||||
* O **conteúdo** é o que você retornar.
|
||||
|
||||
Durante `resources/list`, o cliente recebe isto:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "get_config",
|
||||
"uri": "config://app",
|
||||
"description": "The active shop configuration.",
|
||||
"mimeType": "text/plain"
|
||||
}
|
||||
```
|
||||
|
||||
E quando ele lê `config://app`, sua função roda e o valor de retorno volta como texto:
|
||||
|
||||
```python
|
||||
result.contents # [TextResourceContents(uri="config://app", mime_type="text/plain", text="theme=dark\nlanguage=en")]
|
||||
```
|
||||
|
||||
!!! tip
|
||||
Listar é barato. Sua função **não** é chamada durante `resources/list`, só durante
|
||||
`resources/read`, e apenas para a URI que foi pedida. Exponha mil recursos
|
||||
e você só paga pelos que alguém abrir.
|
||||
|
||||
### Experimente {#try-it}
|
||||
|
||||
Execute o servidor com o MCP Inspector:
|
||||
|
||||
```console
|
||||
uv run mcp dev server.py
|
||||
```
|
||||
|
||||
Abra a URL que ele imprime e vá até a aba **Resources**. `config://app` está na lista, com sua descrição. Clique nele e o Inspector o lê: ali estão suas duas linhas de configuração.
|
||||
|
||||
## Templates de recurso {#resource-templates}
|
||||
|
||||
Uma URI por registro não escala. Coloque um **placeholder** na URI e um parâmetro correspondente na função:
|
||||
|
||||
```python title="server.py" hl_lines="12-13"
|
||||
--8<-- "docs_src/resources/tutorial002.py"
|
||||
```
|
||||
|
||||
`{user_id}` na URI, `user_id: str` na função. O contrato inteiro é esse.
|
||||
|
||||
Agora isso é um **template de recurso** (resource template), e ele se muda: sai de `resources/list` e passa a aparecer em `resources/templates/list`, como um padrão em vez de um endereço:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "get_user_profile",
|
||||
"uriTemplate": "users://{user_id}/profile",
|
||||
"description": "A customer's profile.",
|
||||
"mimeType": "text/plain"
|
||||
}
|
||||
```
|
||||
|
||||
O cliente preenche o placeholder e lê uma URI concreta: `users://42/profile`, `users://ada/profile`. Uma única função responde a todas elas, com o valor capturado passado como `user_id`:
|
||||
|
||||
```python
|
||||
result.contents # [TextResourceContents(uri="users://42/profile", text="User 42: 12 orders since 2021.")]
|
||||
```
|
||||
|
||||
Repare na `uri` do resultado. É a URI **concreta** que o cliente pediu, não o template.
|
||||
|
||||
!!! check
|
||||
Os placeholders e os parâmetros precisam bater. Renomeie o parâmetro da função para
|
||||
`user` enquanto a URI ainda diz `{user_id}` e o decorador se recusa **em tempo de importação**,
|
||||
antes que qualquer cliente chegue perto:
|
||||
|
||||
```text
|
||||
ValueError: Mismatch between URI parameters {'user_id'} and function parameters {'user'}
|
||||
```
|
||||
|
||||
Uma divergência dessas só pode ser bug, então o SDK torna impossível iniciar o servidor com uma.
|
||||
|
||||
A sintaxe dos placeholders é a da [RFC 6570](https://datatracker.ietf.org/doc/html/rfc6570): `{+path}` para valores com vários segmentos, `{?q,lang}` para parâmetros de query opcionais, e mais. O SDK também aplica, por padrão, verificações de segurança de caminho aos valores extraídos. Veja **[Templates de URI e segurança de caminhos](uri-templates.md)** para a referência completa.
|
||||
|
||||
`get_user_profile` também pode receber um parâmetro anotado com `Context`. O SDK o injeta sem nunca tratá-lo como parâmetro da URI, e a página **[O Context](../handlers/context.md)** cobre o que ele oferece a você.
|
||||
|
||||
## O que você retorna {#what-you-return}
|
||||
|
||||
Você não está limitado a `str`. Dê a cada recurso um `mime_type` e retorne o que fizer sentido:
|
||||
|
||||
```python title="server.py" hl_lines="8-9 14-15 20-21"
|
||||
--8<-- "docs_src/resources/tutorial003.py"
|
||||
```
|
||||
|
||||
* `readme` retorna uma `str`, então ela é enviada como está. Esse é o caso comum.
|
||||
* `catalog_stats` retorna um `dict`, então o SDK o serializa em **texto JSON** para você:
|
||||
|
||||
```json
|
||||
{
|
||||
"books": 1204,
|
||||
"authors": 391
|
||||
}
|
||||
```
|
||||
|
||||
* `placeholder_cover` retorna `bytes`, então o cliente recebe um `BlobResourceContents` em vez de um `TextResourceContents`, com seus bytes codificados em base64 no campo `blob`.
|
||||
|
||||
A mesma regra vale para qualquer outra coisa serializável em JSON: uma lista, um modelo Pydantic, uma dataclass. Se não é `str` nem `bytes`, vira JSON.
|
||||
|
||||
O `mime_type` é você quem declara, e o padrão é `text/plain`. O SDK nunca inspeciona o que você retorna para adivinhá-lo, então um recurso `dict` que você não rotula continua sendo anunciado como texto puro.
|
||||
|
||||
!!! tip
|
||||
`@mcp.resource()` também aceita `name=`, `title=` e `description=` quando você não
|
||||
quer derivá-los da função. E quando não há função nenhuma a escrever,
|
||||
`mcp.server.mcpserver.resources` tem classes `Resource` prontas (`TextResource`,
|
||||
`BinaryResource`, `FileResource`, `HttpResource`, `DirectoryResource`) que você registra
|
||||
com `mcp.add_resource(...)`.
|
||||
|
||||
Um cliente também pode **assinar** um recurso e ser notificado quando ele muda; essa metade da história é do cliente e está em **[O cliente](../client/index.md)**.
|
||||
|
||||
## Recapitulando {#recap}
|
||||
|
||||
* `@mcp.resource(uri)` em uma função a transforma em um recurso. A URI é o endereço, o valor de retorno é o conteúdo, a docstring é a descrição.
|
||||
* Um `{placeholder}` na URI a transforma em um **template**: ele é listado em `resources/templates/list` e uma única função atende a toda URI que corresponder.
|
||||
* Os nomes dos placeholders devem ser iguais aos nomes dos parâmetros da função. Erre isso e você descobre em tempo de importação, não em produção.
|
||||
* Sua função roda quando o recurso é **lido**, não quando é listado.
|
||||
* `str` vira texto, `bytes` vira um blob em base64, qualquer outra coisa vira texto JSON. `mime_type=` é como você rotula isso.
|
||||
* Ferramentas são para o modelo agir. Recursos são para a aplicação ler.
|
||||
|
||||
A terceira primitiva, aquela que uma pessoa escolhe em um menu, são os **[Prompts](prompts.md)**.
|
||||
250
i18n/pt/pages/servers/structured-output.md
Normal file
250
i18n/pt/pages/servers/structured-output.md
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
---
|
||||
translation:
|
||||
sections: [a838d57f003aed44, 857d03886a0137ed, 42d9efcb9f542867, 2290ff08435b5573, e866c192e11d1c14, 6cdbad079f7b47f0, d4b607372fb28b51, 18dbf726ac45e0b7, c6f7d2a148aa49f4, c851964bb3301907, d715db6f8dccc9cc, ef86634aa70498a7]
|
||||
tool: 1
|
||||
---
|
||||
# Saída estruturada {#structured-output}
|
||||
|
||||
Uma ferramenta (tool) que retorna uma `str` simples produz o resultado duas vezes: como texto em `content` e como `{"result": "..."}` em `structured_content`.
|
||||
|
||||
Esta página trata desse segundo canal: de onde ele vem, todas as formas que ele pode assumir e como o SDK garante que ele seja confiável.
|
||||
|
||||
A versão curta: **a anotação do tipo de retorno é o schema de saída**. Você já a escreveu.
|
||||
|
||||
## O schema de saída {#the-output-schema}
|
||||
|
||||
```python title="server.py" hl_lines="9"
|
||||
--8<-- "docs_src/structured_output/tutorial001.py"
|
||||
```
|
||||
|
||||
A linha que importa é a assinatura: `-> int`.
|
||||
|
||||
Por causa dela, a ferramenta que o SDK envia durante `tools/list` carrega um `output_schema` ao lado do schema de entrada que ele monta a partir dos seus parâmetros (**[Ferramentas](tools.md)** cobre esse):
|
||||
|
||||
```json
|
||||
{
|
||||
"properties": {
|
||||
"result": {"title": "Result", "type": "integer"}
|
||||
},
|
||||
"required": ["result"],
|
||||
"title": "get_temperatureOutput",
|
||||
"type": "object"
|
||||
}
|
||||
```
|
||||
|
||||
Um `int` sozinho não é um objeto JSON, então o SDK o **envolve** em `{"result": ...}`. Chame a ferramenta e os dois canais vêm preenchidos:
|
||||
|
||||
```python
|
||||
result.content # [TextContent(text="17")]
|
||||
result.structured_content # {"result": 17}
|
||||
```
|
||||
|
||||
Todo escalar recebe o mesmo wrapper: `str`, `int`, `float`, `bool`, `bytes`, `None`.
|
||||
|
||||
## Dois canais {#two-channels}
|
||||
|
||||
Por que enviar o mesmo valor duas vezes?
|
||||
|
||||
* `content` é para o **modelo**. Um modelo de linguagem lê texto; essa é a única parte do resultado que ele vê.
|
||||
* `structured_content` é para a **aplicação** dentro da qual o modelo roda: código que quer `17`, não uma frase contendo "17".
|
||||
* `output_schema` é o contrato entre os dois, publicado antes mesmo de a ferramenta ser chamada.
|
||||
|
||||
Você retorna um único valor Python. O SDK preenche os três.
|
||||
|
||||
## Retorne um modelo {#return-a-model}
|
||||
|
||||
Declare a forma como um `BaseModel` do Pydantic e retorne uma instância:
|
||||
|
||||
```python title="server.py" hl_lines="8-11 15"
|
||||
--8<-- "docs_src/structured_output/tutorial002.py"
|
||||
```
|
||||
|
||||
`WeatherData` agora **é** o schema. Sem wrapper, sem chave `result`:
|
||||
|
||||
```json
|
||||
{
|
||||
"properties": {
|
||||
"temperature": {"description": "Degrees Celsius.", "title": "Temperature", "type": "number"},
|
||||
"humidity": {"description": "Relative humidity, 0 to 1.", "title": "Humidity", "type": "number"},
|
||||
"conditions": {"title": "Conditions", "type": "string"}
|
||||
},
|
||||
"required": ["temperature", "humidity", "conditions"],
|
||||
"title": "WeatherData",
|
||||
"type": "object"
|
||||
}
|
||||
```
|
||||
|
||||
`structured_content` é o objeto, campo por campo:
|
||||
|
||||
```python
|
||||
result.structured_content # {"temperature": 16.2, "humidity": 0.83, "conditions": "Overcast"}
|
||||
```
|
||||
|
||||
E o modelo não fica de fora. O SDK serializa o mesmo objeto como texto JSON para `content`:
|
||||
|
||||
```json
|
||||
{
|
||||
"temperature": 16.2,
|
||||
"humidity": 0.83,
|
||||
"conditions": "Overcast"
|
||||
}
|
||||
```
|
||||
|
||||
Repare que o `Field(description=...)` em `temperature` e `humidity` foi parar no schema. O mesmo `Field` que descrevia as suas **entradas** descreve as suas saídas.
|
||||
|
||||
!!! info
|
||||
Se você já usou o `response_model` do FastAPI, já conhece isso: um modelo Pydantic como a resposta
|
||||
declarada, serializado e documentado para você. A única diferença é que aqui a anotação de retorno
|
||||
é a declaração inteira.
|
||||
|
||||
## Um `TypedDict` {#a-typeddict}
|
||||
|
||||
Nem toda forma merece uma classe. Um `TypedDict` produz o mesmo schema:
|
||||
|
||||
```python title="server.py" hl_lines="8"
|
||||
--8<-- "docs_src/structured_output/tutorial003.py"
|
||||
```
|
||||
|
||||
Um `TypedDict` é um `dict` comum em tempo de execução, então é isso que você monta e retorna. O schema, a validação e o `structured_content` são idênticos aos da versão com `BaseModel` (menos as descrições, para as quais o `TypedDict` não tem lugar).
|
||||
|
||||
## Uma dataclass {#a-dataclass}
|
||||
|
||||
Dataclasses também funcionam, assim como qualquer classe comum cujos atributos tenham anotações de tipo. O SDK monta um modelo Pydantic a partir das anotações por baixo dos panos.
|
||||
|
||||
```python title="server.py" hl_lines="8-9"
|
||||
--8<-- "docs_src/structured_output/tutorial004.py"
|
||||
```
|
||||
|
||||
Três formas de escrever, um schema só. Use a que a sua base de código já tem.
|
||||
|
||||
## Listas {#lists}
|
||||
|
||||
Uma `list[...]` também não é um objeto JSON, então ela recebe o wrapper `{"result": ...}`, com o tipo dos seus itens como uma referência `$defs` dentro dele:
|
||||
|
||||
```python title="server.py" hl_lines="15"
|
||||
--8<-- "docs_src/structured_output/tutorial005.py"
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"$defs": {
|
||||
"WeatherData": {
|
||||
"properties": {
|
||||
"temperature": {"title": "Temperature", "type": "number"},
|
||||
"humidity": {"title": "Humidity", "type": "number"},
|
||||
"conditions": {"title": "Conditions", "type": "string"}
|
||||
},
|
||||
"required": ["temperature", "humidity", "conditions"],
|
||||
"title": "WeatherData",
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"result": {"items": {"$ref": "#/$defs/WeatherData"}, "title": "Result", "type": "array"}
|
||||
},
|
||||
"required": ["result"],
|
||||
"title": "get_forecastOutput",
|
||||
"type": "object"
|
||||
}
|
||||
```
|
||||
|
||||
Peça uma previsão de dois dias e `structured_content` vem como `{"result": [{...}, {...}]}`. `content` vira **dois** blocos `TextContent`, um por item: uma lista é achatada para o modelo em vez de ser despejada como uma única string.
|
||||
|
||||
`tuple[...]`, uniões e `Optional[...]` são envolvidos da mesma forma.
|
||||
|
||||
## Dicionários {#dictionaries}
|
||||
|
||||
`dict[str, ...]` é o único genérico que já *é* um objeto JSON, então ele não é envolvido:
|
||||
|
||||
```python title="server.py" hl_lines="9"
|
||||
--8<-- "docs_src/structured_output/tutorial006.py"
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"additionalProperties": {"type": "number"},
|
||||
"title": "get_temperaturesDictOutput",
|
||||
"type": "object"
|
||||
}
|
||||
```
|
||||
|
||||
```python
|
||||
result.structured_content # {"London": 16.2, "Reykjavik": 4.4}
|
||||
```
|
||||
|
||||
As chaves precisam ser `str`. Um `dict[int, float]` não pode ser um objeto JSON, então ele recai no wrapper `{"result": ...}`.
|
||||
|
||||
## Validação {#validation}
|
||||
|
||||
`output_schema` não é documentação. O que quer que a sua função retorne é **validado contra ele** antes de sair do servidor.
|
||||
|
||||
Você não percebe enquanto monta o valor à mão: o Pydantic já garantiu que o seu `WeatherData` era um `WeatherData`. Você percebe no dia em que os dados vêm de algum lugar que você não controla:
|
||||
|
||||
```python title="server.py" hl_lines="9 21"
|
||||
--8<-- "docs_src/structured_output/tutorial007.py"
|
||||
```
|
||||
|
||||
A anotação promete `WeatherData`. A resposta do serviço upstream parou de enviar `humidity`.
|
||||
|
||||
!!! check
|
||||
Chame `get_weather` e ela não entrega discretamente ao cliente um objeto pela metade. A chamada falha,
|
||||
e as primeiras linhas do erro dão o nome do campo:
|
||||
|
||||
```text
|
||||
Error executing tool get_weather: 1 validation error for WeatherData
|
||||
humidity
|
||||
Field required [type=missing, input_value={'temperature': 16.2, 'conditions': 'Overcast'}, input_type=dict]
|
||||
```
|
||||
|
||||
Esse texto volta como o resultado da ferramenta com `is_error=True`, então o modelo sabe que a chamada
|
||||
falhou em vez de ler, com toda a confiança, um clima que não existe.
|
||||
|
||||
Retornar um `dict` comum de uma ferramenta `-> WeatherData` não tem problema, aliás. É exatamente isso que `json.loads` produziu. A validação é feita sobre o valor, não sobre o tipo Python.
|
||||
|
||||
## Desativando {#opting-out}
|
||||
|
||||
Às vezes a anotação de retorno é para o seu verificador de tipos, não para o protocolo. Passe `structured_output=False` e a ferramenta fica só com texto:
|
||||
|
||||
```python title="server.py" hl_lines="6"
|
||||
--8<-- "docs_src/structured_output/tutorial008.py"
|
||||
```
|
||||
|
||||
Sem `output_schema`, sem wrapper, sem validação. `structured_content` é `None` e `content` é a string que você retornou.
|
||||
|
||||
O oposto, `structured_output=True`, transforma a detecção automática em exigência: uma ferramenta cujo tipo de retorno não consegue produzir um schema levanta uma exceção no momento do import em vez de recair para texto.
|
||||
|
||||
## Uma classe sem anotações de tipo {#a-class-without-type-hints}
|
||||
|
||||
Existe um jeito de acabar sem estrutura sem ter pedido por isso: retornar uma classe que **não tem anotações no corpo**.
|
||||
|
||||
```python title="server.py" hl_lines="6-9"
|
||||
--8<-- "docs_src/structured_output/tutorial009.py"
|
||||
```
|
||||
|
||||
`Station` define `name` e `online` dentro de `__init__`, mas a *classe* não declara nada. O SDK lê as anotações da classe, não encontra nenhuma e desiste.
|
||||
|
||||
!!! warning
|
||||
Ele desiste **em silêncio**. `output_schema` é `None`, `structured_content` é `None`, e o texto
|
||||
que o modelo lê é o `repr` do objeto:
|
||||
|
||||
```text
|
||||
"<server.Station object at 0x7f539d75b230>"
|
||||
```
|
||||
|
||||
Nenhum erro, nenhum aviso, uma ferramenta inútil. Mova as anotações para o corpo da classe, ou passe
|
||||
`structured_output=True`, que transforma isso em um erro de verdade no momento em que o módulo é importado:
|
||||
`Function get_station: return type <class 'server.Station'> is not serializable for structured output`.
|
||||
|
||||
!!! tip
|
||||
Precisa de controle total (montar o `CallToolResult` você mesmo, ou anexar um `_meta` que a
|
||||
aplicação enxerga mas o modelo não)? Isso está em **[O Server de baixo nível](../advanced/low-level-server.md)**.
|
||||
|
||||
## Recapitulando {#recap}
|
||||
|
||||
* A **anotação do tipo de retorno** é o schema de saída. Ela é publicada em `tools/list` como `output_schema`.
|
||||
* Escalares, listas, tuplas e uniões são envolvidos em `{"result": ...}`. Modelos, `TypedDict`s, dataclasses, classes anotadas e `dict[str, ...]` já são objetos e ficam como estão.
|
||||
* Todo resultado carrega `content` (texto, para o modelo) **e** `structured_content` (dados, para a aplicação).
|
||||
* O que você retorna é validado contra o schema. Uma divergência vira um erro de ferramenta, não um resultado corrompido.
|
||||
* `structured_output=False` deixa uma ferramenta de fora. Uma classe sem anotações de tipo fica de fora em silêncio; fique atento a isso.
|
||||
|
||||
Agora você domina tudo o que uma ferramenta pode dizer de volta. A seguir, a segunda primitiva: **[Recursos](resources.md)**.
|
||||
177
i18n/pt/pages/servers/tools.md
Normal file
177
i18n/pt/pages/servers/tools.md
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
---
|
||||
translation:
|
||||
sections: [e4cc390d56573409, 8566e2b68594e9ad, 2c97b9f888398951, 048e5471dfa71aea, 3076b1e16ad95950, edbedf2a16e71311, 3d8ef8da89fa87c1, f6c0e02e6ea5a363]
|
||||
tool: 1
|
||||
---
|
||||
# Ferramentas {#tools}
|
||||
|
||||
Uma **ferramenta** (tool) é uma função que o modelo pode chamar.
|
||||
|
||||
Você declara uma colocando `@mcp.tool()` em uma função Python comum. A API inteira é essa.
|
||||
|
||||
## Sua primeira ferramenta {#your-first-tool}
|
||||
|
||||
```python title="server.py" hl_lines="6-8"
|
||||
--8<-- "docs_src/tools/tutorial001.py"
|
||||
```
|
||||
|
||||
Veja o que você escreveu. Não há schemas, nem JSON, nem protocolo, só uma função. O SDK lê três coisas dela:
|
||||
|
||||
* O **nome** da ferramenta é o nome da função: `search_books`.
|
||||
* A **descrição** que o modelo vê é a docstring: `Search the catalog by title or author.`
|
||||
* Os **argumentos** que o modelo pode passar vêm das anotações de tipo: `query: str` e `limit: int`.
|
||||
|
||||
### O schema de entrada {#the-input-schema}
|
||||
|
||||
A partir dessas anotações de tipo, o SDK gera um JSON Schema e o envia ao cliente durante `tools/list`:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {"title": "Query", "type": "string"},
|
||||
"limit": {"title": "Limit", "type": "integer"}
|
||||
},
|
||||
"required": ["query", "limit"],
|
||||
"title": "search_booksArguments"
|
||||
}
|
||||
```
|
||||
|
||||
Os dois argumentos estão em `required` porque nenhum deles tem valor padrão. Você vai resolver isso daqui a pouco. (As chaves `title` são artefatos do Pydantic; as propriedades, seus tipos e `required` são o contrato.)
|
||||
|
||||
!!! tip
|
||||
Aqui, as anotações de tipo não são documentação. Elas são **o contrato**. Se um cliente enviar `"limit": "ten"`,
|
||||
o SDK rejeita isso antes mesmo de a sua função executar.
|
||||
|
||||
### O que o modelo recebe de volta {#what-the-model-gets-back}
|
||||
|
||||
Chame a ferramenta com `{"query": "dune", "limit": 5}` e o resultado tem duas partes:
|
||||
|
||||
```python
|
||||
result.content # [TextContent(text="Found 3 books matching 'dune' (showing up to 5).")]
|
||||
result.structured_content # {'result': "Found 3 books matching 'dune' (showing up to 5)."}
|
||||
```
|
||||
|
||||
`content` é o texto que o **modelo** lê. `structured_content` são dados tipados para a **aplicação cliente**. Ele está ali porque você declarou o tipo de retorno como `-> str`.
|
||||
|
||||
Não se preocupe com `structured_content` por enquanto. Retorne objetos Python de verdade das suas ferramentas e a coisa certa acontece; a página **[Saída estruturada](structured-output.md)** trata exatamente disso.
|
||||
|
||||
### Experimente {#try-it}
|
||||
|
||||
Execute o servidor com o MCP Inspector:
|
||||
|
||||
```console
|
||||
uv run mcp dev server.py
|
||||
```
|
||||
|
||||
Abra a URL que ele imprime, vá até a aba **Tools** e chame `search_books`.
|
||||
|
||||
O Inspector renderiza um formulário com um campo de texto obrigatório `query` e um campo numérico obrigatório `limit`. Ele montou esse formulário a partir das suas anotações de tipo. Todos os outros clientes MCP vão fazer o mesmo.
|
||||
|
||||
## Argumentos opcionais {#optional-arguments}
|
||||
|
||||
Dê um valor padrão a um parâmetro e ele deixa de ser obrigatório. É só isso. É apenas Python.
|
||||
|
||||
```python title="server.py" hl_lines="7"
|
||||
--8<-- "docs_src/tools/tutorial002.py"
|
||||
```
|
||||
|
||||
O schema acompanha:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {"title": "Query", "type": "string"},
|
||||
"limit": {"default": 10, "title": "Limit", "type": "integer"}
|
||||
},
|
||||
"required": ["query"],
|
||||
"title": "search_booksArguments"
|
||||
}
|
||||
```
|
||||
|
||||
`limit` saiu de `required` e ganhou `"default": 10`. Um cliente que o omite recebe `10`, exatamente como aconteceria em Python.
|
||||
|
||||
## Schemas mais ricos com `Field` {#richer-schemas-with-field}
|
||||
|
||||
As anotações de tipo levam você longe, mas às vezes você quer *descrever* um argumento, ou restringi-lo.
|
||||
|
||||
Envolva o tipo em `Annotated` e adicione um `Field` do Pydantic:
|
||||
|
||||
```python title="server.py" hl_lines="12-14"
|
||||
--8<-- "docs_src/tools/tutorial003.py"
|
||||
```
|
||||
|
||||
Três novidades, todas nos parâmetros:
|
||||
|
||||
* `Field(description=...)`: uma descrição por argumento que o modelo lê junto com a docstring.
|
||||
* `Field(ge=1, le=50)`: limites numéricos. Eles entram no schema como `"minimum": 1, "maximum": 50`.
|
||||
* `Literal["fiction", "non-fiction", "poetry"]`: um enum. O modelo só pode escolher um desses valores.
|
||||
|
||||
!!! check
|
||||
Restrições não são enfeite. Chame a ferramenta com `limit=999` e o SDK responde com um
|
||||
erro de ferramenta **antes de a sua função executar**:
|
||||
|
||||
```text
|
||||
Input should be less than or equal to 50
|
||||
```
|
||||
|
||||
Esse erro volta para o modelo como o resultado da ferramenta, e o modelo o lê e tenta de novo com
|
||||
um valor válido. Você escreveu `le=50` uma vez e ganhou de graça agentes que se corrigem sozinhos.
|
||||
|
||||
!!! info
|
||||
Se você já usou FastAPI ou Pydantic, já sabe tudo isso. É o mesmo `Field`,
|
||||
o mesmo `Annotated`, a mesma validação. Não há nada específico de MCP para aprender aqui.
|
||||
|
||||
## Um modelo como parâmetro {#a-model-as-a-parameter}
|
||||
|
||||
Quando uma ferramenta recebe mais do que alguns poucos argumentos, agrupe-os em um modelo Pydantic:
|
||||
|
||||
```python title="server.py" hl_lines="8-11 15"
|
||||
--8<-- "docs_src/tools/tutorial004.py"
|
||||
```
|
||||
|
||||
O schema de `Book` fica aninhado dentro do schema de entrada da ferramenta (como uma referência em `$defs`), o modelo o preenche como um objeto JSON, e sua função recebe uma **instância real de `Book`**, já validada, com os atributos `.title`, `.author` e `.year`.
|
||||
|
||||
Você pode misturar à vontade: parâmetros simples ao lado de parâmetros de modelo, modelos aninhados, listas de modelos. É Pydantic de ponta a ponta.
|
||||
|
||||
## `async def` {#async-def}
|
||||
|
||||
Se uma ferramenta faz I/O (chama uma API, lê um arquivo, consulta um banco de dados), declare-a como `async def` e use `await` dentro dela. O SDK se encarrega de aguardá-la.
|
||||
|
||||
Uma ferramenta com `def` comum também funciona: o SDK a executa em uma thread, então ela nunca bloqueia o servidor.
|
||||
|
||||
Não há mais nada para configurar.
|
||||
|
||||
## Nomes, títulos e anotações {#names-titles-and-annotations}
|
||||
|
||||
Tudo o que o SDK infere, você pode sobrescrever no decorador:
|
||||
|
||||
```python title="server.py" hl_lines="7-10"
|
||||
--8<-- "docs_src/tools/tutorial005.py"
|
||||
```
|
||||
|
||||
* `title` é um nome legível por humanos, pensado para interfaces. Os clientes mostram *"Search the catalog"* em vez de `search_books`.
|
||||
* `annotations` são **dicas** de comportamento para o cliente:
|
||||
* `read_only_hint=True`: esta ferramenta não altera nada.
|
||||
* `open_world_hint=False`: ela opera sobre um conjunto fechado de coisas (este catálogo), não sobre a web aberta.
|
||||
* As outras duas, `destructive_hint` e `idempotent_hint`, descrevem uma ferramenta que *escreve*: ela pode
|
||||
apagar alguma coisa? E chamá-la duas vezes dá no mesmo que chamá-la uma vez? A especificação define as duas
|
||||
apenas para ferramentas que não são somente leitura, então elas não diriam nada em `search_books`.
|
||||
|
||||
Um cliente bem-comportado as usa para decidir coisas como *"preciso perguntar ao usuário antes de executar isto?"*. São dicas, não segurança. Nunca conte com um cliente respeitando-as.
|
||||
|
||||
!!! tip
|
||||
`@mcp.tool()` também aceita `name=` e `description=` se você não quiser derivá-los
|
||||
do nome da função e da docstring. Na maioria das vezes você quer.
|
||||
|
||||
## Recapitulando {#recap}
|
||||
|
||||
* `@mcp.tool()` em uma função a transforma em ferramenta. O nome vem da função, a descrição vem da docstring.
|
||||
* As anotações de tipo **são** o schema de entrada. Valores padrão tornam os argumentos opcionais.
|
||||
* `Annotated[..., Field(...)]` adiciona descrições e restrições; `Literal` adiciona enums.
|
||||
* Um modelo Pydantic como parâmetro é a forma de receber um "corpo" estruturado.
|
||||
* Argumentos inválidos são rejeitados para você, com um erro que o modelo consegue ler e do qual consegue se recuperar.
|
||||
* `async def` para I/O, `def` comum para todo o resto.
|
||||
|
||||
**[Saída estruturada](structured-output.md)** é o que acontece com o valor que você devolve no `return`.
|
||||
291
i18n/pt/pages/servers/uri-templates.md
Normal file
291
i18n/pt/pages/servers/uri-templates.md
Normal file
|
|
@ -0,0 +1,291 @@
|
|||
---
|
||||
translation:
|
||||
sections: [4a7033e1ed8ad602, 55dcbfff0c6271bf, 101ef9d14bf4ec46, 4b6c4a845438abc7, f98b46bafbee4acd]
|
||||
tool: 1
|
||||
---
|
||||
# Templates de URI e segurança de caminhos {#uri-templates-and-path-safety}
|
||||
|
||||
Esta é a referência da sintaxe de templates de URI que
|
||||
[`@mcp.resource`](resources.md) aceita e da política de segurança de
|
||||
caminhos que o SDK aplica aos valores extraídos. Para uma introdução ao
|
||||
que são recursos e quando usá-los, comece por
|
||||
**[Recursos](resources.md)**; esta página parte do princípio de que você já
|
||||
está à vontade declarando um recurso e quer o conjunto completo de
|
||||
operadores, os controles de segurança ou a integração de baixo nível.
|
||||
|
||||
A sintaxe dos templates é a [RFC 6570](https://datatracker.ietf.org/doc/html/rfc6570).
|
||||
O SDK dá suporte a um subconjunto escolhido para casar as URIs de
|
||||
`resources/read` que chegam, mais uma camada de segurança que rejeita
|
||||
valores que seriam resolvidos para fora do diretório que você pretende
|
||||
servir. Para os detalhes no nível do protocolo (formatos de mensagem,
|
||||
ciclo de vida, paginação), veja a
|
||||
[especificação de recursos do MCP](https://modelcontextprotocol.io/specification/latest/server/resources).
|
||||
|
||||
## O conjunto completo de operadores {#the-full-operator-set}
|
||||
|
||||
O placeholder simples, `{user_id}`, é o que **[Recursos](resources.md)** apresenta. Existem mais
|
||||
quatro formas de operador; aqui estão todas em um só servidor, para você
|
||||
vê-las lado a lado:
|
||||
|
||||
```python title="server.py" hl_lines="16-17 22-23 28-29 34-35 40-41"
|
||||
--8<-- "docs_src/uri_templates/tutorial001.py"
|
||||
```
|
||||
|
||||
Cada decorador destacado é um jeito diferente de recortar a URI.
|
||||
As seções abaixo percorrem todos eles, de cima para baixo.
|
||||
|
||||
### Expansão simples: `{name}` {#simple-expansion-name}
|
||||
|
||||
`books://{isbn}` é a forma simples, do dia a dia. O placeholder mapeia
|
||||
para o parâmetro `isbn`, então um cliente que lê `books://978-0441172719`
|
||||
chama `get_book("978-0441172719")`.
|
||||
|
||||
Um `{name}` simples para na primeira `/`. `books://978/extra` não casa
|
||||
porque a barra depois de `978` encerra a captura e `/extra` fica
|
||||
sobrando.
|
||||
|
||||
### Conversão de tipos {#type-conversion}
|
||||
|
||||
Os valores extraídos chegam como strings, mas você pode declarar um tipo
|
||||
mais específico e o SDK converte. `orders://{order_id}` vai parar em uma
|
||||
função cujo parâmetro é `order_id: int`, então ler `orders://12345` chama
|
||||
`get_order(12345)`, e não `get_order("12345")`. O handler faz contas com
|
||||
ele (`order_id + 1`) sem precisar de cast.
|
||||
|
||||
### Caminhos com vários segmentos: `{+name}` {#multi-segment-paths-name}
|
||||
|
||||
Para capturar um valor que contém barras, use `{+name}`. Com
|
||||
`manuals://{+path}`:
|
||||
|
||||
* `manuals://returns.md` dá `path = "returns.md"`
|
||||
* `manuals://printing/setup.md` dá `path = "printing/setup.md"`
|
||||
|
||||
Recorra a `{+name}` sempre que o valor for hierárquico: caminhos do
|
||||
sistema de arquivos, chaves de objetos aninhados, caminhos de URL que
|
||||
você repassa como proxy.
|
||||
|
||||
### Parâmetros de query: `{?a,b,c}` {#query-parameters-abc}
|
||||
|
||||
`reviews://{isbn}{?limit,sort}` coloca `limit` e `sort` depois do `?`.
|
||||
O caminho identifica *qual* livro; a query ajusta *como* você o lê.
|
||||
|
||||
O casamento dos parâmetros de query é tolerante: a ordem não importa, os
|
||||
extras são ignorados e os parâmetros omitidos caem nos valores padrão da
|
||||
sua função. Então `reviews://978-0441172719` usa `limit=10, sort="newest"`,
|
||||
e `reviews://978-0441172719?sort=top` sobrescreve apenas `sort`.
|
||||
|
||||
### Segmentos de caminho como lista: `{/name*}` {#path-segments-as-a-list-name}
|
||||
|
||||
Se você quer cada segmento do caminho como um item separado de uma lista,
|
||||
em vez de uma única string com barras, use `{/name*}`. Com
|
||||
`shelves://browse{/path*}`, um cliente que lê
|
||||
`shelves://browse/fiction/sci-fi` chama
|
||||
`browse_shelf(["fiction", "sci-fi"])`.
|
||||
|
||||
### Referência de templates {#template-reference}
|
||||
|
||||
Os padrões mais comuns:
|
||||
|
||||
| Padrão | Entrada de exemplo | Você recebe |
|
||||
|--------------|-----------------------|-------------------------|
|
||||
| `{name}` | `alice` | `"alice"` |
|
||||
| `{name}` | `docs/intro.md` | *não casa* (para na `/`) |
|
||||
| `{+path}` | `docs/intro.md` | `"docs/intro.md"` |
|
||||
| `{.ext}` | `.json` | `"json"` |
|
||||
| `{/segment}` | `/v2` | `"v2"` |
|
||||
| `{?key}` | `?key=value` | `"value"` |
|
||||
| `{?a,b}` | `?a=1&b=2` | `"1"`, `"2"` |
|
||||
| `{/path*}` | `/a/b/c` | `["a", "b", "c"]` |
|
||||
|
||||
### O que o parser rejeita {#what-the-parser-rejects}
|
||||
|
||||
Alguns formatos de template são barrados logo de início, em vez de
|
||||
falharem na primeira requisição. `@mcp.resource` faz o parse do template
|
||||
quando o decorador executa, então nenhum deles chega a um servidor em
|
||||
execução.
|
||||
|
||||
`UriTemplate.parse()` levanta `InvalidUriTemplate` nestes casos:
|
||||
|
||||
* **Duas variáveis sem nada entre elas.** `manuals://{+path}{ext}`
|
||||
é rejeitado: o casamento não tem como saber onde `path` termina e
|
||||
`ext` começa. Coloque um literal entre elas (`manuals://{+path}/{ext}`)
|
||||
ou use um operador que traga seu próprio delimitador.
|
||||
`manuals://{+path}{.ext}` é aceito porque `{.ext}` contribui ele mesmo
|
||||
com o `.`.
|
||||
* **Mais de uma variável de vários segmentos.** No máximo uma entre
|
||||
`{+var}`, `{#var}` ou uma variável explodida (`{/var*}`, `{.var*}`,
|
||||
`{;var*}`) por template. Duas são inerentemente ambíguas: não há um
|
||||
critério consistente para decidir qual delas absorve um segmento extra.
|
||||
* **Os erros de sintaxe de sempre**: uma chave não fechada, um nome de
|
||||
variável usado duas vezes ou uma funcionalidade da RFC 6570 à qual o
|
||||
SDK não dá suporte, como o modificador de prefixo `{var:3}` ou a
|
||||
explosão de query `{?vars*}`.
|
||||
|
||||
Além disso, `@mcp.resource` levanta `ValueError` quando um parâmetro do
|
||||
handler está vinculado a uma variável de query na sequência
|
||||
`{?...}`/`{&...}` do final do template, mas não tem valor padrão em
|
||||
Python. O casamento dessas variáveis é tolerante (um cliente pode deixar
|
||||
qualquer uma delas de fora), então um parâmetro sem valor padrão só
|
||||
apareceria como um erro interno opaco na primeira requisição que o
|
||||
omitisse. `reviews://{isbn}{?limit,sort}` no servidor acima é a versão
|
||||
bem formada: `limit` e `sort` têm, ambos, valores padrão.
|
||||
|
||||
## Segurança {#security}
|
||||
|
||||
Os parâmetros de template vêm do cliente. Se eles chegarem sem
|
||||
verificação a operações de sistema de arquivos ou de banco de dados,
|
||||
valores como `../../etc/passwd` podem ser resolvidos para fora do
|
||||
diretório que você pretendia servir.
|
||||
|
||||
### O que o SDK verifica por padrão {#what-the-sdk-checks-by-default}
|
||||
|
||||
Antes de o seu handler executar, o SDK rejeita qualquer parâmetro que:
|
||||
|
||||
* escaparia do seu diretório de partida por meio de componentes `..`
|
||||
* parece um caminho absoluto (`/etc/passwd`, `C:\Windows`) ou um caminho
|
||||
do Windows relativo a uma unidade (`C:foo`). Um valor relativo a
|
||||
unidade e um identificador com namespace como `x:y` são
|
||||
indistinguíveis como strings, então qualquer valor do tipo uma letra
|
||||
seguida de dois-pontos é rejeitado por padrão; isente o parâmetro se
|
||||
ele recebe valores assim de forma legítima
|
||||
* contém um byte nulo (`\x00`)
|
||||
|
||||
A verificação de `..` é feita por componente, não por busca de
|
||||
substring. Valores como `v1.0..v2.0` ou `HEAD~3..HEAD` passam porque ali
|
||||
`..` não é um segmento de caminho isolado.
|
||||
|
||||
Essas verificações se aplicam ao valor decodificado, então elas pegam
|
||||
tentativas de path traversal independentemente de como foram codificadas
|
||||
na URI (`../etc`, `..%2Fetc`, `%2E%2E/etc`, `..%5Cetc`, `%00` são todos
|
||||
barrados).
|
||||
|
||||
!!! check
|
||||
Leia `manuals://../etc/passwd` do servidor acima e a requisição é
|
||||
rejeitada na hora: o casamento de templates para na primeira falha,
|
||||
então nenhum template posterior (potencialmente mais permissivo) é
|
||||
tentado como fallback. O cliente vê o mesmo erro `-32602` "Unknown
|
||||
resource" que veria para uma URI que não casa com nenhum template, e
|
||||
`read_manual` nunca executa.
|
||||
|
||||
### Handlers de sistema de arquivos: use safe_join {#filesystem-handlers-use-safe_join}
|
||||
|
||||
As verificações embutidas barram os casos comuns, mas não têm como
|
||||
conhecer o limite do seu sandbox. Para acesso ao sistema de arquivos,
|
||||
use `safe_join` para resolver o caminho e verificar que ele continua
|
||||
dentro do seu diretório base:
|
||||
|
||||
```python title="server.py" hl_lines="4 14"
|
||||
--8<-- "docs_src/uri_templates/tutorial002.py"
|
||||
```
|
||||
|
||||
`safe_join` pega escapes por link simbólico, sequências `..` e truques
|
||||
com caminhos absolutos que uma verificação simples de string deixaria
|
||||
passar. Se o caminho resolvido escapa de `DOCS_ROOT`, a função levanta
|
||||
`PathEscapeError`, que chega ao cliente como um `ResourceError`.
|
||||
|
||||
### Quando o comportamento padrão atrapalha {#when-the-defaults-get-in-the-way}
|
||||
|
||||
Às vezes as verificações bloqueiam valores legítimos. Uma ferramenta de
|
||||
importação de catálogo pode receber de propósito um caminho absoluto, ou
|
||||
um parâmetro pode ser uma referência relativa como `../sibling` que o
|
||||
seu handler interpreta com segurança sem tocar no sistema de arquivos.
|
||||
Isente esse parâmetro ou afrouxe a política para o servidor inteiro:
|
||||
|
||||
```python title="server.py" hl_lines="9 16-19"
|
||||
--8<-- "docs_src/uri_templates/tutorial003.py"
|
||||
```
|
||||
|
||||
* `security=ResourceSecurity(exempt_params={"source"})` no decorador
|
||||
pula as verificações só para aquele parâmetro, só naquele recurso. O
|
||||
resto do servidor mantém a política padrão.
|
||||
* `resource_security=` no construtor de `MCPServer` define o padrão
|
||||
para todos os recursos. Aqui, `relaxed` desliga por completo a
|
||||
verificação de `..`.
|
||||
|
||||
As verificações configuráveis:
|
||||
|
||||
| Configuração | Padrão | O que faz |
|
||||
|-------------------------|---------|-------------------------------------|
|
||||
| `reject_path_traversal` | `True` | Rejeita sequências `..` que escapam do diretório de partida |
|
||||
| `reject_absolute_paths` | `True` | Rejeita `/foo`, `C:\foo`, caminhos UNC e o `C:foo` relativo a unidade (também pega `x:y`) |
|
||||
| `reject_null_bytes` | `True` | Rejeita valores que contêm `\x00` |
|
||||
| `exempt_params` | vazio | Nomes de parâmetros para os quais pular as verificações |
|
||||
|
||||
Essas verificações são um pré-filtro heurístico; para acesso ao sistema
|
||||
de arquivos, `safe_join` continua sendo a fronteira de contenção.
|
||||
|
||||
!!! tip
|
||||
Se o seu handler não consegue atender à requisição (o arquivo não
|
||||
existe, o id é desconhecido), levante uma exceção. O SDK a transforma
|
||||
em uma resposta de erro. Veja **[Tratamento de erros](handling-errors.md)** para a
|
||||
diferença entre um erro de protocolo e um erro de ferramenta.
|
||||
|
||||
## Recursos no Server de baixo nível {#resources-on-the-low-level-server}
|
||||
|
||||
Se você está construindo sobre o `Server` de baixo nível (veja **[O Server
|
||||
de baixo nível](../advanced/low-level-server.md)**), registra handlers diretamente para os
|
||||
métodos de protocolo `resources/list` e `resources/read`. Não há
|
||||
decorador; quem retorna os tipos do protocolo é você.
|
||||
|
||||
### Recursos estáticos {#static-resources}
|
||||
|
||||
Para URIs fixas, mantenha um registro e despache por casamento exato:
|
||||
|
||||
```python title="server.py" hl_lines="17 21 27"
|
||||
--8<-- "docs_src/uri_templates/tutorial004.py"
|
||||
```
|
||||
|
||||
O handler de listagem diz aos clientes o que está disponível; o handler
|
||||
de leitura serve o conteúdo. Consulte primeiro o seu registro, caia nos
|
||||
templates (abaixo) se tiver algum, e levante uma exceção para qualquer
|
||||
outra coisa.
|
||||
|
||||
### Templates {#templates}
|
||||
|
||||
O motor de templates que o `MCPServer` usa fica em
|
||||
`mcp.shared.uri_template` e funciona sozinho. Você ganha o mesmo parse e
|
||||
o mesmo casamento; o roteamento e a política de segurança, você monta por
|
||||
conta própria.
|
||||
|
||||
```python title="server.py" hl_lines="13-16 22-25 29 33 45"
|
||||
--8<-- "docs_src/uri_templates/tutorial005.py"
|
||||
```
|
||||
|
||||
Três coisas acontecem nas linhas destacadas:
|
||||
|
||||
* **Faça o parse uma vez, case a cada requisição.** `UriTemplate.parse()`
|
||||
monta o template; `template.match(uri)` retorna as variáveis extraídas
|
||||
como um `dict`, ou `None` se a URI não se encaixa. A decodificação de
|
||||
URL acontece dentro de `match()`; os valores decodificados são
|
||||
retornados como estão, sem validação de segurança de caminho. Os
|
||||
valores saem como strings: converta-os você mesmo
|
||||
(`int(matched["id"])`, `Path(matched["path"])`).
|
||||
* **Aplique você mesmo as verificações de segurança.** As verificações de
|
||||
`..` e de caminho absoluto que o `MCPServer` executa por padrão ficam em
|
||||
`mcp.shared.path_security`. `read_manual_safely` as chama antes de
|
||||
tocar em `MANUALS`. Se um parâmetro não é um caminho do sistema de
|
||||
arquivos (um ISBN, uma consulta de busca), pule as verificações para
|
||||
esse valor: você controla a política por handler, e não por meio de um
|
||||
objeto de configuração.
|
||||
* **Liste os templates a partir da mesma fonte.** Os clientes descobrem
|
||||
templates por meio de `resources/templates/list`. `str(template)`
|
||||
devolve a string original do template, então a listagem e o casamento
|
||||
compartilham uma única fonte da verdade.
|
||||
|
||||
## Recapitulando {#recap}
|
||||
|
||||
* `{name}` casa com um segmento; `{+name}` mantém as barras; `{?a,b}`
|
||||
puxa da query string; `{/name*}` divide os segmentos em uma lista.
|
||||
* Duas variáveis sem nada entre elas, ou uma segunda variável de vários
|
||||
segmentos, são rejeitadas na hora do parse. Um parâmetro vinculado a
|
||||
uma variável de query em um `{?...}`/`{&...}` final deve declarar um
|
||||
valor padrão em Python.
|
||||
* Anote o parâmetro (`order_id: int`) e o SDK converte.
|
||||
* A política de segurança padrão rejeita `..`, caminhos absolutos e bytes
|
||||
nulos antes de o seu handler executar; sobrescreva por recurso com
|
||||
`security=ResourceSecurity(...)` ou no servidor inteiro com
|
||||
`resource_security=`.
|
||||
* Para acesso ao sistema de arquivos, `safe_join` é a fronteira de
|
||||
contenção.
|
||||
* No `Server` de baixo nível, faça o parse com `UriTemplate.parse()`,
|
||||
case com `.match()` e aplique `mcp.shared.path_security` você mesmo.
|
||||
Loading…
Reference in a new issue