IMPLEMENTATION_PLAN.md 7.0 KB

Ephemeris MCP — Implementation Plan

Last updated: 2026-05-10

Vision

A standalone, agnostic celestial computation engine exposed via MCP. It provides raw astronomical data as structured JSON. Consumers bring their own meaning.

Core principle: separate what the sky is from what it means.


Commit Plan

v0.1: core sky-state slice

This is the first version worth committing as a usable baseline.

In scope

  • server boots cleanly
  • health and root endpoints work
  • SQLite cache works
  • shared datetime parsing is stable
  • these tools are callable and return structured JSON:
    • get_planetary_positions
    • get_solar_events
    • get_lunar_state
    • get_moon_phase
    • get_sidereal_time
    • get_constellation_at_ecliptic
    • list_available_bodies
  • docs point to the wiki and describe the shipped slice

Explicitly out of scope

  • get_objects_above_horizon
  • get_satellite_passes
  • get_iss_position
  • TLE ingestion and refresh
  • bright stars and Messier support
  • any interpretive or consumer-specific layer

Acceptance bar

  • python -m pytest passes
  • run.sh starts the server on port 7015
  • tests.sh succeeds in the repo-local virtualenv
  • core tools return deterministic JSON and use cache keys consistently

Architecture Decisions

Decision Choice Rationale
Library Swiss Ephemeris (pyswisseph) High precision, direct fit for the core use case
Runtime FastMCP + FastAPI behind uvicorn Matches the MCP server pattern used elsewhere
Cache SQLite with TTL No external DB dependency for the first slice
Port 7015 Reserved for this service in the workspace
Host 0.0.0.0 Bind on all interfaces for LAN access
Entry point main.py at repo root, package app factory under src/ephemeris_mcp/ Keeps the runtime easy to launch and test
NumPy Not included Avoid unnecessary build friction
Python 3.13 Matches the local environment
Naming ephemeris-mcp Clear service identity

Data files

  • JPL ephemeris data lives in ./data/
  • satellite support remains planned, not part of v0.1

Tool Surface

Group A — Celestial Mechanics (serves astro-mcp)

# Tool Input Output
A1 get_planetary_positions datetime?, lat?, lon?, elevation?, geocentric? Array of bodies with ecliptic/equatorial coords, distance, speed
A2 get_solar_events date, lat, lon Sunrise/set, solar noon, all twilight boundaries, day length
A3 get_lunar_state datetime?, lat?, lon? Phase name, illumination %, age, RA/dec, next phase datetime
A4 get_moon_phase datetime?, lat?, lon? Quick moon-phase view for manual use
A5 get_sidereal_time datetime?, lat?, lon? GST, LST, obliquity of ecliptic
A6 get_constellation_at_ecliptic ecliptic_lon IAU constellation name/abbrev at that ecliptic longitude

Group B — Sky Objects & Satellite Tracking (serves satrack-mcp)

# Tool Input Output
B1 get_objects_above_horizon lat, lon, elevation?, categories? Visible objects filtered by category (planets, moon, sun, bright_stars, messier)
B2 get_satellite_passes lat, lon, norad_id or name, start_datetime?, hours? Predicted passes with altitude/azimuth/duration/times
B3 get_iss_position lat?, lon? Current ISS position + next visible pass

Group C — Discovery & Utility

# Tool Input Output
C1 list_available_bodies category? List of all computable bodies and their metadata
C2 get_constellations_list All 88 IAU constellations with boundaries metadata

Parameter: categories in get_objects_above_horizon

Accepts a comma-separated string, defaults to "planets,moon,sun" (the most probable standard set for astro-mcp):

  • planets — Mercury through Pluto
  • moon
  • sun
  • bright_stars — navigational stars, magnitude < 1.5
  • messier — Messier deep-sky objects (when above horizon)

Cross-cutting design

  • All datetimes: ISO 8601 string or Unix timestamp. Omit → now.
  • All outputs echo input params back for correlation/logging.
  • Errors: structured MCP error objects with code + message.
  • All calculations are deterministic and reproducible.

Out of Scope (all slices)

  • Natal chart computation (house cusps, ascendant) → astro-mcp
  • Aspect calculation (conjunction, opposition, trine) → astro-mcp
  • Transit interpretation (meaning of planet hitting natal position) → astro-mcp
  • Synastry (chart-to-chart comparison) → astro-mcp
  • Zodiac sign interpretation → astro-mcp
  • Any narrative/textual output → consumers layer this

Implementation Phases

Phase 1 — v0.1 core slice

  • Define the commit boundary for the first working version
  • Create a stable app factory and uvicorn entry point
  • Make /health and / deterministic and testable
  • Keep the SQLite cache working as a shared primitive
  • Preserve the core tool surface for sky-state queries
  • Keep the implementation aligned with the wiki project page

Phase 2 — satellite and horizon tools

  • get_objects_above_horizon
  • get_satellite_passes
  • get_iss_position
  • TLE storage and refresh

Phase 3 — polish and validation

  • tighter input validation
  • better astronomical accuracy checks
  • mcporter smoke test
  • load and cache behavior checks

Phase 4 — docs

  • keep README.md, AGENTS.md, and wiki references aligned
  • add more operational notes if the runtime changes

Dependencies

# Core runtime
fastmcp>=2.0.0
fastapi>=0.115.0
uvicorn[standard]>=0.30.0
pydantic>=2.8.0

# Ephemeris computation
pyswisseph>=2.10  # Swiss Ephemeris C bindings
jplephem>=2.16    # JPL ephemeris file reader

# Satellite tracking
sgp4>=2.22        # SGP4 orbit propagation (no numpy dependency in 2.x)

# Utilities
python-dotenv>=1.0.1
requests>=2.32.0  # For TLE fetch from CelesTrak

# Testing
pytest>=8.0.0

No numpy — swisseph is pure C; sgp4 2.x doesn't require numpy.


SQLite Schema (cache + future TLE)

-- General computation cache with TTL
CREATE TABLE IF NOT EXISTS cache (
    key          TEXT PRIMARY KEY,
    value        TEXT NOT NULL,        -- JSON serialized result
    expires_at   REAL NOT NULL         -- Unix timestamp
);

CREATE INDEX idx_cache_expires ON cache(expires_at);

-- TLE data storage
CREATE TABLE IF NOT EXISTS tle_data (
    norad_id     INTEGER PRIMARY KEY,
    name         TEXT,
    line1        TEXT NOT NULL,
    line2        TEXT NOT NULL,
    last_fetched REAL NOT NULL
);

Open Questions (for later)

  1. Should we bundle DE440s or DE441 ephemeris? DE440s is smaller (~50 MB vs ~110 MB) and precise enough for all our use cases.
  2. Bright star catalog: embed a static list or fetch from a source? (For get_objects_above_horizon with bright_stars category.)
  3. Background TLE refresh daemon or keep it on-demand only?