Skip to main content
Glama
rwxproject
by rwxproject
weather.py7.35 kB
""" Weather Tools Module Provides weather information tools using OpenWeatherMap API. Supports both real API calls and mock data for development/testing. """ import json import logging from typing import Any, Dict, Optional import httpx from fastmcp import FastMCP from .base import BaseTool from ..config.settings import get_settings class WeatherTools(BaseTool): """ Collection of weather-related tools. Provides weather information using OpenWeatherMap API with fallback to mock data for development and testing purposes. """ def __init__(self): super().__init__(name="weather") self.settings = get_settings() def _get_api_key(self) -> Optional[str]: """Get weather API key from environment.""" return getattr(self.settings, 'weather_api_key', None) def _use_mock_data(self) -> bool: """Check if mock data should be used.""" return getattr(self.settings, 'use_mock_weather_data', True) def _get_mock_weather_data(self, location: str) -> Dict[str, Any]: """Return realistic mock weather data for testing.""" return { "location": location, "temperature": 22.5, "temperature_unit": "°C", "humidity": 65, "humidity_unit": "%", "pressure": 1013.25, "pressure_unit": "hPa", "wind_speed": 3.2, "wind_speed_unit": "m/s", "wind_direction": 180, "wind_direction_unit": "degrees", "weather": "Partly cloudy", "description": "Few clouds", "visibility": 10000, "visibility_unit": "meters", "source": "mock_data", "timestamp": "2024-01-15T12:00:00Z" } async def _fetch_real_weather_data(self, location: str) -> Dict[str, Any]: """Fetch real weather data from OpenWeatherMap API.""" api_key = self._get_api_key() if not api_key: raise ValueError("WEATHER_API_KEY environment variable is required for real weather data") # Validate API key format (OpenWeatherMap keys are 32 hexadecimal characters) if not isinstance(api_key, str) or len(api_key) != 32: raise ValueError(f"Invalid API key format: expected 32 characters, got {len(api_key) if api_key else 0}") url = "https://api.openweathermap.org/data/2.5/weather" params = { "q": location, "appid": api_key, "units": "metric" } self.logger.debug(f"Making weather API request to {url} with key ending in ...{api_key[-4:]}") try: async with httpx.AsyncClient() as client: response = await client.get(url, params=params, timeout=10.0) response.raise_for_status() data = response.json() # Transform API response to standardized format weather_data = { "location": f"{data['name']}, {data['sys']['country']}", "temperature": data['main']['temp'], "temperature_unit": "°C", "humidity": data['main']['humidity'], "humidity_unit": "%", "pressure": data['main']['pressure'], "pressure_unit": "hPa", "wind_speed": data.get('wind', {}).get('speed', 0), "wind_speed_unit": "m/s", "wind_direction": data.get('wind', {}).get('deg', 0), "wind_direction_unit": "degrees", "weather": data['weather'][0]['main'], "description": data['weather'][0]['description'], "visibility": data.get('visibility', 0), "visibility_unit": "meters", "source": "openweathermap", "timestamp": data.get('dt', 0) } return weather_data except httpx.HTTPStatusError as e: if e.response.status_code == 401: # Provide more detailed error message for API key issues self.logger.error(f"API key authentication failed - key ending in ...{api_key[-4:]}") raise ValueError(f"Invalid API key provided for weather service. Please verify your OpenWeatherMap API key is active and correct.") elif e.response.status_code == 404: raise ValueError(f"Location '{location}' not found") else: raise ValueError(f"Weather API error: HTTP {e.response.status_code}") except httpx.TimeoutException: raise ValueError("Weather API request timed out") except httpx.RequestError as e: raise ValueError(f"Weather API request failed: {str(e)}") except (KeyError, json.JSONDecodeError) as e: raise ValueError(f"Invalid weather API response format: {str(e)}") def register_with_mcp(self, mcp: FastMCP) -> None: """ Register weather tools with the FastMCP instance. Args: mcp: The FastMCP instance to register with """ @mcp.tool() async def get_current_weather(location: str) -> Dict[str, Any]: """ Get current weather information for a specific location. Args: location: The location to get weather for (city name, "city,country", etc.) Returns: Dictionary containing current weather information including: - location: Formatted location name - temperature: Current temperature in Celsius - humidity: Humidity percentage - pressure: Atmospheric pressure in hPa - wind_speed: Wind speed in m/s - wind_direction: Wind direction in degrees - weather: Main weather condition - description: Detailed weather description - visibility: Visibility in meters - source: Data source (mock_data or openweathermap) - timestamp: Data timestamp Raises: ValueError: If location is invalid or API request fails """ self._log_tool_call("get_current_weather", location=location) if not location or not location.strip(): raise ValueError("Location cannot be empty") location = location.strip() try: if self._use_mock_data(): self.logger.info(f"Returning mock weather data for: {location}") return self._get_mock_weather_data(location) else: self.logger.info(f"Fetching real weather data for: {location}") return await self._fetch_real_weather_data(location) except Exception as e: self.logger.error(f"Error getting weather for {location}: {str(e)}") raise self.logger.info("Registered weather tools: get_current_weather")

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/rwxproject/mcp-server-template'

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