Port: 7016 (ephemeris-mcp is 7015)
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.
astro-mcp/
├── Dockerfile
├── docker-compose.yml
├── main.py
├── requirements.txt
├── .gitignore
├── .env / .env.example
├── 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
│ ├── server.py
│ ├── ephemeris_client.py
│ ├── astrology.py
│ ├── models.py
│ ├── storage.py
│ ├── dashboard.py
│ └── tools.py
├── templates/
│ ├── base.html
│ ├── dashboard.html
│ └── persons.html
└── tests/
├── conftest.py
├── test_astrology.py
├── test_tools.py
├── test_storage.py
└── test_server.py
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
No pyswisseph -- all astronomical data comes from ephemeris-mcp via MCP client.
sse_client(url) + ClientSession. Ephemeris URL via EPHEMERIS_MCP_URL env var.async def, SQLite uses asyncio.to_thread().astrology.py has no async, no I/O.Person DB schema with non-destructive migrations:
CREATE TABLE persons (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
nickname TEXT UNIQUE,
birth_datetime TEXT NOT NULL, -- naive LOCAL time (no offset)
birthplace TEXT,
latitude REAL NOT NULL,
longitude REAL NOT NULL,
elevation REAL DEFAULT 0.0,
timezone TEXT, -- IANA name (e.g. "Europe/Vienna")
alive INTEGER DEFAULT 1,
private INTEGER DEFAULT 0,
gender TEXT,
description TEXT,
notes TEXT,
birth_time_known INTEGER DEFAULT 1,
created_at TEXT NOT NULL,
updated_at TEXT
);
Datetime storage convention: birth_datetime is always naive local time
(no Z, no +01:00). The IANA timezone column is combined with it by
_get_person_birth_data() to produce UTC. Do NOT store UTC or offset-aware
datetimes in this column. For historical dates (pre-1890), zoneinfo will
use LMT automatically when given the IANA name.
| 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 |
| Tool | Description |
|---|---|
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 |
services:
astro-mcp:
build: {context: ., dockerfile: Dockerfile}
image: astro-mcp:latest
container_name: astro-mcp
restart: unless-stopped
ports: ["7016:7016"]
env_file: ["./env"]
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
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