Skip to content

Commit 8e4985b

Browse files
committed
♻️ Update to latest MCP patterns: FastMCP, prompts, resources
1 parent 5bcf687 commit 8e4985b

File tree

2 files changed

+215
-274
lines changed

2 files changed

+215
-274
lines changed

examples/weather_mcp/weather_server.py

Lines changed: 121 additions & 191 deletions
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,27 @@
33
Weather MCP Server Example
44
55
This example demonstrates a complete MCP server that provides weather information
6-
using a real weather API. It showcases:
7-
- Proper MCP server structure
8-
- API integration patterns
9-
- Error handling
10-
- Input validation
11-
- Response formatting
6+
using the official MCP FastMCP implementation.
127
138
Usage:
149
python weather_server.py
1510
1611
Requirements:
17-
pip install mcp uvicorn fastapi httpx pydantic
12+
pip install mcp uvicorn fastapi httpx pydantic langchain-core
1813
"""
1914

2015
import asyncio
2116
import logging
22-
from typing import Dict, List, Optional, Any
2317
from datetime import datetime
18+
from typing import Dict, Optional
2419

20+
from mcp.server.fastmcp import FastMCP
21+
import mcp.types as types
22+
from pydantic import BaseModel
2523
import httpx
26-
from pydantic import BaseModel, ValidationError
2724

28-
# In a real implementation, you'd import these from the MCP library
29-
# For this example, we'll use simplified versions
25+
# Initialize FastMCP server
26+
mcp = FastMCP("weather_tools")
3027

3128
class WeatherData(BaseModel):
3229
"""Weather data model"""
@@ -37,207 +34,140 @@ class WeatherData(BaseModel):
3734
wind_speed: float
3835
timestamp: datetime
3936

40-
class WeatherMCPServer:
41-
"""Weather MCP Server providing weather information tools"""
37+
# System prompt for the weather assistant
38+
@mcp.prompt()
39+
def system_prompt() -> str:
40+
"""Define the AI assistant's role"""
41+
return """
42+
You are a weather assistant that provides accurate weather information.
43+
Use the available tools to fetch current conditions and forecasts.
44+
Always specify temperature units and provide clear, concise responses.
45+
"""
46+
47+
@mcp.prompt()
48+
def error_prompt() -> str:
49+
"""Handle error cases"""
50+
return """
51+
I apologize, but I was unable to fetch the weather data.
52+
This could be due to:
53+
- Invalid location name
54+
- Weather service unavailability
55+
- Network connectivity issues
56+
Please try again with a valid city name.
57+
"""
58+
59+
# Resource for cached weather data
60+
@mcp.resource("weather://{location}")
61+
async def get_weather_resource(location: str) -> Dict:
62+
"""Get cached weather data for a location"""
63+
# In a real implementation, this would check a cache first
64+
return {
65+
"location": location,
66+
"last_updated": datetime.now().isoformat(),
67+
"cache_status": "miss"
68+
}
69+
70+
# Weather tools
71+
@mcp.tool()
72+
async def get_current_weather(location: str, units: str = "celsius") -> Dict:
73+
"""
74+
Get current weather for a location
4275
43-
def __init__(self, api_key: Optional[str] = None):
44-
self.api_key = api_key or "demo_key" # In production, use environment variable
45-
self.base_url = "https://api.openweathermap.org/data/2.5"
46-
self.logger = logging.getLogger(__name__)
76+
Args:
77+
location: City name (e.g., 'San Francisco, CA')
78+
units: Temperature units ('celsius', 'fahrenheit', 'kelvin')
4779
48-
# Register our tools
49-
self.tools = {
50-
"get_current_weather": {
51-
"description": "Get current weather for a location",
52-
"parameters": {
53-
"type": "object",
54-
"properties": {
55-
"location": {
56-
"type": "string",
57-
"description": "City name (e.g., 'San Francisco, CA')"
58-
},
59-
"units": {
60-
"type": "string",
61-
"enum": ["celsius", "fahrenheit", "kelvin"],
62-
"default": "celsius",
63-
"description": "Temperature units"
64-
}
65-
},
66-
"required": ["location"]
67-
},
68-
"handler": self.get_current_weather
80+
Returns:
81+
Dictionary with current weather data
82+
"""
83+
try:
84+
# First check resource cache
85+
cache = await get_weather_resource(location)
86+
87+
# Convert units for API
88+
api_units = {"celsius": "metric", "fahrenheit": "imperial", "kelvin": "standard"}
89+
unit_param = api_units.get(units, "metric")
90+
91+
# Simulated API call (replace with real API in production)
92+
weather_data = {
93+
"name": location,
94+
"main": {
95+
"temp": 22.5,
96+
"humidity": 65
6997
},
70-
"get_weather_forecast": {
71-
"description": "Get 5-day weather forecast for a location",
72-
"parameters": {
73-
"type": "object",
74-
"properties": {
75-
"location": {
76-
"type": "string",
77-
"description": "City name (e.g., 'San Francisco, CA')"
78-
},
79-
"days": {
80-
"type": "integer",
81-
"minimum": 1,
82-
"maximum": 5,
83-
"default": 3,
84-
"description": "Number of forecast days (1-5)"
85-
}
86-
},
87-
"required": ["location"]
88-
},
89-
"handler": self.get_weather_forecast
90-
}
98+
"weather": [{"description": "partly cloudy"}],
99+
"wind": {"speed": 3.2}
91100
}
92-
93-
async def get_current_weather(self, location: str, units: str = "celsius") -> Dict[str, Any]:
94-
"""Get current weather for a location"""
95-
try:
96-
# Convert units for API
97-
api_units = {"celsius": "metric", "fahrenheit": "imperial", "kelvin": "standard"}
98-
unit_param = api_units.get(units, "metric")
99-
100-
# Make API request (simulated for demo)
101-
weather_data = await self._fetch_weather_data(location, unit_param)
102-
103-
# Format response
104-
result = {
101+
102+
return {
103+
"success": True,
104+
"data": {
105105
"location": weather_data["name"],
106106
"temperature": weather_data["main"]["temp"],
107107
"description": weather_data["weather"][0]["description"].title(),
108108
"humidity": weather_data["main"]["humidity"],
109109
"wind_speed": weather_data["wind"]["speed"],
110110
"units": units,
111-
"timestamp": datetime.now().isoformat()
111+
"timestamp": datetime.now().isoformat(),
112+
"cache_info": cache
112113
}
113-
114-
return {
115-
"success": True,
116-
"data": result,
117-
"message": f"Current weather for {result['location']}"
118-
}
119-
120-
except Exception as e:
121-
self.logger.error(f"Weather API error: {e}")
122-
return {
123-
"success": False,
124-
"error": f"Unable to fetch weather data: {str(e)}",
125-
"message": "Weather service temporarily unavailable"
126-
}
127-
128-
async def get_weather_forecast(self, location: str, days: int = 3) -> Dict[str, Any]:
129-
"""Get weather forecast for a location"""
130-
try:
131-
# Simulate forecast data (in real implementation, call forecast API)
132-
forecast_data = await self._fetch_forecast_data(location, days)
133-
134-
forecasts = []
135-
for day_data in forecast_data["list"][:days]:
136-
forecasts.append({
137-
"date": datetime.fromtimestamp(day_data["dt"]).strftime("%Y-%m-%d"),
138-
"temperature_high": day_data["main"]["temp_max"],
139-
"temperature_low": day_data["main"]["temp_min"],
140-
"description": day_data["weather"][0]["description"].title(),
141-
"humidity": day_data["main"]["humidity"]
142-
})
143-
144-
return {
145-
"success": True,
146-
"data": {
147-
"location": location,
148-
"forecast": forecasts,
149-
"days": len(forecasts)
150-
},
151-
"message": f"{days}-day forecast for {location}"
152-
}
153-
154-
except Exception as e:
155-
self.logger.error(f"Forecast API error: {e}")
156-
return {
157-
"success": False,
158-
"error": f"Unable to fetch forecast: {str(e)}",
159-
"message": "Forecast service temporarily unavailable"
160-
}
161-
162-
async def _fetch_weather_data(self, location: str, units: str) -> Dict[str, Any]:
163-
"""Fetch current weather data from API (simulated)"""
164-
# In a real implementation, this would make an HTTP request:
165-
# async with httpx.AsyncClient() as client:
166-
# response = await client.get(f"{self.base_url}/weather",
167-
# params={"q": location, "appid": self.api_key, "units": units})
168-
# return response.json()
114+
}
169115

170-
# Simulated response for demo
116+
except Exception as e:
117+
logging.error(f"Weather API error: {e}")
171118
return {
172-
"name": location,
173-
"main": {
174-
"temp": 22.5,
175-
"humidity": 65,
176-
"temp_max": 25.0,
177-
"temp_min": 18.0
178-
},
179-
"weather": [{"description": "partly cloudy"}],
180-
"wind": {"speed": 3.2}
119+
"success": False,
120+
"error": str(e),
121+
"prompt": "error_prompt"
181122
}
123+
124+
@mcp.tool()
125+
async def get_weather_forecast(location: str, days: int = 3) -> Dict:
126+
"""
127+
Get weather forecast for a location
182128
183-
async def _fetch_forecast_data(self, location: str, days: int) -> Dict[str, Any]:
184-
"""Fetch forecast data from API (simulated)"""
185-
# Simulated forecast response
186-
import time
187-
current_time = int(time.time())
129+
Args:
130+
location: City name (e.g., 'San Francisco, CA')
131+
days: Number of forecast days (1-5)
132+
133+
Returns:
134+
Dictionary with forecast data
135+
"""
136+
try:
137+
forecasts = []
138+
current_time = datetime.now().timestamp()
188139

189-
forecast_list = []
190140
for i in range(days):
191-
day_timestamp = current_time + (i * 24 * 60 * 60) # Add days
192-
forecast_list.append({
193-
"dt": day_timestamp,
194-
"main": {
195-
"temp_max": 25.0 - i,
196-
"temp_min": 18.0 - i,
197-
"humidity": 60 + i * 2
198-
},
199-
"weather": [{"description": f"day {i+1} weather"}]
141+
day_timestamp = current_time + (i * 24 * 60 * 60)
142+
forecasts.append({
143+
"date": datetime.fromtimestamp(day_timestamp).strftime("%Y-%m-%d"),
144+
"temperature_high": 25.0 - i,
145+
"temperature_low": 18.0 - i,
146+
"description": f"Day {i+1} forecast",
147+
"humidity": 60 + i * 2
200148
})
201149

202-
return {"list": forecast_list}
203-
204-
def list_tools(self) -> List[str]:
205-
"""Return list of available tools"""
206-
return list(self.tools.keys())
207-
208-
def get_tool_info(self, tool_name: str) -> Optional[Dict[str, Any]]:
209-
"""Get information about a specific tool"""
210-
return self.tools.get(tool_name)
211-
212-
async def main():
213-
"""Demo the weather MCP server"""
214-
# Create server
215-
weather_server = WeatherMCPServer()
216-
217-
print("🌤️ Weather MCP Server Demo")
218-
print("=" * 40)
219-
220-
# List available tools
221-
print(f"📋 Available tools: {weather_server.list_tools()}")
222-
print()
223-
224-
# Test current weather
225-
print("🔍 Testing current weather...")
226-
result = await weather_server.get_current_weather("San Francisco, CA")
227-
print(f"✅ Result: {result}")
228-
print()
229-
230-
# Test forecast
231-
print("🔍 Testing weather forecast...")
232-
result = await weather_server.get_weather_forecast("New York, NY", days=3)
233-
print(f"✅ Result: {result}")
234-
print()
235-
236-
print("🎉 Weather MCP Server demo completed!")
150+
return {
151+
"success": True,
152+
"data": {
153+
"location": location,
154+
"forecast": forecasts,
155+
"days": len(forecasts)
156+
}
157+
}
158+
159+
except Exception as e:
160+
logging.error(f"Forecast API error: {e}")
161+
return {
162+
"success": False,
163+
"error": str(e),
164+
"prompt": "error_prompt"
165+
}
237166

238167
if __name__ == "__main__":
239168
# Set up logging
240169
logging.basicConfig(level=logging.INFO)
241170

242-
# Run the demo
243-
asyncio.run(main())
171+
# Run the MCP server
172+
print("🌤️ Starting Weather MCP Server...")
173+
mcp.run(transport="streamable-http")

0 commit comments

Comments
 (0)