|
|
@@ -0,0 +1,189 @@
|
|
|
+# 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**:
|
|
|
+ ```sql
|
|
|
+ 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
|
|
|
+
|
|
|
+```yaml
|
|
|
+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
|