Criando um Servidor MCP em Python para Previsão do Tempo
Este tutorial irá guiá-lo na construção de um servidor MCP de exemplo — um servidor de previsão do tempo — e na conexão com um host compatível (como o Claude for Desktop). O passo a passo começa com uma configuração básica e evolui para casos de uso mais avançados, sempre explicando o porquê de cada etapa.
O que você vai construir
Neste tutorial, você irá criar um servidor MCP de exemplo para previsão do tempo, que disponibiliza quatro ferramentas principais:
get_current_weather_city
: retorna as condições meteorológicas atuais para uma cidade brasileira.get_forecast_city
: retorna a previsão do tempo para uma cidade brasileira.get_weather_coordinates
: retorna as condições meteorológicas para coordenadas específicas (latitude/longitude).get_weather_state_capital
: retorna as condições meteorológicas da capital de um estado brasileiro.
O servidor será utilizado por um host (como o Claude for Desktop), que se conecta a ele via protocolo MCP. Assim, LLMs poderão consultar informações meteorológicas em tempo real para diferentes cidades e estados do Brasil.
Conceitos principais do MCP
Servidores MCP podem fornecer três tipos de funcionalidades:
- Resources: dados semelhantes a arquivos (respostas de API, conteúdo de arquivos).
- Tools: funções que LLMs podem chamar (após permissão do usuário).
- Prompts: templates pré-definidos para guiar o LLM.
Neste tutorial, o foco será nas tools — funções que podem ser chamadas por modelos de linguagem para obter informações sob demanda.
Pré-requisitos
Antes de começar, certifique-se de atender aos seguintes requisitos:
- Ter o Python 3.10 ou superior instalado.
- Ter familiaridade básica com LLMs (como o Claude).
- Possuir uma conta gratuita no OpenWeatherMap para obter sua chave de API (necessária para acessar os dados climáticos).
- Ter o Claude for Desktop instalado em sua máquina (necessário para testar a integração MCP).
Preparando o ambiente
A seguir, você irá preparar o ambiente de desenvolvimento. Cada etapa é explicada para que você entenda o propósito de cada comando.
- Instale a ferramenta de inicialização
uv
O uv
é um gerenciador de ambientes e dependências rápido para Python, que facilita a criação e o gerenciamento do seu projeto.
curl -LsSf https://astral.sh/uv/install.sh | sh
- Crie um novo projeto e acesse a pasta
Este comando inicializa a estrutura do projeto e entra na pasta criada.
uv init weather
cd weather
- Crie e ative o ambiente virtual
O ambiente virtual isola as dependências do projeto, evitando conflitos com outros projetos Python.
uv venv
source .venv/bin/activate
- Instale as dependências necessárias
Aqui você instala o MCP SDK, o cliente HTTP assíncrono httpx
e o dotenv
para ler variáveis de ambiente.
uv add "mcp[cli]" httpx dotenv
- Crie o arquivo principal do servidor
Este será o arquivo onde você implementará o servidor MCP e as ferramentas de previsão do tempo.
touch weather.py
- Crie o arquivo
.env
com sua chave da API
O arquivo .env
armazena sua chave da API do OpenWeatherMap de forma segura e fora do código-fonte.
OPENWEATHER_API_KEY=SUA_CHAVE_AQUI
Substitua SUA_CHAVE_AQUI
pela chave obtida no site do OpenWeatherMap.
Definindo ferramentas
A partir deste ponto, você irá construir o código do servidor MCP, sempre com explicações antes de cada bloco para garantir o entendimento do que está sendo feito.
1. Importações e configuração inicial
Aqui você prepara o ambiente do seu projeto: importa as bibliotecas essenciais, configura o carregamento das variáveis do .env
e inicializa o servidor MCP. Isso garante que seu código estará pronto para lidar com requisições HTTP, acessar a chave da API e expor as ferramentas para o protocolo MCP.
# Importa tipos para tipagem estática e módulos de utilidades
from typing import Any, Optional
import httpx # Cliente HTTP assíncrono
from mcp.server.fastmcp import FastMCP # Framework MCP
import os
from dotenv import load_dotenv # Para carregar variáveis de ambiente do .env
# Inicializa o servidor FastMCP com o nome do serviço
mcp = FastMCP("weather-brazil")
# URLs base das APIs utilizadas e User-Agent customizado
INMET_API_BASE = "https://apitempo.inmet.gov.br"
OPENWEATHER_API_BASE = "https://api.openweathermap.org/data/2.5"
USER_AGENT = "weather-brazil-app/1.0"
# Carrega variáveis de ambiente do arquivo .env (ex: API KEY)
load_dotenv()
OPENWEATHER_API_KEY = os.getenv("OPENWEATHER_API_KEY")
2. Função utilitária para requisições HTTP
Agora vamos criar uma função que faz requisições HTTP de forma segura e assíncrona. Assim, você pode buscar dados de APIs externas (como a do OpenWeatherMap) sem travar o servidor e com tratamento de erros.
# Cabeçalho padrão para requisições HTTP
default_headers = {"User-Agent": USER_AGENT}
async def make_request(
url: str, headers: Optional[dict] = None
) -> Optional[dict[str, Any]]:
"""
Faz requisições HTTP GET de forma assíncrona, com tratamento de erros.
Retorna o JSON da resposta ou None em caso de erro.
"""
if headers is None:
headers = default_headers
async with httpx.AsyncClient() as client:
try:
response = await client.get(url, headers=headers, timeout=30.0)
response.raise_for_status() # Lança exceção se status não for 2xx
return response.json()
except httpx.HTTPStatusError as e:
print(f"HTTP Error {e.response.status_code}: {e.response.text}")
return None
except httpx.TimeoutException:
print("Request timeout")
return None
except Exception as e:
print(f"Unexpected error: {str(e)}")
return None
3. Funções de formatação dos dados
Os dados das APIs geralmente vêm em formato bruto. Aqui, você aprende a transformar essas informações em textos claros e bonitos para mostrar ao usuário, tanto para o clima atual quanto para a previsão.
def format_current_weather(data: Optional[dict]) -> str:
"""
Formata os dados meteorológicos atuais para exibição amigável.
Recebe o dicionário retornado pela API e devolve uma string formatada.
"""
if not data:
return "Não foi possível obter dados meteorológicos."
weather = data.get("weather", [{}])[0] # Descrição do clima
main = data.get("main", {}) # Temperatura, umidade, pressão
wind = data.get("wind", {}) # Velocidade do vento
return f"""
Condições Atuais - {data.get("name", "Localização desconhecida")}
Temperatura: {main.get("temp", "N/A")}°C (Sensação térmica: {main.get("feels_like", "N/A")}°C)
Condição: {weather.get("description", "N/A").title()}
Umidade: {main.get("humidity", "N/A")}%
Pressão: {main.get("pressure", "N/A")} hPa
Vento: {wind.get("speed", "N/A")} m/s
Visibilidade: {data.get("visibility", "N/A") / 1000 if data.get("visibility") else "N/A"} km
"""
def format_forecast(data: Optional[dict]) -> str:
"""
Formata a previsão meteorológica para exibição amigável.
Mostra os próximos 5 períodos de previsão (3 em 3 horas).
"""
if not data or "list" not in data:
return "Não foi possível obter previsão meteorológica."
forecasts = []
city_name = data.get("city", {}).get("name", "Localização desconhecida")
forecasts.append(f"Previsão para {city_name}\n")
# Itera sobre os próximos 5 períodos
for item in data["list"][:5]:
dt_txt = item.get("dt_txt", "") # Data/hora
weather = item.get("weather", [{}])[0]
main = item.get("main", {})
wind = item.get("wind", {})
forecast = f"""
{dt_txt}
Temperatura: {main.get("temp", "N/A")}°C (Min: {main.get("temp_min", "N/A")}°C, Max: {main.get("temp_max", "N/A")}°C)
Condição: {weather.get("description", "N/A").title()}
Umidade: {main.get("humidity", "N/A")}%
Vento: {wind.get("speed", "N/A")} m/s
Chuva: {item.get("pop", 0) * 100:.0f}% de probabilidade
"""
forecasts.append(forecast)
return "\n---\n".join(forecasts)
4. Definição dos tools MCP
Chegou a hora de criar as ferramentas (tools) que o LLM poderá chamar! Cada função aqui representa um comando que o usuário pode acionar para obter informações meteorológicas de diferentes formas: por cidade, previsão, coordenadas ou capital do estado.
@mcp.tool()
async def get_current_weather_city(city: str, state: str = "") -> str:
"""
Obter condições meteorológicas atuais para uma cidade brasileira.
Args:
city: Nome da cidade (ex: São Paulo, Rio de Janeiro)
state: Estado opcional (ex: SP, RJ) - ajuda na precisão
"""
# Monta a query da cidade (ex: "Recife,PE,BR")
query = f"{city}"
if state:
query += f",{state}"
query += ",BR" # Brasil
url = f"{OPENWEATHER_API_BASE}/weather"
params = {
"q": query,
"appid": OPENWEATHER_API_KEY,
"units": "metric",
"lang": "pt_br",
}
# Constrói URL com parâmetros (ex: ...?q=Recife,PE,BR&appid=...)
param_str = "&".join([f"{k}={v}" for k, v in params.items()])
full_url = f"{url}?{param_str}"
data = await make_request(full_url)
return format_current_weather(data)
@mcp.tool()
async def get_forecast_city(city: str, state: str = "") -> str:
"""
Obter previsão meteorológica para uma cidade brasileira.
Args:
city: Nome da cidade (ex: São Paulo, Rio de Janeiro)
state: Estado opcional (ex: SP, RJ) - ajuda na precisão
"""
# Monta a query da cidade (ex: "Porto Alegre,RS,BR")
query = f"{city}"
if state:
query += f",{state}"
query += ",BR" # Brasil
url = f"{OPENWEATHER_API_BASE}/forecast"
params = {
"q": query,
"appid": OPENWEATHER_API_KEY,
"units": "metric",
"lang": "pt_br",
}
# Constrói URL com parâmetros
param_str = "&".join([f"{k}={v}" for k, v in params.items()])
full_url = f"{url}?{param_str}"
data = await make_request(full_url)
return format_forecast(data)
@mcp.tool()
async def get_weather_coordinates(latitude: float, longitude: float) -> str:
"""
Obter condições meteorológicas atuais para coordenadas específicas.
Args:
latitude: Latitude da localização (ex: -23.55)
longitude: Longitude da localização (ex: -46.63)
"""
url = f"{OPENWEATHER_API_BASE}/weather"
params = {
"lat": latitude,
"lon": longitude,
"appid": OPENWEATHER_API_KEY,
"units": "metric",
"lang": "pt_br",
}
# Constrói URL com parâmetros
param_str = "&".join([f"{k}={v}" for k, v in params.items()])
full_url = f"{url}?{param_str}"
data = await make_request(full_url)
return format_current_weather(data)
5. Dicionário de capitais e tool auxiliar
Para facilitar a vida do usuário, criamos um dicionário com todas as capitais do Brasil. Assim, basta informar o nome ou sigla do estado para receber o clima da capital correspondente, sem precisar lembrar o nome exato da cidade.
# Dicionário de capitais brasileiras para facilitar consultas por estado
CAPITAIS_BRASIL = {
"acre": ("Rio Branco", "AC"),
"alagoas": ("Maceió", "AL"),
"amapá": ("Macapá", "AP"),
"amazonas": ("Manaus", "AM"),
"bahia": ("Salvador", "BA"),
"ceará": ("Fortaleza", "CE"),
"distrito federal": ("Brasília", "DF"),
"espírito santo": ("Vitória", "ES"),
"goiás": ("Goiânia", "GO"),
"maranhão": ("São Luís", "MA"),
"mato grosso": ("Cuiabá", "MT"),
"mato grosso do sul": ("Campo Grande", "MS"),
"minas gerais": ("Belo Horizonte", "MG"),
"pará": ("Belém", "PA"),
"paraíba": ("João Pessoa", "PB"),
"paraná": ("Curitiba", "PR"),
"pernambuco": ("Recife", "PE"),
"piauí": ("Teresina", "PI"),
"rio de janeiro": ("Rio de Janeiro", "RJ"),
"rio grande do norte": ("Natal", "RN"),
"rio grande do sul": ("Porto Alegre", "RS"),
"rondônia": ("Porto Velho", "RO"),
"roraima": ("Boa Vista", "RR"),
"santa catarina": ("Florianópolis", "SC"),
"são paulo": ("São Paulo", "SP"),
"sergipe": ("Aracaju", "SE"),
"tocantins": ("Palmas", "TO"),
}
@mcp.tool()
async def get_weather_state_capital(state: str) -> str:
"""
Obter condições meteorológicas da capital de um estado brasileiro.
Args:
state: Nome do estado ou sigla (ex: São Paulo, SP, Minas Gerais, MG)
"""
state_lower = state.lower()
# Procura por nome completo ou sigla (case-insensitive)
capital_info = None
for state_name, (capital, sigla) in CAPITAIS_BRASIL.items():
if state_lower == state_name or state_lower == sigla.lower():
capital_info = (capital, sigla)
break
if not capital_info:
return f"Estado '{state}' não encontrado. Use o nome completo ou sigla (ex: São Paulo ou SP)."
capital, sigla = capital_info
# Reutiliza o tool de cidade para buscar a capital
return await get_current_weather_city(capital, sigla)
6. Execução do servidor
Por fim, este bloco garante que, ao rodar o script, o servidor MCP será iniciado corretamente. Ele também faz uma checagem para garantir que a chave da API está configurada, evitando erros comuns de configuração.
if __name__ == "__main__":
# Mensagem inicial para o usuário
print("🌦️ Servidor MCP Weather Brasil")
print("⚠️ IMPORTANTE: Configure sua API key do OpenWeatherMap!")
print(" 1. Cadastre-se em: https://openweathermap.org/api")
print(" 2. Substitua OPENWEATHER_API_KEY no código")
print(" 3. Execute novamente")
# Verifica se a API KEY está configurada
if OPENWEATHER_API_KEY == "SUA_API_KEY_AQUI":
print("\n❌ API key não configurada!")
exit(1)
# Inicia o servidor MCP usando transporte STDIO
mcp.run(transport="stdio")
Conectando ao Claude for Desktop
Para integrar o servidor MCP ao Claude for Desktop, edite o arquivo de configuração conforme o seu sistema operacional:
- macOS/Linux:
~/.config/claude_desktop/claude_desktop_config.json
- Windows:
%APPDATA%\Claude\claude_desktop_config.json
Adicione a seguinte configuração, ajustando os caminhos conforme o local do seu projeto:
{
"mcpServers": {
"weather": {
"command": "/Users/SEU_USUARIO/.local/bin/uv",
"args": [
"--directory",
"/Users/SEU_USUARIO/PASTA_ONDE_ESTA_O_PROJETO/weather",
"run",
"weather.py"
],
"env": {
"PATH": "/Users/SEU_USUARIO/.local/bin:/usr/local/bin:/usr/bin:/bin"
}
}
}
}
Salve o arquivo e reinicie o Claude for Desktop para que a integração seja reconhecida.
Testando no Claude
Abra o Claude e acesse a seção “Search and tools”. Você verá as quatro ferramentas disponíveis:
get_current_weather_city
get_forecast_city
get_weather_coordinates
get_weather_state_capital
Você pode chamar as ferramentas diretamente nas conversas. Exemplos de uso:
get_current_weather_city
— "Como está o tempo em Recife, PE?"get_forecast_city
— "Qual a previsão para Porto Alegre, RS?"get_weather_coordinates
— "Me dê o clima para latitude -23.55 e longitude -46.63."get_weather_state_capital
— "Como está o tempo na capital de Minas Gerais?"
Assim, é possível consultar condições e previsões meteorológicas para cidades, capitais ou coordenadas específicas diretamente pelo Claude.
Depuração
Se aparecer a mensagem "No tools found":
- Verifique se o servidor está sendo executado corretamente.
- Revise a configuração no arquivo
claude_desktop_config.json
. - Confira se existe comunicação via STDIO (sem erros silenciosos).
Próximos passos
- Adicionar parâmetros nos tools (ex: data ou localização).
- Incluir resources (ex: enviar arquivos com dados climáticos).
- Criar prompts reutilizáveis.
- Usar HTTP/SSE como transporte em vez de STDIO.
Resumo
Neste tutorial, você aprendeu a:
- Criar um servidor MCP em Python.
- Expor quatro ferramentas:
get_current_weather_city
get_forecast_city
get_weather_coordinates
get_weather_state_capital
- Conectar o servidor ao Claude for Desktop via STDIO.
Agora você pode expandir para novos usos — como banco de dados, GitHub, e-mail, automações e muito mais.
Para mais informações sobre o protocolo MCP, consulte a documentação oficial: https://modelcontextprotocol.io/introduction