get_moon_info
Retrieve the Moon's phase, illumination, and position data for any specified date and time zone to support stargazing planning and astronomical observations.
Instructions
Get detailed information about the Moon's phase and position.
Args: time: Date string "YYYY-MM-DD HH:MM:SS" time_zone: IANA timezone string
Returns: Dict with keys "data", "_meta". "data" contains illumination, phase_name, age_days, etc.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| time | Yes | ||
| time_zone | Yes |
Implementation Reference
- src/functions/celestial/impl.py:67-97 (handler)The MCP tool handler for get_moon_info. Decorated with @mcp.tool(), it validates and parses the time input, localizes to timezone, calls the calculate_moon_info helper in a thread, and formats the response.@mcp.tool() async def get_moon_info( time: str, time_zone: str ) -> Dict[str, Any]: """Get detailed information about the Moon's phase and position. Args: time: Date string "YYYY-MM-DD HH:MM:SS" time_zone: IANA timezone string Returns: Dict with keys "data", "_meta". "data" contains illumination, phase_name, age_days, etc. """ try: # Try standard format first dt = datetime.strptime(time, "%Y-%m-%d %H:%M:%S") except ValueError: try: # Try ISO format dt = datetime.fromisoformat(time) except ValueError: raise ValueError(f"Time string '{time}' matches neither '%Y-%m-%d %H:%M:%S' nor ISO format.") if dt.tzinfo is None: tz = pytz.timezone(time_zone) dt = tz.localize(dt) result = await asyncio.to_thread(calculate_moon_info, dt) return format_response(result)
- src/celestial.py:96-169 (helper)Supporting helper function that performs the actual astronomical calculations for moon phase, illumination, age, elongation, and distance using Astropy.def calculate_moon_info(time: Union[Time, datetime]) -> Dict[str, Any]: """ Calculate detailed information about the Moon's phase and position. Args: time: Observation time (Astropy Time or timezone-aware datetime). Returns: Dict containing: - illumination: Fraction of the moon illuminated (0.0 to 1.0) - phase_name: String description of the phase (e.g. "Waxing Gibbous") - age_days: Approximate age of the moon in days (since New Moon) - elongation: Angular separation from Sun in degrees - earth_distance: Distance from Earth in km """ # Convert local time to UTC if input is datetime if isinstance(time, datetime): if time.tzinfo is None: raise ValueError("Input datetime must be timezone-aware for local time.") time = Time(time.astimezone(pytz.UTC)) sun = get_sun(time) moon = get_body("moon", time) # Elongation (angular separation) elongation = sun.separation(moon) # Illumination fraction (0-1) # The illuminated fraction k is given by k = (1 - cos(i))/2 where i is phase angle (approx elongation) # New Moon (0 deg): (1 - 1)/2 = 0 # Full Moon (180 deg): (1 - (-1))/2 = 1 illumination = (1 - np.cos(elongation.rad)) / 2.0 # Phase angle for naming (requires Ecliptic longitude) sun_ecl = sun.transform_to(GeocentricTrueEcliptic(obstime=time)) moon_ecl = moon.transform_to(GeocentricTrueEcliptic(obstime=time)) # Calculate longitude difference (Moon - Sun) lon_diff = (moon_ecl.lon.deg - sun_ecl.lon.deg) % 360 # Determine Phase Name # New Moon: 0 # First Quarter: 90 # Full Moon: 180 # Last Quarter: 270 if lon_diff < 1 or lon_diff > 359: phase_name = "New Moon" elif 1 <= lon_diff < 89: phase_name = "Waxing Crescent" elif 89 <= lon_diff <= 91: phase_name = "First Quarter" elif 91 < lon_diff < 179: phase_name = "Waxing Gibbous" elif 179 <= lon_diff <= 181: phase_name = "Full Moon" elif 181 < lon_diff < 269: phase_name = "Waning Gibbous" elif 269 <= lon_diff <= 271: phase_name = "Last Quarter" else: phase_name = "Waning Crescent" # Age in days (approximate) # Synodic month is ~29.53 days. Age = (lon_diff / 360) * 29.53 age_days = (lon_diff / 360.0) * 29.53059 return { "illumination": float(illumination), "phase_name": phase_name, "age_days": float(age_days), "elongation": float(elongation.deg), "earth_distance": float(moon.distance.to(u.km).value) }