|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
from typing import Any |
|
|
|
import httpx |
|
from mcp.server.fastmcp import FastMCP |
|
|
|
mcp = FastMCP("weather") |
|
|
|
NWS_API_BASE = "https://api.weather.gov" |
|
USER_AGENT = "weather-app/1.0" |
|
|
|
|
|
async def make_nws_request(url: str) -> dict[str, Any] | None: |
|
r"""Make a request to the NWS API with proper error handling.""" |
|
headers = {"User-Agent": USER_AGENT, "Accept": "application/geo+json"} |
|
async with httpx.AsyncClient() as client: |
|
try: |
|
response = await client.get(url, headers=headers, timeout=30.0) |
|
response.raise_for_status() |
|
return response.json() |
|
except Exception: |
|
return None |
|
|
|
|
|
def format_alert(feature: dict) -> str: |
|
r"""Format an alert feature into a readable string.""" |
|
props = feature["properties"] |
|
return f""" |
|
Event: {props.get('event', 'Unknown')} |
|
Area: {props.get('areaDesc', 'Unknown')} |
|
Severity: {props.get('severity', 'Unknown')} |
|
Description: {props.get('description', 'No description available')} |
|
Instructions: {props.get('instruction', 'No specific instructions provided')} |
|
""" |
|
|
|
|
|
@mcp.tool() |
|
async def get_alerts(state: str) -> str: |
|
r"""Get weather alerts for a US state. |
|
|
|
Args: |
|
state: Two-letter US state code (e.g. CA, NY) |
|
""" |
|
url = f"{NWS_API_BASE}/alerts/active/area/{state}" |
|
data = await make_nws_request(url) |
|
|
|
if not data or "features" not in data: |
|
return "Unable to fetch alerts or no alerts found." |
|
|
|
if not data["features"]: |
|
return "No active alerts for this state." |
|
|
|
alerts = [format_alert(feature) for feature in data["features"]] |
|
return "\n---\n".join(alerts) |
|
|
|
|
|
@mcp.tool() |
|
async def get_forecast(latitude: float, longitude: float) -> str: |
|
r"""Get weather forecast for a location. |
|
|
|
Args: |
|
latitude: Latitude of the location |
|
longitude: Longitude of the location |
|
""" |
|
|
|
points_url = f"{NWS_API_BASE}/points/{latitude},{longitude}" |
|
points_data = await make_nws_request(points_url) |
|
|
|
if not points_data: |
|
return "Unable to fetch forecast data for this location." |
|
|
|
|
|
forecast_url = points_data["properties"]["forecast"] |
|
forecast_data = await make_nws_request(forecast_url) |
|
|
|
if not forecast_data: |
|
return "Unable to fetch detailed forecast." |
|
|
|
|
|
periods = forecast_data["properties"]["periods"] |
|
forecasts = [] |
|
for period in periods[:5]: |
|
forecast = f""" |
|
{period['name']}: |
|
Temperature: {period['temperature']}°{period['temperatureUnit']} |
|
Wind: {period['windSpeed']} {period['windDirection']} |
|
Forecast: {period['detailedForecast']} |
|
""" |
|
forecasts.append(forecast) |
|
|
|
return "\n---\n".join(forecasts) |
|
|
|
|
|
def main(transport: str = "stdio"): |
|
r"""Weather MCP Server |
|
|
|
This server provides weather-related functionalities implemented via the Model Context Protocol (MCP). |
|
It demonstrates how to establish interactions between AI models and external tools using MCP. |
|
|
|
The server supports two modes of operation: |
|
|
|
1. stdio mode (default): |
|
|
|
- Communicates with clients via standard input/output streams, ideal for local command-line usage. |
|
|
|
- Example usage: python mcp_server.py [--transport stdio] |
|
|
|
2. SSE mode (Server-Sent Events): |
|
|
|
- Communicates with clients over HTTP using server-sent events, suitable for persistent network connections. |
|
|
|
- Runs by default at http://127.0.0.1:8000. |
|
|
|
- Example usage: python mcp_server.py --transport sse |
|
""" |
|
if transport == 'stdio': |
|
mcp.run(transport='stdio') |
|
elif transport == 'sse': |
|
mcp.run(transport='sse') |
|
|
|
|
|
if __name__ == "__main__": |
|
|
|
main("stdio") |
|
|