- Data Hackers Newsletter
- Posts
- FASTMCP: A forma mais eficiente de construir MCP Servers para especialistas em Python
FASTMCP: A forma mais eficiente de construir MCP Servers para especialistas em Python
Conheça o recurso que permite construir MCP servers e clients prontos para produção com código mínimo
O Model Context Protocol (MCP) revolucionou a forma como os Large Language Models (LLMs) interagem com ferramentas externas, fontes de dados e serviços. No entanto, construir MCP servers do zero tradicionalmente exigia navegar por código boilerplate complexo e especificações detalhadas de protocolos. O FastMCP elimina esse obstáculo, fornecendo um framework baseado em decorators e totalmente Pythonic que permite aos desenvolvedores construir MCP servers e clients prontos para produção com código mínimo.
Neste guia completo, você aprenderá como construir MCP servers e clients usando FastMCP, com tratamento de erros, boas práticas e estratégias de implantação, tornando-o ideal tanto para iniciantes quanto para desenvolvedores intermediários.
Pré-requisitos essenciais
Antes de iniciar este tutorial, certifique-se de ter:
Python 3.10 ou superior (3.11+ recomendado para melhor performance assíncrona)
pip ou uv (uv é recomendado para deployment do FastMCP e necessário para ferramentas CLI)
Um editor de código (VS Code é uma excelente opção)
Familiaridade com Terminal/Command Line para executar scripts Python
Também é benéfico ter:
Bom conhecimento de programação Python (funções, decorators, type hints)
Alguma compreensão da sintaxe async/await (opcional, mas útil para exemplos avançados)
Familiaridade com conceitos de JSON e REST API
Uso básico de terminal de linha de comando
O que é o Model Context Protocol?
O Model Context Protocol (MCP) é um padrão aberto criado pela Anthropic que fornece uma interface universal para aplicações de IA se conectarem de forma segura com ferramentas externas, fontes de dados e serviços. O MCP padroniza como os LLMs interagem com sistemas externos, da mesma forma que as APIs web padronizaram a comunicação de serviços web.
Características principais do MCP
Característica | Descrição |
|---|---|
Comunicação padronizada | Usa JSON-RPC 2.0 para mensagens estruturadas e confiáveis |
Bidirecional | Suporta tanto requisições de clients para servers quanto respostas de volta |
Segurança | Suporte integrado para padrões de autenticação e autorização |
Transporte flexível | Funciona com qualquer mecanismo de transporte (stdio, HTTP, WebSocket, SSE) |
Arquitetura MCP: Servers e Clients
O MCP segue uma arquitetura client-server clara:
MCP Server: Expõe capacidades (tools, resources, prompts) que aplicações externas podem usar. Pense nele como uma API backend especificamente projetada para integração com LLM.
MCP Client: Embutido em aplicações de IA (como Claude Desktop, Cursor IDE ou aplicações customizadas) que se conectam aos MCP servers para acessar seus recursos.
Componentes principais do MCP
Os MCP servers expõem três tipos principais de capacidades:
Tools: Funções executáveis que LLMs podem chamar para executar ações. Tools podem consultar bancos de dados, chamar APIs, realizar cálculos ou disparar workflows.
Resources: Dados somente leitura que MCP clients podem buscar e usar como contexto. Resources podem ser conteúdos de arquivos, dados de configuração ou conteúdo gerado dinamicamente.
Prompts: Templates de mensagem reutilizáveis que guiam o comportamento do LLM. Prompts fornecem instruções consistentes para operações de múltiplas etapas ou raciocínio especializado.
O que é FastMCP?
FastMCP é um framework Python de alto nível que simplifica o processo de construção de MCP servers e clients. Criado para reduzir dores de cabeça no desenvolvimento, o FastMCP possui as seguintes características:
API baseada em decorators: Decorators Python (
@mcp.tool,@mcp.resource,@mcp.prompt) eliminam boilerplateType Safety: Type hints completos e validação usando o sistema de tipos do Python
Suporte Async/Await: Python assíncrono moderno para operações de alta performance
Múltiplos transportes: Suporte para stdio, HTTP, WebSocket e SSE
Testing integrado: Teste fácil de client-server sem complexidade de subprocess
Pronto para produção: Recursos como tratamento de erros, logging e configuração para deployments de produção
Filosofia do FastMCP
O FastMCP se baseia em três princípios fundamentais:
Abstrações de alto nível: Menos código e ciclos de desenvolvimento mais rápidos
Simplicidade: Boilerplate mínimo permite foco na funcionalidade em vez de detalhes de protocolo
Pythonic: Idiomas naturais do Python o tornam familiar para desenvolvedores Python
Instalação e configuração
Comece instalando o FastMCP e as dependências necessárias. Recomendamos usar uv.
uv pip install fastmcp
Se você não tem o uv, instale-o com pip:
pip install uv
Ou instale o FastMCP diretamente com pip:
pip install fastmcp
Verifique se o FastMCP está instalado:
python -c "from fastmcp import FastMCP; print('FastMCP instalado com sucesso')"
Construindo seu primeiro MCP Server
Vamos criar um MCP server prático que demonstra tools, resources e prompts. Construiremos um Calculator Server que fornece operações matemáticas, recursos de configuração e prompts de instrução.
Passo 1: Configurando a estrutura do projeto
Primeiro, precisamos criar um diretório de projeto e inicializar seu ambiente. Crie uma pasta para seu projeto:
mkdir fastmcp-calculator
Em seguida, navegue até a pasta do seu projeto:
cd fastmcp-calculator
Inicialize seu projeto com os arquivos necessários:
uv init --python 3.11
Passo 2: Criando o MCP Server
Nosso Calculator MCP Server é um servidor MCP simples demonstrando tools, resources e prompts. Dentro da pasta do seu projeto, crie um arquivo chamado calculator_server.py e adicione o seguinte código.
import logging
import sys
from typing import Dict
from fastmcp import FastMCP
# Configure logging para stderr (crítico para integridade do protocolo MCP)
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
stream=sys.stderr
)
logger = logging.getLogger(__name__)
# Crie a instância do servidor FastMCP
mcp = FastMCP(name="CalculatorServer")
O servidor importa FastMCP e configura logging para stderr. O protocolo MCP exige que toda saída, exceto mensagens de protocolo, seja direcionada para stderr para evitar corromper a comunicação. A chamada FastMCP(name="CalculatorServer") cria a instância do servidor. Isso gerencia automaticamente todo o gerenciamento de protocolo.
Agora, vamos criar nossos tools.
@mcp.tool
def add(a: float, b: float) -> float:
"""
Adiciona dois números.
Args:
a: Primeiro número
b: Segundo número
Returns:
Soma de a e b
"""
try:
result = a + b
logger.info(f"Adição realizada: {a} + {b} = {result}")
return result
except TypeError as e:
logger.error(f"Erro de tipo em add: {e}")
raise ValueError(f"Tipos de entrada inválidos: {e}")
@mcp.tool
def subtract(a: float, b: float) -> float:
"""
Subtrai b de a.
Args:
a: Primeiro número (minuendo)
b: Segundo número (subtraendo)
Returns:
Diferença de a e b
"""
try:
result = a - b
logger.info(f"Subtração realizada: {a} - {b} = {result}")
return result
except TypeError as e:
logger.error(f"Erro de tipo em subtract: {e}")
raise ValueError(f"Tipos de entrada inválidos: {e}")
Definimos funções para adição e subtração. Ambas são envolvidas em um bloco try-catch para lançar erros de valor, registrar as informações e retornar o resultado.
@mcp.tool
def multiply(a: float, b: float) -> float:
"""
Multiplica dois números.
Args:
a: Primeiro número
b: Segundo número
Returns:
Produto de a e b
"""
try:
result = a * b
logger.info(f"Multiplicação realizada: {a} * {b} = {result}")
return result
except TypeError as e:
logger.error(f"Erro de tipo em multiply: {e}")
raise ValueError(f"Tipos de entrada inválidos: {e}")
@mcp.tool
def divide(a: float, b: float) -> float:
"""
Divide a por b.
Args:
a: Dividendo (numerador)
b: Divisor (denominador)
Returns:
Quociente de a dividido por b
Raises:
ValueError: Se tentar dividir por zero
"""
try:
if b == 0:
logger.warning(f"Tentativa de divisão por zero: {a} / {b}")
raise ValueError("Não é possível dividir por zero")
result = a / b
logger.info(f"Divisão realizada: {a} / {b} = {result}")
return result
except (TypeError, ZeroDivisionError) as e:
logger.error(f"Erro em divide: {e}")
raise ValueError(f"Erro de divisão: {e}")
Quatro funções decoradas (@mcp.tool) expõem operações matemáticas. Cada tool inclui:
Type hints para parâmetros e valores de retorno
Docstrings abrangentes (MCP usa essas como descrições de tools)
Tratamento de erro com blocos try-except
Logging para debugging e monitoramento
Validação de entrada
Vamos seguir para construir resources.
@mcp.resource("config://calculator/settings")
def get_settings() -> Dict:
"""
Fornece configuração da calculadora e operações disponíveis.
Returns:
Dicionário contendo configurações e metadados da calculadora
"""
logger.debug("Buscando configurações da calculadora")
return {
"version": "1.0.0",
"operations": ["add", "subtract", "multiply", "divide"],
"precision": "IEEE 754 double precision",
"max_value": 1.7976931348623157e+308,
"min_value": -1.7976931348623157e+308,
"supports_negative": True,
"supports_decimals": True
}
@mcp.resource("docs://calculator/guide")
def get_guide() -> str:
"""
Fornece um guia do usuário para o servidor calculadora.
Returns:
String contendo guia de uso e exemplos
"""
logger.debug("Recuperando guia da calculadora")
guide = """
## Guia do Usuário da Calculadora
### Operações Disponíveis
1. **add(a, b)**: Retorna a + b
Exemplo: add(5, 3) = 8
2. **subtract(a, b)**: Retorna a - b
Exemplo: subtract(10, 4) = 6
3. **multiply(a, b)**: Retorna a * b
Exemplo: multiply(7, 6) = 42
4. **divide(a, b)**: Retorna a / b
Exemplo: divide(20, 4) = 5.0
## Tratamento de Erros
- Divisão por zero levantará um ValueError
- Entradas não numéricas levantarão um ValueError
- Todas as entradas devem ser números válidos (int ou float)
## Precisão
A calculadora usa aritmética de ponto flutuante de precisão dupla IEEE 754.
Os resultados podem conter pequenos erros de arredondamento para algumas operações.
"""
return guide
Duas funções decoradas (@mcp.resource) fornecem dados estáticos e dinâmicos:
config://calculator/settings: Retorna metadados sobre a calculadoradocs://calculator/guide: Retorna um guia do usuário formatadoFormato URI distingue tipos de resources (convenção:
type://category/resource)
Vamos construir nossos prompts.
@mcp.prompt
def calculate_expression(expression: str) -> str:
"""
Fornece instruções para avaliar uma expressão matemática.
Args:
expression: Uma expressão matemática para avaliar
Returns:
Prompt formatado instruindo o LLM sobre como avaliar a expressão
"""
logger.debug(f"Gerando prompt de cálculo para: {expression}")
prompt = f"""
Por favor, avalie a seguinte expressão matemática passo a passo:
Expressão: {expression}
Instruções:
1. Divida a expressão em operações individuais
2. Use a tool de calculadora apropriada para cada operação
3. Siga a ordem de operações (parênteses, multiplicação/divisão, adição/subtração)
4. Mostre todas as etapas intermediárias
5. Forneça o resultado final
Tools disponíveis: add, subtract, multiply, divide
"""
return prompt.strip()
Finalmente, adicione o script de inicialização do servidor.
if __name__ == "__main__":
logger.info("Iniciando Calculator MCP Server...")
try:
# Execute o servidor com transporte stdio (padrão para Claude Desktop)
mcp.run(transport="stdio")
except KeyboardInterrupt:
logger.info("Servidor interrompido pelo usuário")
sys.exit(0)
except Exception as e:
logger.error(f"Erro fatal: {e}", exc_info=True)
sys.exit(1)
O decorator @mcp.prompt cria templates de instrução que guiam o comportamento do LLM para tarefas complexas.
Passo 3: Construindo o MCP Client
Neste passo, vamos demonstrar como interagir com o Calculator MCP Server que criamos acima. Crie um novo arquivo chamado calculator_client.py.
import asyncio
import logging
import sys
from typing import Any
from fastmcp import Client, FastMCP
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
stream=sys.stderr
)
logger = logging.getLogger(__name__)
async def main():
"""
Função principal do client demonstrando interação com o servidor.
"""
from calculator_server import mcp as server
logger.info("Inicializando Calculator Client...")
try:
async with Client(server) as client:
logger.info("✓ Conectado ao Calculator Server")
# DESCOBRIR CAPACIDADES
print("\n" + "="*60)
print("1. DESCOBRINDO CAPACIDADES DO SERVIDOR")
print("="*60)
# Listar tools disponíveis
tools = await client.list_tools()
print(f"\nTools Disponíveis ({len(tools)}):")
for tool in tools:
print(f" • {tool.name}: {tool.description}")
# Listar resources disponíveis
resources = await client.list_resources()
print(f"\nResources Disponíveis ({len(resources)}):")
for resource in resources:
print(f" • {resource.uri}: {resource.name or resource.uri}")
# Listar prompts disponíveis
prompts = await client.list_prompts()
print(f"\nPrompts Disponíveis ({len(prompts)}):")
for prompt in prompts:
print(f" • {prompt.name}: {prompt.description}")
# CHAMAR TOOLS
print("\n" + "="*60)
print("2. CHAMANDO TOOLS")
print("="*60)
# Adição simples
print("\nTeste 1: Adicionando 15 + 27")
result = await client.call_tool("add", {"a": 15, "b": 27})
result_value = extract_tool_result(result)
print(f" Resultado: 15 + 27 = {result_value}")
# Divisão com tratamento de erro
print("\nTeste 2: Dividindo 100 / 5")
result = await client.call_tool("divide", {"a": 100, "b": 5})
result_value = extract_tool_result(result)
print(f" Resultado: 100 / 5 = {result_value}")
# Caso de erro: divisão por zero
print("\nTeste 3: Divisão por Zero (Tratamento de Erro)")
try:
result = await client.call_tool("divide", {"a": 10, "b": 0})
print(f" Sucesso inesperado: {result}")
except Exception as e:
print(f" ✓ Erro capturado corretamente: {str(e)}")
# LER RESOURCES
print("\n" + "="*60)
print("3. LENDO RESOURCES")
print("="*60)
# Ler resource de configurações
print("\nBuscando Configurações da Calculadora...")
settings_resource = await client.read_resource("config://calculator/settings")
print(f" Versão: {settings_resource[0].text}")
# Ler resource de guia
print("\nBuscando Guia da Calculadora...")
guide_resource = await client.read_resource("docs://calculator/guide")
# Imprimir primeiros 200 caracteres do guia
guide_text = guide_resource[0].text[:200] + "..."
print(f" {guide_text}")
# ENCADEANDO OPERAÇÕES
print("\n" + "="*60)
print("4. ENCADEANDO MÚLTIPLAS OPERAÇÕES")
print("="*60)
# Calcular: (10 + 5) * 3 - 7
print("\nCalculando: (10 + 5) * 3 - 7")
# Passo 1: Adicionar
print(" Passo 1: Adicionar 10 + 5")
add_result = await client.call_tool("add", {"a": 10, "b": 5})
step1 = extract_tool_result(add_result)
print(f" Resultado: {step1}")
# Passo 2: Multiplicar
print(" Passo 2: Multiplicar 15 * 3")
mult_result = await client.call_tool("multiply", {"a": step1, "b": 3})
step2 = extract_tool_result(mult_result)
print(f" Resultado: {step2}")
# Passo 3: Subtrair
print(" Passo 3: Subtrair 45 - 7")
final_result = await client.call_tool("subtract", {"a": step2, "b": 7})
final = extract_tool_result(final_result)
print(f" Resultado Final: {final}")
# OBTER TEMPLATE DE PROMPT
print("\n" + "="*60)
print("5. USANDO TEMPLATES DE PROMPT")
print("="*60)
expression = "25 * 4 + 10 / 2"
print(f"\nTemplate de Prompt para: {expression}")
prompt_response = await client.get_prompt(
"calculate_expression",
{"expression": expression}
)
print(f" Template:\n{prompt_response.messages[0].content.text}")
logger.info("✓ Operações do client concluídas com sucesso")
except Exception as e:
logger.error(f"Erro do client: {e}", exc_info=True)
sys.exit(1)
A partir do código acima, o client usa async with Client(server) para gerenciamento seguro de conexão. Isso automaticamente gerencia a configuração e limpeza da conexão.
Também precisamos de uma função auxiliar para lidar com os resultados.
def extract_tool_result(response: Any) -> Any:
"""
Extrai o valor real do resultado de uma resposta de tool.
MCP envolve resultados em objetos de conteúdo, este helper os desempacota.
"""
try:
if hasattr(response, 'content') and response.content:
content = response.content[0]
# Prefere conteúdo de texto explícito quando disponível (TextContent)
if hasattr(content, 'text') and content.text is not None:
# Se o texto é JSON, tenta parsear e extrair um campo `result`
import json as _json
text_val = content.text
try:
parsed_text = _json.loads(text_val)
# Se JSON contém um campo result, retorna-o
if isinstance(parsed_text, dict) and 'result' in parsed_text:
return parsed_text.get('result')
return parsed_text
except _json.JSONDecodeError:
# Tenta converter texto simples em número
try:
if '.' in text_val:
return float(text_val)
return int(text_val)
except Exception:
return text_val
# Tenta extrair resultado JSON via `.json()` do modelo ou dict-like `.json`
if hasattr(content, 'json'):
try:
if callable(content.json):
json_str = content.json()
import json as _json
try:
parsed = _json.loads(json_str)
except _json.JSONDecodeError:
return json_str
else:
parsed = content.json
# Se parsed é um dict, tenta formas comuns
if isinstance(parsed, dict):
# Se resultado aninhado existe
if 'result' in parsed:
res = parsed.get('result')
elif 'text' in parsed:
res = parsed.get('text')
else:
res = parsed
# Se res é str que parece um número, converte
if isinstance(res, str):
try:
if '.' in res:
return float(res)
return int(res)
except Exception:
return res
return res
return parsed
except Exception:
pass
return response
except Exception as e:
logger.warning(f"Não foi possível extrair resultado: {e}")
return response
if __name__ == "__main__":
logger.info("Calculator Client Iniciando...")
asyncio.run(main())
Olhando para o código acima, antes de usar tools, o client lista as capacidades disponíveis. O await client.list_tools() obtém todos os metadados de tools, incluindo descrições. O await client.list_resources() descobre resources disponíveis. Por último, o await client.list_prompts() encontrará templates de prompt disponíveis.
Passo 4: Executando o Server e o Client
Você abrirá dois terminais. No terminal 1, inicie o servidor:
python calculator_server.py
No terminal 2, execute o client:
python calculator_client.py
Padrões avançados com FastMCP
Embora nosso exemplo de calculadora use lógica básica, o FastMCP é projetado para lidar com cenários complexos e prontos para produção. À medida que você escala seus MCP servers, pode aproveitar:
Operações assíncronas
Use async def para tools que executam tarefas vinculadas a I/O, como consultas de banco de dados ou chamadas de API.
@mcp.tool
async def fetch_user_data(user_id: int) -> Dict:
"""Busca dados do usuário de forma assíncrona"""
async with aiohttp.ClientSession() as session:
async with session.get(f"https://api.example.com/users/{user_id}") as response:
return await response.json()
Resources dinâmicos
Resources podem aceitar argumentos (por exemplo, resource://users/{user_id}) para buscar pontos de dados específicos dinamicamente.
Validação de tipo complexo
Use modelos Pydantic ou type hints complexos do Python para garantir que o LLM envie dados no formato exato que seu backend requer.
Transportes customizados
Embora tenhamos usado stdio, o FastMCP também suporta SSE (Server-Sent Events) para integrações baseadas na web e ferramentas de UI customizadas.
Melhores práticas de tratamento de erros
Prática | Descrição |
|---|---|
Captura específica de exceções | Use TypeError, ZeroDivisionError em vez de Exception genérica |
Mensagens de erro significativas | Forneça contexto claro para usuários |
Logging detalhado | Registre erros com stack traces para debugging |
Propagação graciosa | Não deixe o servidor crashar, retorne erros estruturados |
Perguntas frequentes
P: FastMCP é adequado para produção?
R: Sim, FastMCP é projetado para produção com recursos de logging, tratamento de erros e suporte a múltiplos transportes.
P: Posso usar FastMCP com outros LLMs além do Claude?
R: Sim, FastMCP implementa o padrão MCP aberto e pode ser usado com qualquer client compatível com MCP.
P: Preciso conhecer async/await para usar FastMCP?
R: Não para casos de uso básicos, mas conhecimento de async/await ajuda em cenários avançados com operações I/O.
P: Como faço deploy de um servidor FastMCP?
R: Você pode fazer deploy como um processo standalone, container Docker, ou integrar com frameworks web existentes.
Conclusão
FastMCP preenche a lacuna entre o complexo Model Context Protocol e a experiência de desenvolvedor limpa e baseada em decorators que programadores Python esperam. Ao remover o boilerplate associado ao JSON-RPC 2.0 e ao gerenciamento manual de transporte, permite que você se concentre no que importa: construir as ferramentas que tornam os LLMs mais capazes.
Neste tutorial, cobrimos:
A arquitetura central do MCP (Servers vs. Clients)
Como definir Tools para ações, Resources para dados e Prompts para instruções
Como construir um client funcional para testar e encadear sua lógica de servidor
Seja construindo um utilitário simples ou uma camada complexa de orquestração de dados, FastMCP fornece o caminho mais "Pythonic" para um ecossistema agêntico pronto para produção.
O que você vai construir em seguida? Confira a documentação do FastMCP para explorar estratégias de deployment mais avançadas e integrações de UI.