feat: add weather tool with NWS API integration and update dependencies

This commit is contained in:
2025-06-21 09:41:03 +08:00
parent 5e37cee394
commit f7b4e0a664
3 changed files with 102 additions and 0 deletions
+2
View File
@@ -17,6 +17,7 @@ dependencies = [
"fastapi[standard]>=0.115.0",
"filelock>=3.16.1",
"gguf>=0.13.0",
"httpx>=0.28.1",
"huggingface-hub[hf-xet]>=0.32.0",
"importlib-metadata>=8.6.1 ; python_full_version < '3.10'",
"ipykernel>=6.29.5",
@@ -28,6 +29,7 @@ dependencies = [
"llama-cpp-python>=0.3.9",
"llguidance>=0.7.11,<0.8.0 ; platform_machine == 'aarch64' or platform_machine == 'arm64' or platform_machine == 'x86_64'",
"lm-format-enforcer>=0.10.11,<0.11",
"mcp[cli]>=1.9.4",
"mistral-common[opencv]>=1.5.4",
"msgspec>=0.19.0",
"ninja>=1.11.1.4",
+4
View File
@@ -2067,6 +2067,7 @@ dependencies = [
{ name = "fastapi", extra = ["standard"] },
{ name = "filelock" },
{ name = "gguf" },
{ name = "httpx" },
{ name = "huggingface-hub", extra = ["hf-xet"] },
{ name = "ipykernel" },
{ name = "ipywidgets" },
@@ -2077,6 +2078,7 @@ dependencies = [
{ name = "llama-cpp-python" },
{ name = "llguidance", marker = "platform_machine == 'aarch64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" },
{ name = "lm-format-enforcer" },
{ name = "mcp", extra = ["cli"] },
{ name = "mistral-common", extra = ["opencv"] },
{ name = "msgspec" },
{ name = "ninja" },
@@ -2133,6 +2135,7 @@ requires-dist = [
{ name = "fastapi", extras = ["standard"], specifier = ">=0.115.0" },
{ name = "filelock", specifier = ">=3.16.1" },
{ name = "gguf", specifier = ">=0.13.0" },
{ name = "httpx", specifier = ">=0.28.1" },
{ name = "huggingface-hub", extras = ["hf-xet"], specifier = ">=0.32.0" },
{ name = "importlib-metadata", marker = "python_full_version < '3.10'", specifier = ">=8.6.1" },
{ name = "ipykernel", specifier = ">=6.29.5" },
@@ -2144,6 +2147,7 @@ requires-dist = [
{ name = "llama-cpp-python", specifier = ">=0.3.9" },
{ name = "llguidance", marker = "platform_machine == 'aarch64' or platform_machine == 'arm64' or platform_machine == 'x86_64'", specifier = ">=0.7.11,<0.8.0" },
{ name = "lm-format-enforcer", specifier = ">=0.10.11,<0.11" },
{ name = "mcp", extras = ["cli"], specifier = ">=1.9.4" },
{ name = "mistral-common", extras = ["opencv"], specifier = ">=1.5.4" },
{ name = "msgspec", specifier = ">=0.19.0" },
{ name = "ninja", specifier = ">=1.11.1.4" },
+96
View File
@@ -0,0 +1,96 @@
from typing import Any
import httpx
from mcp.server.fastmcp import FastMCP
# Initialize FastMCP server
mcp = FastMCP("weather")
# Constants
NWS_API_BASE = "https://api.weather.gov"
USER_AGENT = "weather-app/1.0"
async def make_nws_request(url: str) -> dict[str, Any] | None:
"""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:
"""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:
"""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:
"""Get weather forecast for a location.
Args:
latitude: Latitude of the location
longitude: Longitude of the location
"""
# First get the forecast grid endpoint
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."
# Get the forecast URL from the points response
forecast_url = points_data["properties"]["forecast"]
forecast_data = await make_nws_request(forecast_url)
if not forecast_data:
return "Unable to fetch detailed forecast."
# Format the periods into a readable forecast
periods = forecast_data["properties"]["periods"]
forecasts = []
for period in periods[:5]: # Only show next 5 periods
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)
if __name__ == "__main__":
# Initialize and run the server
mcp.run(transport="stdio")