Definindo Ferramentas Chamáveis em um Servidor MCP
Uma ferramenta chamável é a maneira mais direta pela qual um servidor MCP dá ao modelo algo a fazer.
Acertar uma ferramenta significa que o modelo pode encontrá-la, entender o que ela faz, chamá-la com argumentos válidos e dar sentido ao que retorna.
Resumo
Uma ferramenta no SDK Python do MCP começa como uma função comum decorada com @mcp.tool().
O nome da função, seus parâmetros com dicas de tipo e sua docstring juntos se tornam o esquema que um cliente usa para decidir quando e como chamá-la.
Os manipuladores devem permanecer estreitos e previsíveis, fazendo um trabalho bem feito em vez de ramificar em vários comportamentos não relacionados com base em um argumento de flag.
Erros devem ser levantados como exceções com uma mensagem clara, não engolidos ou retornados como strings ambíguas, para que o modelo receba feedback útil quando uma chamada falhar.
Entradas estruturadas e validadas (via modelos Pydantic ou dicas de tipo simples) capturam argumentos ruins antes que o código do seu manipulador seja executado.
Receita
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("tool-recipes")
@mcp.tool()
def get_weather(city: str, units: str = "celsius") -> str:
"""Get the current weather for a city.
Args:
city: The city name, e.g. "Austin" or "Tokyo".
units: Either "celsius" or "fahrenheit". Defaults to "celsius".
"""
if units not in ("celsius", "fahrenheit"):
raise ValueError('units must be "celsius" or "fahrenheit"')
return f"72 degrees {units} and sunny in {city}"Quando usar isso:
- O modelo precisa executar uma ação ou computação, não apenas ler dados estáticos.
- A operação tem um conjunto pequeno e bem tipado de entradas que se mapeiam claramente para parâmetros de função.
- Você deseja que a mesma capacidade seja acessível a partir do Claude Code, Claude Desktop ou um aplicativo SDK de Agente personalizado sem reescrevê-la por cliente.
- O resultado da chamada deve se tornar parte da conversa, não apenas ser armazenado em cache ou exibido separadamente.
Exemplo de Trabalho
# server.py
from typing import Literal
from mcp.server.fastmcp import FastMCP
from pydantic import BaseModel, Field
mcp = FastMCP("task-tools")
# In-memory store for this example; swap for a real database in production.
_tasks: dict[int, dict] = {}
_next_id = 1
class CreateTaskInput(BaseModel):
title: str = Field(..., min_length=1, max_length=200, description="Short task title")
priority: Literal["low", "medium", "high"] = Field(
"medium", description="Task priority level"
)
@mcp.tool()
def create_task(input: CreateTaskInput) -> str:
"""Create a new task and return its id.
Use this when the user asks to add, track, or remember a to-do item.
"""
global _next_id
task_id = _next_id
_next_id += 1
_tasks[task_id] = {"title": input.title, "priority": input.priority, "done": False}
return f"Created task {task_id}: {input.title} (priority: {input.priority})"
@mcp.tool()
def complete_task(task_id: int) -> str:
"""Mark an existing task as done.
Args:
task_id: The numeric id returned by create_task.
"""
task = _tasks.get(task_id)
if task is None:
raise ValueError(f"No task found with id {task_id}")
task["done"] = True
return f"Task {task_id} ({task['title']}) marked done"
@mcp.tool()
def list_tasks(only_open: bool = True) -> str:
"""List tracked tasks.
Args:
only_open: If True, hide tasks already marked done. Defaults to True.
"""
rows = [
f"#{tid} [{t['priority']}] {t['title']}{' (done)' if t['done'] else ''}"
for tid, t in _tasks.items()
if not (only_open and t["done"])
]
return "\n".join(rows) if rows else "No tasks found."
if __name__ == "__main__":
mcp.run(transport="stdio")O que isso demonstra:
- Três ferramentas estreitas e de propósito único (
create_task,complete_task,list_tasks) em vez de uma ferramenta com um flagmode. - Um modelo Pydantic (
CreateTaskInput) validando uma entrada estruturada com uma restrição de comprimento e um conjunto restrito de valores permitidos. - Um tipo
Literalrestringindoprioritya exatamente três strings válidas, aplicadas antes que o corpo do manipulador seja executado. - Tratamento de erros consistente: IDs desconhecidos levantam
ValueErrorcom uma mensagem nomeando o problema específico. - Retornos de string simples que leem naturalmente quando o modelo os exibe em uma conversa.
Mergulho Profundo
Como Funciona
- Quando o servidor inicia,
FastMCPinspeciona cada função decorada com@mcp.tool()e constrói um Esquema JSON a partir de suas dicas de tipo e valores padrão. - A docstring da função se torna a descrição da ferramenta que o cliente usa para decidir quando a ferramenta é relevante.
- Quando um cliente chama
list_tools(), ele recebe este esquema e descrição para cada ferramenta registrada, sem que você precise escrever nenhum esquema manualmente. - Quando o modelo decide chamar uma ferramenta, o cliente envia o nome da ferramenta mais um objeto JSON de argumentos; o SDK valida esse objeto contra o esquema gerado antes de invocar sua função.
- Se a validação falhar (tipo incorreto, campo obrigatório ausente, valor fora de um conjunto
Literal), o SDK retorna um erro de esquema para o cliente sem nunca chamar seu manipulador.
Regras de Nomeação e Descrição em Resumo
| Elemento | Boa Prática | Por que Importa |
|---|---|---|
| Nome da ferramenta | snake_case, começando com verbo (create_task, não task) | O modelo associa nomes à intenção; um verbo sinaliza uma ação |
| Primeira linha da docstring | Uma frase declarando exatamente o que a ferramenta faz | Este é o texto principal que o modelo usa para decidir a relevância |
Args: na docstring | Uma linha por parâmetro, linguagem clara | Esclarece unidades, formatos e intervalos válidos que a dica de tipo não pode expressar |
| Nomes dos parâmetros | Descritivos, não abreviados (city, não c) | Reduz chamadas ambíguas ou malformadas |
Notas de Python
# Prefer a constrained type over a loose string when the valid set is small and known.
from typing import Literal
@mcp.tool()
def set_status(status: Literal["open", "in_progress", "closed"]) -> str:
"""Set the status of the current item."""
return f"Status set to {status}"
# For richer validation (ranges, formats, nested fields), use a Pydantic model
# as the single parameter, as shown in CreateTaskInput above.Parâmetros e Valores de Retorno
| Parâmetro | Tipo | Descrição |
|---|---|---|
| nome da função | str (via nome def) | Torna-se o identificador da ferramenta; mantenha-o estável assim que os clientes dependerem dele |
| dicas de tipo | Tipos Python / Literal / modelo Pydantic | Compilado automaticamente no Esquema JSON da ferramenta |
| docstring | str | Torna-se a descrição da ferramenta mostrada ao modelo |
| valor de retorno | str ou conteúdo estruturado | Envolto no formato de conteúdo do MCP e retornado ao cliente |
Armadilhas
- Nomes de ferramentas vagos como
handleouprocess. O modelo não tem nada para associar a intenção. Correção: use um nome específico, começando com verbo, comocreate_taskousend_email. - Uma ferramenta com um parâmetro de string
modeouactionque ramifica em comportamentos não relacionados. Isso confunde a descoberta baseada em esquema e aumenta a chance de uma chamada incorreta. Correção: divida-a em ferramentas separadas e de escopo restrito. - Retornar
Noneou uma string vazia em caso de falha em vez de levantar uma exceção. O modelo não tem sinal de que algo deu errado e pode relatar sucesso incorretamente. Correção: levante uma exceção com uma mensagem específica; deixe o SDK traduzi-la em um erro de ferramenta. - Docstrings ausentes ou superficiais. Sem uma descrição, o modelo está adivinhando o que uma ferramenta faz apenas pelo seu nome. Correção: sempre inclua um resumo de uma linha mais um bloco
Args:. - Ignorar a validação de entrada em intervalos numéricos ou de string. Um manipulador que confia cegamente em suas entradas pode falhar em casos extremos como contagens negativas ou strings vazias. Correção: use
Literal, restriçõesFielddo Pydantic ou verificações explícitas no início do manipulador. - Mutar estado global compartilhado sem nenhuma proteção. Duas chamadas rápidas de ferramentas contra o mesmo armazenamento em memória podem competir ou corromper dados. Correção: use um armazenamento de dados real com transações, ou adicione bloqueio, quando você sair de uma demonstração.
Alternativas
| Alternativa | Use Quando | Não Use Quando |
|---|---|---|
| Recurso MCP | A operação é uma leitura pura sem efeitos colaterais | O modelo precisa tomar uma ação ou realizar uma computação |
| Prompt MCP | Você deseja padronizar instruções, não expor uma ação chamável | O cliente precisa de um valor de resultado de volta na conversa |
| Chamada de função direta (não MCP) | A capacidade será usada apenas por um aplicativo específico | Você deseja que a mesma capacidade seja reutilizável em Claude Code, Claude Desktop e aplicativos personalizados |
Um manipulador @mcp.server de baixo nível (em vez de FastMCP) | Você precisa de controle total sobre o handshake do protocolo ou fiação de transporte personalizada | Você só precisa expor um punhado de ferramentas diretas rapidamente |
FAQs
Como o SDK transforma uma função Python em um esquema de ferramenta?
FastMCPlê as dicas de tipo da função para construir um Esquema JSON para seus parâmetros.- Ele lê a docstring para preencher a descrição da ferramenta mostrada ao modelo.
- Nenhuma criação manual de esquema é necessária para parâmetros escalares típicos ou modelos Pydantic.
Preciso de Pydantic, ou dicas de tipo simples são suficientes?
Dicas de tipo simples (str, int, bool, Literal[...]) são suficientes para ferramentas simples. Use um parâmetro BaseModel do Pydantic quando precisar de restrições em nível de campo, estruturas aninhadas ou lógica de validação reutilizável em várias ferramentas.
O que acontece se o modelo chamar uma ferramenta com os tipos de argumento errados?
O SDK valida os argumentos recebidos contra o esquema gerado antes que seu manipulador seja executado. Argumentos inválidos produzem um erro de esquema retornado ao cliente, e o corpo da sua função nunca é executado.
Uma ferramenta deve retornar texto simples ou dados estruturados?
- Strings simples funcionam bem quando o resultado se destina a ser lido diretamente na conversa.
- Conteúdo estruturado vale a pena retornar quando um cliente ou ferramenta downstream precisa analisar o resultado programaticamente.
Quão específica deve ser a docstring de uma ferramenta?
Específica o suficiente para que um leitor não familiarizado com sua base de código possa adivinhar exatamente quando chamá-la e o que cada argumento significa, incluindo unidades, formatos ou intervalos válidos que não são óbvios a partir da dica de tipo.
É melhor ter uma ferramenta flexível ou várias ferramentas estreitas?
Várias ferramentas estreitas, na maioria dos casos. Uma ferramenta com um flag mode ou action esconde vários comportamentos por trás de um único esquema, o que torna mais difícil para o modelo escolher corretamente e mais difícil para você testar cada comportamento isoladamente.
Como sinalizar um erro de um manipulador de ferramenta?
@mcp.tool()
def withdraw(account_id: str, amount: float) -> str:
"""Withdraw funds from an account."""
if amount <= 0:
raise ValueError("amount must be positive")
return f"Withdrew {amount} from {account_id}"Levantar uma exceção padrão com uma mensagem clara é suficiente; o SDK a converte em um erro de ferramenta que o cliente pode mostrar ao modelo.
Uma ferramenta pode chamar outra ferramenta internamente?
Nada impede que um manipulador chame uma função Python comum que outra ferramenta também chama, mas as ferramentas não devem chamar umas às outras através da própria camada de protocolo MCP. Compartilhe a lógica através da composição de funções comuns em vez disso.
Qual é o risco de usar estado mutável global dentro de uma ferramenta?
Chamadas concorrentes podem competir com o estado compartilhado, produzindo resultados inconsistentes ou dados corrompidos. É aceitável para uma demonstração ou protótipo, mas ferramentas de produção devem usar um armazenamento de dados real com garantias transacionais adequadas.
O nome da ferramenta tem que corresponder ao nome da função Python?
Por padrão, sim, FastMCP usa o nome da função como o nome da ferramenta. Mantenha-o estável assim que qualquer cliente começar a depender dele, pois uma renomeação altera o que o cliente vê durante a descoberta.
Quantos parâmetros são muitos para uma ferramenta?
Não há limite rígido, mas se uma ferramenta precisar de muitos parâmetros opcionais para cobrir diferentes casos de uso, isso geralmente é um sinal de que ela deve ser dividida em mais de uma ferramenta focada.
Valores de parâmetros padrão podem ser usados na assinatura de uma ferramenta?
Sim. Um parâmetro como units: str = "celsius" se torna um campo opcional no esquema gerado, e o cliente pode omiti-lo completamente para usar o padrão.
Relacionado
- Entendendo MCP: Ferramentas, Recursos e Prompts - a base conceitual do que é uma ferramenta versus um recurso ou prompt.
- Noções Básicas de Conceitos Principais do MCP - um servidor e cliente de ferramenta única mínima para construir.
- Expondo Dados Legíveis como Recursos MCP - o primitivo irmão para dados somente leitura.
- Comparação de Ferramentas MCP vs. Recursos vs. Prompts - decidindo quando uma capacidade pertence a uma ferramenta.
- Melhores Práticas de Conceitos Principais do MCP - uma lista de verificação para qualidade de esquema e nomenclatura.
Versões da Stack: Escrito contra a linha de modelos Claude atual em ~junho de 2026 - Claude Fable 5, Claude Opus 4.8, Claude Sonnet 5 (o padrão) e Claude Haiku 4.5 - e a especificação atual do Model Context Protocol. Nomes de modelos, versões de SDK e a especificação MCP mudam rapidamente - verifique os detalhes atuais em platform.claude.com/docs e modelcontextprotocol.io antes de confiar neles.