Crie uma tool que consulta uma API externa real — aprenda a fazer requisições HTTP dentro de um MCP Server.
Para nosso server de clima, vamos usar a wttr.in — uma API de clima gratuita, sem necessidade de cadastro ou chave de autenticação. Ela retorna dados meteorológicos em texto simples ou JSON, é extremamente simples de usar e perfeita para aprendizado. Basta fazer uma requisição GET e os dados chegam prontos.
A wttr.in é acessada via URL simples. Para obter dados em JSON, basta adicionar o parâmetro ?format=j1:
# Exemplos de URL:
https://wttr.in/São Paulo?format=j1
https://wttr.in/Tokyo?format=j1
https://wttr.in/London?format=j1
O JSON retornado contém temperatura, condição climática, umidade, sensação térmica, velocidade do vento e muito mais. Vamos extrair os dados mais relevantes para criar uma resposta útil.
A wttr.in é um projeto open source hospedado no GitHub. Suporta mais de 50 idiomas, aceita nomes de cidades em qualquer idioma e também funciona com coordenadas geográficas. É usada por milhões de desenvolvedores e não requer autenticação — ideal para prototipagem rápida e projetos de aprendizado como o nosso.
Antes de implementar a tool, teste a API diretamente no navegador ou terminal. Acesse https://wttr.in/São Paulo?format=j1 e veja a estrutura do JSON. Isso ajuda a entender quais campos estão disponíveis e como extrair os dados que queremos. Você também pode testar no terminal com curl "wttr.in/São Paulo?format=j1".
Para fazer requisições HTTP dentro do nosso server MCP, precisamos de uma biblioteca HTTP. Em Python, as duas opções principais são requests (síncrona) e httpx (com suporte async). Para servers MCP, o httpx é a escolha preferida porque suporta chamadas assíncronas nativamente, o que combina melhor com a arquitetura do MCP.
Instale o httpx no seu projeto usando o gerenciador de pacotes uv:
# Adicionar httpx como dependência
uv add httpx
Com o httpx, podemos usar async with httpx.AsyncClient() para fazer requisições HTTP assíncronas dentro de tools MCP. A função da tool será declarada como async def, permitindo que o server continue responsivo enquanto aguarda a resposta da API externa.
Agora vamos implementar a tool de consulta ao clima. Ela receberá o nome de uma cidade como string, fará uma requisição HTTP para a wttr.in, parseará o JSON de resposta e extrairá as informações mais relevantes: temperatura, condição climática, umidade e sensação térmica.
from mcp.server.fastmcp import FastMCP
import httpx
mcp = FastMCP("Clima")
@mcp.tool()
async def consultar_clima(cidade: str) -> str:
"""Consulta o clima atual de uma cidade."""
async with httpx.AsyncClient() as client:
url = f"https://wttr.in/{cidade}?format=j1"
response = await client.get(url, timeout=10.0)
response.raise_for_status()
dados = response.json()
current = dados["current_condition"][0]
temp = current["temp_C"]
sensacao = current["FeelsLikeC"]
umidade = current["humidity"]
descricao = current["weatherDesc"][0]["value"]
return (
f"Clima em {cidade}:\n"
f"Temperatura: {temp}°C\n"
f"Sensação térmica: {sensacao}°C\n"
f"Umidade: {umidade}%\n"
f"Condição: {descricao}"
)
Note que usamos async def e await na tool. O FastMCP suporta tools assíncronas nativamente — basta declarar a função como async. O timeout=10.0 garante que a requisição não fique pendurada para sempre se a API não responder. Sempre defina timeouts em requisições HTTP externas.
A forma como você retorna os dados da tool influencia diretamente a qualidade da resposta que o modelo dará ao usuário. Retornar uma string formatada e legível é muito melhor do que retornar o JSON bruto da API. O modelo recebe essa string e a apresenta ao usuário de forma natural e conversacional.
A resposta da tool deve ser uma string estruturada mas legível. O modelo lê essa string e a transforma em linguagem natural para o usuário. Compare as duas abordagens:
{"temp_C": "25", "humidity": "60", "weatherDesc": [{"value": "Clear"}], ...}
Clima em São Paulo:
Temperatura: 25°C
Umidade: 60%
Condição: Clear
Quando sua tool depende de uma API externa, muitas coisas podem dar errado: a cidade pode não existir, a internet pode cair, a API pode estar fora do ar, ou a requisição pode demorar demais. Cada um desses cenários precisa ser tratado individualmente para que o server continue funcionando e o modelo receba uma mensagem de erro útil.
@mcp.tool()
async def consultar_clima(cidade: str) -> str:
"""Consulta o clima atual de uma cidade."""
try:
async with httpx.AsyncClient() as client:
url = f"https://wttr.in/{cidade}?format=j1"
response = await client.get(url, timeout=10.0)
response.raise_for_status()
dados = response.json()
# ... extrair e retornar dados
except httpx.HTTPStatusError:
return f"Erro: cidade '{cidade}' não encontrada."
except httpx.ConnectError:
return "Erro: sem conexão com a internet."
except httpx.TimeoutException:
return "Erro: a API demorou demais para responder."
except Exception as e:
return f"Erro inesperado: {str(e)}"
Nunca confie em APIs externas! Toda requisição HTTP pode falhar. Uma tool que funciona perfeitamente no seu teste local pode quebrar em produção porque a API ficou fora do ar, mudou o formato da resposta ou atingiu o rate limit. O tratamento de erros não é opcional — é obrigatório para qualquer tool que faz requisições externas.
A API respondeu com código de erro. Cidade não encontrada (404) ou erro interno da API (500). Retorne mensagem clara indicando o problema.
Não foi possível conectar à API — geralmente significa sem internet ou DNS não resolvido. Informe o usuário que a conexão falhou.
A API demorou mais que o timeout definido (10 segundos). Pode ser sobrecarga do servidor. Informe que a API não respondeu a tempo.
Qualquer erro inesperado — JSON mal formatado, campo ausente, etc. O except genérico no final garante que nenhum erro escape sem tratamento.
Com a tool implementada e o tratamento de erros no lugar, configure o server no Claude Desktop e teste com diferentes cidades e cenários. O objetivo é verificar que a tool retorna dados corretos e que os erros são tratados graciosamente.
Teste com perguntas naturais para verificar que o Claude usa a tool corretamente:
Observe como o Claude interpreta os dados retornados pela tool. Ele não apenas repete os números — ele contextualiza. Se a temperatura é 35°C, ele pode dizer "está bastante quente". Se a umidade é 90%, ele pode avisar sobre a sensação abafada. Quanto mais clara a resposta da tool, melhor a interpretação do modelo.
Este server de clima é um exemplo clássico de "API bridge" — um padrão muito comum em servers MCP. O server atua como ponte entre o modelo e uma API externa, traduzindo a requisição do modelo em chamada HTTP e a resposta HTTP em texto para o modelo. Esse mesmo padrão pode ser aplicado a qualquer API: previsão do tempo, cotações, notícias, dados financeiros, status de serviços e muito mais.
Próximo Módulo: 3.6 — Adicionando Resources