"""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")