33Weather MCP Server Example
44
55This 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
138Usage:
149 python weather_server.py
1510
1611Requirements:
17- pip install mcp uvicorn fastapi httpx pydantic
12+ pip install mcp uvicorn fastapi httpx pydantic langchain-core
1813"""
1914
2015import asyncio
2116import logging
22- from typing import Dict , List , Optional , Any
2317from 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
2523import 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
3128class 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
238167if __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