|
|
""" |
|
|
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') |
|
|
|
|
|
|
|
|
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 = {} |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
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 = {} |
|
|
self.cache_expiry = 3600 |
|
|
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() |
|
|
|
|
|
|
|
|
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'] |
|
|
|
|
|
|
|
|
coordinates = None |
|
|
|
|
|
|
|
|
if self.foursquare_api_key: |
|
|
coordinates = self._get_coordinates_foursquare(location) |
|
|
|
|
|
|
|
|
if not coordinates: |
|
|
coordinates = self._get_coordinates_nominatim(location) |
|
|
|
|
|
|
|
|
if not coordinates: |
|
|
raise ValueError(f"Could not retrieve coordinates for '{location}'. Please try a more specific location or check the spelling.") |
|
|
|
|
|
|
|
|
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: |
|
|
|
|
|
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() |
|
|
|
|
|
|
|
|
api_config = APIConfig() |
|
|
|
|
|
|
|
|
coordinate_service = DynamicCoordinateService() |
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
|
|
|
CITY_COORDINATES = { |
|
|
|
|
|
'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' |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|