Spaces:
Running
Running
| """ | |
| Astroseek.com MCP Integration | |
| Fetch chart data and interpretations from Astroseek | |
| """ | |
| import requests | |
| from typing import Dict, List, Optional | |
| import time | |
| from urllib.parse import urlencode | |
| from bs4 import BeautifulSoup | |
| import json | |
| class AstroseekMCP: | |
| """MCP Server for Astroseek.com integration""" | |
| BASE_URL = "https://horoscopes.astro-seek.com" | |
| RATE_LIMIT = 1.0 # seconds between requests | |
| def __init__(self): | |
| self.last_request_time = 0 | |
| self.session = requests.Session() | |
| self.session.headers.update({ | |
| 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36' | |
| }) | |
| def _rate_limit(self): | |
| """Enforce rate limiting""" | |
| elapsed = time.time() - self.last_request_time | |
| if elapsed < self.RATE_LIMIT: | |
| time.sleep(self.RATE_LIMIT - elapsed) | |
| self.last_request_time = time.time() | |
| def calculate_chart( | |
| self, | |
| birth_date: str, # Format: "YYYY-MM-DD" | |
| birth_time: str, # Format: "HH:MM" | |
| city: str, | |
| country: str = "" | |
| ) -> Dict: | |
| """ | |
| Calculate natal chart using Astroseek | |
| Args: | |
| birth_date: Birth date in YYYY-MM-DD format | |
| birth_time: Birth time in HH:MM format | |
| city: City name (e.g., "New York", "London", "Tokyo") | |
| country: Country name (optional, helps with disambiguation) | |
| Returns chart data including: | |
| - Planet positions | |
| - House cusps | |
| - Aspects | |
| - Dignities | |
| """ | |
| self._rate_limit() | |
| # Parse date and time | |
| year, month, day = birth_date.split("-") | |
| hour, minute = birth_time.split(":") | |
| # Format city for Astroseek | |
| # Astroseek uses autocomplete, so we'll send the city name | |
| # and let their system handle the geocoding | |
| city_param = f"{city}, {country}" if country else city | |
| # Astroseek URL parameters (based on actual form structure) | |
| params = { | |
| "send_calculation": "1", | |
| "narozeni_den": int(day), | |
| "narozeni_mesic": int(month), | |
| "narozeni_rok": int(year), | |
| "narozeni_hodina": int(hour), | |
| "narozeni_minuta": int(minute), | |
| "narozeni_sekunda": "0", | |
| "narozeni_city": city_param, | |
| "narozeni_timezone_form": "auto", | |
| "narozeni_timezone_dst_form": "auto", | |
| "house_system": "placidus" | |
| } | |
| # Actual calculation URL | |
| url = f"{self.BASE_URL}/calculate-birth-chart-horoscope-online/" | |
| try: | |
| response = self.session.get(url, params=params, timeout=10) | |
| response.raise_for_status() | |
| # Parse HTML response | |
| soup = BeautifulSoup(response.content, 'html.parser') | |
| # Extract chart data (this is a simplified example) | |
| chart_data = { | |
| "birth_date": birth_date, | |
| "birth_time": birth_time, | |
| "location": city_param, | |
| "planets": self._extract_planets(soup), | |
| "houses": self._extract_houses(soup), | |
| "aspects": self._extract_aspects(soup), | |
| "source": "horoscopes.astro-seek.com" | |
| } | |
| return chart_data | |
| except Exception as e: | |
| return { | |
| "error": f"Failed to fetch chart from Astroseek: {str(e)}", | |
| "note": "Astroseek integration requires web scraping - consider using astro.com API or local ephemeris" | |
| } | |
| def _extract_planets(self, soup: BeautifulSoup) -> Dict[str, Dict]: | |
| """Extract planet positions from Astroseek HTML""" | |
| # This is a placeholder - actual implementation would parse Astroseek's HTML structure | |
| planets = {} | |
| # Look for planet table | |
| planet_table = soup.find('table', {'class': 'tabulka_planeta'}) | |
| if planet_table: | |
| rows = planet_table.find_all('tr') | |
| for row in rows[1:]: # Skip header | |
| cols = row.find_all('td') | |
| if len(cols) >= 3: | |
| planet_name = cols[0].text.strip() | |
| position = cols[1].text.strip() | |
| retrograde = "R" in cols[2].text | |
| planets[planet_name] = { | |
| "position": position, | |
| "retrograde": retrograde | |
| } | |
| return planets | |
| def _extract_houses(self, soup: BeautifulSoup) -> List[Dict]: | |
| """Extract house cusps from Astroseek HTML""" | |
| houses = [] | |
| # This is a placeholder | |
| house_table = soup.find('table', {'class': 'tabulka_domy'}) | |
| if house_table: | |
| rows = house_table.find_all('tr') | |
| for i, row in enumerate(rows[1:], 1): # Skip header | |
| cols = row.find_all('td') | |
| if len(cols) >= 2: | |
| cusp = cols[1].text.strip() | |
| houses.append({ | |
| "house": i, | |
| "cusp": cusp | |
| }) | |
| return houses | |
| def _extract_aspects(self, soup: BeautifulSoup) -> List[Dict]: | |
| """Extract aspects from Astroseek HTML""" | |
| aspects = [] | |
| # This is a placeholder | |
| aspect_table = soup.find('table', {'class': 'aspekty'}) | |
| if aspect_table: | |
| rows = aspect_table.find_all('tr') | |
| for row in rows[1:]: # Skip header | |
| cols = row.find_all('td') | |
| if len(cols) >= 4: | |
| planet1 = cols[0].text.strip() | |
| aspect_type = cols[1].text.strip() | |
| planet2 = cols[2].text.strip() | |
| orb = cols[3].text.strip() | |
| aspects.append({ | |
| "planet1": planet1, | |
| "aspect": aspect_type, | |
| "planet2": planet2, | |
| "orb": orb | |
| }) | |
| return aspects | |
| def get_daily_horoscope(self, sign: str) -> Dict[str, str]: | |
| """Fetch daily horoscope for a sign from Astroseek""" | |
| self._rate_limit() | |
| # Note: Astro-Seek doesn't have simple daily horoscope URLs | |
| # They offer personalized horoscopes that require birth data | |
| return { | |
| "error": "Daily horoscope feature not available", | |
| "note": "Astro-Seek offers personalized daily horoscopes that require birth chart data. Use the calculate_chart function instead to get personalized transit information.", | |
| "alternative": "For general daily horoscopes, consider using astrology.com, horoscope.com, or other providers with public APIs." | |
| } | |
| def get_transits(self, date: Optional[str] = None) -> Dict: | |
| """Fetch current or future transits""" | |
| self._rate_limit() | |
| url = f"{self.BASE_URL}/planetary-transits" | |
| if date: | |
| url += f"?date={date}" | |
| try: | |
| response = self.session.get(url, timeout=10) | |
| response.raise_for_status() | |
| soup = BeautifulSoup(response.content, 'html.parser') | |
| # Parse transit data | |
| transits = { | |
| "date": date or "current", | |
| "planets": {}, | |
| "source": "astroseek.com" | |
| } | |
| # This would parse the actual transit table | |
| return transits | |
| except Exception as e: | |
| return {"error": f"Failed to fetch transits: {str(e)}"} | |
| # MCP Tool Definitions | |
| def get_mcp_tools() -> List[Dict]: | |
| """Return list of available Astroseek MCP tools""" | |
| return [ | |
| { | |
| "name": "calculate_chart", | |
| "description": "Calculate natal chart using Astroseek", | |
| "parameters": { | |
| "birth_date": "YYYY-MM-DD", | |
| "birth_time": "HH:MM", | |
| "city": "string (e.g., 'New York', 'London', 'Tokyo')", | |
| "country": "string (optional, helps with disambiguation)" | |
| } | |
| }, | |
| { | |
| "name": "get_daily_horoscope", | |
| "description": "Fetch daily horoscope for a zodiac sign", | |
| "parameters": { | |
| "sign": "string (Aries, Taurus, etc.)" | |
| } | |
| }, | |
| { | |
| "name": "get_transits", | |
| "description": "Fetch current or future planetary transits", | |
| "parameters": { | |
| "date": "YYYY-MM-DD (optional)" | |
| } | |
| } | |
| ] | |
| # Main entry point for local MCP server | |
| if __name__ == "__main__": | |
| import sys | |
| if len(sys.argv) > 1 and sys.argv[1] == "tools": | |
| print(json.dumps(get_mcp_tools(), indent=2)) | |
| else: | |
| print("Astroseek MCP Server") | |
| print("Available tools:", json.dumps(get_mcp_tools(), indent=2)) | |
| # Test example | |
| client = AstroseekMCP() | |
| print("\nTesting Astroseek integration...") | |
| print("Note: Web scraping integration - may require updates based on Astroseek's HTML structure") | |