PROJECT.md 8.8 KB

Astro-MCP Implementation Plan

Port: 7016 (ephemeris-mcp is 7015)

What We're Building

An MCP server that consumes ephemeris-mcp:get_sky_state via MCP-over-SSE client, then performs pure astrological calculations (zodiac signs, houses, aspects, angles) on top of that astronomical data. No interpretation -- only structured JSON output. Includes a person database (SQLite), a Jinja2+vanillaJS dashboard, and a Docker container.

Project Structure

astro-mcp/
├── Dockerfile
├── docker-compose.yml
├── main.py                          # entrypoint, same pattern as ephemeris-mcp
├── requirements.txt
├── .gitignore
├── README.md
├── PROJECT.md
├── run.sh
├── killserver.sh
├── restart.sh
├── tests.sh
├── data/                            # SQLite DBs at runtime
├── logs/                            # PID + server.log
├── src/
│   └── astro_mcp/
│       ├── __init__.py
│       ├── config.py                # PORT=7016, dirs, ephemeris URL
│       ├── server.py                # FastAPI + FastMCP + lifespan + HTTP routes
│       ├── ephemeris_client.py      # MCP SSE client calling ephemeris-mcp:get_sky_state
│       ├── astrology.py             # pure calc: zodiac, houses, aspects, angles
│       ├── models.py                # Pydantic models / type aliases for chart structs
│       ├── storage.py               # SQLite: persons table + chart cache
│       ├── dashboard.py             # Jinja2 templates + dashboard routes
│       └── tools.py                 # @mcp.tool() definitions (wiring layer)
├── templates/
│   ├── base.html
│   ├── dashboard.html
│   └── persons.html
└── tests/
    ├── conftest.py
    ├── test_astrology.py            # unit tests for pure calc functions
    ├── test_tools.py                # integration tests with mocked ephemeris client
    └── test_server.py               # HTTP endpoint smoke tests

Dependencies (requirements.txt)

fastapi>=0.115.0
uvicorn[standard]>=0.30.0
fastmcp>=2.0.0
mcp>=1.0.0
pydantic>=2.8.0
jinja2>=3.1.0
python-dotenv>=1.0.1
pytest>=8.0.0

Note: No pyswisseph -- all astronomical data comes from ephemeris-mcp via MCP client. No heavy native deps beyond what FastMCP/mcp pulls in.

Key Design Decisions

  1. MCP client pattern: Same as hermes-mcp's argus_client.py -- sse_client(url) + ClientSession. The ephemeris URL is configurable via EPHEMERIS_MCP_URL env var (default http://ephemeris-mcp:7015/mcp/sse for Docker, http://127.0.0.1:7015/mcp/sse for local dev).

  2. Async throughout: The ephemeris client is async (sse_client is async context manager). The FastMCP tools are async def. The SQLite access uses asyncio.to_thread() to avoid blocking.

  3. Calculation module is pure sync: astrology.py has no async, no I/O -- just math on dicts/lists. Easy to unit test without any event loop.

  4. Ephemeris client call_sky_state helper: A single internal async function that calls ephemeris-mcp:get_sky_state and returns the parsed dict. All tools go through this. Timeout: 8s.

  5. House systems: Placidus default, with house_system parameter on chart tools. Start with Placidus + Equal House only. The house calculation uses the sidereal time from get_sky_state output (sidereal_time.local_sidereal_time).

  6. Aspect orbs (defaults): conjunction 8, sextile 6, square 8, trine 8, opposition 8. Configurable per-tool via optional orb_limits dict.

  7. Person DB schema:

    CREATE TABLE persons (
       id TEXT PRIMARY KEY,
       name TEXT NOT NULL,
       nickname TEXT UNIQUE,
       birth_datetime TEXT NOT NULL,  -- ISO 8601 UTC
       latitude REAL NOT NULL,
       longitude REAL NOT NULL,
       elevation REAL DEFAULT 0.0,
       created_at TEXT NOT NULL       -- ISO 8601 UTC
    );
    
  8. Docker: Based on python:3.13-slim, no build-essential needed (no pyswisseph). Health check hits http://127.0.0.1:7016/health.

MCP Tool Surface (7 tools)

Tool Description
get_planetary_positions Wrapper around ephemeris-mcp, adds sign + degree + retrograde. Optional bodies filter.
calculate_natal_chart Full natal chart: planets in signs/houses, aspects (applying/separating), angles. Params: birth_datetime, lat, lon, optional house_system, orb_limits.
calculate_transit_chart Transit planets + natal-to-transit aspects + transiting planets in natal houses. Same params as natal + transit_datetime.
calculate_synastry_chart Two birth data sets -> interaspects, house overlays, composite chart (midpoint method), Davison chart.
get_transit_preview Person ID + time range -> list of events (exact aspects, ingresses, stations, lunar phases). Optional event_types filter.
person_manage action param: add, get, list, update, delete. Birth data in add/update.
list_house_systems Discovery tool -- returns supported house systems.

Docker Compose

services:
  astro-mcp:
    build: {context: ., dockerfile: Dockerfile}
    image: astro-mcp:latest
    container_name: astro-mcp
    restart: unless-stopped
    ports: ["7016:7016"]
    environment:
      ASTRO_HOST: 0.0.0.0
      ASTRO_PORT: 7016
      ASTRO_DATA_DIR: /app/data
      ASTRO_LOG_DIR: /app/logs
      ASTRO_DB_PATH: /app/data/astro.sqlite3
      EPHEMERIS_MCP_URL: http://ephemeris-mcp:7015/mcp/sse
    volumes: ["./data:/app/data", "./logs:/app/logs"]
    healthcheck:
      test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:7016/health').read()"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 20s

Implementation Order

Phase 1 -- Scaffold

  • Create directory structure, .gitignore, requirements.txt, src/astro_mcp/ package
  • main.py entry point (copy ephemeris-mcp pattern)
  • config.py (PORT=7016, dirs, EPHEMERIS_MCP_URL)
  • server.py basic FastAPI + FastMCP with /health and /
  • run.sh, killserver.sh, restart.sh, tests.sh
  • Dockerfile + docker-compose.yml
  • Verify: ./run.sh starts, curl /health returns ok, commit

Phase 2 -- Astrology calculation module (pure, no I/O)

  • astrology.py: zodiac sign from ecliptic longitude, house cusps (Placidus + Equal), aspect detection with orbs, angle calculation (ASC/MC from sidereal time)
  • Pydantic models for chart structures
  • tests/test_astrology.py: comprehensive unit tests
  • Verify: pytest tests/test_astrology.py -q passes, commit

Phase 3 -- Ephemeris client + planetary positions tool

  • ephemeris_client.py: call_sky_state(datetime, lat, lon) using sse_client
  • tools.py: get_planetary_positions tool -- calls sky_state, adds sign/degree/retrograde to each body
  • Test with mocked client
  • Verify: tool returns enhanced body array, commit

Phase 4 -- Person storage + management

  • storage.py: persons DB, same cache pattern as ephemeris-mcp but using asyncio.to_thread for DB access
  • person_manage tool (add/get/list/update/delete)
  • tests/test_tools.py: person CRUD tests
  • Verify: CRUD round-trips, commit

Phase 5 -- Natal chart tool

  • Wire calculate_natal_chart in tools.py
  • Calls call_sky_state -> feeds planetary positions into astrology module -> assembles full chart dict
  • Test with mocked ephemeris output: verify all chart sections present (planets, houses, aspects, angles)
  • Verify: structured natal chart output, commit

Phase 6 -- Transit + Synastry tools

  • calculate_transit_chart: calls sky_state twice (natal + transit datetimes), computes aspects between them
  • calculate_synastry_chart: calls sky_state twice, computes interaspects, house overlays, composite (midpoint), Davison
  • Tests for both
  • Verify, commit

Phase 7 -- Transit preview

  • get_transit_preview: Given person_id + start/end range, iterates through time steps to find exact aspects (orb <= 0d01), ingresses (sign changes), retrograde stations (speed_lon sign changes), lunar phase changes
  • Uses the person DB + ephemeris client calls
  • This is the most algorithmically complex tool -- scan daily then refine with hourly substeps around detected events
  • Verify, commit

Phase 8 -- Dashboard

  • dashboard.py: Jinja2 templates mounted on FastAPI routes (/dashboard, /dashboard/persons, /dashboard/persons/<id>)
  • Vanilla JS for add/edit/delete persons via fetch API
  • Templates: base layout, person list, person detail with chart preview link
  • Mount routes in server.py lifespan or at module level
  • Verify: browser test, commit

Phase 9 -- Integration + docs

  • Full test suite running via tests.sh
  • README.md: tool descriptions, example calls, Docker usage
  • PROJECT.md: technical specs, schema, architecture
  • Final commit and push