# 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 - [x] 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) ```sql -- 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?