""" Central API configuration and environment setup. """ import os import requests from typing import Dict, Tuple, Optional import json import time from urllib.parse import quote class APIConfig: """Centralized API configuration and validation.""" def __init__(self): self.foursquare_api_key = os.environ.get('FOURSQUARE_API_KEY') self.anthropic_api_key = os.environ.get('ANTHROPIC_API_KEY') # Set environment if not already set and key is available if self.foursquare_api_key and not os.environ.get('FOURSQUARE_API_KEY'): os.environ['FOURSQUARE_API_KEY'] = self.foursquare_api_key def validate_apis(self) -> Dict[str, bool]: """Validate API keys and connectivity.""" results = {} # Test Foursquare API if self.foursquare_api_key: try: url = "https://api.foursquare.com/v3/places/search" headers = { "accept": "application/json", "Authorization": self.foursquare_api_key } params = {"query": "test", "limit": 1} response = requests.get(url, headers=headers, params=params, timeout=5) results['foursquare'] = response.status_code == 200 except Exception: results['foursquare'] = False else: results['foursquare'] = False # Check Anthropic API key existence results['anthropic'] = bool(self.anthropic_api_key) return results def print_status(self): """Print API configuration status.""" print("\n🔧 API Configuration Status:") validation = self.validate_apis() if validation['foursquare']: print("✓ Foursquare API: Connected and working") else: print("âš ī¸ Foursquare API: Connection issues or invalid key") if validation['anthropic']: print("✓ Anthropic API Key: Configured") else: print("âš ī¸ Anthropic API Key: Not configured") print("â„šī¸ Set environment variable: ANTHROPIC_API_KEY=your_key") class DynamicCoordinateService: """Service for dynamically fetching coordinates for locations.""" def __init__(self): self.coordinate_cache = {} # Cache for recently fetched coordinates self.cache_expiry = 3600 # Cache expires after 1 hour self.foursquare_api_key = os.environ.get('FOURSQUARE_API_KEY') def get_coordinates(self, location: str) -> Optional[str]: """ Get coordinates for a location dynamically using multiple geocoding services. Raises ValueError if coordinates cannot be found. """ location_key = location.lower().strip() # Check cache first if location_key in self.coordinate_cache: cached_data = self.coordinate_cache[location_key] if time.time() - cached_data['timestamp'] < self.cache_expiry: return cached_data['coordinates'] # Try multiple geocoding services in order of preference coordinates = None # 1. Try Foursquare API first (most accurate for places) if self.foursquare_api_key: coordinates = self._get_coordinates_foursquare(location) # 2. Fall back to Nominatim (OpenStreetMap) - free but rate limited if not coordinates: coordinates = self._get_coordinates_nominatim(location) # 3. If all services fail, raise an error if not coordinates: raise ValueError(f"Could not retrieve coordinates for '{location}'. Please try a more specific location or check the spelling.") # Cache the successful result self.coordinate_cache[location_key] = { 'coordinates': coordinates, 'timestamp': time.time() } return coordinates def _get_coordinates_foursquare(self, location: str) -> Optional[str]: """Get coordinates using Foursquare Places API.""" try: url = "https://api.foursquare.com/v3/places/search" headers = { "accept": "application/json", "Authorization": self.foursquare_api_key } params = { "query": location, "limit": 1 } response = requests.get(url, headers=headers, params=params, timeout=10) response.raise_for_status() data = response.json() if data.get('results'): geocode = data['results'][0].get('geocodes', {}).get('main', {}) if 'latitude' in geocode and 'longitude' in geocode: lat = geocode['latitude'] lon = geocode['longitude'] return f"{lat},{lon}" except Exception as e: print(f"Foursquare geocoding failed for '{location}': {e}") return None def _get_coordinates_nominatim(self, location: str) -> Optional[str]: """Get coordinates using Nominatim (OpenStreetMap) geocoding service.""" try: # Rate limit: max 1 request per second for Nominatim time.sleep(1) encoded_location = quote(location) url = f"https://nominatim.openstreetmap.org/search" params = { 'q': location, 'format': 'json', 'limit': 1, 'addressdetails': 1 } headers = { 'User-Agent': 'MCP-Server/1.0 (Dynamic Coordinate Service)' } response = requests.get(url, params=params, headers=headers, timeout=10) response.raise_for_status() data = response.json() if data: lat = data[0]['lat'] lon = data[0]['lon'] return f"{lat},{lon}" except Exception as e: print(f"Nominatim geocoding failed for '{location}': {e}") return None def get_coordinates_tuple(self, location: str) -> Optional[Tuple[float, float]]: """Get coordinates as a tuple of (latitude, longitude). Raises ValueError if not found.""" coords_str = self.get_coordinates(location) if coords_str: try: lat, lon = coords_str.split(',') return (float(lat), float(lon)) except (ValueError, IndexError): raise ValueError(f"Invalid coordinate format for '{location}'") return None def clear_cache(self): """Clear the coordinate cache.""" self.coordinate_cache.clear() # Global configuration instance api_config = APIConfig() # Dynamic coordinate service instance coordinate_service = DynamicCoordinateService() # Legacy function for backward compatibility def get_city_coordinates(location: str) -> Optional[str]: """ Get coordinates for a location dynamically. This replaces the old static CITY_COORDINATES lookup. """ return coordinate_service.get_coordinates(location) # Backward compatibility: Minimal CITY_COORDINATES for legacy code # This is deprecated - use coordinate_service.get_coordinates() instead CITY_COORDINATES = { # Common US cities for backward compatibility only 'seattle': '47.6062,-122.3321', 'portland': '45.5155,-122.6789', 'san francisco': '37.7749,-122.4194', 'los angeles': '34.0522,-118.2437', 'denver': '39.7392,-104.9903', 'chicago': '41.8781,-87.6298', 'new york': '40.7128,-74.0060', 'boston': '42.3601,-71.0589', 'miami': '25.7617,-80.1918', 'austin': '30.2672,-97.7431', 'phoenix': '33.4484,-112.0740', 'atlanta': '33.7490,-84.3880', 'nashville': '36.1627,-86.7816', 'minneapolis': '44.9778,-93.2650', 'lynnwood': '47.8209,-122.3151' } # Note: This static mapping is deprecated. # For new code, use: coordinate_service.get_coordinates(location) for dynamic lookup