Skip to content

Commit f5e851e

Browse files
Add quickstart-server example for the "Building MCP servers" tutorial
Import the weather server from the external `quickstart-resources` repo into `examples/servers/quickstart-server/`, adapting it for the monorepo. The layout uses a top-level `weather.py` script with a stub `mcp_quickstart_server/` package for hatchling, keeping the code faithful to what the tutorial guide shows readers. Changes from the original: - `FastMCP` → `MCPServer` (renamed in the SDK) - `mcp[cli]` → plain `mcp` (CLI extras not needed at runtime) - Bare `dict` → `dict[str, Any]` for type safety - Added `list[str]` annotation and `-> None` return type for pyright
1 parent 229f6a5 commit f5e851e

File tree

5 files changed

+144
-0
lines changed

5 files changed

+144
-0
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# A Weather MCP Server
2+
3+
See the [Build an MCP server](https://modelcontextprotocol.io/docs/develop/build-server) tutorial for more information.

examples/servers/quickstart-server/mcp_quickstart_server/__init__.py

Whitespace-only changes.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
[project]
2+
name = "mcp-quickstart-server"
3+
version = "0.1.0"
4+
description = "Tutorial companion: a weather MCP server using the NWS API"
5+
requires-python = ">=3.10"
6+
dependencies = [
7+
"httpx>=0.27",
8+
"mcp",
9+
]
10+
11+
[build-system]
12+
requires = ["hatchling"]
13+
build-backend = "hatchling.build"
14+
15+
[tool.hatch.build.targets.wheel]
16+
packages = ["mcp_quickstart_server"]
17+
18+
[tool.ruff]
19+
line-length = 120
20+
target-version = "py310"
21+
22+
[tool.ruff.lint]
23+
select = ["E", "F", "I"]
24+
ignore = []
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
from typing import Any
2+
3+
import httpx
4+
from mcp.server.mcpserver import MCPServer
5+
6+
# Initialize MCP server
7+
mcp = MCPServer("weather")
8+
9+
# Constants
10+
NWS_API_BASE = "https://api.weather.gov"
11+
USER_AGENT = "weather-app/1.0"
12+
13+
14+
async def make_nws_request(url: str) -> dict[str, Any] | None:
15+
"""Make a request to the NWS API with proper error handling."""
16+
headers = {"User-Agent": USER_AGENT, "Accept": "application/geo+json"}
17+
async with httpx.AsyncClient() as client:
18+
try:
19+
response = await client.get(url, headers=headers, timeout=30.0)
20+
response.raise_for_status()
21+
return response.json()
22+
except Exception:
23+
return None
24+
25+
26+
def format_alert(feature: dict[str, Any]) -> str:
27+
"""Format an alert feature into a readable string."""
28+
props = feature["properties"]
29+
return f"""
30+
Event: {props.get("event", "Unknown")}
31+
Area: {props.get("areaDesc", "Unknown")}
32+
Severity: {props.get("severity", "Unknown")}
33+
Description: {props.get("description", "No description available")}
34+
Instructions: {props.get("instruction", "No specific instructions provided")}
35+
"""
36+
37+
38+
@mcp.tool()
39+
async def get_alerts(state: str) -> str:
40+
"""Get weather alerts for a US state.
41+
42+
Args:
43+
state: Two-letter US state code (e.g. CA, NY)
44+
"""
45+
url = f"{NWS_API_BASE}/alerts/active/area/{state}"
46+
data = await make_nws_request(url)
47+
48+
if not data or "features" not in data:
49+
return "Unable to fetch alerts or no alerts found."
50+
51+
if not data["features"]:
52+
return "No active alerts for this state."
53+
54+
alerts = [format_alert(feature) for feature in data["features"]]
55+
return "\n---\n".join(alerts)
56+
57+
58+
@mcp.tool()
59+
async def get_forecast(latitude: float, longitude: float) -> str:
60+
"""Get weather forecast for a location.
61+
62+
Args:
63+
latitude: Latitude of the location
64+
longitude: Longitude of the location
65+
"""
66+
# First get the forecast grid endpoint
67+
points_url = f"{NWS_API_BASE}/points/{latitude},{longitude}"
68+
points_data = await make_nws_request(points_url)
69+
70+
if not points_data:
71+
return "Unable to fetch forecast data for this location."
72+
73+
# Get the forecast URL from the points response
74+
forecast_url = points_data["properties"]["forecast"]
75+
forecast_data = await make_nws_request(forecast_url)
76+
77+
if not forecast_data:
78+
return "Unable to fetch detailed forecast."
79+
80+
# Format the periods into a readable forecast
81+
periods = forecast_data["properties"]["periods"]
82+
forecasts: list[str] = []
83+
for period in periods[:5]: # Only show next 5 periods
84+
forecast = f"""
85+
{period["name"]}:
86+
Temperature: {period["temperature"]}°{period["temperatureUnit"]}
87+
Wind: {period["windSpeed"]} {period["windDirection"]}
88+
Forecast: {period["detailedForecast"]}
89+
"""
90+
forecasts.append(forecast)
91+
92+
return "\n---\n".join(forecasts)
93+
94+
95+
def main() -> None:
96+
"""Run the weather MCP server."""
97+
mcp.run(transport="stdio")
98+
99+
100+
if __name__ == "__main__":
101+
main()

uv.lock

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)