hermes-astrology / astrodienst_mcp.py
aamanlamba's picture
Add 3 new MCP server integrations: Local HERMES, Astro.com, Gemini AI
9e59094 verified
"""
Astro.com (AstroDienst) MCP Integration
Fetches classical Hellenistic charts from astro.com
"""
import requests
from typing import Dict, List, Optional
import time
from urllib.parse import urlencode
from bs4 import BeautifulSoup
import json
import re
class AstroDienstMCP:
"""MCP Server for Astro.com integration"""
BASE_URL = "https://www.astro.com"
RATE_LIMIT = 2.0 # seconds between requests (be respectful)
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 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/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 generate_chart(
self,
birth_date: str, # Format: "DD.MM.YYYY"
birth_time: str, # Format: "HH:MM"
city: str,
country: str = "",
house_system: str = "W" # W = Whole Sign, P = Placidus, K = Koch, etc.
) -> Dict:
"""
Generate classical chart from Astro.com
Args:
birth_date: Birth date in DD.MM.YYYY format
birth_time: Birth time in HH:MM format
city: City name
country: Country name (optional)
house_system: House system (W=Whole Sign, P=Placidus, K=Koch, E=Equal)
Returns:
Chart data including planet positions, houses, aspects
"""
self._rate_limit()
# Astro.com guest chart URL
# Note: This is a simplified version - actual implementation would need
# to handle astro.com's location database and chart generation properly
return {
"service": "Astro.com Chart Generator",
"birth_date": birth_date,
"birth_time": birth_time,
"location": f"{city}, {country}" if country else city,
"house_system": self._get_house_system_name(house_system),
"chart_url": self._build_chart_url(birth_date, birth_time, city, house_system),
"note": "For full chart data, please visit astro.com directly. This integration provides chart URL generation.",
"instructions": [
"1. Visit the generated chart URL",
"2. Chart will be displayed with classical aspects",
"3. Use Whole Sign houses (W) for Hellenistic astrology",
"4. Planet positions shown in traditional format"
],
"hellenistic_features": {
"whole_sign_houses": house_system == "W",
"traditional_aspects": True,
"sect_aware": "Manual calculation needed",
"essential_dignities": "Available in extended chart"
},
"source": "AstroDienst MCP Integration"
}
def _get_house_system_name(self, code: str) -> str:
"""Convert house system code to name"""
systems = {
"W": "Whole Sign (Hellenistic)",
"P": "Placidus",
"K": "Koch",
"E": "Equal House",
"R": "Regiomontanus",
"C": "Campanus",
"B": "Alcabitius"
}
return systems.get(code, "Unknown")
def _build_chart_url(self, birth_date: str, birth_time: str, city: str, house_system: str) -> str:
"""
Build astro.com chart URL
Note: This is a simplified version. Astro.com requires proper location codes
and has a complex URL structure. For production, use their API or manual entry.
"""
# Simplified URL - users would need to fill in details manually
return f"{self.BASE_URL}/cgi/genchart.cgi?btyp=w2gw&rs=3&usechpref=1&hsy={house_system}"
def get_chart_interpretation(
self,
chart_data: Dict,
focus: str = "general"
) -> Dict:
"""
Get classical Hellenistic interpretation guidance
Args:
chart_data: Chart data from generate_chart()
focus: Interpretation focus (general, career, relationships, health)
Returns:
Interpretation guidance based on Hellenistic principles
"""
interpretations = {
"general": {
"steps": [
"1. **Identify the Sect**: Determine if day or night chart (Sun above/below horizon)",
"2. **Assess Sect Light**: Evaluate Sun (day) or Moon (night) as primary luminary",
"3. **Find Benefics/Malefics**: Identify Jupiter/Venus (benefic) and Saturn/Mars (malefic)",
"4. **Check Essential Dignities**: Evaluate planetary strength by sign placement",
"5. **Examine House Rulers**: Note which planets rule key houses (1st, 10th, 7th, 4th)",
"6. **Analyze Aspects**: Traditional aspects (conjunction, sextile, square, trine, opposition)",
"7. **Consider Lots**: Calculate Lot of Fortune and Lot of Spirit"
],
"key_principles": [
"Sect determines benefic/malefic team alignment",
"Stronger planet (by dignity) wins conflicting testimonies",
"House rulers show life area conditions",
"Aspects by degree and sign, with orbs"
]
},
"career": {
"focus_points": [
"**10th House**: Career, reputation, public life",
"**10th Ruler**: Shows career significations",
"**MC Sign**: Public image and recognition",
"**Lot of Fortune**: Material success indicators",
"**Sun** (day) or **Moon** (night): Overall life direction"
]
},
"relationships": {
"focus_points": [
"**7th House**: Marriage and partnerships",
"**7th Ruler**: Partner characteristics",
"**Venus**: Love, beauty, harmony",
"**Lot of Eros**: Romantic desires",
"**Moon**: Emotional connections"
]
},
"health": {
"focus_points": [
"**6th House**: Illness and obstacles",
"**1st House**: Physical body and vitality",
"**Lot of Fortune**: Physical health",
"**Malefics**: Areas of potential difficulty",
"**Moon**: Bodily fluctuations"
]
}
}
result = interpretations.get(focus, interpretations["general"])
result["focus"] = focus
result["methodology"] = "Classical Hellenistic Astrology (1st-7th century CE)"
result["sources"] = [
"Vettius Valens - Anthology",
"Claudius Ptolemy - Tetrabiblos",
"Dorotheus of Sidon - Carmen Astrologicum",
"Paulus Alexandrinus - Introductory Matters"
]
result["modern_reference"] = "Chris Brennan - Hellenistic Astrology: The Study of Fate and Fortune (2017)"
return result
def calculate_hellenistic_aspects(
self,
planet_positions: Dict[str, float]
) -> List[Dict]:
"""
Calculate traditional Hellenistic aspects
Traditional aspects in Hellenistic astrology:
- Conjunction (0°): Same sign, close degrees
- Sextile (60°): 2-3 signs apart
- Square (90°): 3-4 signs apart
- Trine (120°): 4-5 signs apart
- Opposition (180°): 6-7 signs apart
Args:
planet_positions: Dict of planet names to absolute degrees (0-360)
Returns:
List of aspects with orbs and interpretations
"""
aspects = []
aspect_types = {
0: {"name": "Conjunction", "nature": "Blending", "orb": 8},
60: {"name": "Sextile", "nature": "Harmonious", "orb": 6},
90: {"name": "Square", "nature": "Tension", "orb": 7},
120: {"name": "Trine", "nature": "Flowing", "orb": 8},
180: {"name": "Opposition", "nature": "Polarity", "orb": 8}
}
planets = list(planet_positions.keys())
for i, planet1 in enumerate(planets):
for planet2 in planets[i+1:]:
angle = abs(planet_positions[planet1] - planet_positions[planet2])
if angle > 180:
angle = 360 - angle
for aspect_angle, aspect_info in aspect_types.items():
if abs(angle - aspect_angle) <= aspect_info["orb"]:
aspects.append({
"planet1": planet1,
"planet2": planet2,
"aspect": aspect_info["name"],
"angle": round(angle, 2),
"orb": round(abs(angle - aspect_angle), 2),
"nature": aspect_info["nature"],
"interpretation": f"{planet1} {aspect_info['name'].lower()} {planet2}: {aspect_info['nature']} connection"
})
return aspects
def get_whole_sign_houses(
self,
ascendant_sign: str,
ascendant_degree: float = 0
) -> Dict:
"""
Calculate Whole Sign house system (traditional Hellenistic)
In Whole Sign houses:
- Ascendant sign = entire 1st house
- Next sign = entire 2nd house
- And so on...
Args:
ascendant_sign: Rising sign
ascendant_degree: Degree of ascendant (for reference, not used in house calculation)
Returns:
Complete house system with signs
"""
signs = ["Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo",
"Libra", "Scorpio", "Sagittarius", "Capricorn", "Aquarius", "Pisces"]
try:
start_index = signs.index(ascendant_sign)
except ValueError:
return {"error": f"Invalid sign: {ascendant_sign}"}
houses = {}
for house_num in range(1, 13):
sign_index = (start_index + house_num - 1) % 12
houses[house_num] = {
"sign": signs[sign_index],
"start_degree": 0,
"end_degree": 30,
"system": "Whole Sign"
}
return {
"ascendant": ascendant_sign,
"ascendant_degree": ascendant_degree,
"house_system": "Whole Sign (Hellenistic)",
"houses": houses,
"note": "In Whole Sign houses, each house occupies an entire sign. This is the original house system used by Hellenistic astrologers.",
"source": "Classical Hellenistic tradition"
}
def get_mcp_tools() -> List[Dict]:
"""Return list of available AstroDienst MCP tools"""
return [
{
"name": "generate_chart",
"description": "Generate classical Hellenistic chart from Astro.com",
"parameters": {
"birth_date": "string (DD.MM.YYYY)",
"birth_time": "string (HH:MM)",
"city": "string",
"country": "string (optional)",
"house_system": "string (W=Whole Sign, P=Placidus, K=Koch, E=Equal)"
},
"returns": "Chart URL and configuration for astro.com"
},
{
"name": "get_chart_interpretation",
"description": "Get Hellenistic interpretation guidance",
"parameters": {
"chart_data": "dict (from generate_chart)",
"focus": "string (general, career, relationships, health)"
},
"returns": "Step-by-step interpretation guide with classical principles"
},
{
"name": "calculate_hellenistic_aspects",
"description": "Calculate traditional aspects with orbs",
"parameters": {
"planet_positions": "dict {planet_name: degree_0_to_360}"
},
"returns": "List of aspects with interpretations"
},
{
"name": "get_whole_sign_houses",
"description": "Calculate Whole Sign house system (Hellenistic)",
"parameters": {
"ascendant_sign": "string (zodiac sign)",
"ascendant_degree": "float (optional, for reference)"
},
"returns": "Complete 12-house layout in Whole Sign system"
}
]
# Main entry point
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("=" * 70)
print("AstroDienst (Astro.com) MCP Server")
print("Classical Hellenistic Chart Integration")
print("=" * 70)
print("\nAvailable Tools:")
print(json.dumps(get_mcp_tools(), indent=2))
# Test example
print("\n" + "=" * 70)
print("TEST: Generate Chart")
print("=" * 70)
server = AstroDienstMCP()
result = server.generate_chart(
birth_date="15.01.1990",
birth_time="12:00",
city="New York",
country="USA",
house_system="W"
)
print(json.dumps(result, indent=2))
print("\n" + "=" * 70)
print("TEST: Whole Sign Houses")
print("=" * 70)
result = server.get_whole_sign_houses("Leo", 15.5)
print(json.dumps(result, indent=2))
print("\n" + "=" * 70)
print("TEST: Interpretation Guide")
print("=" * 70)
result = server.get_chart_interpretation({}, "general")
print(json.dumps(result, indent=2))