IMPLEMENTATION_PLAN.md 8.8 KB

Ephemeris MCP — Implementation Plan

Last updated: 2026-06-07

Vision

A standalone Swiss Ephemeris computation service exposed via MCP. It provides raw astronomical and astrological-geometric data as structured JSON. Consumers bring their own interpretation.

Core principle: provide Swiss Ephemeris as a service so downstream servers don't have to bundle it.


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
    • get_sky_state
  • 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

v0.2: house systems + data directory split

Extends v0.2 with house cusp computation and separates ephemeris data from runtime cache for clean Docker deployment.

In scope

  • get_sky_state accepts house_system parameter (22 systems: P, K, E, W, A, C, M, R, T, U, V, X, Y, H, O, F, D, G, I, J, L, N, Q, S, Z)
  • house cusps and angles (ASC, MC, DSC, IC) computed server-side via swe.houses()
  • data/ split into data/ephe/ (static .se1 files, image-bundled) and data/cache/ (runtime sqlite, volume-mounted)
  • Dockerfile COPYs data/ephe/ for self-contained image
  • docker-compose mounts only data/cache/ volume
  • Chiron calculation fixed (was broken when seas_18.se1 was excluded from Docker context)

Acceptance bar

  • container rebuilt from scratch produces correct Chiron positions
  • get_sky_state with house_system returns cusps and angles matching Swiss Ephemeris
  • all v0.1 acceptance tests still pass

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

  • Swiss Ephemeris data lives in ./data/ephe/
  • seas_18.se1 is required for Chiron
  • Runtime cache (ephemeris.sqlite3) lives in ./data/cache/
  • satellite support remains planned, not part of v0.2

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
A7 get_sky_state datetime?, lat?, lon?, elevation?, geocentric?, house_system? Consolidated raw astronomical snapshot for downstream chart engines. Optionally includes house cusps and angles for 22 supported house systems.

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 interpretation (element balance, stelliums, aspect patterns) → 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.2 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

Phase 5 — container packaging

  • add a Dockerfile for the current service entrypoint
  • add compose wiring for data/ephe/ (COPY) and data/cache/ (volume mount)
  • document the runtime env and required Swiss Ephemeris files
  • verify the container on the target main server
  • split data/ into ephe/ (static, image-bundled) and cache/ (runtime, volume-mounted)

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?