|
@@ -4,7 +4,7 @@
|
|
|
|
|
|
|
|
## What We're Building
|
|
## 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.
|
|
|
|
|
|
|
+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) and a Docker container.
|
|
|
|
|
|
|
|
## Project Structure
|
|
## Project Structure
|
|
|
|
|
|
|
@@ -12,40 +12,39 @@ An MCP server that consumes `ephemeris-mcp:get_sky_state` via MCP-over-SSE clien
|
|
|
astro-mcp/
|
|
astro-mcp/
|
|
|
├── Dockerfile
|
|
├── Dockerfile
|
|
|
├── docker-compose.yml
|
|
├── docker-compose.yml
|
|
|
-├── main.py # entrypoint, same pattern as ephemeris-mcp
|
|
|
|
|
|
|
+├── main.py
|
|
|
├── requirements.txt
|
|
├── requirements.txt
|
|
|
├── .gitignore
|
|
├── .gitignore
|
|
|
|
|
+├── .env / .env.example
|
|
|
├── README.md
|
|
├── README.md
|
|
|
├── PROJECT.md
|
|
├── PROJECT.md
|
|
|
-├── run.sh
|
|
|
|
|
-├── killserver.sh
|
|
|
|
|
-├── restart.sh
|
|
|
|
|
-├── tests.sh
|
|
|
|
|
|
|
+├── run.sh / killserver.sh / restart.sh / tests.sh
|
|
|
├── data/ # SQLite DBs at runtime
|
|
├── data/ # SQLite DBs at runtime
|
|
|
├── logs/ # PID + server.log
|
|
├── logs/ # PID + server.log
|
|
|
├── src/
|
|
├── src/
|
|
|
│ └── astro_mcp/
|
|
│ └── astro_mcp/
|
|
|
│ ├── __init__.py
|
|
│ ├── __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)
|
|
|
|
|
|
|
+│ ├── config.py
|
|
|
|
|
+│ ├── server.py
|
|
|
|
|
+│ ├── ephemeris_client.py
|
|
|
|
|
+│ ├── astrology.py
|
|
|
|
|
+│ ├── models.py
|
|
|
|
|
+│ ├── storage.py
|
|
|
|
|
+│ ├── dashboard.py
|
|
|
|
|
+│ └── tools.py
|
|
|
├── templates/
|
|
├── templates/
|
|
|
│ ├── base.html
|
|
│ ├── base.html
|
|
|
│ ├── dashboard.html
|
|
│ ├── dashboard.html
|
|
|
│ └── persons.html
|
|
│ └── persons.html
|
|
|
└── tests/
|
|
└── tests/
|
|
|
├── conftest.py
|
|
├── 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
|
|
|
|
|
|
|
+ ├── test_astrology.py
|
|
|
|
|
+ ├── test_tools.py
|
|
|
|
|
+ ├── test_storage.py
|
|
|
|
|
+ └── test_server.py
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-## Dependencies (requirements.txt)
|
|
|
|
|
|
|
+## Dependencies
|
|
|
|
|
|
|
|
```
|
|
```
|
|
|
fastapi>=0.115.0
|
|
fastapi>=0.115.0
|
|
@@ -58,49 +57,57 @@ python-dotenv>=1.0.1
|
|
|
pytest>=8.0.0
|
|
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.
|
|
|
|
|
|
|
+No `pyswisseph` -- all astronomical data comes from ephemeris-mcp via MCP client.
|
|
|
|
|
|
|
|
## Key Design Decisions
|
|
## 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**:
|
|
|
|
|
|
|
+1. **MCP client pattern**: `sse_client(url)` + `ClientSession`. Ephemeris URL via `EPHEMERIS_MCP_URL` env var.
|
|
|
|
|
+2. **Async throughout**: ephemeris client is async, FastMCP tools are `async def`, SQLite uses `asyncio.to_thread()`.
|
|
|
|
|
+3. **Calculation module is pure sync**: `astrology.py` has no async, no I/O.
|
|
|
|
|
+4. **Datetime normalization**: all datetimes converted to UTC ISO strings before sending to ephemeris server.
|
|
|
|
|
+5. **House systems**: Placidus (default), Equal, Whole Sign.
|
|
|
|
|
+6. **Transit orbs**: per-planet radii (Sun/Moon 1.5°, personal 1.0°, slow 1.5°, outer 1.0°), aspect type multipliers (conj/opp 1.0, sq/tr 0.75, sex 0.5). Transit orb = (transiting_radius + natal_radius) × multiplier.
|
|
|
|
|
+7. **Transit significance scoring** (0-10): based on aspect type, transiting planet importance, natal target importance, orb tightness.
|
|
|
|
|
+8. **Person DB schema** with non-destructive migrations:
|
|
|
```sql
|
|
```sql
|
|
|
CREATE TABLE persons (
|
|
CREATE TABLE persons (
|
|
|
id TEXT PRIMARY KEY,
|
|
id TEXT PRIMARY KEY,
|
|
|
name TEXT NOT NULL,
|
|
name TEXT NOT NULL,
|
|
|
nickname TEXT UNIQUE,
|
|
nickname TEXT UNIQUE,
|
|
|
- birth_datetime TEXT NOT NULL, -- ISO 8601 UTC
|
|
|
|
|
|
|
+ birth_datetime TEXT NOT NULL,
|
|
|
latitude REAL NOT NULL,
|
|
latitude REAL NOT NULL,
|
|
|
longitude REAL NOT NULL,
|
|
longitude REAL NOT NULL,
|
|
|
elevation REAL DEFAULT 0.0,
|
|
elevation REAL DEFAULT 0.0,
|
|
|
- created_at TEXT NOT NULL -- ISO 8601 UTC
|
|
|
|
|
|
|
+ created_at TEXT NOT NULL,
|
|
|
|
|
+ updated_at TEXT
|
|
|
);
|
|
);
|
|
|
|
|
+ -- Migration: ALTER TABLE persons ADD COLUMN birthplace TEXT
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-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 (12 tools)
|
|
|
|
|
+
|
|
|
|
|
+### Core tools (raw birth data)
|
|
|
|
|
+
|
|
|
|
|
+| Tool | Description |
|
|
|
|
|
+|---|---|
|
|
|
|
|
+| `get_planetary_positions` | Planetary positions with zodiac signs, degrees, retrograde flags |
|
|
|
|
|
+| `calculate_natal_chart` | Full natal chart. Params: birth_datetime, latitude, longitude |
|
|
|
|
|
+| `calculate_transit_chart` | Transit chart. Params: birth_datetime, transit_datetime, lat, lon, transit_lat, transit_lon |
|
|
|
|
|
+| `calculate_synastry_chart` | Relationship chart. Params: person1_datetime/lat/lon, person2_datetime/lat/lon |
|
|
|
|
|
+| `calculate_composite_chart` | Composite chart (midpoint method). Params: same as synastry |
|
|
|
|
|
+| `get_transit_preview` | Daily transit-to-natal aspect snapshot. Params: birth_datetime, lat, lon, start_date, end_date |
|
|
|
|
|
+| `person_manage` | CRUD for persons DB. Actions: add, get (by id or nickname), list, update, delete |
|
|
|
|
|
+| `list_house_systems` | List supported house systems |
|
|
|
|
|
|
|
|
-## MCP Tool Surface (7 tools)
|
|
|
|
|
|
|
+### _byId tools (database-backed, accept person_id or nickname)
|
|
|
|
|
|
|
|
| Tool | Description |
|
|
| 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. |
|
|
|
|
|
|
|
+| `calculate_natal_chart_by_id` | Natal chart by person_id |
|
|
|
|
|
+| `calculate_transit_chart_by_id` | Transit chart by person_id + transit_datetime |
|
|
|
|
|
+| `calculate_synastry_chart_by_id` | Synastry for person1_id + person2_id |
|
|
|
|
|
+| `calculate_composite_chart_by_id` | Composite for person1_id + person2_id |
|
|
|
|
|
+| `get_transit_preview_by_id` | Transit preview by person_id + date range |
|
|
|
|
|
|
|
|
## Docker Compose
|
|
## Docker Compose
|
|
|
|
|
|
|
@@ -112,13 +119,13 @@ services:
|
|
|
container_name: astro-mcp
|
|
container_name: astro-mcp
|
|
|
restart: unless-stopped
|
|
restart: unless-stopped
|
|
|
ports: ["7016:7016"]
|
|
ports: ["7016:7016"]
|
|
|
|
|
+ env_file: ["./env"]
|
|
|
environment:
|
|
environment:
|
|
|
ASTRO_HOST: 0.0.0.0
|
|
ASTRO_HOST: 0.0.0.0
|
|
|
ASTRO_PORT: 7016
|
|
ASTRO_PORT: 7016
|
|
|
ASTRO_DATA_DIR: /app/data
|
|
ASTRO_DATA_DIR: /app/data
|
|
|
ASTRO_LOG_DIR: /app/logs
|
|
ASTRO_LOG_DIR: /app/logs
|
|
|
ASTRO_DB_PATH: /app/data/astro.sqlite3
|
|
ASTRO_DB_PATH: /app/data/astro.sqlite3
|
|
|
- EPHEMERIS_MCP_URL: http://ephemeris-mcp:7015/mcp/sse
|
|
|
|
|
volumes: ["./data:/app/data", "./logs:/app/logs"]
|
|
volumes: ["./data:/app/data", "./logs:/app/logs"]
|
|
|
healthcheck:
|
|
healthcheck:
|
|
|
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:7016/health').read()"]
|
|
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:7016/health').read()"]
|
|
@@ -128,62 +135,21 @@ services:
|
|
|
start_period: 20s
|
|
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
|
|
|
|
|
|
|
+## Implementation Status
|
|
|
|
|
+
|
|
|
|
|
+### Completed
|
|
|
|
|
+- Phase 1: Scaffold, server, scripts, Docker
|
|
|
|
|
+- Phase 2: Astrology calculation module (pure math)
|
|
|
|
|
+- Phase 3: Ephemeris client + all MCP tool definitions
|
|
|
|
|
+- Phase 4: Person storage + management
|
|
|
|
|
+- Phase 5: Natal chart tool
|
|
|
|
|
+- Phase 6: Transit + Synastry tools
|
|
|
|
|
+- Phase 7: Transit preview (daily snapshots with significance scoring)
|
|
|
|
|
+- Composite chart tool (standalone)
|
|
|
|
|
+- _byId convenience tools for all chart functions
|
|
|
|
|
+- Schema migrations for birthplace column
|
|
|
|
|
+- 102 tests passing
|
|
|
|
|
+
|
|
|
|
|
+### Remaining
|
|
|
|
|
+- Phase 8: Dashboard (Jinja2 + vanilla JS for person management)
|
|
|
|
|
+- Phase 9: Final integration + docs
|