hermes-astrology / hermes_local_mcp.py
aamanlamba's picture
Add 3 new MCP server integrations: Local HERMES, Astro.com, Gemini AI
8e283f0 verified
"""
Local HERMES MCP Server
Demonstrates MCP integration with core HERMES features
"""
import json
from typing import Dict, List, Optional
from datetime import datetime
import sys
class HermesLocalMCP:
"""Local MCP Server for HERMES core features"""
def __init__(self):
self.server_name = "HERMES Local MCP"
self.version = "1.0.0"
# Essential Dignities Data (from app.py)
DOMICILES = {
"Sun": ["Leo"],
"Moon": ["Cancer"],
"Mercury": ["Gemini", "Virgo"],
"Venus": ["Taurus", "Libra"],
"Mars": ["Aries", "Scorpio"],
"Jupiter": ["Sagittarius", "Pisces"],
"Saturn": ["Capricorn", "Aquarius"]
}
EXALTATIONS = {
"Sun": "Aries",
"Moon": "Taurus",
"Mercury": "Virgo",
"Venus": "Pisces",
"Mars": "Capricorn",
"Jupiter": "Cancer",
"Saturn": "Libra"
}
DETRIMENTS = {
"Sun": ["Aquarius"],
"Moon": ["Capricorn"],
"Mercury": ["Sagittarius", "Pisces"],
"Venus": ["Aries", "Scorpio"],
"Mars": ["Taurus", "Libra"],
"Jupiter": ["Gemini", "Virgo"],
"Saturn": ["Cancer", "Leo"]
}
FALLS = {
"Sun": ["Libra"],
"Moon": ["Scorpio"],
"Mercury": ["Pisces"],
"Venus": ["Virgo"],
"Mars": ["Cancer"],
"Jupiter": ["Capricorn"],
"Saturn": ["Aries"]
}
BOUNDS = {
"Aries": [
{"start": 0, "end": 6, "ruler": "Jupiter"},
{"start": 6, "end": 12, "ruler": "Venus"},
{"start": 12, "end": 20, "ruler": "Mercury"},
{"start": 20, "end": 25, "ruler": "Mars"},
{"start": 25, "end": 30, "ruler": "Saturn"}
],
"Taurus": [
{"start": 0, "end": 8, "ruler": "Venus"},
{"start": 8, "end": 14, "ruler": "Mercury"},
{"start": 14, "end": 22, "ruler": "Jupiter"},
{"start": 22, "end": 27, "ruler": "Saturn"},
{"start": 27, "end": 30, "ruler": "Mars"}
],
# Add remaining signs...
}
def calculate_essential_dignity(
self,
planet: str,
sign: str,
degree: float,
is_day_chart: bool = True
) -> Dict:
"""
Calculate essential dignity for a planet
Args:
planet: Planet name (Sun, Moon, Mercury, Venus, Mars, Jupiter, Saturn)
sign: Zodiac sign
degree: Degree within sign (0-30)
is_day_chart: Whether it's a day or night chart
Returns:
Complete dignity assessment with score and interpretation
"""
dignity_score = 0
dignities = []
debilities = []
# Check domicile (+5)
if sign in self.DOMICILES.get(planet, []):
dignity_score += 5
dignities.append("Domicile (+5)")
# Check exaltation (+4)
if sign == self.EXALTATIONS.get(planet):
dignity_score += 4
dignities.append("Exaltation (+4)")
# Check detriment (-5)
if sign in self.DETRIMENTS.get(planet, []):
dignity_score -= 5
debilities.append("Detriment (-5)")
# Check fall (-4)
if sign in self.FALLS.get(planet, []):
dignity_score -= 4
debilities.append("Fall (-4)")
# Determine planetary condition
if dignity_score >= 4:
condition = "Very Strong"
elif dignity_score >= 2:
condition = "Strong"
elif dignity_score >= -1:
condition = "Moderate/Peregrine"
elif dignity_score >= -4:
condition = "Weak/Debilitated"
else:
condition = "Very Weak"
return {
"planet": planet,
"sign": sign,
"degree": degree,
"chart_type": "Day Chart" if is_day_chart else "Night Chart",
"dignity_score": dignity_score,
"dignities": dignities,
"debilities": debilities,
"condition": condition,
"interpretation": self._get_dignity_interpretation(planet, sign, condition),
"source": "HERMES Local MCP v1.0"
}
def _get_dignity_interpretation(self, planet: str, sign: str, condition: str) -> str:
"""Generate interpretation text"""
if condition == "Very Strong":
return f"{planet} in {sign} is very strong, able to express its significations powerfully and effectively."
elif condition == "Strong":
return f"{planet} in {sign} is well-placed and can express its nature with strength."
elif condition == "Moderate/Peregrine":
return f"{planet} in {sign} is peregrine (wandering), having neutral strength without major dignities or debilities."
elif condition == "Weak/Debilitated":
return f"{planet} in {sign} is debilitated, struggling to express its significations effectively."
else:
return f"{planet} in {sign} is severely debilitated, facing major obstacles in expressing its nature."
def calculate_annual_profection(
self,
birth_date: str,
current_age: int
) -> Dict:
"""
Calculate annual profection for a given age
Args:
birth_date: Birth date in YYYY-MM-DD format
current_age: Current age in years
Returns:
Profected house and time-lord information
"""
# Profections cycle through 12 houses
profected_house = (current_age % 12) + 1
house_themes = {
1: "Self, body, vitality, new beginnings",
2: "Resources, possessions, values, income",
3: "Siblings, communication, short journeys, learning",
4: "Home, family, foundations, parents",
5: "Creativity, children, pleasure, romance",
6: "Health, service, daily work, obstacles",
7: "Partnerships, marriage, open enemies",
8: "Death, inheritance, transformation, others' resources",
9: "Philosophy, long journeys, higher education, religion",
10: "Career, public reputation, status, achievements",
11: "Friends, hopes, wishes, groups, benefactors",
12: "Isolation, hidden matters, spirituality, undoing"
}
return {
"birth_date": birth_date,
"current_age": current_age,
"profected_house": profected_house,
"house_theme": house_themes.get(profected_house, "Unknown"),
"interpretation": f"At age {current_age}, the profection activates the {profected_house}th house. This year's themes center around: {house_themes.get(profected_house, 'Unknown')}.",
"lord_of_year": f"The ruler of the {profected_house}th house is the Lord of the Year",
"source": "HERMES Local MCP v1.0"
}
def calculate_lot_of_fortune(
self,
asc_degree: float,
sun_degree: float,
moon_degree: float,
is_day_chart: bool = True
) -> Dict:
"""
Calculate Lot of Fortune
Formula:
- Day chart: Ascendant + Moon - Sun
- Night chart: Ascendant + Sun - Moon
Args:
asc_degree: Ascendant in absolute degrees (0-360)
sun_degree: Sun position in absolute degrees
moon_degree: Moon position in absolute degrees
is_day_chart: Whether it's a day chart
Returns:
Lot of Fortune position and interpretation
"""
if is_day_chart:
fortune = (asc_degree + moon_degree - sun_degree) % 360
formula = "ASC + Moon - Sun"
else:
fortune = (asc_degree + sun_degree - moon_degree) % 360
formula = "ASC + Sun - Moon"
# Convert to sign and degree
sign_num = int(fortune / 30)
degree = fortune % 30
signs = ["Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo",
"Libra", "Scorpio", "Sagittarius", "Capricorn", "Aquarius", "Pisces"]
sign = signs[sign_num]
return {
"lot": "Fortune",
"position": f"{degree:.2f}° {sign}",
"absolute_degree": fortune,
"formula": formula,
"chart_type": "Day Chart" if is_day_chart else "Night Chart",
"interpretation": f"The Lot of Fortune represents the body, health, and material fortune. Placed at {degree:.2f}° {sign}, it shows where fortune and prosperity manifest.",
"source": "HERMES Local MCP v1.0"
}
def get_technique_explanation(self, technique: str) -> Dict:
"""
Get educational explanation for a technique
Args:
technique: Name of technique (dignities, profections, lots, etc.)
Returns:
Educational content about the technique
"""
explanations = {
"essential_dignities": {
"name": "Essential Dignities",
"summary": "A system for evaluating planetary strength based on zodiacal position",
"description": """Essential dignities measure how well-placed a planet is in the zodiac.
The system includes five major categories:
1. **Domicile** (+5): Planet in its home sign
2. **Exaltation** (+4): Planet in its sign of greatest strength
3. **Triplicity** (+3): Planet ruling the element (day/night)
4. **Bound/Term** (+2): Planet ruling a specific degree range
5. **Decan/Face** (+1): Planet ruling a 10-degree section
Debilities are the opposites:
- **Detriment** (-5): Opposite of domicile
- **Fall** (-4): Opposite of exaltation
A planet's dignity score indicates its ability to act effectively.""",
"source": "Ptolemy's Tetrabiblos, Vettius Valens",
"period": "Hellenistic (1st-7th century CE)"
},
"annual_profections": {
"name": "Annual Profections",
"summary": "A timing technique that activates different life areas each year",
"description": """Annual profections move the Ascendant forward one house per year of life.
The technique works by:
1. Starting at the Ascendant at birth (age 0)
2. Moving one house forward each birthday
3. The profected house becomes active that year
4. The ruler of that house becomes 'Lord of the Year'
This creates a 12-year cycle where each house theme is activated in sequence.
The Lord of the Year becomes especially important for predicting events.""",
"source": "Vettius Valens, Firmicus Maternus",
"period": "Hellenistic (2nd century CE)"
},
"lots": {
"name": "Lots/Parts",
"summary": "Calculated sensitive points derived from planet positions",
"description": """Lots (Arabic Parts) are sensitive points calculated from planetary positions.
The most important lot is the **Lot of Fortune**:
- Day chart: ASC + Moon - Sun
- Night chart: ASC + Sun - Moon
Other important lots include:
- **Lot of Spirit**: Opposite calculation to Fortune
- **Lot of Eros**: Related to desire and love
- **Lot of Necessity**: Related to constraint and obligation
Lots represent abstract topics and show where certain life themes manifest.""",
"source": "Vettius Valens, Paulus Alexandrinus",
"period": "Hellenistic (1st-4th century CE)"
}
}
result = explanations.get(technique.lower().replace(" ", "_"), {
"name": technique,
"summary": "Technique not found in database",
"description": "No explanation available for this technique.",
"source": "Unknown",
"period": "Unknown"
})
result["requested_technique"] = technique
result["server"] = "HERMES Local MCP v1.0"
return result
def get_mcp_tools() -> List[Dict]:
"""Return list of available HERMES Local MCP tools"""
return [
{
"name": "calculate_essential_dignity",
"description": "Calculate essential dignity score for a planet in a sign",
"parameters": {
"planet": "string (Sun, Moon, Mercury, Venus, Mars, Jupiter, Saturn)",
"sign": "string (Aries, Taurus, Gemini, etc.)",
"degree": "float (0-30)",
"is_day_chart": "boolean (optional, default True)"
},
"returns": "Dignity score, condition, and interpretation"
},
{
"name": "calculate_annual_profection",
"description": "Calculate annual profection for a given age",
"parameters": {
"birth_date": "string (YYYY-MM-DD)",
"current_age": "integer"
},
"returns": "Profected house and Lord of the Year information"
},
{
"name": "calculate_lot_of_fortune",
"description": "Calculate the Lot of Fortune position",
"parameters": {
"asc_degree": "float (0-360)",
"sun_degree": "float (0-360)",
"moon_degree": "float (0-360)",
"is_day_chart": "boolean (optional, default True)"
},
"returns": "Lot position and interpretation"
},
{
"name": "get_technique_explanation",
"description": "Get educational explanation for a traditional technique",
"parameters": {
"technique": "string (essential_dignities, annual_profections, lots, etc.)"
},
"returns": "Educational content with historical sources"
}
]
# Main entry point for MCP server
if __name__ == "__main__":
if len(sys.argv) > 1 and sys.argv[1] == "tools":
print(json.dumps(get_mcp_tools(), indent=2))
else:
print("=" * 70)
print("HERMES Local MCP Server v1.0")
print("=" * 70)
print("\nAvailable Tools:")
print(json.dumps(get_mcp_tools(), indent=2))
# Test examples
print("\n" + "=" * 70)
print("TEST: Essential Dignity - Venus in Aries")
print("=" * 70)
server = HermesLocalMCP()
result = server.calculate_essential_dignity("Venus", "Aries", 8.5, True)
print(json.dumps(result, indent=2))
print("\n" + "=" * 70)
print("TEST: Annual Profection - Age 35")
print("=" * 70)
result = server.calculate_annual_profection("1990-01-15", 35)
print(json.dumps(result, indent=2))
print("\n" + "=" * 70)
print("TEST: Lot of Fortune")
print("=" * 70)
result = server.calculate_lot_of_fortune(120, 295, 210, True)
print(json.dumps(result, indent=2))