Files
hope-voice-api/agent/weather_mcp.py
T
Shane 510a577761 feat: add get_weather MCP tool using wttr.in API
- New agent/weather_mcp.py: stdio MCP server with get_weather(location)
  that returns a short conversational summary (temp, conditions, high/low)
- Register weather toolset in build_mcp_toolsets() alongside web-access
- Update system prompt with Weather section
2026-08-22 15:26:25 -04:00

89 lines
2.9 KiB
Python

"""Weather MCP server — exposes a get_weather tool backed by wttr.in.
Runs over stdio inside the voice container. The agent attaches it via
MCPServerStdio, so the LLM can call get_weather during a conversation to
report current conditions and today's forecast for any location.
Backed by wttr.in (no API key needed):
- GET https://wttr.in/{location}?format=j1 -> full JSON weather report
The tool returns a short, conversational summary suitable for speech
(1-2 sentences), not raw data.
"""
from __future__ import annotations
import os
import httpx
from mcp.server.fastmcp import FastMCP
WTTR_BASE = os.environ.get("WTTR_BASE", "https://wttr.in").rstrip("/")
mcp = FastMCP("weather")
def _summarize(data: dict, location: str) -> str:
"""Build a short spoken-style weather summary from wttr.in j1 JSON."""
current = data.get("current_condition", [{}])[0] or {}
area = data.get("nearest_area", [{}])[0] or {}
city = (area.get("areaName", [{}])[0].get("value") or location).strip()
temp_c = current.get("temp_C")
feels_c = current.get("FeelsLikeC")
desc = (current.get("weatherDesc", [{}])[0].get("value") or "").strip().lower()
astronomy = data.get("astronomy", [{}])[0] or {}
sunrise = astronomy.get("sunrise", "")
sunset = astronomy.get("sunset", "")
today = (data.get("weather") or [None])[0] or {}
high_c = today.get("maxtempC")
min_c = today.get("mintempC")
parts: list[str] = []
if temp_c is not None:
lead = f"It's {temp_c} degrees and {desc or 'clear'} in {city}"
if feels_c is not None and feels_c != temp_c:
lead += f", feels like {feels_c}"
parts.append(lead + ".")
if high_c is not None and min_c is not None:
parts.append(f"Today's high is {high_c} and the low is {min_c}.")
if sunrise and sunset:
parts.append(f"Sunrise was at {sunrise} and sunset is at {sunset}.")
return " ".join(parts) or f"I have no detailed weather for {city} right now."
@mcp.tool()
async def get_weather(location: str) -> str:
"""Get the current weather and today's forecast for a location.
Use this when the user asks about current conditions, temperature,
or the forecast for a place. Returns a short conversational summary
suitable for reading aloud (1-2 sentences).
"""
try:
async with httpx.AsyncClient(timeout=30) as client:
resp = await client.get(
f"{WTTR_BASE}/{location}?format=j1",
headers={"User-Agent": "curl/8.0"},
)
resp.raise_for_status()
data = resp.json()
except Exception: # noqa: BLE001
return "I couldn't get the weather for that location"
try:
summary = _summarize(data, location)
except Exception: # noqa: BLE001
return "I couldn't get the weather for that location"
return summary
if __name__ == "__main__":
mcp.run(transport="stdio")