Skip to main content
Glama
weather_mcp_example.md9.42 kB
# Weather MCP Server Example This example demonstrates a production-ready MCP server that fetches weather data from the OpenWeather API. ## Capabilities - Tool: `get_weather` - Fetch forecast for a specified city and number of days - Resource: `weather://{city}/current` - Get current weather for a specified city - Dual operation as both MCP server and CLI tool ## Implementation ```python #!/usr/bin/env python3 # /// script # requires-python = ">=3.8" # dependencies = [ # "mcp[cli]>=0.1.0", # "requests>=2.31.0", # "pydantic>=2.0.0", # ] # /// import os import sys import logging import argparse from typing import Annotated, Dict, Any, Optional import requests from pydantic import BaseModel, Field from mcp.server.fastmcp import FastMCP from mcp.shared.exceptions import McpError from mcp.types import ErrorData, INTERNAL_ERROR, INVALID_PARAMS # Set up logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger("weather_mcp") def configure_logging(debug=False): if debug: logger.setLevel(logging.DEBUG) # Create FastMCP server mcp = FastMCP("Weather Service") # Define parameter models with validation class WeatherParams(BaseModel): """Parameters for weather forecast.""" city: Annotated[str, Field(description="City name")] days: Annotated[ Optional[int], Field(default=1, ge=1, le=5, description="Number of days (1-5)"), ] def fetch_weather(city: str, days: int = 1) -> Dict[str, Any]: """Fetch weather data from OpenWeather API.""" logger.debug(f"Fetching weather for {city}, {days} days") # Get API key from environment api_key = os.environ.get("OPENWEATHER_API_KEY") if not api_key: raise McpError(ErrorData( code=INTERNAL_ERROR, message="OPENWEATHER_API_KEY environment variable is required" )) try: # Make API request response = requests.get( "https://api.openweathermap.org/data/2.5/forecast", params={ "q": city, "cnt": days * 8, # API returns data in 3-hour steps "appid": api_key, "units": "metric" }, timeout=10 ) response.raise_for_status() return response.json() except requests.RequestException as e: logger.error(f"Weather API request failed: {str(e)}", exc_info=True) raise McpError(ErrorData( code=INTERNAL_ERROR, message=f"Weather API error: {str(e)}" )) @mcp.tool() def get_weather(city: str, days: int = 1) -> str: """Get weather forecast for a city.""" try: # Validate and fetch data if days < 1 or days > 5: raise ValueError("Days must be between 1 and 5") weather_data = fetch_weather(city, days) # Format the response forecast_items = weather_data.get("list", []) if not forecast_items: return f"No forecast data available for {city}" result = f"Weather forecast for {city}:\n\n" current_date = None for item in forecast_items: # Group by date dt_txt = item.get("dt_txt", "") date_part = dt_txt.split(" ")[0] if dt_txt else "" if date_part and date_part != current_date: current_date = date_part result += f"## {current_date}\n\n" # Add forecast details time_part = dt_txt.split(" ")[1].split(":")[0] + ":00" if dt_txt else "" temp = item.get("main", {}).get("temp", "N/A") weather_desc = item.get("weather", [{}])[0].get("description", "N/A") result += f"- **{time_part}**: {temp}°C, {weather_desc}\n" return result except ValueError as e: raise McpError(ErrorData(code=INVALID_PARAMS, message=str(e))) except McpError: raise except Exception as e: logger.error(f"Unexpected error: {str(e)}", exc_info=True) raise McpError(ErrorData( code=INTERNAL_ERROR, message=f"Error getting weather forecast: {str(e)}" )) @mcp.resource("weather://{city}/current") def get_current_weather_resource(city: str) -> str: """Get current weather for a city.""" try: # Get API key api_key = os.environ.get("OPENWEATHER_API_KEY") if not api_key: raise ValueError("OPENWEATHER_API_KEY environment variable is required") # Fetch current weather response = requests.get( "https://api.openweathermap.org/data/2.5/weather", params={"q": city, "appid": api_key, "units": "metric"}, timeout=10 ) response.raise_for_status() # Format response data = response.json() return { "temperature": data.get("main", {}).get("temp", "N/A"), "conditions": data.get("weather", [{}])[0].get("description", "N/A"), "humidity": data.get("main", {}).get("humidity", "N/A"), "wind_speed": data.get("wind", {}).get("speed", "N/A"), "timestamp": data.get("dt", 0) } except Exception as e: logger.error(f"Error getting current weather: {str(e)}", exc_info=True) raise McpError(ErrorData( code=INTERNAL_ERROR, message=f"Error getting current weather: {str(e)}" )) # Dual mode operation (MCP server or CLI tool) if __name__ == "__main__": # Test mode if "--test" in sys.argv: logger.info("Testing Weather MCP server initialization...") try: api_key = os.environ.get("OPENWEATHER_API_KEY") if not api_key: raise ValueError("OPENWEATHER_API_KEY environment variable is required") logger.info("Weather MCP server initialization test successful") sys.exit(0) except Exception as e: logger.error(f"Weather MCP server initialization test failed: {str(e)}") sys.exit(1) # MCP server mode elif len(sys.argv) == 1 or (len(sys.argv) == 2 and sys.argv[1] == "--debug"): if "--debug" in sys.argv: configure_logging(debug=True) logger.info("Starting Weather MCP server") mcp.run() # CLI tool mode else: args = argparse.ArgumentParser().parse_args() if hasattr(args, 'city') and args.city: print(get_weather(args.city, getattr(args, 'days', 1))) else: print("Error: --city is required for CLI mode") sys.exit(1) ``` ## Key Design Patterns ### 1. Structured Error Handling ```python try: # Operation that might fail except ValueError as e: # Client errors - invalid input raise McpError(ErrorData(code=INVALID_PARAMS, message=str(e))) except requests.RequestException as e: # External service errors raise McpError(ErrorData(code=INTERNAL_ERROR, message=f"API error: {str(e)}")) except Exception as e: # Unexpected errors raise McpError(ErrorData(code=INTERNAL_ERROR, message=f"Unexpected error: {str(e)}")) ``` ### 2. Parameter Validation ```python # Schema-based validation with Pydantic class WeatherParams(BaseModel): city: Annotated[str, Field(description="City name")] days: Annotated[Optional[int], Field(default=1, ge=1, le=5)] # Runtime validation if days < 1 or days > 5: raise ValueError("Days must be between 1 and 5") ``` ### 3. Environment Variable Management ```python api_key = os.environ.get("OPENWEATHER_API_KEY") if not api_key: raise McpError(ErrorData( code=INTERNAL_ERROR, message="OPENWEATHER_API_KEY environment variable is required" )) ``` ### 4. Resource URI Templates ```python @mcp.resource("weather://{city}/current") def get_current_weather_resource(city: str) -> str: """Get current weather for a city.""" # Implementation... ``` ### 5. Configurable Logging ```python def configure_logging(debug=False): if debug: logger.setLevel(logging.DEBUG) # Usage logger.debug("Detailed operation information") logger.info("Normal operational messages") logger.warning("Something concerning but not critical") logger.error("Something went wrong", exc_info=True) ``` ## Testing and Usage ### Unit Testing ```python @patch('weather_mcp.fetch_weather') def test_get_weather_formatting(mock_fetch): # Setup test data mock_fetch.return_value = {"list": [{"dt_txt": "2023-04-15 12:00:00", "main": {"temp": 15.2}, "weather": [{"description": "clear sky"}]}]} # Call function result = get_weather("London", 1) # Verify results assert "Weather forecast for London" in result assert "**12:00**: 15.2°C, clear sky" in result ``` ### Running Tests ```bash # Run all tests uv run -m pytest # Run with coverage uv run -m pytest --cov=weather_mcp ``` ### Registering with Claude/RooCode Add to MCP settings (`~/Library/Application Support/Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/cline_mcp_settings.json`): ```json { "mcpServers": { "weather": { "command": "uv", "args": ["run", "-s", "/path/to/weather_mcp.py"], "env": { "OPENWEATHER_API_KEY": "your_api_key_here" }, "disabled": false } } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/MammothGrowth/dbt-cli-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server