Prechádzať zdrojové kódy

feat: deliver natal charts as MCP resources

Lukas Goldschmidt 1 mesiac pred
rodič
commit
ed7f85b7c9

+ 17 - 3
AGENTS.md

@@ -1,4 +1,4 @@
-# AGENTS.md — Astro-MCP Agent Guide
+# AGENTS.md — Astro-MCP v0.2.0 Agent Guide
 
 ## Datetime Convention (CRITICAL)
 
@@ -17,7 +17,7 @@ Example: Chaka Khan born 9:05 PM in Chicago → store as:
 The conversion to UTC happens inside `_get_person_birth_data()` using
 `zoneinfo.ZoneInfo`. Do NOT pre-convert to UTC before storing.
 
-### 2. Direct-Call Tools (calculate_natal_chart, render_natal_chart, etc.)
+### 2. Direct-Call Tools (calculate_natal_chart, calculate_transit_chart, etc.)
 
 - `birth_datetime`: **UTC or offset-aware** ISO 8601
 
@@ -28,7 +28,7 @@ Examples:
 These tools pass the datetime directly to the ephemeris server. No timezone
 conversion is performed.
 
-### 3. _byId Tools (calculate_natal_chart_by_id, render_natal_chart_by_id)
+### 3. _byId Tools (calculate_natal_chart_by_id, calculate_transit_chart_by_id)
 
 - Pass `person_id` (or nickname) only.
 - Timezone conversion is handled automatically by `_get_person_birth_data()`.
@@ -63,3 +63,17 @@ manually for historical dates.
    = UTC-6, not UTC-7).
 3. **Missing `tz` field** — falls back to LMT from longitude, which is approximately
    correct but not precise. Always set `tz`.
+
+## v0.2.0 chart delivery
+
+Chart drawing is stable and unchanged. The former `render_*` MCP tools are removed.
+Rendered natal charts for database persons are delivered through:
+
+```text
+astro://charts/natal/{person_id}
+/charts/natal/{person_id}.{format}
+```
+
+The MCP resource and HTTP route call the existing natal calculation and renderer.
+Transit, synastry, composite, and Davison chart resources are not implemented in
+v0.2.0.

+ 0 - 173
AstroChart_Source_Reference.md

@@ -1,173 +0,0 @@
-# AstroChart — Source Code Reference
-
-**Library**: AstroChart (a.k.a. AstroDraw)
-**Description**: A free and open-source JavaScript/TypeScript library for generating SVG charts to display planets in astrology.
-**Doc site**: https://astrodraw.github.io/
-**GitHub repo**: https://github.com/AstroDraw/AstroChart
-**Branch**: `main`
-
----
-
-## Repository Structure
-
-The source code lives under `project/` (not `src/`). Build output (the bundle consumed end-users) is in `dist/`.
-
-```
-AstroChart/
-├── dist/
-│   ├── astrochart.js              # Webpack-bundled build (99KB) — what the docs site loads
-│   └── project/                   # Sub-bundle for project-mode imports
-├── project/
-│   ├── src/                       # TypeScript source
-│   │   ├── index.ts               (172B)   — Public API entry, re-exports Chart
-│   │   ├── chart.ts               (4KB)    — Chart class, public entry point
-│   │   ├── svg.ts                 (84KB)   — SVG drawing primitives + all planet/zodiac glyphs
-│   │   ├── radix.ts               (19KB)   — Radix chart composition (drawBg, drawUniverse, drawPoints...)
-│   │   ├── transit.ts             (13KB)   — Transit chart overlay
-│   │   ├── aspect.ts              (8KB)    — Aspect calculation engine
-│   │   ├── zodiac.ts              (10KB)   — Zodiac logic, dignities, retrograde
-│   │   ├── settings.ts            (8KB)    — Default config (colors, ratios, symbols, scale)
-│   │   ├── utils.ts               (12KB)   — Geometry: getPointPosition, getRulerPositions, collision
-│   │   ├── animation/
-│   │   │   ├── animator.ts        (5KB)    — Animated chart transitions
-│   │   │   └── timer.ts           (1KB)    — Animation timing helper
-│   │   └── *.test.ts              — Jest test suite
-│   ├── examples/                  — Usage examples
-│   └── __tests__/                 — Additional tests
-├── website/                       # Docusaurus doc site source (what renders astrodraw.github.io)
-├── docs/                          # Doc site content (markdown)
-├── doc/                           # Older documentation
-├── package.json
-├── tsconfig.json
-├── webpack.config.js
-└── jest.config.js
-```
-
----
-
-## Chart Rendering Flow
-
-### Public API
-
-```typescript
-// project/src/chart.ts
-class Chart {
-  constructor(elementId: string, width: number, height: number, settings?: Partial<Settings>)
-  radix(data: AstroData): Radix      // Draw a natal/radix chart
-  transit(data: AstroData): Transit  // Draw a transit overlay
-  scale(factor: number): void        // Scale the chart
-  calibrate(): Chart                 // Debug overlay
-}
-```
-
-```typescript
-// project/src/index.ts — re-exports Chart as default
-```
-
-### Drawing Pipeline (inside `Radix`)
-
-Order of operations in `Radix` constructor + draw methods:
-
-| Step | Method | File | What it does |
-|------|--------|------|--------------|
-| 1 | `drawBg()` | `project/src/radix.ts` | Background hemisphere via `paper.segment()` |
-| 2 | `drawUniverse()` | `project/src/radix.ts` | 12 zodiac sign color segments + 12 zodiac symbol glyphs |
-| 3 | `drawRuler()` | `project/src/radix.ts` | Degree tick marks around the wheel |
-| 4 | `drawPoints()` | `project/src/radix.ts` | Planet symbols, pointer lines, degree/retro/dignity labels |
-| 5 | `drawCusps()` | `project/src/radix.ts` | 12 cusp lines (dashed around planets), house numbers |
-| 6 | `drawAxis()` | `project/src/radix.ts` | Asc/Ds/Ic/Mc axis lines with symbols |
-| 7 | `drawCircles()` | `project/src/radix.ts` | Outer, inner, indoor circle strokes |
-
-Aspects are drawn via `Radix.aspects()` which uses `project/src/aspect.ts`.
-
----
-
-## Key Source Files (download URLs)
-
-### Core rendering
-
-| File | Description |
-|------|-------------|
-| [svg.ts](https://raw.githubusercontent.com/AstroDraw/AstroChart/main/project/src/svg.ts) | SVG primitives: `segment()`, `circle()`, `line()`, `text()`, and 50+ glyph methods (`sun()`, `moon()`, `aries()`, `taurus()`, ... `pluto()`, `chiron()`, `nnode()`, `fortune()`). The `<path d="...">` arc math lives at lines ~1468-1478. |
-| [radix.ts](https://raw.githubusercontent.com/AstroDraw/AstroChart/main/project/src/radix.ts) | Chart composition — orchestrates all SVG calls. `LocatedPoint` interface, `AstroData` type, collision avoidance via `assemble()`. |
-| [chart.ts](https://raw.githubusercontent.com/AstroDraw/AstroChart/main/project/src/chart.ts) | Public entry point. Creates `SVG`, delegates to `Radix` or `Transit`. |
-
-### Data + logic
-
-| File | Description |
-|------|-------------|
-| [settings.ts](https://raw.githubusercontent.com/AstroDraw/AstroChart/main/project/src/settings.ts) | Default constants: `COLORS_SIGNS`, `SYMBOL_SIGNS`, `INNER_CIRCLE_RADIUS_RATIO`, `INDOOR_CIRCLE_RADIUS_RATIO`, `RULER_RADIUS`, `SYMBOL_SCALE`, `PADDING`, etc. |
-| [utils.ts](https://raw.githubusercontent.com/AstroDraw/AstroChart/main/project/src/utils.ts) | `getPointPosition()`, `getRulerPositions()`, `getDashedLinesPositions()`, `getDescriptionPosition()`, `assemble()`, `validate()`, `radiansToDegree()`. |
-| [zodiac.ts](https://raw.githubusercontent.com/AstroDraw/AstroChart/main/project/src/zodiac.ts) | Zodiac sign boundaries, essential dignities (domicile, exaltation, detriment, fall), retrograde detection. |
-| [aspect.ts](https://raw.githubusercontent.com/AstroDraw/AstroChart/main/project/src/aspect.ts) | `AspectCalculator` class — computes aspects between planet points (conjunction, opposition, trine, square, sextile, etc.). |
-| [transit.ts](https://raw.githubusercontent.com/AstroDraw/AstroChart/main/project/src/transit.ts) | Transit chart — draws on top of an existing radix. |
-| [animation/animator.ts](https://raw.githubusercontent.com/AstroDraw/AstroChart/main/project/src/animation/animator.ts) | Animated chart transitions. |
-| [animation/timer.ts](https://raw.githubusercontent.com/AstroDraw/AstroChart/main/project/src/animation/timer.ts) | Animation timing helper. |
-| [index.ts](https://raw.githubusercontent.com/AstroDraw/AstroChart/main/project/src/index.ts) | Public API barrel file. |
-
-### Built artifact
-
-| File | Description |
-|------|-------------|
-| [dist/astrochart.js](https://raw.githubusercontent.com/AstroDraw/AstroChart/main/dist/astrochart.js) | Webpack bundle — the file loaded by the doc site and distributed to end-users. |
-
----
-
-## SVG Primitives API (svg.ts)
-
-| Method | Signature | Purpose |
-|--------|-----------|---------|
-| `segment()` | `(x, y, radius, a1, a2, thickness, lFlag?, sFlag?)` | Arc slice (used for sign segments, background) |
-| `circle()` | `(cx, cy, radius)` | Circle stroke (chart rings) |
-| `line()` | `(x1, y1, x2, y2)` | Straight line (cusps, pointers, ruler ticks) |
-| `text()` | `(txt, x, y, size, color)` | Symbol/text glyph |
-| `getSymbol()` | `(name, x, y)` | Dispatches to the correct glyph method |
-| `radialLine()` | — | Radial spoke from center to edge |
-
-**Key constants** (defined in `settings.ts`, used in `svg.ts` line ~1468):
-- `LARGE_ARC_FLAG = 1`
-- `SWEET_FLAG = 0`
-
----
-
-## Doc Site (what renders the GitHub Pages UI)
-
-The documentation site that appears at https://astrodraw.github.io/ is built with **Docusaurus v2.3.1** (seen in `<meta name="generator">`).
-
-| File | Description |
-|------|-------------|
-| [website/](https://github.com/AstroDraw/AstroChart/tree/main/website) | Docusaurus source (docusaurus.config, sidebar, pages) |
-| [docs/](https://github.com/AstroDraw/AstroChart/tree/main/docs) | Doc content markdown |
-
-The homepage hero banner contains an inline SVG showing a React-like logo (unrelated to this library — it's Docusaurus template branding). The actual AstroChart demo rendering on the page uses the `Chart` class targeting `<div id="paper">`.
-
----
-
-## Key Data Structures
-
-```typescript
-// From radix.ts
-export type Points = Record<string, number[]>
-export interface LocatedPoint {
-  name: string   // planet name, e.g. "Sun", "Moon"
-  x: number      // computed screen x
-  y: number      // computed screen y
-  r: number      // collision radius
-  angle: number  // shifted position in degrees
-  pointer?: number
-  index?: number
-}
-export interface AstroData {
-  planets: Points   // {"Sun":[120], "Moon":[45], ...}
-  cusps: number[]   // [300, 340, 30, 60, 75, 90, 116, 172, 210, 236, 250, 274]
-}
-```
-
----
-
-## Notes
-
-- The library is **TypeScript** (compiled via webpack, not tsc).
-- No runtime dependencies — pure DOM SVG manipulation.
-- Tests use **Jest** (`jest.config.js`).
-- The `project/` subdirectory is the actual library source; the repo root contains build/config/docs.

+ 23 - 420
IMPLEMENTATION_PLAN.md

@@ -1,428 +1,31 @@
-# Astro MCP Implementation Plan
+# Astro-MCP v0.2.0 Implementation Plan
 
-> Based on: WISHLIST_ANALYSIS.md
-> Strategy: Expand existing tools rather than creating new ones where possible.
-> Each phase is independently testable. Stop after each phase for review.
+This is the active implementation plan for version 0.2.0. The completed plan is maintained in `IMPLEMENTATION_PLAN_v0.2.0.md`.
 
----
+## Delivered scope
 
-## Phase 0: Fix Natal Chart Applying/Separating Bug
+- Remove all MCP tools whose names begin with `render_`.
+- Keep chart drawing and rendering implementation unchanged.
+- Expose the default SVG database-backed natal chart as `astro://charts/natal/{person_id}`.
+- Expose database-backed natal chart graphics through `/charts/natal/{person_id}.{format}` using the renderer's existing format suffixes.
+- Reuse the existing natal calculation and renderer.
+- Keep all structured calculation tools available.
 
-**File**: `src/astro_mcp/tools.py`, `calculate_natal_chart` function
+## Explicit non-goals
 
-The natal chart tool builds planet lines without `speed_lon`, then calls `compute_aspects` on bodies that also lack `speed_lon`. This means `applying` is always `None` for natal aspects.
+- No chart calculation changes.
+- No renderer or drawing changes.
+- No new rendering options or formats.
+- No transit, synastry, composite, or Davison chart resources.
+- No compatibility wrappers, legacy render tools, migration routes, or caching layer.
 
-**Change**: When building the `aspect_bodies` list in `calculate_natal_chart`, include `speed_lon` from the raw ephemeris data. This requires keeping a lookup of `speed_lon` by body name from the raw bodies.
+## Verification
 
-```python
-# Before (line ~170):
-aspect_bodies = [{"name": p["body"], "lon": p["absolute_lon"]} for p in planets]
+- `render_*` MCP tools are absent.
+- The natal resource template is registered.
+- A database-backed natal resource returns an image artifact.
+- The HTTP natal route returns the existing artifact.
+- Calculation tools remain available.
+- The full test suite passes.
 
-# After:
-speed_lookup = {b["body"]: b.get("speed_lon") for b in raw_bodies}
-aspect_bodies = [{"name": p["body"], "lon": p["absolute_lon"], "speed_lon": speed_lookup.get(p["body"])} for p in planets]
-```
-
-**Tests**: Add a test in `test_tools.py` that verifies `applying` is not `None` for natal chart aspects when planets have different speeds.
-
-**Effort**: ~5 lines changed.
-
----
-
-## Phase 1: Chart Overview Post-Processing Functions
-
-**File**: `src/astro_mcp/astrology.py` (new functions)
-
-Add these pure functions that operate on the planet list returned by `calculate_natal_chart`:
-
-### 1.1 `get_element_balance(planets) -> dict`
-Count planets by element. Each sign maps to an element:
-- Fire: Aries, Leo, Sagittarius
-- Earth: Taurus, Virgo, Capricornus
-- Air: Gemini, Libra, Aquarius
-- Water: Cancer, Scorpius, Pisces
-
-Returns: `{"fire": N, "earth": N, "air": N, "water": N, "percentages": {...}}`
-
-### 1.2 `get_modality_balance(planets) -> dict`
-Count planets by modality:
-- Cardinal: Aries, Cancer, Libra, Capricornus
-- Fixed: Taurus, Leo, Scorpius, Aquarius
-- Mutable: Gemini, Virgo, Sagittarius, Pisces
-
-Returns: `{"cardinal": N, "fixed": N, "mutable": N, "percentages": {...}}`
-
-### 1.3 `get_hemisphere_emphasis(planets) -> dict`
-Count planets by house ranges:
-- Upper (houses 7-12), Lower (1-6), East (10-3), West (4-9)
-
-Returns: `{"upper": N, "lower": N, "east": N, "west": N}`
-
-### 1.4 `detect_stelliums(planets) -> list`
-Scan for 3+ planets in the same sign OR same house.
-
-Returns: `[{"type": "sign"|"house", "key": str, "planets": [str, ...]}, ...]`
-
-### 1.5 `get_empty_houses(planets) -> list[int]`
-Return house numbers (1-12) with no planets.
-
-### 1.6 `get_chart_ruler(ascendant_sign, planets) -> dict`
-Sign-to-ruler mapping:
-- Aries -> Mars, Taurus -> Venus, Gemini -> Mercury, Cancer -> Moon,
-- Leo -> Sun, Virgo -> Mercury, Libra -> Venus, Scorpius -> Pluto (traditional: Mars),
-- Sagittarius -> Jupiter, Capricornus -> Saturn, Aquarius -> Uranus (traditional: Saturn),
-- Pisces -> Neptune (traditional: Jupiter)
-
-Return the ruling planet's full data from the planets list.
-
-### 1.7 `get_house_rulers(houses, planets) -> list`
-For each house cusp sign, find the ruling planet and its condition.
-
-### 1.8 `group_planets_by_house(planets) -> dict`
-Returns: `{house_number: [planet_names]}`
-
-### 1.9 `group_planets_by_sign(planets) -> dict`
-Returns: `{sign_name: [planet_names]}`
-
-### 1.10 `get_house_type_counts(planets) -> dict`
-Angular (1,4,7,10), Succedent (2,5,8,11), Cadent (3,6,9,12).
-
-### 1.11 `get_retrograde_planets(planets) -> list`
-Filter planets where `retrograde=True`, return with sign + house.
-
-### 1.12 `get_nodal_axis(planets, houses) -> dict`
-Extract `true_node` from planets, compute South Node (opposite point).
-
-### 1.13 `get_saturn_info(planets) -> dict`
-Extract Saturn from planets list with sign, house, retrograde.
-
-### 1.14 `get_part_of_fortune(ascendant_lon, sun_lon, moon_lon) -> dict`
-Formula: `normalize_degrees(ascendant_lon + moon_lon - sun_lon)`. Return sign + degree.
-
-### 1.15 `get_pluto_polarity_point(pluto_lon) -> dict`
-Formula: `normalize_degrees(pluto_lon + 180)`. Return sign + degree.
-
-**Tests**: Add all functions to `test_astrology.py` with known inputs.
-
-**Effort**: ~200 lines in astrology.py, ~150 lines in test_astrology.py.
-
----
-
-## Phase 2: Aspect Pattern Detection
-
-**File**: `src/astro_mcp/astrology.py`
-
-### 2.1 `detect_aspect_patterns(planets, aspects) -> list`
-
-Scan the aspect list for known patterns:
-
-**T-square**: Find an opposition (A-B). Check if any planet C forms a square to both A and B. Apex = C.
-
-**Grand Trine**: Find 3 planets where each pair is in trine (3 trines total). All 3 must be in the same element.
-
-**Grand Cross**: Find 4 planets forming 2 oppositions and 4 squares (each planet opposes one and squares two others).
-
-**Yod**: Find 2 planets in sextile. Check if any planet C forms a quincunx (150°) to both. Note: quincunx is not in the current aspect definitions -- add it first.
-
-**Add quincunx to ASPECT_DEFINITIONS**:
-```python
-{"name": "quincunx", "angle": 150.0, "default_orb": 3.0, "symbol": "Qx"}
-```
-
-Returns: `[{"type": "T-square"|"Grand Trine"|"Grand Cross"|"Yod", "planets": [...], "apex": str|null, "modality": str|null, "element": str|null}]`
-
-**Tests**: Create synthetic planet configurations that form each pattern type and verify detection.
-
-**Effort**: ~150 lines in astrology.py, ~100 lines in tests.
-
----
-
-## Phase 3: Chart Shape Detection
-
-**File**: `src/astro_mcp/astrology.py`
-
-### 3.1 `detect_chart_shape(planets) -> dict`
-
-Analyze the angular distribution of planets to classify the chart shape:
-
-1. Sort planets by longitude
-2. Compute gaps between adjacent planets (including wraparound)
-3. Find the largest gap
-4. Classify:
-   - **Bundle**: All planets within 120° arc (largest gap >= 240°)
-   - **Bowl**: All planets within 180° arc (largest gap >= 180°)
-   - **Bucket**: All planets within 240° arc, with one planet opposite (singleton) -- largest gap < 240° but > 120°
-   - **Splash**: Planets distributed around the full 360° (largest gap < 120°)
-   - **Locomotive**: Planets within ~240° with a "locomotive" planet at the start of the empty arc
-   - **Seesaw**: Two clusters of planets roughly opposite each other
-   - **Splay**: Three or more planet pairs/groups distributed around the chart
-
-Returns: `{"shape": str, "largest_gap": float, "gap_start": float, "gap_end": float}`
-
-**Tests**: Create synthetic planet configurations for each shape.
-
-**Effort**: ~80 lines in astrology.py, ~80 lines in tests.
-
----
-
-## Phase 4: Expand calculate_natal_chart with Overview
-
-**File**: `src/astro_mcp/tools.py`
-
-### 4.1 Add `include_overview` parameter to `calculate_natal_chart`
-
-```python
-async def calculate_natal_chart(
-    ...existing params...,
-    include_overview: bool = False,
-    include_patterns: bool = False,
-    include_karmic: bool = False,
-    top_n_aspects: int | None = None,
-) -> dict[str, Any]:
-```
-
-When `include_overview=True`, add an `"overview"` key to the output containing:
-- element_balance, modality_balance, hemisphere_emphasis
-- stelliums, empty_houses
-- chart_ruler, house_rulers
-- planets_by_house, planets_by_sign
-- house_type_counts
-- retrograde_planets
-- lunar_phase (from ephemeris `lunar_state`)
-
-When `include_patterns=True`, add an `"aspect_patterns"` key with T-square, Grand Trine, Grand Cross, Yod, and chart_shape.
-
-When `include_karmic=True`, add a `"karmic"` key with:
-- nodal_axis, saturn_info, pluto_polarity_point
-- nodal_aspects (filter aspects for true_node)
-- saturn_aspects (filter aspects for Saturn hard aspects to personal planets)
-- retrograde_planets (emphasize personal planet retrogrades)
-- 12th_house_analysis (cusp sign, planets, ruler)
-
-When `top_n_aspects` is set, limit the aspects list to the N tightest.
-
-**Tests**: Add tests in `test_tools.py` verifying each flag produces the expected output sections.
-
-**Effort**: ~100 lines in tools.py, ~150 lines in tests.
-
----
-
-## Phase 5: Expand calculate_synastry_chart
-
-**File**: `src/astro_mcp/tools.py`
-
-### 5.1 Add parameters to `calculate_synastry_chart`
-
-```python
-async def calculate_synastry_chart(
-    ...existing params...,
-    top_n_aspects: int | None = None,
-    karmic_filter: bool = False,
-    significator_filter: bool = False,
-) -> dict[str, Any]:
-```
-
-- `top_n_aspects`: Limit interaspects to top N by orb
-- `karmic_filter`: Only return interaspects involving Saturn, Pluto, or true_node
-- `significator_filter`: Only return interaspects involving Venus-Mars, Moon-Venus, Sun-Moon, Sun-Saturn pairs
-
-Add a `"summary"` key to the output containing:
-- `top_aspects`: Top 10 interaspects by orb
-- `saturn_contacts`: All Saturn interchart aspects
-- `node_contacts`: All Node interchart aspects
-- `venus_mars_contacts`: All Venus-Mars interchart aspects
-- `sun_moon_contacts`: All Sun-Moon interchart aspects
-
-**Tests**: Add tests verifying filters and summary sections.
-
-**Effort**: ~60 lines in tools.py, ~100 lines in tests.
-
----
-
-## Phase 6: Full Davison Chart Tool
-
-**File**: `src/astro_mcp/tools.py`
-
-### 6.1 New tool: `calculate_davison_chart`
-
-```python
-@mcp.tool()
-async def calculate_davison_chart(
-    person1_datetime: str,
-    person1_latitude: float,
-    person1_longitude: float,
-    person2_datetime: str,
-    person2_latitude: float,
-    person2_longitude: float,
-    elevation: float = 0.0,
-    house_system: str = "placidus",
-    orb_limits: dict[str, float] | None = None,
-) -> dict[str, Any]:
-```
-
-Algorithm:
-1. Compute Davison midpoint datetime (same as existing `compute_davison_chart`)
-2. Compute Davison midpoint lat/lon (average of both birth locations)
-3. Call `call_sky_state` at the Davison midpoint datetime + location
-4. Calculate houses from the Davison LST + latitude
-5. Build planet list with house placement
-6. Calculate aspects between Davison planets
-7. Calculate angles
-8. Return full chart structure (same format as `calculate_natal_chart`)
-
-### 6.2 New tool: `calculate_davison_chart_by_id`
-
-Same as above but accepts `person1_id` and `person2_id` from the database.
-
-### 6.3 Update `calculate_synastry_chart`
-
-Change the Davison section from returning just midpoint coordinates to returning the full chart. Or alternatively, keep the midpoint data and add a `"davison_chart_full"` key.
-
-**Recommendation**: Keep the existing midpoint data (for backward compatibility) and add a `include_davison_full: bool = False` parameter. When True, compute and include the full Davison chart.
-
-**Tests**: Add tests for Davison chart calculation with known birth data.
-
-**Effort**: ~150 lines in tools.py, ~100 lines in tests.
-
----
-
-## Phase 7: Transit Tools for Composite/Davison
-
-**File**: `src/astro_mcp/tools.py`
-
-### 7.1 New tool: `get_composite_transit_preview`
-
-```python
-@mcp.tool()
-async def get_composite_transit_preview(
-    person1_datetime: str,
-    person1_latitude: float,
-    person1_longitude: float,
-    person2_datetime: str,
-    person2_latitude: float,
-    person2_longitude: float,
-    start_date: str,
-    end_date: str,
-    min_significance: float = 0.0,
-) -> dict[str, Any]:
-```
-
-Algorithm:
-1. Calculate composite chart (reuse `calculate_composite_chart`)
-2. For each day in range, get transiting planet positions
-3. Compute aspects between transiting planets and composite planet positions
-4. Return daily snapshots (same format as `get_transit_preview`)
-
-### 7.2 New tool: `get_davison_transit_preview`
-
-Same pattern but using Davison chart positions as the "natal" targets.
-
-### 7.3 `_byId` variants
-
-Add `get_composite_transit_preview_by_id` and `get_davison_transit_preview_by_id`.
-
-**Tests**: Add tests for composite and Davison transit previews.
-
-**Effort**: ~150 lines in tools.py, ~100 lines in tests.
-
----
-
-## Phase 8: Return Type Updates
-
-**File**: `src/astro_mcp/tools.py`
-
-### 8.1 Update `_tool_names()`
-
-Add all new tools to the `_tool_names()` function in `server.py`:
-- `calculate_davison_chart`
-- `calculate_davison_chart_by_id`
-- `get_composite_transit_preview`
-- `get_composite_transit_preview_by_id`
-- `get_davison_transit_preview`
-- `get_davison_transit_preview_by_id`
-
-### 8.2 Update root endpoint test
-
-Update `test_root_lists_tools` in `test_server.py` to include the new tool names.
-
----
-
-## Phase 9: Karmic Relationship Synthesis
-
-**File**: `src/astro_mcp/tools.py`
-
-### 9.1 New tool: `get_karmic_relationship_summary`
-
-```python
-@mcp.tool()
-async def get_karmic_relationship_summary(
-    person1_id: str,
-    person2_id: str,
-    house_system: str = "placidus",
-) -> dict[str, Any]:
-```
-
-Algorithm:
-1. Get synastry chart (with karmic_filter=True)
-2. Get composite chart
-3. Get Davison chart
-4. Extract karmic indicators:
-   - Saturn-Node contacts (synastry)
-   - Pluto-Node contacts (synastry)
-   - Node conjunctions (synastry)
-   - Saturn personal planet contacts (synastry)
-   - Composite Saturn/Pluto/Node positions
-   - Davison Saturn/Pluto/Node positions
-5. Return structured summary with karmic weight assessment
-
-**Effort**: ~100 lines in tools.py, ~50 lines in tests.
-
----
-
-## Phase 10: Nice-to-Have (Deferred)
-
-These are lower priority and can be done after the above phases are complete:
-
-1. **Vertex axis** (~25 lines in astrology.py)
-2. **Fixed star conjunctions** (~100 lines + star catalog data)
-3. **Coalescent chart** (~150 lines, complex algorithm)
-4. **Relationship type classifier** (~80 lines, heuristic)
-5. **Compatibility scoring** (~100 lines, heuristic)
-6. **Eclipse proximity** (needs external eclipse data source)
-7. **Planetary nodes** (needs specialized ephemeris data)
-
----
-
-## Summary of Changes
-
-| Phase | Files Changed | New Lines (approx) | New Tools |
-|---|---|---|---|
-| 0: Bug fix | tools.py | ~5 | 0 |
-| 1: Post-processing | astrology.py, test_astrology.py | ~350 | 0 |
-| 2: Aspect patterns | astrology.py, test_astrology.py | ~250 | 0 |
-| 3: Chart shape | astrology.py, test_astrology.py | ~160 | 0 |
-| 4: Natal overview | tools.py, test_tools.py | ~250 | 0 (params) |
-| 5: Synastry filters | tools.py, test_tools.py | ~160 | 0 (params) |
-| 6: Davison chart | tools.py, test_tools.py, server.py | ~250 | 2 |
-| 7: Composite/Davison transits | tools.py, test_tools.py, server.py | ~250 | 4 |
-| 8: Return types | server.py, test_server.py | ~20 | 0 |
-| 9: Karmic synthesis | tools.py, test_tools.py | ~150 | 1 |
-| **Total** | | **~1,845** | **7** |
-
----
-
-## Testing Strategy
-
-- All new astrology functions get unit tests in `test_astrology.py`
-- All new tool behaviors get integration tests in `test_tools.py` (with mocked ephemeris)
-- The existing 103 tests must continue to pass
-- Run `pytest tests/` after each phase
-
-## Verification Checklist
-
-After all phases:
-- [ ] `pytest tests/` passes with no failures
-- [ ] All new functions have type annotations
-- [ ] All new tools appear in `GET /` output
-- [ ] All new tools have docstrings
-- [ ] No breaking changes to existing tool signatures (new params have defaults)
+See `IMPLEMENTATION_PLAN_v0.2.0.md` for the formal detailed plan and scope boundaries.

+ 151 - 0
IMPLEMENTATION_PLAN_v0.2.0.md

@@ -0,0 +1,151 @@
+# Astro MCP v0.2.0 Implementation Plan
+
+## Purpose
+
+Replace the current MCP chart-rendering tools with resource-based delivery for agents and HTTP URL-based delivery for humans.
+
+This version concerns only how already-rendered charts are exposed and delivered. The existing chart drawing and rendering implementation is stable and must not be changed.
+
+## Scope
+
+### In scope
+
+- Remove all MCP tools whose names begin with `render_`.
+- Expose database-backed natal chart graphics as MCP resources.
+- Expose database-backed natal chart graphics through a human-facing HTTP URL.
+- Reuse the existing natal chart calculation and rendering code without changing its behavior.
+- Implement natal chart delivery in this version.
+- Reserve the URI namespace for future chart types without implementing them now.
+
+### Out of scope
+
+- Changes to chart calculations.
+- Changes to chart drawing, layout, styles, colors, dimensions, or format conversion.
+- Changes to `chart_renderer.py` or its rendering functions.
+- New rendering features or rendering options.
+- Transit, synastry, composite, or Davison chart resources.
+- Legacy wrappers, compatibility routes, migration behavior, or transition support.
+- Reordering or canonicalizing `person1_id` and `person2_id`.
+
+## Agreed URI scheme
+
+The resource namespace is organized by chart type:
+
+```text
+astro://charts/{chart_type}/{identifiers}?{options}
+```
+
+The natal chart resource implemented in v0.2.0 is:
+
+```text
+astro://charts/natal/{person_id}
+```
+
+Example:
+
+```text
+astro://charts/natal/einstein
+```
+
+The chart is rendered for a person resolved from the persons database. Only database-backed person identifiers are accepted.
+
+Query parameters may be supported only when they already map to capabilities of the current implementation. No new rendering behavior is introduced by this plan.
+
+## MCP resource behavior
+
+The natal resource handler will:
+
+1. Receive a natal chart resource URI.
+2. Extract and validate `person_id`.
+3. Resolve the person using the existing database lookup behavior.
+4. Use the existing database-backed natal chart calculation path.
+5. Call the existing natal chart renderer unchanged.
+6. Return the rendered chart as MCP resource content with the appropriate MIME type.
+
+The resource is intended to give an agent a graphic artifact that it can read, save, or attach. It must not expose the current large and complicated `render_*` tool interface.
+
+The v0.2.0 MCP resource returns the default SVG artifact with MIME type `image/svg+xml`. The resource URI has no rendering-option query parameters in this implementation.
+
+## Human-facing HTTP delivery
+
+Add an HTTP chart URL for the same database-backed natal chart artifact:
+
+```text
+/charts/natal/{person_id}.{format}
+```
+
+Examples:
+
+```text
+/charts/natal/einstein.svg
+/charts/natal/einstein.png
+```
+
+The HTTP endpoint accepts the renderer's existing format suffixes (`svg`, `png`, `jpg`, `jpeg`) and the existing `size` query parameter. It must call the same existing natal calculation and rendering path and return the resulting artifact with the correct HTTP `Content-Type`.
+
+The HTTP endpoint must not modify or duplicate chart drawing logic.
+
+## Shared delivery path
+
+MCP resource delivery and HTTP delivery should share the non-rendering orchestration needed to:
+
+- Parse the chart identity.
+- Validate the person identifier.
+- Resolve the person from the database.
+- Invoke the existing natal chart calculation path.
+- Invoke the existing natal chart renderer.
+- Package the result for MCP or HTTP delivery.
+
+The renderer itself remains untouched. Any required adapter should be limited to delivery integration.
+
+## MCP tool removal
+
+Remove every MCP tool whose name begins with `render_` from the exposed tool surface and delete the associated tool wrappers. No render tool remains in v0.2.0.
+
+Do not remove or alter the underlying renderer functions, including the existing natal wheel renderer. They remain internal implementation code for artifact generation.
+
+Do not remove or alter the chart calculation tools.
+
+## Future namespace reservation
+
+The namespace leaves room for these future resource families:
+
+```text
+astro://charts/transit/{person_id}
+astro://charts/synastry/{person1_id}/{person2_id}
+astro://charts/composite/{person1_id}/{person2_id}
+astro://charts/davison/{person1_id}/{person2_id}
+```
+
+These are not implemented in v0.2.0.
+
+For future two-person chart types, `person1_id` and `person2_id` are intentionally distinct. The drawing roles are not assumed to be interchangeable, so the identifiers must not be reordered or canonicalized by this plan.
+
+## Verification requirements
+
+Before v0.2.0 is considered complete, verify that:
+
+- The `render_*` MCP tools are no longer registered or exposed.
+- Existing chart calculation tools remain available.
+- `astro://charts/natal/{person_id}` is discoverable as a resource template.
+- A valid database person produces an SVG natal chart resource.
+- Supported raster output is delivered with the correct MIME type.
+- Unknown persons produce an appropriate resource error.
+- Invalid resource input is rejected appropriately.
+- The human-facing natal chart URL returns the existing rendered artifact.
+- The MCP resource and HTTP endpoint use the existing rendering behavior.
+- No chart drawing or rendering implementation files were changed unnecessarily.
+
+## Implementation boundary
+
+The implementation is complete when the following delivery model works:
+
+```text
+MCP resource URI or HTTP chart URL
+    -> database-backed person lookup
+    -> existing natal chart calculation
+    -> existing chart renderer
+    -> MCP artifact or HTTP artifact response
+```
+
+No additional chart features or rendering changes belong in v0.2.0.

+ 0 - 141
INITIAL_IDEA.md

@@ -1,141 +0,0 @@
-# Initial Idea: astro-mcp
-
-## Topic
-Building a new MCP server named **astro-mcp** that acts as an MCP client to the existing **ephemeris-mcp** service, consuming its `get_sky_state` tool and exposing calculated astrological data (naked chart calculations) via MCP tools for downstream interpretation by services like astro_service.
-
-## Goal
-- Provide a pure calculation layer that converts astronomical data from ephemeris-mcp into structured astrological data
-- Calculate natal charts (planetary positions, houses, aspects), transit charts, and synastry charts
-- Offer transit previews: given a person and a time range, list significant transit events (exact aspects, ingresses, stations)
-- Maintain a lightweight database of persons with birth datetime, latitude, longitude, and optional nickname
-- Output structured data (JSON-serializable) suitable for downstream astrological interpretation tools
-- Do NOT provide interpretations - only calculations and raw data
-- Follow the established MCP server manifest conventions (FastMCP + FastAPI, SSE transport, logging under ./logs/, health endpoint, etc.)
-- Serve as a reusable data layer for astrological services and coding agents
-- Provide a lightweight dashboard (Jinja2 + vanilla JS) for person management
-- Consider optional SVG chart generation for visual representation (to be implemented in a later phase)
-
-## Constraints & Context
-- **Audience**: Downstream astrological services (like astro_service for horoscopes) and coding agents that need calculated astrological data for interpretation.
-- **Manifest compliance**: Must adhere to `/home/lucky/.openclaw/workspace/MCP_SERVER_MANIFEST.md`:
-  - Use FastMCP behind FastAPI.
-  - Include `run.sh`, `killserver.sh`, `restart.sh`, `tests.sh`.
-  - Mount SSE at `/mcp/sse` with `TransportSecuritySettings(enable_dns_rebinding_protection=False)`.
-  - Provide minimal landing page (`GET /`) and liveness check (`GET /health`).
-  - Keep `/health` lightweight.
-  - Keep app‑specific code isolated from generic server plumbing.
-  - Prefer non‑destructive schema changes (if any persistence is needed).
-  - Store PID and logs under `./logs/`.
-- **Data source**: The server will call `ephemeris-mcp:get_sky_state` (via MCP client) to get planetary positions for any given datetime/location, then perform astrological calculations (zodiac signs, houses, aspects, etc.) based on that data.
-- **Language/runtime**: Python (use existing venv at `.venv`). Dependencies listed in `requirements.txt`.
-- **Extensibility**: Design should allow adding more chart types (progressions, solar returns, etc.) and house systems without breaking changes.
-- **Person database**: Simple SQLite table `persons` with columns: id, name, birth_datetime (UTC), latitude, longitude, elevation (optional), created_at.
-- **Output format**: Structured data (dictionaries/lists) that can be JSON serialized - no interpretive text.
-- **Interpretation boundary**: This server stops at calculation; interpretation happens in downstream services.
-- **Astrological calculation standards** (based on industry practice):
-  - Zodiac signs: 12 signs, 30° each
-  - House systems: Support multiple systems (Placidus default, Koch, Equal House, etc.)
-  - Aspects: Conjunction (0°), Sextile (60°), Square (90°), Trine (120°), Opposition (180°) with configurable orbs
-  - Planets: Sun through Pluto, plus lunar nodes, Chiron (all available from ephemeris-mcp output)
-  - Retrograde calculation: Based on apparent motion (speed_lon) from ephemeris-mcp data
-  - Angles: Ascendant, Midheaven, Descendant, IC calculated from latitude, longitude, and sidereal time (from ephemeris-mcp)
-- **Dashboard**: A simple Jinja2 + vanilla JS dashboard for person management (add, view, list persons) accessible at `/dashboard` or similar.
-- **SVG Charts**: Optional feature to generate SVG visualizations of charts (natal, transit, synastry) for downstream services that wish to display them. This would be an additional tool/resource and is not required for the core calculation layer.
-
-## Energy Preference
-Practical, focused exploration – we want concrete calculation tool definitions and a clear implementation path for pure data provision.
-
-## Ideas Collected
-
-**[Tool #1] get_planetary_positions** (foundational)
-_Concept:_ Wrapper around `ephemeris-mcp:get_sky_state` that returns the `planetary_positions.bodies` array with added zodiac sign and degree information (e.g., {"body": "sun", "sign": "Taurus", "degree": 12.5, ...}). Optionally filtered by a list of bodies. Includes retrograde flag based on speed_lon from ephemeris data.  
-_Novelty:_ Provides astronomical data enhanced with astrological basics (signs, degrees, retrograde) as the base for all chart calculations.  
-_Optional Parameters:_ `bodies` (list of strings) to filter which planets to return; defaults to all bodies.
-
-**[Tool #2] calculate_natal_chart**
-_Concept:_ Takes birth datetime, latitude, longitude, and optional house system (default Placidus) to calculate a complete natal chart structure including:
-  - planets: array of objects with body, sign, degree, house, retrograde flag
-  - houses: array of 12 house cusps (sign and degree)
-  - aspects: array of objects (planet1, planet2, aspect_type, orb, applying/separating)
-  - angles: ascendant, midheaven, descendant, ic (each with sign and degree)
-  - chart_metadata: datetime, latitude, longitude, house_system  
-_Novelty:_ Returns a complete, structured natal chart that downstream services can interpret for personality insights.  
-_Optional Parameters:_ `house_system` (string, default "Placidus"); `orb_limits` (dict of aspect->max orb, defaults to standard orbs: conjunction 8°, sextile 6°, square 8°, trine 8°, opposition 8°).
-
-**[Tool #3] calculate_transit_chart**
-_Concept:_ Takes natal chart data (or birth data) and transit datetime to calculate:
-  - transiting_planets: current planetary positions (same format as natal chart planets)
-  - natal_to_transit_aspects: aspects between transiting planets and natal planets/angles
-  - transit_to_natal_aspects: aspects between natal planets and transiting planets (same data, different perspective)
-  - notable_periods: when slow planets make exact aspects (within 0°01' orb) to natal points
-  - transiting_planets_in_houses: which natal house each transiting planet occupies  
-_Novelty:_ Provides structured transit analysis data for timing events and understanding current influences.  
-_Optional Parameters:_ `house_system` (default Placidus), `orb_limits` (same defaults as natal chart).
-
-**[Tool #4] calculate_synastry_chart**
-_Concept:_ Takes two sets of birth data (or two natal charts) to calculate relationship astrology structure including:
-  - chart1_natal: first person's natal chart structure
-  - chart2_natal: second person's natal chart structure
-  - interaspects: aspects between chart1 planets and chart2 planets
-  - house_overlays: for each person, where the other person's planets fall in their houses
-  - composite_chart: midpoint method chart structure
-  - davison_chart: davison relationship chart structure  
-_Novelty:_ Provides structured relationship astrology data for downstream interpretation of interpersonal dynamics.  
-_Optional Parameters:_ `house_system` (default Placidus for both charts), `orb_limits` (same defaults).
-
-**[Tool #5] get_transit_preview**
-_Concept:_ Given a person identifier (from the database) and a time range (start date, end date, or duration like "next 30 days"), returns a list of significant transit events within that period as structured data:
-  - events: array of objects, each containing:
-    - datetime: ISO timestamp of the event
-    - event_type: "exact_aspect", "ingress", "retrograde_station_start", "retrograde_station_end", "lunar_phase_change"
-    - description: machine-readable string (e.g., "transiting_jupiter_square_natal_mars")
-    - planets_involved: array of body names
-    - orb: exactness of the aspect (for aspect events)
-    - additional_data: any relevant specifics (e.g., new moon phase)
-  - time_range: requested start and end times  
-_Novelty:_ Enables astrological services to quickly answer questions like "What transits are coming up for Lukas in the next month?" with structured data they can interpret.  
-_Optional Parameters:_ `house_system` (default Placidus), `orb_limits` (default orbs), `event_types` (list of event types to include, defaults to all).
-
-**[Tool #6] person_manage**
-_Concept:_ CRUD-lite operations for the persons database:
-  - add_person (name, birth_datetime, latitude, longitude, elevation optional, nickname)
-  - get_person (by id or nickname)
-  - list_persons (optional filter)
-  - update_person / delete_person (if needed)  
-_Novelty:_ Avoids requiring the agent to repeat birth data on every request; enables quick retrieval for transit previews and chart calculations.  
-_Note:_ This tool set is complemented by a lightweight dashboard for manual person management.
-
-**[Resource #1] chart_cache**
-_Concept:_ A read-only MCP resource that provides access to recently calculated charts (with timestamps) to avoid recalculating the same chart multiple times in a session.  
-_Novelty:_ Improves performance for services that need to reference the same chart multiple times.
-
-**[Tool #7] set_default_location**
-_Concept:_ (If we decide to allow mutable state) a tool that updates the server’s default latitude/longitude for subsequent chart calculations, stored in a tiny SQLite table.  
-_Novelty:_ Avoids requiring the agent to repeat location coordinates on every request for charts from the same location.
-
-**[Idea #8] generate_svg_chart** (optional, future feature)
-_Concept:_ Given a chart structure (natal, transit, or synastry), generate an SVG visualization of the chart.
-_Novelty:_ Provides a visual representation that can be embedded in web interfaces or reports.
-_Note:_ This is an optional feature to be considered after the core calculation tools are stable. It would require an additional tool or resource and may depend on an SVG generation library.
-
-## Next Steps (to be fleshed out in a later convergent phase)
-1. Scaffold the project using the MCP Server Manifest (create dir, .gitignore, requirements.txt, README.md, PROJECT.md).
-2. Implement the MCP client call to `ephemeris-mcp:get_sky_state` (likely via MCP SSE client or direct HTTP if exposed).
-3. Create astrological calculation module (zodiac signs, houses, aspects) using the astronomical data from ephemeris-mcp.
-4. Create SQLite schema for persons table and chart cache.
-5. Define core tools: `get_planetary_positions`, `calculate_natal_chart`, `person_manage` (add/get/list).
-6. Implement `get_transit_preview` using the person database and astrological calculation module.
-7. Add `calculate_transit_chart` and `calculate_synastry_chart` as needed.
-8. Add the health endpoint and landing page per manifest.
-9. Add a lightweight dashboard (Jinja2 + vanilla JS) for person management.
-10. Write `run.sh`/`killserver.sh` scripts and verify logging/pid handling.
-11. Iteratively add more chart tools/resources based on priority.
-12. Ensure all code is isolated in `src/astro_mcp/` with server wiring in `src/astro_mcp/server.py`.
-13. Keep the MCP surface small and read‑only where possible (chart calculation tools are read-only; person DB is read/write but limited).
-14. Document usage in README.md with example calls from coding agents/astro_service (e.g., "get transit preview for Lukas next month").
-15. Add a test suite (`tests.sh`) that exercises each tool against mocked ephemeris-mcp data.
-16. Consider house system options (Placidus, Koch, Equal House, etc.) and allow configuration.
-17. (Optional, later phase) Consider implementing `generate_svg_chart` tool/resource for SVG chart visualization.
-
----
-*End of initial idea capture. Ready to organize, prioritize, and plan actions.*

+ 0 - 137
ORGANIZED_PLAN.md

@@ -1,137 +0,0 @@
-# Astro-MCP: Organized Themes and Action Plan
-
-## Theme 1: Foundation & Infrastructure
-- **Core Requirements**: Follow MCP Server Manifest conventions (FastMCP + FastAPI, SSE transport, logging, health endpoint)
-- **Person Database**: SQLite database for storing birth data (name, birth_datetime, lat/lon, nickname)
-- **Project Structure**: Isolate app-specific code in `src/astro_mcp/` with server wiring in `src/astro_mcp/server.py`
-- **Housekeeping Scripts**: `run.sh`, `killserver.sh`, `restart.sh`, `tests.sh`
-- **Dashboard**: Lightweight Jinja2 + vanilla JS dashboard for person management
-- **SVG Charts (Future)**: Optional SVG chart generation for visual representation
-
-## Theme 2: Astronomical Data Integration
-- **Primary Data Source**: `ephemeris-mcp:get_sky_state` via MCP client
-- **Data Transformation**: Convert astronomical output to astrological basics (zodiac signs, degrees, retrograde)
-- **Calculations**: Zodiac signs (12 signs, 30° each), house systems, aspects, angles (ASC, MC, DSC, IC)
-- **Standards**: Use data directly from ephemeris-mcp (no external ephemeris libraries needed)
-
-## Theme 3: Core Chart Calculation Tools (with Optional Parameters)
-- **Natal Chart Tool**: Complete birth chart with planets in signs/houses, aspects, angles
-  - Optional params: house_system (default Placidus), orb_limits (standard orbs)
-- **Transit Chart Tool**: Current positions + aspects to natal chart
-  - Optional params: house_system (default Placidus), orb_limits (standard orbs)
-- **Synastry Chart Tool**: Relationship analysis (interaspects, house overlays, composite/davison charts)
-  - Optional params: house_system (default Placidus), orb_limits (standard orbs)
-- **Chart Caching**: Optional resource to avoid recalculating same charts
-
-## Theme 4: Transit & Timing Analysis
-- **Transit Preview**: Given person + time range, return significant events (exact aspects, ingresses, stations)
-- **Event Types**: Exact aspects (orb ≤ 0°01'), planet ingresses, retrograde stations, lunar phase changes
-- **Structured Output**: Machine-readable descriptions for downstream interpretation
-- **Optional Parameters**: house_system, orb_limits, event_types (filter which event types to include)
-
-## Theme 5: Person Management
-- **CRUD-lite Operations**: Add, get, list persons (with optional update/delete)
-- **Birth Data Storage**: UTC datetime, latitude, longitude, elevation, nickname
-- **Dashboard**: Lightweight web interface for manual person management
-- **Efficiency**: Avoid repeating birth data on every request
-
-## Theme 6: Configuration & Extensibility
-- **House Systems**: Support multiple systems (Placidus default, Koch, Equal House, etc.)
-- **Aspect Configuration**: Configurable orbs for different aspect types
-- **Chart Types**: Design for adding progressions, solar returns, etc.
-- **Location Defaults**: Optional tool to set default lat/lon for repeated calculations from same location
-
-## Theme 7: Manifest Compliance & Quality
-- **Health Endpoint**: Lightweight `/health` for liveness checks
-- **Landing Page**: Minimal `GET /` endpoint
-- **SSE Transport**: Mount FastMCP at `/mcp` with SSE at `/mcp/sse`
-- **Security**: Use `TransportSecuritySettings(enable_dns_rebinding_protection=False)`
-- **Logging/PID**: Store under `./logs/` directory
-- **Testing**: Comprehensive test suite via `tests.sh`
-
-# Action Plan
-
-## Phase 1: Project Setup & Foundation
-1. [ ] Initialize project structure per MCP Server Manifest
-2. [ ] Create `.gitignore`, `requirements.txt`, `README.md`, `PROJECT.md`
-3. [ ] Set up `src/astro_mcp/` directory with `server.py`
-4. [ ] Implement basic FastAPI app with health and landing endpoints
-5. [ ] Create dashboard template directory and basic Jinja2 template
-6. [ ] Create `run.sh`, `killserver.sh`, `restart.sh` scripts
-7. [ ] Initialize git repository and make initial commit
-
-## Phase 2: Data Layer & Person Management
-1. [ ] Design SQLite schema for persons table
-2. [ ] Implement person_manage tool (add_person, get_person, list_persons)
-3. [ ] Create database initialization/connection handling
-4. [ ] Test person CRUD operations
-5. [ ] Create dashboard routes and templates for person management
-6. [ ] Document API for person management
-
-## Phase 3: Astronomical Data Integration
-1. [ ] Implement MCP client to call `ephemeris-mcp:get_sky_state`
-2. [ ] Create `get_planetary_positions` tool wrapper
-3. [ ] Add zodiac sign calculation (0-360° → sign + degree)
-4. [ ] Add retrograde flag detection from speed_lon
-5. [ ] Test with sample ephemeris-mcp output
-6. [ ] Verify structured output format
-7. [ ] Add optional `bodies` parameter to filter planets
-
-## Phase 4: Core Chart Calculations
-1. [ ] Implement house system calculations (start with Placidus)
-2. [ ] Calculate planetary house placements
-3. [ ] Implement aspect detection (conjunction, sextile, square, trine, opposition)
-4. [ ] Calculate orbs and applying/separating
-5. [ ] Calculate angles (ASC, MC, DSC, IC) from lat/lon and sidereal time
-6. [ ] Assemble complete natal chart structure
-7. [ ] Add optional house_system parameter (default Placidus)
-8. [ ] Add optional orb_limits parameter (standard defaults)
-9. [ ] Test calculate_natal_chart tool with sample data
-
-## Phase 5: Transit & Relationship Charts
-1. [ ] Implement calculate_transit_chart tool
-2. [ ] Implement calculate_synastry_chart tool
-3. [ ] Add optional parameters (house_system, orb_limits) to both
-4. [ ] Verify chart caching mechanism (optional)
-5. [ ] Test all chart tools with various inputs and parameter combinations
-
-## Phase 6: Transit Preview & Events
-1. [ ] Implement transit preview algorithms (ingresses, retrograde stations, lunar phases)
-2. [ ] Implement exact aspect detection within time ranges
-3. [ ] Create get_transit_preview tool
-4. [ ] Add optional parameters: house_system, orb_limits, event_types
-5. [ ] Test with sample person data and date ranges
-
-## Phase 7: Integration & Compliance
-1. [ ] Mount all tools as MCP tools via FastMCP
-2. [ ] Ensure proper SSE transport at `/mcp/sse`
-3. [ ] Verify health endpoint is lightweight
-4. [ ] Test dashboard functionality and routes
-5. [ ] Test housekeeping scripts (run/kill/restart)
-6. [ ] Verify logging and PID file handling
-7. [ ] Run full test suite via `tests.sh`
-
-## Phase 8: Documentation & Examples
-1. [ ] Update README.md with usage examples
-2. [ ] Document API for downstream services (like astro_service)
-3. [ ] Provide example calls for transit previews ("What transits for Lukas next month?")
-4. [ ] Document optional parameters for all tools
-5. [ ] Document dashboard usage for person management
-6. [ ] Document configuration options (house systems, etc.)
-7. [ ] Finalize PROJECT.md with technical specifications
-
-## Phase 9: Optional Enhancements (Post-Core)
-1. [ ] Consider implementing `generate_svg_chart` tool/resource for SVG chart visualization
-2. [ ] Evaluate SVG generation libraries (svgwrite, etc.)
-3. [ ] Implement SVG chart generation for natal, transit, and synastry charts
-4. [ ] Add SVG chart as optional MCP resource or tool
-5. [ ] Test SVG output with various chart types
-
-# Immediate Next Steps
-
-1. Create project structure and initialize git repo
-2. Implement basic server with health endpoint and dashboard routes
-3. Add person management SQLite layer
-4. Integrate with ephemeris-mcp:get_sky_state
-5. Build get_planetary_positions tool with optional bodies parameter
-6. Develop natal chart calculation with optional parameters

+ 107 - 154
PROJECT.md

@@ -1,166 +1,119 @@
-# Astro-MCP Implementation Plan
+# Astro-MCP v0.2.0
+
+## Purpose
+
+Astro-MCP is a Python MCP server that consumes `ephemeris-mcp:get_sky_state` through MCP-over-SSE and provides structured astrological calculations backed by a SQLite persons database.
+
+Version 0.2.0 adds the finalized delivery boundary for rendered charts:
+
+- Calculation remains exposed through MCP tools.
+- Rendered natal charts are exposed through the MCP resource `astro://charts/natal/{person_id}`.
+- Rendered natal charts are exposed to human-facing clients through `/charts/natal/{person_id}.{format}`.
+- The previous `render_*` MCP tools are removed.
+- Existing chart drawing and rendering code is reused unchanged.
+
+## Runtime
+
+- Python 3.13
+- FastAPI
+- FastMCP
+- MCP SSE transport
+- SQLite person database
+- Jinja2 dashboard
+- Existing SVG/PNG/JPG chart renderer
+
+The server listens on port 7016 by default. `ASTRO_PORT` controls the port.
+
+## Runtime structure
+
+```text
+src/astro_mcp/
+├── server.py              # FastAPI app, MCP server, resources, HTTP route
+├── tools.py               # Tool registration facade
+├── chart_tools.py         # Direct calculation tools
+├── by_id_tools.py         # Database-backed calculation tools
+├── chart_resources.py     # Natal chart artifact delivery
+├── chart_renderer.py      # Stable chart drawing and format conversion
+├── chart_helpers.py       # Renderer output helpers
+├── chart_styles.py        # Renderer styles and proportions
+├── storage.py             # SQLite persistence
+├── dashboard.py           # Person-management routes
+└── ephemeris_client.py    # Upstream MCP client
+```
 
-**Port: 7016** (ephemeris-mcp is 7015)
+`chart_renderer.py`, `chart_helpers.py`, and `chart_styles.py` are the chart production implementation. v0.2.0 changes delivery only; those files are not part of the delivery redesign.
 
-## What We're Building
+## MCP surface
 
-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.
+### Calculation tools
 
-## Project Structure
+The server exposes tools for planetary positions, natal charts, transit charts, synastry, composite and Davison calculations, transit previews, relationship summaries, person management, and house-system listing. Direct tools accept birth data. `_by_id` variants resolve persons from the database.
 
-```
-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
-```
+### Rendered chart resource
 
-## Dependencies
+The implemented v0.2.0 resource template is:
 
+```text
+astro://charts/natal/{person_id}
 ```
-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
+
+A resource read resolves the person, calls the existing `calculate_natal_chart_by_id` path, calls the existing `render_natal_wheel` function, and returns the resulting chart artifact with its MIME type.
+
+No `render_*` MCP tool exists in v0.2.0.
+
+### Human-facing chart URL
+
+```text
+/charts/natal/{person_id}.{format}
 ```
 
-No `pyswisseph` -- all astronomical data comes from ephemeris-mcp via MCP client.
-
-## Key Design Decisions
-
-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
-   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.
-
-## 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 |
-
-### _byId tools (database-backed, accept person_id or nickname)
-
-| 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 |
-
-## Docker Compose
-
-```yaml
-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
+The supported format suffixes are those already supported by the renderer: `svg`, `png`, `jpg`, and `jpeg`. The existing `size` query parameter may also be supplied.
+
+The route returns the rendered bytes/text with the corresponding image content type. It reuses the same artifact path as the MCP resource.
+
+## Persons database
+
+The persons database stores:
+
+- `birth_datetime`: naive local time with no UTC offset
+- `timezone`: IANA timezone name
+- `latitude`, `longitude`, and optional elevation
+- identity and descriptive fields
+
+The conversion from stored local time to UTC occurs in `_get_person_birth_data()` and must not be duplicated by delivery code.
+
+## v0.2.0 status
+
+Implemented:
+
+- Structured chart calculation tools
+- Database-backed person lookup
+- Stable natal chart drawing and output conversion
+- MCP natal chart resource
+- HTTP natal chart artifact route
+- Removal of all `render_*` MCP tools
+- Dashboard and person management
+
+Not implemented in v0.2.0:
+
+- Transit chart resources
+- Synastry resources
+- Composite resources
+- Davison resources
+- New chart drawing features
+- New rendering styles, formats, or options
+- Caching, compatibility wrappers, or migration routes
+
+These are explicit non-goals, not unfinished items within the v0.2.0 delivery change.
+
+## Verification
+
+Run:
+
+```text
+pytest
 ```
 
-## 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 9: Final integration + docs
+The v0.2.0 verification must confirm that calculation tools remain available, `render_*` tools are absent, the natal resource template is registered, and the HTTP chart route returns the existing rendered artifact.
+
+The detailed delivery plan is `IMPLEMENTATION_PLAN_v0.2.0.md`.

+ 81 - 117
README.md

@@ -1,155 +1,119 @@
-# astro-mcp
+# astro-mcp v0.2.0
 
-MCP server for astrological chart calculations. Consumes `ephemeris-mcp:get_sky_state`
-via MCP-over-SSE client and exposes calculated astrological data (natal charts, transits,
-synastry, transit previews) as structured JSON tools.
+Version 0.2.0.
 
-**Version: 0.8.0**
+Astro-MCP is an MCP server for astrological chart calculations and database-backed chart artifact delivery. It consumes `ephemeris-mcp:get_sky_state` through MCP-over-SSE and exposes structured calculation tools, MCP resources for rendered charts, and HTTP chart URLs for human-facing clients.
 
-## Quick Start
+## v0.2.0 delivery surface
 
-```bash
-python -m venv .venv
-source .venv/bin/activate
-pip install -r requirements.txt
+Chart rendering is implemented internally and is not exposed as MCP tools. The `render_*` MCP tools have been removed.
+
+Rendered natal charts for persons in the database are available as the MCP resource template:
+
+```text
+astro://charts/natal/{person_id}
+```
+
+The resource returns the default SVG artifact. The human-facing HTTP equivalent accepts the existing renderer format suffixes:
+
+```text
+/charts/natal/{person_id}.{format}
+```
+
+Examples:
+
+```text
+astro://charts/natal/einstein
+/charts/natal/einstein.svg
+/charts/natal/einstein.png
+```
+
+The MCP resource and HTTP route use the existing natal calculation and chart-rendering implementation. They do not add rendering behavior or alter the chart drawing code.
+
+Only natal chart artifact delivery is implemented in v0.2.0. Other chart resource families are not exposed yet.
+
+## Quick start
+
+```text
+python3 -m venv .venv
+.venv/bin/pip install -r requirements.txt
 ./run.sh
 ```
 
-Server listens on port 7016 (configurable via `ASTRO_PORT`).
+The server listens on port 7016 by default. The port is configurable with `ASTRO_PORT`.
 
 ## Docker
 
-```bash
+```text
 docker compose up --build
 ```
 
 Health check: `GET http://localhost:7016/health`
 
-## Configuration (.env)
+## Configuration
 
 | Variable | Default | Description |
 |---|---|---|
 | `ASTRO_HOST` | `0.0.0.0` | Bind address |
 | `ASTRO_PORT` | `7016` | Listen port |
-| `ASTRO_DATA_DIR` | `./data` | SQLite DB directory |
-| `ASTRO_LOG_DIR` | `./logs` | Log file directory |
+| `ASTRO_DATA_DIR` | `./data` | SQLite database directory |
+| `ASTRO_LOG_DIR` | `./logs` | Log directory |
 | `EPHEMERIS_MCP_URL` | `http://192.168.0.200:7015/mcp/sse` | Ephemeris MCP endpoint |
-| `GEONAMES_USERNAME` | *(empty)* | GeoNames username for birthplace autocomplete |
-| `DASHBOARD_24H_TIME` | `true` | Use 24h time format in forms (false = 12h with AM/PM) |
-
-## MCP Endpoint
-
-SSE transport at `http://localhost:7016/mcp/sse`
-
-## Tools
+| `GEONAMES_USERNAME` | empty | GeoNames username for birthplace autocomplete |
+| `DASHBOARD_24H_TIME` | `true` | Use 24-hour time in dashboard forms |
 
-### Core tools (raw birth data)
+## MCP endpoint
 
-| Tool | Description |
-|---|---|
-| `get_planetary_positions` | Planetary positions with zodiac signs, degrees, retrograde flags |
-| `calculate_natal_chart` | Complete natal chart (planets, houses, aspects, angles) |
-| `calculate_transit_chart` | Transit chart with natal-to-transit aspects |
-| `calculate_synastry_chart` | Relationship chart for two people |
-| `calculate_composite_chart` | Composite chart via midpoint method |
-| `get_transit_preview` | Daily transit-to-natal aspect snapshot with significance scoring |
-| `person_manage` | CRUD for persons database |
-| `list_house_systems` | List supported house systems |
+SSE transport: `http://localhost:7016/mcp/sse`
 
-### _byId tools (database-backed)
+## Calculation tools
 
-Each core chart tool has a `_byId` variant that accepts a `person_id` (from the persons
-database) instead of raw birth data. All other optional parameters override the defaults.
+The calculation surface remains structured-data oriented:
 
-| Tool | Description |
+| Tool | Purpose |
 |---|---|
-| `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 chart for person1_id + person2_id |
-| `calculate_composite_chart_by_id` | Composite chart for person1_id + person2_id |
-| `get_transit_preview_by_id` | Transit preview by person_id + date range |
-
-## Person Database
-
-### Schema
-
-```sql
-CREATE TABLE persons (
-    id              TEXT PRIMARY KEY,
-    name            TEXT NOT NULL,
-    nickname        TEXT UNIQUE,
-    birth_datetime  TEXT NOT NULL,
-    latitude        REAL NOT NULL,
-    longitude       REAL NOT NULL,
-    elevation       REAL DEFAULT 0.0,
-    birthplace      TEXT,
-    alive           BOOLEAN DEFAULT 1,
-    private         BOOLEAN DEFAULT 0,
-    gender          TEXT,
-    description     TEXT,
-    notes           TEXT,
-    timezone        TEXT,
-    birth_time_known BOOLEAN DEFAULT 1,
-    created_at      TEXT NOT NULL,
-    updated_at      TEXT
-);
+| `get_planetary_positions` | Planetary positions with signs, degrees, and retrograde flags |
+| `calculate_natal_chart` | Natal chart from direct birth data |
+| `calculate_transit_chart` | Transit-to-natal chart calculation |
+| `calculate_synastry_chart` | Two-person relationship chart calculation |
+| `calculate_composite_chart` | Composite chart calculation |
+| `calculate_davison_chart` | Davison chart calculation |
+| `get_transit_preview` | Transit-to-natal snapshots over a date range |
+| `get_composite_transit_preview` | Composite transit preview |
+| `get_davison_transit_preview` | Davison transit preview |
+| `get_karmic_relationship_summary` | Structured relationship summary |
+| `person_manage` | Person database management |
+| `list_house_systems` | Supported house systems |
+
+Database-backed calculation variants include the relevant `_by_id` tools and accept a person ID or nickname.
+
+## Person database datetime convention
+
+The database stores `birth_datetime` as naive local time and `timezone` as an IANA timezone name. For example:
+
+```text
+birth_datetime = 1953-03-23T21:05:00
+timezone = America/Chicago
 ```
 
-All columns added via non-destructive `ALTER TABLE` migrations.
+UTC conversion happens in `_get_person_birth_data()`. Do not store UTC or offset-aware values in the database `birth_datetime` column.
 
-### person_manage tool
+## Dashboard
 
-Supports actions: `add`, `get` (by id or nickname), `list`, `update`, `delete`.
+The person-management dashboard is available at:
 
-Example workflow:
+```text
+http://localhost:7016/dashboard
 ```
-person_manage(action="add", name="Me", birth_datetime="1965-07-02T00:05:00+02:00",
-              birthplace="Graz, Austria", latitude=47.076668, longitude=15.421371)
-=> {"person": {"id": "abc12345", ...}}
 
-# Then use the _byId tools:
-calculate_natal_chart_by_id(person_id="abc12345")
-calculate_transit_chart_by_id(person_id="abc12345", transit_datetime="2026-06-02T12:00:00")
-```
+It supports listing, adding, editing, importing, exporting, and deleting persons. Chart artifact delivery is available independently through the `/charts/natal/{person_id}.{format}` HTTP route.
 
-## Dashboard
+## Verification
+
+Run the test suite with:
 
-Person management dashboard at `http://localhost:7016/dashboard`
-
-### Features
-
-- **Person list** with name, nickname, birth date, location, gender, status badges
-- **Add/Edit form** with:
-  - Native date picker + time input (12h or 24h based on `DASHBOARD_24H_TIME`)
-  - Timezone select with 50+ IANA zones grouped by region
-  - Live UTC offset display (e.g. "UTC+01:00 (CEST)")
-  - GeoNames autocomplete for birthplace (requires `GEONAMES_USERNAME`) — auto-fills lat/lon/elevation/timezone
-- **Detail view** with all fields, notes, metadata
-- **Export** individual persons or all persons as JSON
-- **Import** persons from JSON file (single object or array)
-- **Delete** with confirmation dialog
-
-### Import/Export Format
-
-```json
-[
-  {
-    "name": "John Doe",
-    "nickname": "john",
-    "birth_datetime": "1990-05-15T08:30:00+02:00",
-    "birthplace": "Vienna, Austria",
-    "latitude": 48.2082,
-    "longitude": 16.3738,
-    "elevation": 171,
-    "alive": true,
-    "private": false,
-    "gender": "male",
-    "description": "A sample person",
-    "notes": "Some notes here",
-    "timezone": "Europe/Vienna",
-    "birth_time_known": true
-  }
-]
+```text
+pytest
 ```
 
-Required fields: `name`, `birth_datetime`, `latitude`, `longitude`. All others are optional.
+The v0.2.0 contract includes removal of all `render_*` MCP tools, registration of `astro://charts/natal/{person_id}`, and the human-facing natal chart HTTP route.

+ 0 - 475
WISHLIST_ANALYSIS.md

@@ -1,475 +0,0 @@
-# Astro MCP Wishlist Analysis
-
-> Generated: 2026-06-04
-> Analyzed against: src/astro_mcp/astrology.py, src/astro_mcp/tools.py, tests/
-
----
-
-## 1. NATAL ASTROLOGY WISHLIST
-
-### 1.1 Already Implemented (confirmed in code)
-
-| Wishlist Item | Status | Location |
-|---|---|---|
-| Natal chart calculation (planets, houses, angles, aspects) | DONE | `calculate_natal_chart` in tools.py |
-| Planetary positions with sign, degree, retrograde | DONE | `get_planetary_positions`, `calculate_natal_chart` |
-| House systems (Placidus, Equal, Whole Sign) | DONE | `calculate_houses` in astrology.py |
-| Aspect calculation (conj, opp, sq, tr, sx) with orbs | DONE | `compute_aspects` in astrology.py |
-| Person database (store/retrieve birth data) | DONE | `person_manage` in tools.py |
-| Transit chart calculation | DONE | `calculate_transit_chart` in tools.py |
-| Transit preview (daily snapshots + significance) | DONE | `get_transit_preview` in tools.py |
-| Lunar nodes (North/South) | DONE | `true_node` body from ephemeris |
-| Synastry chart | DONE | `calculate_synastry_chart` in tools.py |
-| Composite chart | DONE | `calculate_composite_chart` in tools.py |
-| Multiple birth data formats (ISO 8601 with TZ) | DONE | All tools accept ISO 8601 |
-| Applying/separating flag on aspects | PARTIAL | Computed in transit chart; natal chart does NOT pass speed_lon to compute_aspects |
-
-### 1.2 Missing -- Easy Wins (post-processing of existing data)
-
-#### 1.2.1 Element Balance Report (High Priority)
-- **What**: Count planets by element (fire/earth/air/water) with percentages
-- **Effort**: Trivial -- bucket the 12 planets by sign element
-- **Where**: New function `get_element_balance(planets)` in astrology.py (~15 lines)
-- **Integration**: Add `include_overview: bool` param to `calculate_natal_chart`, or new tool `get_chart_overview`
-
-#### 1.2.2 Modality Balance Report (High Priority)
-- **What**: Count planets by modality (cardinal/fixed/mutable) with percentages
-- **Effort**: Trivial -- bucket by sign modality
-- **Where**: New function `get_modality_balance(planets)` in astrology.py (~15 lines)
-
-#### 1.2.3 Hemisphere Emphasis (Medium Priority)
-- **What**: Report which hemisphere (upper/lower/east/west) has most planets
-- **Effort**: Trivial -- count planets by house ranges (upper=7-12, lower=1-6, etc.)
-- **Where**: New function `get_hemisphere_emphasis(planets)` in astrology.py (~15 lines)
-
-#### 1.2.4 Stellium Detection (High Priority)
-- **What**: Flag any sign or house with 3+ planets
-- **Effort**: Trivial -- scan planet list for sign/house clusters
-- **Where**: New function `detect_stelliums(planets)` in astrology.py (~20 lines)
-
-#### 1.2.5 Empty House List (Low Priority)
-- **What**: Report which houses have no planets
-- **Effort**: Trivial -- invert house placement list
-- **Where**: New function `get_empty_houses(planets)` in astrology.py (~10 lines)
-
-#### 1.2.6 Chart Ruler Identification (High Priority)
-- **What**: Return the planet ruling the Ascendant sign, with its sign/house/aspects
-- **Effort**: Trivial -- sign-to-ruler lookup table (Aries->Mars, Taurus->Venus, etc.), then find that planet in the list
-- **Where**: New function `get_chart_ruler(angles, planets)` in astrology.py (~15 lines)
-
-#### 1.2.7 Sun-Moon Aspect (Medium Priority)
-- **What**: Specifically return the aspect (if any) between Sun and Moon
-- **Effort**: Trivial -- filter existing aspect list for Sun-Moon pair
-- **Where**: Filter on `calculate_natal_chart` output, or param `include_big_three_aspects`
-
-#### 1.2.8 Top Aspects by Orb (High Priority)
-- **What**: Return the N tightest aspects (smallest orbs)
-- **Effort**: Trivial -- aspects already sorted by orb; just add `top_n` parameter
-- **Where**: Add `top_n: int | None` param to `calculate_natal_chart`
-
-#### 1.2.9 Aspects to Big Three (High Priority)
-- **What**: Return all aspects involving Sun, Moon, or Ascendant
-- **Effort**: Trivial -- filter aspect list
-- **Where**: Add `filter_to_big_three: bool` param or post-processing function
-
-#### 1.2.10 Aspects to Chart Ruler (Medium Priority)
-- **What**: Return all aspects involving the chart ruler
-- **Effort**: Trivial -- filter after identifying ruler
-- **Where**: Same as above, chained
-
-#### 1.2.11 House Cusp Signs (High Priority)
-- **What**: Return the sign on each house cusp
-- **Effort**: Already returned in `houses` array (each house has `sign`, `degree`)
-- **Status**: Already done, just needs better documentation
-
-#### 1.2.12 House Rulers (Medium Priority)
-- **What**: For each house, return the ruling planet and its sign/house/condition
-- **Effort**: Trivial -- sign-to-ruler lookup + planet lookup
-- **Where**: New function `get_house_rulers(houses, planets)` in astrology.py (~25 lines)
-
-#### 1.2.13 Planets in Houses Grouping (High Priority)
-- **What**: Group planets by house for quick house-themed reading
-- **Effort**: Trivial -- each planet already has `house` field; just group
-- **Where**: New function `group_planets_by_house(planets)` in astrology.py (~10 lines)
-
-#### 1.2.14 Planets in Signs Grouping (High Priority)
-- **What**: Group planets by sign for quick sign-themed reading
-- **Effort**: Trivial -- each planet already has `sign` field; just group
-- **Where**: New function `group_planets_by_sign(planets)` in astrology.py (~10 lines)
-
-#### 1.2.15 Angular/Succedent/Cadent Count (Medium Priority)
-- **What**: Count planets by house type
-- **Effort**: Trivial -- angular={1,4,7,10}, succedent={2,5,8,11}, cadent={3,6,9,12}
-- **Where**: New function `get_house_type_counts(planets)` in astrology.py (~15 lines)
-
-#### 1.2.16 Retrograde Planet List (Medium Priority)
-- **What**: Return all retrograde planets with sign/house
-- **Effort**: Trivial -- filter planet list where `retrograde=True`
-- **Where**: New function `get_retrograde_planets(planets)` in astrology.py (~10 lines)
-
-#### 1.2.17 Retrograde Emphasis Flag (Low Priority)
-- **What**: Flag charts with 3+ retrograde planets
-- **Effort**: Trivial -- count from retrograde list
-
-#### 1.2.18 Sun/Moon Phase (Low Priority)
-- **What**: Return the lunar phase at birth
-- **Effort**: Ephemeris already returns `lunar_state.phase_name`; just expose it
-- **Where**: Add to `calculate_natal_chart` output
-
-#### 1.2.19 Applying/Separation Fix for Natal Chart (Medium Priority)
-- **What**: The natal chart tool does NOT pass `speed_lon` to `compute_aspects`, so `applying` is always `None`
-- **Effort**: Small fix -- pass speed_lon through in the aspect computation loop
-- **Where**: `calculate_natal_chart` in tools.py, ~line 170
-
-### 1.3 Missing -- New Algorithms Needed
-
-#### 1.3.1 Chart Shape Detection (Medium Priority)
-- **What**: Identify bundle, bowl, bucket, splash, locomotive, seesaw, splay patterns
-- **Effort**: Moderate -- analyze angular distribution of planets
-- **Algorithm**: Compute the largest unoccupied arc; classify based on planet clustering
-- **Where**: New function `detect_chart_shape(planets)` in astrology.py (~80 lines)
-
-#### 1.3.2 Aspect Pattern Detection (High Priority)
-- **What**: T-square, Grand Trine, Grand Cross, Yod detection
-- **Effort**: Moderate -- graph topology analysis on aspect list
-- **Algorithm**:
-  - T-square: find opposition pair, check if both square a common planet
-  - Grand trine: find 3 planets all in trine (same element)
-  - Grand cross: find 4 planets forming 2 oppositions + 4 squares
-  - Yod: find 2 sextile planets both quincunx a third
-- **Where**: New function `detect_aspect_patterns(aspects)` in astrology.py (~150 lines)
-
-#### 1.3.3 Saturn/Jupiter Return Flags (Medium Priority)
-- **What**: Flag if person is currently in Saturn return (~28-30) or Jupiter return (~12 years)
-- **Effort**: Moderate -- compute approximate return dates from birth date + orbital period, or scan transits
-- **Where**: New function `get_return_dates(birth_datetime, planet)` in astrology.py (~40 lines)
-
-#### 1.3.4 Eclipse Proximity (Low Priority)
-- **What**: Flag if any natal planet is within 5° of current eclipse axis
-- **Effort**: Needs eclipse data source not currently in ephemeris
-- **Status**: Deferred -- requires external data
-
----
-
-## 2. KARMIC ASTROLOGY WISHLIST
-
-### 2.1 Already Implemented (confirmed)
-
-| Wishlist Item | Status | Location |
-|---|---|---|
-| Natal chart calculation | DONE | `calculate_natal_chart` |
-| Lunar nodes (North/South) | DONE | `true_node` in ephemeris |
-| Planetary positions with retrograde | DONE | `calculate_natal_chart` |
-| Aspects between planets | DONE | `compute_aspects` |
-| House systems | DONE | `calculate_houses` |
-| Transit chart | DONE | `calculate_transit_chart` |
-| Synastry chart | DONE | `calculate_synastry_chart` |
-| Composite chart | DONE | `calculate_composite_chart` |
-| Person database | DONE | `person_manage` |
-
-### 2.2 Missing -- Easy Wins (post-processing / filtering)
-
-#### 2.2.1 Node Sign/House Lookup (High Priority)
-- **What**: Return North & South Node sign, house, degree
-- **Effort**: Trivial -- `true_node` is already in planet list; South Node = opposite point
-- **Where**: New function `get_nodal_axis(planets, houses)` in astrology.py (~10 lines)
-
-#### 2.2.2 Node Axis Aspects (High Priority)
-- **What**: Identify all aspects from natal planets to the nodal axis
-- **Effort**: Trivial -- filter existing aspects for `true_node` involvement
-- **Where**: Filter on `calculate_natal_chart` output, or `include_nodal_aspects` param
-
-#### 2.2.3 Node Conjunct Planets (High Priority)
-- **What**: Flag planets conjunct South Node (past-life gifts) and North Node (growth)
-- **Effort**: Trivial -- filter node axis aspects for conjunction
-- **Where**: Post-processing of nodal aspects
-
-#### 2.2.4 Node Square Planets (Medium Priority)
-- **What**: Flag planets square the nodal axis ("skipped steps")
-- **Effort**: Trivial -- filter node axis aspects for square
-
-#### 2.2.5 Nodal Axis by House (High Priority)
-- **What**: Return which house the nodal axis falls in
-- **Effort**: Trivial -- `true_node` already has `house` field
-
-#### 2.2.6 Saturn Sign/House (High Priority)
-- **What**: Return Saturn's sign, house, degree, retrograde status
-- **Effort**: Trivial -- Saturn is already in planet list
-- **Where**: New function `get_saturn_info(planets)` in astrology.py (~10 lines)
-
-#### 2.2.7 Saturn Aspects to Personal Planets (High Priority)
-- **What**: Identify Saturn hard aspects to Sun, Moon, Venus, Mars
-- **Effort**: Trivial -- filter aspect list for Saturn + personal planet pairs with hard aspects
-- **Where**: Filter on `calculate_natal_chart` output
-
-#### 2.2.8 Saturn Retrograde Flag (Medium Priority)
-- **What**: Flag Saturn retrograde
-- **Effort**: Trivial -- check Saturn's `retrograde` flag
-
-#### 2.2.9 Saturn-Node Aspects (High Priority)
-- **What**: Identify aspects between Saturn and the nodal axis
-- **Effort**: Trivial -- filter aspect list for Saturn-true_node pairs
-
-#### 2.2.10 Retrograde Planet List (High Priority)
-- **What**: Return all retrograde planets with signs/houses
-- **Effort**: Same as natal wishlist item 1.2.16
-
-#### 2.2.11 12th House Cusp Sign (Medium Priority)
-- **What**: Return the sign on the 12th house cusp
-- **Effort**: Trivial -- `houses[11]` already has this
-
-#### 2.2.12 12th House Planets (Medium Priority)
-- **What**: List any planets in the 12th house
-- **Effort**: Trivial -- filter planets where `house == 12`
-
-#### 2.2.13 12th House Ruler (Low Priority)
-- **What**: Identify the ruler of the 12th house and its sign/house/aspects
-- **Effort**: Trivial -- sign ruler lookup + planet lookup
-
-#### 2.2.14 Karmic Synastry Filters (High Priority)
-- **What**: Saturn-Node, Pluto-Node, Node conjunctions in synastry
-- **Effort**: Trivial -- filter existing interaspects for these pairs
-- **Where**: Add `karmic_filter: bool` param to `calculate_synastry_chart`
-
-#### 2.2.15 Composite Node/Saturn/Pluto (Medium Priority)
-- **What**: Return composite chart nodal axis, Saturn, Pluto
-- **Effort**: Trivial -- already in composite planet list; just surface prominently
-
-#### 2.2.16 Transit Triggers for Karmic Periods (High Priority)
-- **What**: Saturn/Pluto transits to natal nodes
-- **Effort**: Extend `get_transit_preview` with `karmic_only: bool` filter
-
-### 2.3 Missing -- New Algorithms Needed
-
-#### 2.3.1 Pluto Polarity Point (PPP) (High Priority)
-- **What**: Calculate the point opposite Pluto (sign, house, degree)
-- **Effort**: Trivial -- `normalize_degrees(pluto_lon + 180)`
-- **Where**: New function `get_pluto_polarity_point(planets, houses)` in astrology.py (~10 lines)
-
-#### 2.3.2 Saturn Return Timing (Medium Priority)
-- **What**: Calculate current/past/future Saturn return dates
-- **Effort**: Moderate -- Saturn orbital period ~29.5 years; compute from birth date
-- **Where**: New function `get_saturn_return_dates(birth_datetime)` in astrology.py (~30 lines)
-
-#### 2.3.3 Node Return Timing (Medium Priority)
-- **What**: Calculate when transiting nodal axis returns to natal position (~18.6 year cycle)
-- **Effort**: Moderate -- nodal period ~18.6 years
-- **Where**: New function `get_node_return_dates(birth_datetime)` in astrology.py (~20 lines)
-
-### 2.4 Nice-to-Have (New Tools)
-
-#### 2.4.1 Part of Fortune (Low Priority)
-- **What**: Arabic Part of Fortune -- sign, house, aspects
-- **Effort**: Trivial -- formula: ASC + Moon - Sun (in degrees)
-- **Where**: New function `get_part_of_fortune(angles, planets)` in astrology.py (~10 lines)
-
-#### 2.4.2 Chiron Position (Low Priority)
-- **What**: Chiron sign, house, aspects
-- **Effort**: Trivial -- `chiron` is already in the ephemeris mock data; confirm real ephemeris returns it
-- **Status**: Likely already available, needs verification
-
-#### 2.4.3 Vertex Axis (Low Priority)
-- **What**: Vertex sign/house (fated encounters)
-- **Effort**: Moderate -- Vertex = intersection of ecliptic with prime vertical, requires astronomical calculation
-- **Where**: New function `calculate_vertex(lst, latitude)` in astrology.py (~25 lines)
-
-#### 2.4.4 Fixed Star Conjunctions (Low Priority)
-- **What**: Major fixed stars conjunct natal planets
-- **Effort**: Moderate -- need fixed star catalog (Aldebaran, Regulus, Spica, Antares, etc.) + conjunction check
-- **Where**: New data file + new function in astrology.py (~100 lines)
-
-#### 2.4.5 Planetary Nodes (Low Priority)
-- **What**: Nodes of Pluto, Saturn, etc. (Jeffrey Wolf Green's evolutionary astrology)
-- **Effort**: Complex -- requires specialized astronomical data not in standard ephemeris
-- **Status**: Deferred
-
----
-
-## 3. RELATIONSHIP ASTROLOGY WISHLIST
-
-### 3.1 Already Implemented (confirmed)
-
-| Wishlist Item | Status | Location |
-|---|---|---|
-| Natal chart calculation | DONE | `calculate_natal_chart` |
-| Synastry chart (interchart aspects, house overlays) | DONE | `calculate_synastry_chart` |
-| Composite chart calculation | DONE | `calculate_composite_chart` |
-| Person database | DONE | `person_manage` |
-| Aspect calculation with configurable orbs | DONE | `compute_aspects` |
-| House systems | DONE | `calculate_houses` |
-| Transit chart calculation | DONE | `calculate_transit_chart` |
-
-### 3.2 Missing -- Easy Wins (post-processing / filtering)
-
-#### 3.2.1 Interchart Aspects Matrix (High Priority)
-- **What**: Full matrix of aspects between all planets of Person A and Person B
-- **Effort**: Already computed as `interaspects` in `calculate_synastry_chart`
-- **Status**: Done, just needs better surfacing
-
-#### 3.2.2 House Overlay Report (High Priority)
-- **What**: For each planet in Chart A, report which house it falls in Chart B (and vice versa)
-- **Effort**: Already computed as `house_overlays` in `calculate_synastry_chart`
-- **Status**: Done
-
-#### 3.2.3 Top Synastry Aspects (High Priority)
-- **What**: Return the 10-15 tightest/most significant interchart aspects
-- **Effort**: Trivial -- interaspects already sorted by orb; add `top_n` param
-
-#### 3.2.4 Relationship Significator Aspects (High Priority)
-- **What**: Venus-Mars, Moon-Venus, Sun-Moon, Sun-Saturn interchart aspects
-- **Effort**: Trivial -- filter interaspects for these planet pairs
-- **Where**: Add `significator_filter: bool` param to `calculate_synastry_chart`
-
-#### 3.2.5 Saturn Synastry Aspects (High Priority)
-- **What**: Flag all Saturn interchart aspects to personal planets and nodes
-- **Effort**: Trivial -- filter for Saturn as person1_planet or person2_planet
-
-#### 3.2.6 Node Synastry Aspects (High Priority)
-- **What**: Flag all interchart aspects to the nodal axes
-- **Effort**: Trivial -- filter for `true_node` involvement
-
-### 3.3 Missing -- New Tools Needed
-
-#### 3.3.1 Full Davison Chart Calculation (High Priority)
-- **What**: Calculate the midpoint-in-time-and-space chart with full planetary positions, houses, angles, aspects
-- **Current state**: `calculate_synastry_chart` returns Davison midpoint coordinates (date_jd, lat, lon) but does NOT compute planets, houses, or aspects for the Davison chart
-- **Effort**: Moderate -- compute sky state at Davison midpoint datetime + location, then calculate full chart
-- **Where**: New tool `calculate_davison_chart` in tools.py, reusing `calculate_natal_chart` logic (~150 lines)
-
-#### 3.3.2 Davison Transit Preview (Medium Priority)
-- **What**: Transit-to-Davison chart aspect snapshots over a date range
-- **Effort**: Moderate -- reuse `get_transit_preview` with Davison chart positions
-- **Where**: New tool `get_davison_transit_preview` in tools.py (~80 lines)
-
-#### 3.3.3 Composite Transit Report (Medium Priority)
-- **What**: Transits to composite chart planets/angles for timing
-- **Effort**: Moderate -- reuse `get_transit_preview` with composite planet positions
-- **Where**: New tool `get_composite_transit_preview` in tools.py (~80 lines)
-
-#### 3.3.4 Composite Enhanced Interpretation (Medium Priority)
-- **What**: Composite planet meanings, angular planets, unaspected planets, house emphasis, chart ruler
-- **Effort**: Moderate -- post-processing of existing composite chart data
-- **Where**: New function `analyze_composite_chart(composite_data)` in astrology.py (~100 lines)
-
-#### 3.3.5 Karmic Relationship Synthesis (Medium Priority)
-- **What**: Combine synastry + composite + Davison karmic indicators into structured report
-- **Effort**: Moderate -- aggregate filtering across all three chart types
-- **Where**: New tool `get_karmic_relationship_summary` in tools.py (~100 lines)
-
-### 3.4 Nice-to-Have (Complex / Low Priority)
-
-#### 3.4.1 Coalescent Chart (Low Priority)
-- **What**: Lawrence Grinnell's harmonic coalescent method
-- **Effort**: Complex -- compute harmonic numbers from planet pair arcs
-- **Where**: New algorithm in astrology.py (~150 lines)
-
-#### 3.4.2 Relationship Type Classifier (Low Priority)
-- **What**: Classify relationship type (romantic, business, family, friendship, karmic)
-- **Effort**: Heuristic scoring based on aspect patterns
-- **Where**: New function in astrology.py (~80 lines)
-
-#### 3.4.3 Compatibility Score (Low Priority)
-- **What**: Category scores (emotional, sexual, intellectual, commitment, spiritual)
-- **Effort**: Heuristic weighting of aspect types
-- **Where**: New function in astrology.py (~100 lines)
-
-#### 3.4.4 Synthesis Transit Preview (Low Priority)
-- **What**: Combine transit-to-natal (both people) + transit-to-composite/Davison
-- **Effort**: Aggregation of existing transit data
-- **Where**: New tool combining existing transit previews (~60 lines)
-
-#### 3.4.5 Eclipse Activation of Relationship Charts (Low Priority)
-- **What**: Flag eclipses hitting composite/Davison angles or personal planets
-- **Effort**: Needs eclipse data source
-- **Status**: Deferred
-
----
-
-## 4. CONSOLIDATED PRIORITY MATRIX
-
-### Tier 1: Post-Processing Only (Easiest -- add params to existing tools)
-Pure filtering/bucketing of data already returned by existing tools.
-
-| # | Feature | Wishlist(s) | Effort |
-|---|---|---|---|
-| 1 | Element/Modality/Hemisphere balance | Natal | ~45 lines |
-| 2 | Stellium detection | Natal | ~20 lines |
-| 3 | Chart ruler identification | Natal | ~15 lines |
-| 4 | Top aspects by orb | Natal | param only |
-| 5 | Aspects to Big Three / chart ruler | Natal | param only |
-| 6 | House rulers | Natal | ~25 lines |
-| 7 | Planets in houses/signs grouping | Natal | ~20 lines |
-| 8 | Angular/succedent/cadent count | Natal | ~15 lines |
-| 9 | Retrograde planet list | Natal, Karmic | ~10 lines |
-| 10 | Node axis aspects | Karmic | param only |
-| 11 | Saturn aspects to personal planets | Karmic | param only |
-| 12 | Saturn-Node aspects | Karmic | param only |
-| 13 | 12th house analysis | Karmic | ~15 lines |
-| 14 | Top synastry aspects | Relationship | param only |
-| 15 | Relationship significator aspects | Relationship | param only |
-| 16 | Saturn/Node synastry filtering | Relationship | param only |
-| 17 | Lunar phase at birth | Natal | expose existing |
-| 18 | Applying/separating fix for natal | Natal | bug fix |
-
-### Tier 2: New Functions in astrology.py (Moderate -- pure math)
-New algorithms that operate on existing data structures.
-
-| # | Feature | Wishlist(s) | Effort |
-|---|---|---|---|
-| 19 | Chart shape detection | Natal | ~80 lines |
-| 20 | Aspect pattern detection | Natal | ~150 lines |
-| 21 | Pluto Polarity Point | Karmic | ~10 lines |
-| 22 | Part of Fortune | Karmic | ~10 lines |
-| 23 | Vertex axis | Karmic | ~25 lines |
-| 24 | Saturn return dates | Natal, Karmic | ~30 lines |
-| 25 | Node return dates | Karmic | ~20 lines |
-| 26 | Composite enhanced analysis | Relationship | ~100 lines |
-
-### Tier 3: New MCP Tools (More involved -- require ephemeris calls)
-New tools that make additional ephemeris calls.
-
-| # | Feature | Wishlist(s) | Effort |
-|---|---|---|---|
-| 27 | Full Davison chart | Relationship | ~150 lines |
-| 28 | Composite transit preview | Relationship | ~80 lines |
-| 29 | Davison transit preview | Relationship | ~80 lines |
-| 30 | Karmic relationship synthesis | Relationship, Karmic | ~100 lines |
-
-### Tier 4: Nice-to-Have (Complex or needs external data)
-
-| # | Feature | Wishlist(s) | Effort |
-|---|---|---|---|
-| 31 | Coalescent chart | Relationship | ~150 lines |
-| 32 | Fixed star conjunctions | Karmic | ~100 lines + data |
-| 33 | Relationship type classifier | Relationship | ~80 lines |
-| 34 | Compatibility scoring | Relationship | ~100 lines |
-| 35 | Eclipse proximity/activation | Natal, Relationship | needs data source |
-| 36 | Planetary nodes | Karmic | needs special data |
-
----
-
-## 5. KEY ARCHITECTURAL RECOMMENDATIONS
-
-### 5.1 Consolidate Post-Processing into a Chart Overview Tool
-Rather than adding 15 boolean params to `calculate_natal_chart`, create a single new tool:
-
-```
-get_chart_overview(person_id_or_birth_data, include_karmic=false, include_patterns=false)
-```
-
-This tool would call `calculate_natal_chart` internally, then run all post-processing functions on the result. Keeps the base tool clean and provides a rich analysis layer on top.
-
-### 5.2 Add Analysis Params to Synastry
-Add optional params to `calculate_synastry_chart`:
-- `top_n_aspects: int | None` -- limit interaspects to top N by orb
-- `karmic_filter: bool` -- only return Saturn/Pluto/Node interaspects
-- `significator_filter: bool` -- only return Venus-Mars, Moon-Venus, Sun-Moon, Sun-Saturn
-
-### 5.3 Fix Natal Chart Applying/Separating
-The natal chart tool should pass `speed_lon` to `compute_aspects` so the `applying` field is populated. This is a bug/omission, not a new feature.
-
-### 5.4 Davison Chart is the Biggest Gap
-The synastry tool returns Davison midpoint coordinates but not a full chart. A `calculate_davison_chart` tool is the highest-priority new tool -- it reuses existing ephemeris + astrology logic with the midpoint datetime/location.
-
-### 5.5 Agent Guides Already Exist
-The `agent-guides/` directory already contains detailed interpretation guides for natal, karmic, and relationship astrology. These guides are consumed by agents, not by the MCP server. The MCP server's job is to provide the raw computational data; the guides tell agents how to interpret it. This analysis focuses on the computational layer only.

+ 3 - 1
agent-guides/financial-astrology.md

@@ -1,4 +1,6 @@
-# Financial Astrology — Agent Interpretation Guide
+# Financial Astrology — Agent Interpretation Guide (astro-mcp v0.2.0)
+
+> Repository documentation context: astro-mcp v0.2.0. This guide describes interpretation workflows, not additional MCP tools or resources.
 
 > For agents using the astro MCP to perform financial astrology analysis, market timing, and economic cycle interpretation.
 > Resource URI: `astro://guides/financial-astrology`

+ 3 - 1
agent-guides/karmic-astrology.md

@@ -1,4 +1,6 @@
-# Karmic Astrology — Agent Interpretation Guide
+# Karmic Astrology — Agent Interpretation Guide (astro-mcp v0.2.0)
+
+> Repository documentation context: astro-mcp v0.2.0. This guide describes interpretation workflows, not additional MCP tools or resources.
 
 > For agents using the astro MCP to perform karmic chart interpretation.
 > Resource URI: `astro://guides/karmic-astrology`

+ 3 - 1
agent-guides/natal-astrology.md

@@ -1,4 +1,6 @@
-# Natal Astrology — Agent Interpretation Guide
+# Natal Astrology — Agent Interpretation Guide (astro-mcp v0.2.0)
+
+> Repository documentation context: astro-mcp v0.2.0. This guide describes interpretation workflows, not additional MCP tools or resources.
 
 > For agents using the astro MCP to perform natal chart interpretation.
 > Resource URI: `astro://guides/natal-astrology`

+ 3 - 1
agent-guides/relationship-astrology.md

@@ -1,4 +1,6 @@
-# Relationship Astrology — Agent Interpretation Guide
+# Relationship Astrology — Agent Interpretation Guide (astro-mcp v0.2.0)
+
+> Repository documentation context: astro-mcp v0.2.0. This guide describes interpretation workflows, not additional MCP tools or resources.
 
 > For agents using the astro MCP to perform relationship chart interpretation.
 > Resource URI: `astro://guides/relationship-astrology`

+ 103 - 163
agent-guides/server-guide.md

@@ -1,220 +1,160 @@
-# Astro-MCP Server Guide
+# Astro-MCP v0.2.0 Server Guide
 
 ## Overview
 
-Astro-MCP is an astrological chart calculation server. It consumes birth data
-and returns planetary positions, house cusps, angles, and aspects. All
-computation is delegated to a separate **ephemeris-mcp** server which runs the
-Swiss Ephemeris library.
+Astro-MCP calculates astrological chart data from the `ephemeris-mcp` Swiss Ephemeris backend. It also provides database-backed natal chart graphics through an MCP resource and a human-facing HTTP URL.
+
+The server does not interpret charts. Calculation tools return structured data; rendered chart delivery returns graphic artifacts.
 
 ## Architecture
 
-```
-User / Agent
-    |
-    v
-astro-mcp (this server)
-    |  MCP SSE
-    v
-ephemeris-mcp (Swiss Ephemeris backend)
+```text
+Agent or human client
+       |
+       v
+astro-mcp v0.2.0
+       | MCP client
+       v
+ephemeris-mcp
 ```
 
-- **astro-mcp** handles: person database, chart interpretation, rendering,
-  timezone conversion, MCP tool interface
-- **ephemeris-mcp** handles: raw astronomical computation (planetary positions,
-  house cusps, sidereal time)
+Astro-MCP handles person storage, timezone conversion, astrological calculations, stable chart rendering, MCP tools/resources, dashboard routes, and HTTP chart delivery. Ephemeris-MCP handles astronomical calculations.
 
-## Timezone Rules (CRITICAL)
+## Datetime rules
 
-### Single Conversion Point
+The persons database stores:
 
-**`_get_person_birth_data()`** is the ONLY function that converts stored birth
-datetimes to UTC. It reads the naive local time and IANA timezone name from the
-persons database, combines them, and returns a UTC datetime. All `_byId` chart
-tools call this function. **No other function performs timezone conversion.**
+- `birth_datetime`: naive local time with no offset
+- `timezone`: IANA timezone name
 
-### Database Storage Format
+Example:
 
-- `birth_datetime`: naive local time, NO offset (e.g. `"1965-07-02T00:05:00"`)
-- `timezone`: IANA timezone name (e.g. `"Europe/Vienna"`, `"America/New_York"`)
-- `longitude`: used as fallback for LMT when timezone is missing
+```text
+birth_datetime = 1953-03-23T21:05:00
+timezone = America/Chicago
+```
 
-### Direct-Call Tools
+`_get_person_birth_data()` is the single conversion point for database-backed chart calls. It combines the local datetime with the IANA timezone and produces the UTC value used by the ephemeris client.
 
-Tools that accept `birth_datetime` as a parameter (e.g. `calculate_natal_chart`,
-`calculate_transit_chart`) expect **UTC or offset-aware datetimes**. The caller
-is responsible for conversion. Use ISO 8601 format:
-- UTC: `"1965-07-01T23:05:00Z"` or `"1965-07-01T23:05:00+00:00"`
-- Offset-aware: `"1965-07-02T00:05:00+01:00"`
+Direct calculation tools require UTC or offset-aware ISO 8601 datetimes. Database-backed calculation tools require only a person ID or nickname.
 
-### _byId Tools
+For historical dates, provide the IANA timezone. `zoneinfo` handles historical Local Mean Time where applicable.
 
-Tools that look up persons by ID/nickname (e.g. `calculate_natal_chart_by_id`)
-automatically convert to UTC. Just pass the person identifier.
+## Calculation tools
 
-### Person Management
+The current tool surface includes:
 
-#### Adding a Person
+- `get_planetary_positions`
+- `calculate_natal_chart`
+- `calculate_transit_chart`
+- `calculate_synastry_chart`
+- `calculate_composite_chart`
+- `calculate_davison_chart`
+- `get_transit_preview`
+- `get_composite_transit_preview`
+- `get_davison_transit_preview`
+- `get_karmic_relationship_summary`
+- `calculate_natal_chart_by_id`
+- `calculate_transit_chart_by_id`
+- `calculate_synastry_chart_by_id`
+- `calculate_composite_chart_by_id`
+- `calculate_davison_chart_by_id`
+- `get_transit_preview_by_id`
+- `person_manage`
+- `list_house_systems`
 
-Use `person_manage` with `action: "add"`. Required fields:
-- `name`: Full name
-- `birth_datetime`: ISO 8601 **naive local time** (no offset, e.g. `"1990-05-15T10:30:00"`)
-- `latitude`, `longitude`: Birth coordinates
-- `tz`: IANA timezone name (e.g. `"Europe/Vienna"`, `"America/New_York"`)
+Calculation tools return structured data. There are no `render_*` MCP tools in v0.2.0.
 
-Optional: `nickname`, `birthplace`, `elevation`, `gender`, `description`, `notes`,
-`birth_time_known`.
+## Rendered natal chart resource
 
-#### Datetime Convention (CRITICAL)
+The implemented chart resource template is:
 
-The persons database stores **naive local time** + **IANA timezone name**. The
-conversion to UTC happens exactly once, inside `_get_person_birth_data()`, which
-combines them using `zoneinfo.ZoneInfo`. Rules:
+```text
+astro://charts/natal/{person_id}
+```
 
-- `birth_datetime` in the DB: **always naive, always local** (no `Z`, no `+01:00`)
-- `timezone`: IANA name (e.g. `"America/Chicago"`, `"Europe/Berlin"`)
-- `longitude`: fallback for LMT only when `timezone` is NULL (pre-1900 dates)
+Example:
 
-**Do NOT store UTC in the DB.** Do NOT add an offset to `birth_datetime`. If you
-store `"1953-03-23T03:05:00+00:00"` (UTC), the conversion will treat it as
-already-UTC and the chart will be wrong. Instead store `"1953-03-23T21:05:00"`
-(local Chicago time) with `tz="America/Chicago"`.
+```text
+astro://charts/natal/einstein
+```
 
-**Historical note:** Before ~1890, IANA timezones use Local Mean Time (LMT)
-which can differ from modern UTC offsets by minutes. For example, Einstein's
-birth in Ulm (1879) is LMT = UTC+0:53:28, not the modern CET (UTC+1). The
-`zoneinfo` module handles this correctly when given the IANA name. Always
-provide `tz` for historical dates — do not compute the UTC offset manually.
+Reading this resource resolves the person from the database, calculates the natal chart, invokes the existing natal chart renderer, and returns the graphic artifact. The agent can read the resource and save or attach the returned artifact.
 
-#### Listing and Updating
+Only database-backed natal chart delivery is implemented in v0.2.0.
 
-- `person_manage` with `action: "list"` — list all persons
-- `person_manage` with `action: "get"` — retrieve by ID or nickname
-- `person_manage` with `action: "update"` — modify fields
-- `person_manage` with `action: "delete"` — remove person
+## Human-facing chart URL
 
-## Tool Categories
+```text
+/charts/natal/{person_id}.{format}
+```
 
-### Data-Only Tools (return JSON)
+Examples:
 
-| Tool | Description |
-|------|-------------|
-| `get_sky_state` | Raw planetary positions + houses for a datetime/location |
-| `calculate_natal_chart` | Full natal chart: planets, houses, aspects, angles |
-| `calculate_transit_chart` | Transit-to-natal aspects |
-| `calculate_synastry_chart` | Relationship chart for two people |
-| `calculate_composite_chart` | Composite (midpoint) chart |
-| `calculate_davison_chart` | Davison (space-time midpoint) chart |
-| `get_transit_preview` | Daily transit snapshots over a date range |
-| `get_karmic_relationship_summary` | Karmic analysis for a relationship |
+```text
+/charts/natal/einstein.svg
+/charts/natal/einstein.png
+```
 
-### _byId Variants (lookup person from DB)
+The route returns the existing rendered artifact with the correct image MIME type. It shares the artifact-generation path with the MCP resource.
 
-Same as above but with `_byId` suffix. Accept `person_id` (or nickname) instead
-of birth data. Automatically handle timezone conversion.
+## Stable rendering boundary
 
-### Rendering Tools (return SVG)
+The chart drawing implementation is stable and is not part of the v0.2.0 delivery change. Delivery code calls the existing renderer; it does not alter chart geometry, layout, styles, colors, dimensions, or format conversion.
 
-| Tool | Description |
-|------|-------------|
-| `render_natal_chart` | SVG natal chart wheel |
-| `render_transit_chart` | SVG transit chart |
-| `render_synastry_chart` | SVG synastry chart |
-| `render_composite_chart` | SVG composite chart |
-| `render_davison_chart` | SVG Davison chart |
+## Other MCP resources
 
-Each has a `_byId` variant.
+The server also exposes interpretation guides:
 
-### Utility Tools
+- `astro://guides/natal-astrology`
+- `astro://guides/karmic-astrology`
+- `astro://guides/relationship-astrology`
+- `astro://guides/financial-astrology`
+- `astro://guides/server-guide`
 
-| Tool | Description |
-|------|-------------|
-| `person_manage` | CRUD for persons database |
-| `list_house_systems` | List supported house systems (Placidus, Koch, etc.) |
+## Common workflows
 
-## House Systems
+### Get natal data for a stored person
 
-Supported house system codes: P (Placidus), K (Koch), E (Equal), W (Whole Sign),
-A (Alcabitius), C (Campanus), M (Morinus), R (Porphyry), and more. Use
-`list_house_systems` for the full list.
+```text
+calculate_natal_chart_by_id(person_id="einstein")
+```
 
-Default is Placidus.
+### Get a rendered natal chart for a stored person
 
-## Common Workflows
+```text
+Read resource: astro://charts/natal/einstein
+```
 
-### Natal Chart for a Known Person
+### Display a rendered natal chart in a browser
 
-```
-1. person_manage(action="add", name="...", birth_datetime="...", latitude=..., longitude=..., tz="...")
-2. calculate_natal_chart_by_id(person_id="...", include_overview=true)
+```text
+GET /charts/natal/einstein.svg
 ```
 
-### Natal Chart for a One-Off Calculation
+### Calculate a one-off chart
 
-```
+```text
 calculate_natal_chart(
-    birth_datetime="1990-05-15T10:30:00+01:00",  # UTC or offset-aware
+    birth_datetime="1990-05-15T10:30:00+01:00",
     latitude=47.07,
-    longitude=15.42,
-    include_overview=true
+    longitude=15.42
 )
 ```
 
-### Transit Chart
+## Errors
 
-```
-calculate_transit_chart_by_id(
-    person_id="...",
-    transit_datetime="2026-06-07T12:00:00Z"  # UTC
-)
-```
+Calculation tools use structured error results. Resource reads and HTTP chart requests reject unknown persons and unsupported request values. A person resource requires an identifier that resolves by database ID or nickname.
 
-### Relationship Analysis
-
-```
-calculate_synastry_chart_by_id(
-    person1_id="...",
-    person2_id="...",
-    include_davison_full=true
-)
-```
+## v0.2.0 non-goals
 
-### Rendered Chart
+The following are not implemented as chart resources in this version:
 
-```
-render_natal_chart_by_id(
-    person_id="...",
-    style="modern",
-    color_mode="color",
-    size=800
-)
-```
+- Transit charts
+- Synastry charts
+- Composite charts
+- Davison charts
+- New rendering features or options
+- Caching or compatibility routes
 
-## Resources (fetch with astro:// URI)
-
-| URI | Content |
-|-----|---------|
-| `astro://guides/natal-astrology` | Natal chart interpretation guide |
-| `astro://guides/karmic-astrology` | Karmic astrology guide |
-| `astro://guides/relationship-astrology` | Relationship astrology guide |
-| `astro://guides/financial-astrology` | Financial astrology guide |
-
-## Error Handling
-
-All tools return `{"error": "message"}` on failure. Common errors:
-- `"person not found: ..."` — invalid person_id or nickname
-- `"add requires: name, birth_datetime, latitude, longitude"` — missing fields
-- `"empty_response"` — ephemeris server unreachable
-
-## Tips
-
-1. Always use `_byId` tools when working with stored persons — they handle
-   timezone conversion automatically.
-2. For direct-call tools, pass UTC datetimes to avoid ambiguity.
-3. The `include_overview` flag adds element/modality/hemisphere balance,
-   stelliums, empty houses, chart ruler, and house rulers.
-4. The `include_patterns` flag adds T-square, Grand Trine, Grand Cross, Yod
-   detection and chart shape classification.
-5. The `include_karmic` flag adds nodal axis, Saturn, Pluto polarity point,
-   Part of Fortune, and 12th house analysis.
-6. Use `top_n_aspects` to limit aspect output to the N tightest by orb.
+They must not be described as available chart resources until implemented.

+ 3 - 1
docs/astrological_chart_rendering_guide.md

@@ -1,4 +1,6 @@
-# Astrological Chart Rendering — Complete Technical Guide
+# Astrological Chart Rendering — Complete Technical Guide (astro-mcp v0.2.0)
+
+> Repository documentation context: astro-mcp v0.2.0. This document describes the stable existing chart renderer. v0.2.0 changes chart delivery only; it does not change the drawing implementation.
 
 A reference for developers building a natal (birth) chart renderer. Covers geometry, coordinate systems, layers, data tables, glyphs, and the differences between traditional and modern layouts.
 

+ 3 - 1
docs/astronomicon-character-map.md

@@ -1,4 +1,6 @@
-# Astronomicon Font — Character Map
+# Astronomicon Font — Character Map (astro-mcp v0.2.0)
+
+> Repository documentation context: astro-mcp v0.2.0. This is a stable renderer reference.
 
 Free under the [Open Font License](https://scripts.sil.org/OFL).  
 Download: https://astronomicon.co/AstronomiconFonts_1.1.zip

+ 0 - 330
docs/chart-rendering-api-design.md

@@ -1,330 +0,0 @@
-# Chart Rendering — API Design: MCP Tools vs Separate Renderer
-
-**Date:** 2026-06-07
-**Status:** Design discussion — decide before coding
-
----
-
-## The Core Question
-
-Should chart rendering be **inside** existing MCP tools (via `include_svg` param),
-or should it be **separate** dedicated render tools?
-
----
-
-## Option A: `include_svg` on Existing Tools
-
-### How it works
-
-```python
-@mcp.tool()
-async def calculate_natal_chart(
-    birth_datetime: str,
-    latitude: float,
-    longitude: float,
-    # ... existing params ...
-    include_svg: bool = False,
-    svg_style: str = "modern",       # "modern" | "traditional" | "minimal"
-    svg_color: str = "color",        # "color" | "bw"
-    svg_size: int = 600,             # pixel width
-) -> dict[str, Any]:
-    ...
-    result = { ...planets, houses, aspects... }
-    if include_svg:
-        result["chart_svg"] = render_natal_wheel(result, style=svg_style, ...)
-    return result
-```
-
-Same pattern for `calculate_transit_chart`, `calculate_synastry_chart`,
-`calculate_composite_chart`, `calculate_davison_chart`, and all `_byId` variants.
-
-### Pros
-- **One call gets everything** — data + visual in one shot
-- Agent workflow is simpler: "calculate my chart" → gets SVG to display
-- No extra round-trips
-- Backward compatible (default `include_svg=false`)
-
-### Cons
-- **Bloats the response** — a full SVG is ~20-50KB of XML text. When you only
-  want data (most API calls), you pay the cost anyway (even if just in
-  description length for the LLM context).
-- **Mixes concerns** — the calculation tool now also renders. Makes the function
-  harder to maintain.
-- **Style params proliferate** — every chart tool gets 3-4 extra SVG params.
-  With 10 chart tools, that's 30-40 extra parameters to document.
-- **Can't render without recalculating** — if you already have the chart data
-  (e.g. from a previous call or from the DB), you can't just "render it" without
-  re-doing the ephemeris calls.
-- **SVG in JSON is ugly** — escaped XML inside JSON is painful to read/debug.
-
-### Verdict
-**Not recommended** for the main path. The response bloat alone is a problem —
-an LLM calling `calculate_natal_chart` for data processing gets 50KB of SVG
-XML forced into its context window every time.
-
----
-
-## Option B: Separate Dedicated Render Tools (RECOMMENDED)
-
-### How it works
-
-```python
-@mcp.tool()
-async def render_natal_chart(
-    chart_data: dict,
-    style: str = "modern",
-    color_mode: str = "color",
-    size: int = 600,
-    format: str = "svg",         # "svg" | "pdf" | "png"
-) -> dict[str, Any]:
-    """Render a natal chart wheel from chart calculation data.
-
-    Takes the output of calculate_natal_chart (or any chart tool) and
-    renders it as a visual chart wheel. The chart_data parameter accepts
-    the full dict returned by any chart calculation tool.
-    """
-    ...
-    return {
-        "svg": "<svg>...</svg>",     # or base64 png, or pdf path
-        "format": "svg",
-        "width": size,
-        "height": size,
-    }
-```
-
-Plus convenience variants:
-
-```python
-@mcp.tool()
-async def render_natal_chart_by_id(
-    person_id: str,
-    house_system: str = "placidus",
-    style: str = "modern",
-    color_mode: str = "color",
-    size: int = 600,
-    include_table: bool = True,      # include aspect table below wheel
-    include_planet_list: bool = True,
-) -> dict[str, Any]:
-    """Render natal chart for a person from the database.
-
-    Combines calculate_natal_chart + render_natal_chart in one call.
-    Fetches birth data, calculates positions, and renders the wheel.
-    """
-```
-
-### Pros
-- **Clean separation** — calculation tools stay lean, rendering is separate
-- **Flexible** — render any chart data, even from external sources
-- **Multiple formats** — SVG for web, PDF for print, PNG for thumbnails
-- **Caching friendly** — cache rendered SVG by hash of chart data + style params
-- **Agent-friendly** — agent can do:
-  1. `data = calculate_natal_chart(...)` → gets lean JSON
-  2. `svg = render_natal_chart(data)` → gets SVG only when needed
-  3. Or skip step 1: `svg = render_natal_chart_by_id("lucky")` → one-shot
-- **Dashboard-friendly** — HTTP routes can call the same renderer
-
-### Cons
-- Two tool calls if you want both data + SVG (extra round-trip)
-- `render_natal_chart_by_id` duplicates the param lists of the data tools
-
-### Verdict
-**Recommended.** Clean architecture, flexible, doesn't bloat the data tools.
-
----
-
-## Option C: Hybrid (Data tools + Render Resource)
-
-### The idea
-
-Keep data tools pure. Add a **separate render tool** that accepts either
-chart data OR a person_id:
-
-```python
-@mcp.tool()
-async def render_chart(
-    # One of these two is required:
-    chart_data: dict | None = None,
-    person_id: str | None = None,
-    # Rendering options:
-    chart_type: str = "natal",       # "natal" | "transit" | "synastry" | ...
-    style: str = "modern",
-    color_mode: str = "color",
-    format: str = "svg",
-    size: int = 600,
-    transit_date: str | None = None,  # for transit charts
-    person2_id: str | None = None,    # for synastry
-) -> dict[str, Any]:
-    """Universal chart renderer.
-
-    Pass chart_data from any chart tool output, OR pass person_id to
-    auto-calculate + render in one step.
-    """
-```
-
-### Pros
-- Single render tool, not 10+ variants
-- Flexible input: raw data or person DB lookup
-- Easy to extend with new chart types
-
-### Cons
-- Complex parameter validation (mutually exclusive groups)
-- Docstring becomes very long
-- Too many responsibilities in one function
-
-### Verdict
-Nice in theory, messy in practice. Go with Option B.
-
----
-
-## Recommended Design: Option B (Separated)
-
-### Tool Inventory
-
-**Core data tools (unchanged, stay lean):**
-- `calculate_natal_chart` — pure data
-- `calculate_transit_chart` — pure data
-- `calculate_synastry_chart` — pure data
-- `calculate_composite_chart` — pure data
-- `calculate_davison_chart` — pure data
-- All `_byId` variants — pure data
-
-**New render tools:**
-
-| Tool | Purpose |
-|------|---------|
-| `render_natal_chart` | Render natal wheel from chart_data |
-| `render_transit_chart` | Render bi-wheel from transit chart_data |
-| `render_synastry_chart` | Render dual wheel from synastry chart_data |
-| `render_composite_chart` | Render composite wheel from chart_data |
-| `render_davison_chart` | Render Davison wheel from chart_data |
-| `render_natal_chart_by_id` | One-shot: fetch DB + calc + render natal |
-| `render_transit_chart_by_id` | One-shot: fetch DB + calc + render transit |
-| `render_synastry_chart_by_id` | One-shot: fetch DB both + calc + render synastry |
-
-### Render Options (consistent across all render tools)
-
-```python
-style: str = "modern"        # "modern" | "traditional" | "minimal"
-color_mode: str = "color"    # "color" | "bw"
-size: int = 600              # SVG viewBox width in pixels (square)
-format: str = "svg"          # "svg" | "pdf" | "png"
-include_table: bool = false  # Aspect table below wheel
-include_planets: bool = false # Planet data table
-include_houses: bool = false  # House cusp table
-title: str | None = None     # Custom title (default: auto-generated)
-font_family: str = "astronomicon"  # "astronomicon" | "unicode"
-```
-
-### Return Structure
-
-```python
-{
-    "svg": "<svg xmlns=...>...</svg>",    # SVG string (when format="svg")
-    # OR
-    "pdf_b64": "JVBERi0xLjQK...",         # base64 PDF (when format="pdf")
-    # OR
-    "png_b64": "iVBORw0KGgo...",         # base64 PNG (when format="png")
-    "format": "svg",
-    "width": 600,
-    "height": 600,
-    "style": "modern",
-    "color_mode": "color",
-    "included": ["wheel", "table", "planets"],
-    "svg_size_bytes": 28341,
-}
-```
-
-### Agent Workflow Examples
-
-**Use case 1: Agent wants to show a chart in chat**
-```python
-# One call — agent gives SVG to user
-svg_result = render_natal_chart_by_id("lucky", style="modern")
-# Agent outputs: chart_svg string → user sees the wheel
-```
-
-**Use case 2: Agent wants data analysis + chart**
-```python
-# Step 1: Get data (lean, fast)
-data = calculate_natal_chart_by_id("lucky", include_overview=true)
-# Agent analyzes: "Sun in Leo, Moon in Cancer..."
-
-# Step 2: Get chart only if needed
-svg = render_natal_chart(data, style="traditional", color_mode="bw")
-# Agent attaches SVG to response
-```
-
-**Use case 3: Dashboard/web display**
-```
-GET /dashboard/charts/person/{id}/natal?style=modern&color=width=800
-→ Calls render_natal_chart internally → returns SVG inline in HTML
-```
-
-**Use case 4: Print PDF**
-```python
-pdf = render_natal_chart_by_id("lucky", format="pdf", style="traditional",
-                                 include_table=true, include_houses=true)
-# pdf["pdf_b64"] → decode → write to file → send to printer
-```
-
----
-
-## Implementation Plan
-
-### Phase 1: Renderer Core
-- `svgwrite` for SVG generation
-- Layout engine: zodiac ring, house sectors, planet positions, aspect lines
-- Style system: modern/traditional, color/BW
-- Astronomicon font integration via `@font-face` + unicode fallback
-- Output: SVG string
-
-### Phase 2: Render Tools
-- `render_natal_chart` + `render_natal_chart_by_id`
-- PDF export via WeasyPrint
-- PNG export via cairosvg
-- Aspect table SVG
-- Planet/house table SVGs
-
-### Phase 3: Additional Chart Types
-- Transit bi-wheel
-- Synastry dual wheel
-- Composite wheel
-- Davison wheel
-
-### Phase 4: Dashboard
-- `/dashboard/charts/` routes
-- Style picker, format picker, download buttons
-- Person lookup → chart display
-- Transit date slider
-
----
-
-## Open Questions
-
-1. **Render tool count**: 8 render tools (5 chart types + 3 byId) seems like a lot.
-   Could collapse to just `render_chart(chart_data, chart_type)` +
-   `render_chart_by_id(person_id, chart_type)` with a `chart_type` enum.
-   → **Lean toward 2 universal tools** to keep the surface small.
-
-2. **Chart data validation**: What if the agent passes malformed `chart_data`?
-   → Use a Pydantic model to validate the expected structure, return
-   clear error messages.
-
-3. **SVG size limits**: A full chart SVG can be 30-60KB. MCP tool responses
-   should handle this fine, but some LLM context windows may not appreciate it.
-   → Consider a `compact` mode that strips comments/whitespace from SVG.
-
-4. **Transit chart render**: Needs two sets of planet positions + aspect lines.
-   Bi-wheel layout (inner natal + outer transit) or side-by-side?
-   → **Bi-wheel** is the standard, but side-by-side is clearer for >15 aspects.
-   Support both, default to bi-wheel.
-
-5. **Synastry chart render**: Two natal wheels + interaspect lines?
-   → Side-by-side wheels with a middle column of key interaspects.
-   Bi-wheel (person A inner, person B outer) for the visual.
-
-6. **Caching**: SVG is deterministic for (chart_data_hash + style_options).
-   → Cache in memory with LRU eviction. Key: sha256(chart_data + options).
-
-7. **Person info on chart**: Name, birth date, location in the title block?
-   → Extracted from chart_data["input"] if present. Respect person.private flag.

+ 0 - 533
docs/chart-rendering-proposal.md

@@ -1,533 +0,0 @@
-# Astro-MCP Chart Rendering — Planning & Brainstorm Document
-
-**Date:** 2026-06-06
-**Status:** Draft — for review and discussion before implementation
-
----
-
-## 1. Vision & Goals
-
-Astro-MCP already computes everything: planetary positions, houses, aspects, patterns,
-chart shapes, Davison/composite charts, transit previews. What's missing is **visual
-output** — turning that rich data into actual chart wheels and tables that a human can
-look at.
-
-The goal is to add chart rendering that covers:
-1. **Print-ready output** (black/white laser printer, PDF)
-2. **Web-publishable output** (color SVG/HTML with customizable colors)
-3. **Data tables** (aspect tables, planet tables, house tables)
-
----
-
-## 2. What We Can Already Produce (Data Side)
-
-The existing MCP tools return all the data we need:
-
-| Tool | Data Available |
-|---|---|
-| `calculate_natal_chart` | Planets (sign, degree, house, retrograde), houses, aspects, angles, lunar phase, overview (elements, modalities, hemispheres, stelliums, empty houses, chart ruler, house rulers, retro list), aspect patterns, chart shape, karmic (nodal axis, Saturn, Pluto polarity point, Part of Fortune, 12th house) |
-| `calculate_transit_chart` | Natal planets + transiting planets + transit-to-natal aspects + houses |
-| `calculate_synastry_chart` | Both natal charts + interaspects + house overlays + composite + Davison |
-| `calculate_composite_chart` | Composite planets + houses + aspects + angles |
-| `calculate_davison_chart` | Davison planets + houses + aspects + angles |
-| `get_transit_preview` | Daily transit snapshots with significance scores |
-| `get_planetary_positions` | Quick lookup with zodiac positions |
-
-**No new calculation logic is needed** — rendering is purely a presentation layer on
-top of existing tools.
-
----
-
-## 3. Chart Types to Render
-
-### 3.1 Standard Natal Chart Wheel
-The classic circular zodiac wheel:
-- Outer ring: 12 zodiac sign segments with glyphs
-- House divisions (12 sectors) with cusp degrees
-- Planets placed at their ecliptic longitude inside the wheel
-- Aspect lines between planets (color-coded by aspect type)
-- Optional: aspect patterns highlighted (T-square, Grand Trine, etc.)
-
-### 3.2 Bi-Wheel (Transit Chart)
-Two concentric zodiac wheels:
-- Inner wheel: natal planet positions (fixed)
-- Outer wheel: transiting planet positions
-- Aspect lines between natal and transit planets
-- Useful for showing current transits to a natal chart
-
-### 3.3 Synastry / Comparison Wheel
-- Two side-by-side natal wheels, OR
-- Bi-wheel with person A inner, person B outer
-- Interaspect table below
-
-### 3.4 Composite Chart Wheel
-- Single wheel showing midpoint composite planets
-- Houses computed from Davison midpoint date/location
-
-### 3.5 Davison Chart Wheel
-- Single wheel with Davison date/location positions
-
-### 3.6 Aspect Table / Grid
-A matrix grid showing all planet-to-planet aspects:
-- Rows and columns = planets
-- Cells = aspect symbol + orb
-- Color-coded or B&W with line styles
-
-### 3.7 Planet Data Table
-Tabular view of all planet positions:
-- Planet, sign, degree:minute, house, retrograde flag
-- Sortable by longitude, planet name, or house
-
-### 3.8 Houses Table
-- House number, cusp sign, cusp degree, ruler
-
----
-
-## 4. Output Formats & Styles
-
-### 4.1 Black/White (Print) Mode
-- **Target:** Laser printer output (PDF or SVG -> PDF)
-- Aspect lines distinguished by **line style** (solid, dashed, dotted) rather than color
-  - Conjunction/Opposition: solid line
-  - Trine/Square: dashed line
-  - Sextile: dotted line
-  - Quincunx: dash-dot line
-- Retrograde planets marked with an "R" or "℞" suffix
-- Planet glyphs from ZodiacFonts (or unicode fallbacks)
-- Clean, minimal design — works well on A4/Letter
-- Aspect table and planet table also in B&W-friendly format
-
-### 4.2 Color (Web) Mode
-- **Target:** SVG/HTML embedded in dashboard or standalone
-- **User-configurable colors:**
-  - Background color
-  - Zodiac ring color (or per-element coloring: fire=red, earth=green, air=yellow, water=blue)
-  - House sector colors (alternating or custom)
-  - Aspect line colors (conjunction=red, trine=blue, square=red, sextile=light blue, quincunx=orange, opposition=red)
-  - Planet glyph colors
-  - Text colors
-- CSS variables for easy theming
-- Hover tooltips on planets showing exact degree
-- Click-to-highlight aspects
-
-### 4.3 Style Variants
-
-**Traditional / Old-Fashioned:**
-- Ornate zodiac wheel with decorative borders
-- Classical glyphs (unicode or ZodiacFonts serif style)
-- Black ink on white/cream background
-- Roman-style fonts
-- House numbers in Roman numerals
-
-**Modern / Clean:**
-- Minimalist design, generous white space
-- Sans-serif fonts
-- Flat colors or monochrome
-- ZodiacFonts sans-serif style glyphs
-
-**Dark Theme (for web):**
-- Dark background (matching dashboard dark theme: #0f1117)
-- Light glyphs and text
-- Accent colors that work on dark
-
----
-
-## 5. ZodiacFonts.com — Resource Evaluation
-
-**URL:** https://www.zodiacfonts.com/
-**License:** Free tier under SIL OFL (Open Font License). Pro license for premium exclusives.
-
-**What they offer:**
-- 101 free astrology symbols as fonts + SVG + PNG
-- Coverage: zodiac signs (12 + Ophiuchus), planets (Sun through Pluto + asteroids),
-  houses, aspects, dwarf planets, lunar phases
-- Styles: sans-serif and slab-serif
-- Weights: light, regular, bold
-- Formats: font files (TTF/OTF), individual SVG files, PNG
-
-**How we'd use this:**
-1. Use the **font files** directly — embed via `@font-face` in CSS, render zodiac
-   glyphs with a single unicode character. This is the cleanest approach for both
-   web SVG and WeasyPrint PDF generation.
-2. Use **individual SVGs** as fallback — embed as `<image>` or inline `<svg>` in
-   the chart wheel for planet markers.
-3. The sans-serif regular style works best for clean modern charts; the slab-serif
-   for traditional style.
-
-**Integration plan:**
-- Download the free font pack at build/install time
-- Store font files in `static/fonts/` directory
-- CSS: `@font-face { font-family: 'ZodiacFont'; src: url('/static/fonts/zodiac-sans-regular.ttf'); }`
-- Planet glyphs referenced via unicode PUA (Private Use Area) codepoints
-- Fallback to standard unicode astrological symbols (♈♉♊...) if font missing
-
-**Code point mapping (to verify from the actual font):**
-```
-Zodiac signs:    ♈ ♉ ♊ ♋ ♌ ♍ ♎ ♏ ♐ ♑ ♒ ♓  (U+2648..U+2653)
-Planets:         ☉ ☽ ☿ ♀ ♃ ♄ ♅ ♆ ♇ ⚳ ⚷ ⚴ ⚵ ⚶  (various unicode)
-Aspects:         ☌ ⚹ △ □ ⚻ ☍                        (U+260C, U+26B9, etc.)
-```
-
----
-
-## 6. Technical Architecture
-
-### 6.1 Rendering Engine: Pure SVG Generation (SVGWrite)
-
-**Recommended approach:** Use `svgwrite` (Python library) to programmatically
-generate SVG chart wheels directly on the server side.
-
-**Why SVGWrite over alternatives:**
-- Already lightweight, pure Python — no heavy dependencies
-- SVG is scalable, works for both web and print
-- WeasyPrint (already installed!) can convert SVG -> PDF natively
-- Python 3.13 compatible
-- Completely programmatic — we control every element
-- Dashboard already serves Jinja2 templates — SVG can be inline or standalone
-
-**Alternative considered:**
-- **matplotlib** (already installed) — possible but designed for data plots, not
-  radial astrology charts. Aspects would be hacky. Possible for aspect grid heatmaps.
-- **Playwright + HTML template** — overkill, adds Chromium dependency
-- **Cairo (pycairo)** — graphics library, powerful but lower-level than SVGWrite
-- **Client-side JavaScript (D3.js / Canvas)** — ideal for interactive web charts,
-  but won't work for print/PDF on the server side. Could complement server-side.
-
-### 6.2 Proposed Architecture
-
-```
-                        ┌─────────────────────┐
-                        │   MCP Tool (data)    │
-                        │  calculate_natal_    │
-                        │  chart / transit /   │
-                        │  synastry / etc.     │
-                        └────────┬────────────┘
-                                 │ JSON data
-                                 ▼
-                        ┌─────────────────────┐
-                        │   Chart Renderer     │
-                        │  (new module:        │
-                        │   chart_renderer.py) │
-                        │                      │
-                        │  - wheel SVG gen     │
-                        │  - aspect grid SVG   │
-                        │  - planet table SVG  │
-                        │  - style engine      │
-                        │  - color themes      │
-                        └────────┬────────────┘
-                                 │ SVG string
-                    ┌────────────┼─────────────┐
-                    ▼                           ▼
-          ┌──────────────┐           ┌──────────────────┐
-          │  Web Route    │           │  PDF Export       │
-          │  (inline SVG  │           │  (WeasyPrint      │
-          │   in HTML)    │           │   SVG -> PDF)     │
-          └──────────────┘           └──────────────────┘
-```
-
-### 6.3 New Files to Create
-
-```
-src/astro_mcp/
-  chart_renderer.py    — Main rendering module
-  chart_styles.py      — Theme/style definitions (colors, fonts, line styles)
-  chart_templates/     — (optional) Jinja2 templates for HTML wrapping
-
-static/
-  fonts/               — ZodiacFonts TTF files
-  chart-colors.yaml    — User-configurable color themes
-
-templates/
-  chart-wheel.html     — Standalone chart display page
-  chart-aspects.html   — Aspect table page
-  chart-print.html     — Print-optimized layout
-
-docs/
-  chart-rendering-proposal.md  ← this file
-```
-
-### 6.4 New MCP Tools (optional)
-
-New tools that return chart data ready for rendering, or even render and return
-SVG/PDF directly:
-
-```python
-@mcp.tool()
-async def render_chart(
-    chart_data: dict,        # output from calculate_natal_chart etc.
-    format: str = "svg",     # "svg" | "pdf" | "png"
-    style: str = "modern",   # "modern" | "traditional" | "minimal"
-    color_mode: str = "color" | "bw",
-    include_aspects: bool = True,
-    include_table: bool = True,
-) -> dict:
-    """Render an astrological chart wheel as SVG, PDF, or PNG.
-    
-    Returns the rendered chart as base64-encoded data or saves to file
-    and returns the path.
-    """
-```
-
-### 6.5 New HTTP Routes (Dashboard Integration)
-
-```
-GET  /dashboard/charts/person/{id}         — Select chart type + options
-GET  /dashboard/charts/person/{id}/natal   — Render natal chart wheel
-GET  /dashboard/charts/person/{id}/transit?date=... — Transit bi-wheel
-GET  /dashboard/charts/compare/{id1}/{id2} — Synastry bi-wheel
-GET  /dashboard/charts/aspects/{id}         — Aspect grid/table
-```
-
-Each route accepts query params:
-- `format=svg|pdf|png` (default: inline SVG)
-- `style=modern|traditional|minimal`
-- `color=color|bw` (default: color for web, bw for pdf)
-- `width=800` (SVG pixel width)
-- `house_system=placidus`
-
----
-
-## 7. SVG Chart Wheel Construction Details
-
-### 7.1 Coordinate System
-- Center: (cx, cy) — center of the wheel
-- Radius: configurable (default ~200px for 400x400 SVG)
-- Zodiac ring: outer band, divided into 12 segments of 30 degrees each
-  - 0° = top (standard orientation, or ASC at top for house-aligned)
-- House sectors: from center out, divided by cusp longitudes
-- Planet positions: placed at radius between ring and center, at their
-  ecliptic longitude angle
-
-### 7.2 Angle Convention
-- Standard: 0° Aries at the **left** (traditional "9 o'clock" position),
-  increasing counterclockwise
-- Or: ASC at top (90°), then houses increase counterclockwise
-- We should support both orientations
-
-### 7.3 Element Layers (bottom to top)
-1. Background circle
-2. Zodiac ring (12 sign segments with glyphs)
-3. House sectors (from center to zodiac ring)
-4. House cusp lines + degree labels
-5. Aspect lines (between planets)
-6. Planet markers (glyphs + labels)
-7. Angles labels (ASC, MC, DSC, IC)
-8. Title/legend area
-9. Optional: chart info (name, date, location)
-
-### 7.4 Aspect Line Coding
-
-| Aspect  | Color (web) | Line style (B&W) | Symbol |
-|---------|------------|-------------------|--------|
-| Conjunction | Red (#e74c3c) | Solid (2px) | ☌ |
-| Sextile   | Blue (#3498db) | Dotted (1px) | ⚹ |
-| Square    | Red (#c0392b) | Dashed (2px) | □ |
-| Trine     | Green (#2ecc71) | Dashed (2px) | △ |
-| Quincunx  | Orange (#e67e22) | Dash-dot (1px) | ⚻ |
-| Opposition| Red (#e74c3c) | Solid (2px) | ☍ |
-
-Colors fully customizable per theme.
-
----
-
-## 8. Integration with Existing Tools
-
-### 8.1 Flow for "Generate My Chart"
-
-```
-1. call calculate_natal_chart(birth_data, include_overview=true, include_patterns=true)
-   → get full chart JSON
-
-2. pass JSON to chart_renderer.render_wheel(chart_data, style="modern", color="color")
-   → get SVG string
-
-3. either:
-   a. embed SVG inline in Jinja2 template → HTML page (web dashboard)
-   b. pass SVG to WeasyPrint → PDF (download/print)
-   c. use cairosvg to convert SVG → PNG (thumbnail/preview)
-```
-
-### 8.2 Caching Strategy
-- Chart data is deterministic for a given birth time + location + house system
-- Cache rendered SVGs keyed by `(person_id, chart_type, style, color_mode, options)`
-- Invalidate when person data changes
-- Same enrichment caching pattern as news-mcp: compute once, store forever
-  (the chart doesn't change unless the underlying data changes)
-
-### 8.3 Dashboard Person Detail Enhancement
-The existing `/dashboard/persons/{id}` page shows person data but no chart.
-Add a tab or section:
-- "Chart" tab showing the natal wheel
-- Controls for style, color mode, download as PDF/PNG
-- Transit slider (pick a date, see transit bi-wheel)
-
----
-
-## 9. User-Configurable Themes
-
-### 9.1 Configuration File (chart-colors.yaml)
-
-```yaml
-# Default color theme
-zodiac_signs:
-  fire:    "#e74c3c"
-  earth:   "#27ae60"
-  air:     "#f39c12"
-  water:   "#3498db"
-
-aspect_colors:
-  conjunction:  "#e74c3c"
-  sextile:      "#3498db"
-  square:       "#c0392b"
-  trine:        "#2ecc71"
-  quincunx:     "#e67e22"
-  opposition:   "#e74c3c"
-
-# B&W theme
-bw:
-  aspect_styles:
-    conjunction:  { stroke: "#000", stroke_width: 2, dasharray: "none" }
-    sextile:      { stroke: "#000", stroke_width: 1, dasharray: "3,3" }
-    square:       { stroke: "#000", stroke_width: 2, dasharray: "6,3" }
-    trine:        { stroke: "#000", stroke_width: 2, dasharray: "6,3" }
-    quincunx:     { stroke: "#000", stroke_width: 1, dasharray: "6,3,2,3" }
-    opposition:   { stroke: "#000", stroke_width: 2, dasharray: "none" }
-
-# Dark theme (web)
-dark:
-  background: "#0f1117"
-  text: "#c9d1d9"
-  ring_fill: "#161b22"
-  ring_stroke: "#30363d"
-
-# Light theme (print)
-light:
-  background: "#ffffff"
-  text: "#000000"
-  ring_fill: "#f8f4e8"   # parchment
-  ring_stroke: "#8b7355"
-```
-
----
-
-## 10. PDF Export Details
-
-### Method: SVG → WeasyPrint → PDF
-- WeasyPrint is already installed (v68.1)
-- Generates the chart as SVG, wraps in minimal HTML, converts to PDF
-- Supports:
-  - A4, Letter, and custom page sizes
-  - Embedded fonts (ZodiacFonts via @font-face)
-  - Footer with chart info, date generated
-  - Multi-page: chart wheel on page 1, aspect table on page 2, planet data on page 3
-
-### PDF Layout
-```
-Page 1: Title + Chart Wheel
-┌──────────────────────────────┐
-│  John Doe — Natal Chart      │
-│  1965-07-02 00:05 GMT+1     │
-│  Graz, Austria                │
-│                               │
-│       [  CHART WHEEL  ]       │
-│                               │
-│  Placidus | Tropical          │
-└──────────────────────────────┘
-
-Page 2: Aspect Table
-┌──────────────────────────────┐
-│  Aspect Grid                  │
-│  [ colored/symbol grid ]      │
-│                               │
-│  Tightest Aspects:            │
-│  Sun ☌ Moon  0°32'           │
-│  Mars △ Jupiter 1°15'        │
-│  ...                          │
-└──────────────────────────────┘
-
-Page 3: Planet & House Data
-┌──────────────────────────────┐
-│  Planet Positions             │
-│  Sun    Leo    9°23'   10H   │
-│  Moon   Cancer 22°17'  9H   │
-│  ...                          │
-│                               │
-│  House Cusps                  │
-│  ASC  Virgo    28°41'         │
-│  MC   Gemini   25°12'         │
-│  ...                          │
-└──────────────────────────────┘
-```
-
----
-
-## 11. Implementation Phases
-
-### Phase 1: Foundation
-- [ ] Add `svgwrite` dependency
-- [ ] Create `chart_styles.py` with theme definitions
-- [ ] Create `chart_renderer.py` with basic wheel SVG generation
-  - Zodiac ring with 12 segments and glyph labels
-  - House sector divisions
-  - Planet glyphs placed at correct angles
-- [ ] Verify with a simple natal chart (test data)
-
-### Phase 2: Aspect Lines + B&W Support
-- [ ] Draw aspect lines between planets (gray, by type)
-- [ ] Implement B&W line styles (solid/dashed/dotted per aspect)
-- [ ] Add retrograde marking
-- [ ] Add angle labels (ASC, MC, DSC, IC)
-
-### Phase 3: Color Themes + Print
-- [ ] Implement color theme system
-- [ ] Add zodiac sign element coloring
-- [ ] PDF export via WeasyPrint
-- [ ] B&W print-optimized layout
-
-### Phase 4: Additional Chart Types
-- [ ] Bi-wheel (transit chart)
-- [ ] Synastry side-by-side
-- [ ] Aspect grid/table
-- [ ] Planet/house data tables
-
-### Phase 5: Dashboard Integration
-- [ ] Add chart tab to person detail page
-- [ ] Style/color mode selector
-- [ ] PDF download button
-- [ ] Transit date picker with live preview
-
-### Phase 6: Polish
-- [ ] Traditional / old-fashioned style variant
-- [ ] Custom color theme editor in dashboard
-- [ ] Caching of rendered charts
-- [ ] Performance tests (render 100 charts under X seconds)
-
----
-
-## 12. Open Questions / Decisions Needed
-
-1. **Orientation:** Traditional (0° Aries left) or ASC-at-top? Support both?
-2. **Font strategy:** Download ZodiacFonts at setup time, or bundle a subset?
-   → Recommend: download at pip install / first run, cache in `static/fonts/`
-3. **Ophiuchus:** Support optional 13-sign zodiac?
-   → Phase 2+, configurable
-4. **Chart wheel sizing:** Fixed sizes (S/M/L) or fully responsive?
-   → SVG is inherently scalable; generate at request time, scale in browser
-5. **Client-side interactivity:** Pure server SVG, or add JS for tooltips/zoom?
-   → Phase 1: pure server SVG. Phase 5: add optional JS enhancement.
-6. **Person info on chart:** Show name, birth date, location on the chart image?
-   → Yes, configurable: `show_info=true/false`, per privacy setting
-
----
-
-## 13. Real-World Examples to Study
-
-- **astro.com** charts — the gold standard for readability
-- **astro-seek.com** — clean modern SVG charts
-- **Solar Fire** (desktop) — professional traditional charts
-- **OpenAstro.org** (R. Rottenseifner) — open-source Python astro calculations
-- **瑞士Ephemeris** sample charts — B&W laser-printed from SwissEph
-
----
-
-*End of document — ready for review.*

+ 3 - 1
financial-astrology.md

@@ -1,4 +1,6 @@
-# Financial Astrology — A Compiled Reference
+# Financial Astrology — A Compiled Reference (astro-mcp v0.2.0)
+
+> Repository documentation context: astro-mcp v0.2.0. This is interpretation reference material, not an MCP capability specification.
 
 > Compiled from David Williams' *Financial Astrology: How To Forecast Business, and the Stock Market* (American Federation of Astrologers, 1982, 1984, 1988).
 > Additional sources: Edward R. Dewey's *Cycles: The Science of Prediction* (1947), Samuel Benner's *Prophecies of Future Ups and Downs in Prices* (1875), W. D. Gann, N. D. Kondratieff, and various cycle researchers.

+ 3 - 1
karmic-astrology.md

@@ -1,4 +1,6 @@
-# Karmic Astrology — A Research Compiled Reference
+# Karmic Astrology — A Research Compiled Reference (astro-mcp v0.2.0)
+
+> Repository documentation context: astro-mcp v0.2.0. This is interpretation reference material, not an MCP capability specification.
 
 > Compiled from multiple online sources, June 2026.
 > Primary sources: Martin Schulman's *Karmic Astrology* series, Jan Spiller's *Astrology for the Soul*, Jeffrey Wolf Green's Evolutionary Astrology, WildWitchWest, Almanac.com, and various astrology educators.

+ 0 - 108
mcp-wishlists/karmic-astrology.md

@@ -1,108 +0,0 @@
-# Karmic Astrology — Astro MCP Feature Wishlist
-
-> What the astro MCP should compute/support to enable karmic interpretation by agents.
-
-## Already Supported (verify)
-
-- [x] Natal chart calculation (all planets, houses, angles)
-- [x] Lunar nodes (North/South) — sign, house, degree
-- [x] Planetary positions with retrograde flags
-- [x] Aspects between planets (conjunction, opposition, square, trine, sextile)
-- [x] House systems (Placidus, Equal, Whole Sign)
-- [x] Transit chart calculation
-- [x] Synastry chart (interchart aspects, house overlays)
-- [x] Composite chart calculation
-- [x] Person database (store/retrieve birth data)
-
-## Needed for Karmic Interpretation
-
-### 1. Node-Related Computations
-
-| Feature | Description | Priority |
-|---|---|---|
-| **Node sign/house lookup** | Return North & South Node sign, house, degree for any natal chart | High |
-| **Node axis aspects** | Identify all aspects from natal planets to the nodal axis (conjunction, square, opposition, trine, sextile) with orbs | High |
-| **Node conjunct planets** | Flag planets conjunct South Node (past-life gifts/blockages) and North Node (growth direction) | High |
-| **Node square planets** | Flag planets square the nodal axis ("skipped steps" — unresolved past-life issues) | Medium |
-| **Nodal axis by house** | Return which house the nodal axis falls in (life area of karmic focus) | High |
-
-### 2. Saturn Karmic Computations
-
-| Feature | Description | Priority |
-|---|---|---|
-| **Saturn sign/house** | Return Saturn's sign, house, degree, retrograde status | High |
-| **Saturn aspects to personal planets** | Identify Saturn hard aspects (conjunction, square, opposition) to Sun, Moon, Venus, Mars | High |
-| **Saturn retrograde flag** | Flag Saturn retrograde (unfinished past-life duties) | Medium |
-| **Saturn-Node aspects** | Identify aspects between Saturn and the nodal axis (karmic contracts, blocks) | High |
-| **Saturn return timing** | Calculate current/past/future Saturn return dates | Medium |
-
-### 3. Pluto Evolutionary Computations
-
-| Feature | Description | Priority |
-|---|---|---|
-| **Pluto sign/house** | Return Pluto's sign, house, degree | High |
-| **Pluto polarity point** | Calculate the point opposite Pluto (PPP) — sign, house, degree | High |
-| **Pluto-South Node conjunction** | Flag Pluto conjunct South Node (intense past-life pattern) | High |
-| **Pluto aspects to nodes** | Identify all aspects from Pluto to the nodal axis | Medium |
-| **Pluto aspects to personal planets** | Identify Pluto hard aspects to Sun, Moon, Venus, Mars | Medium |
-
-### 4. Retrograde Planet Analysis
-
-| Feature | Description | Priority |
-|---|---|---|
-| **Retrograde planet list** | Return all retrograde planets in a natal chart with their signs/houses | High |
-| **Retrograde personal planets** | Flag retrograde Mercury, Venus, Mars (karmic communication/love/action themes) | Medium |
-| **Retrograde outer planets** | Flag retrograde Jupiter, Saturn, Uranus, Neptune, Pluto | Low |
-
-### 5. 12th House / Spiritual Karma
-
-| Feature | Description | Priority |
-|---|---|---|
-| **12th house cusp sign** | Return the sign on the 12th house cusp | Medium |
-| **12th house planets** | List any planets in the 12th house | Medium |
-| **12th house ruler** | Identify the ruler of the 12th house and its sign/house/aspects | Low |
-
-### 6. Karmic Synastry (Relationship Karma)
-
-| Feature | Description | Priority |
-|---|---|---|
-| **Saturn-Node synastry** | Identify when one person's Saturn aspects the other's nodal axis | High |
-| **Pluto-Node synastry** | Identify when one person's Pluto aspects the other's nodal axis | High |
-| **Node conjunctions in synastry** | Flag when one person's planet conjuncts the other's North or South Node | High |
-| **Saturn personal planet synastry** | Flag Saturn to Sun/Moon/Venus/Mars interchart aspects | High |
-| **Karmic relationship summary** | Generate a summary of karmic indicators between two charts | Medium |
-
-### 7. Composite Chart Karmic Features
-
-| Feature | Description | Priority |
-|---|---|---|
-| **Composite Node axis** | Return composite chart nodal axis sign/house | Medium |
-| **Composite Saturn** | Return composite Saturn sign, house, aspects | Medium |
-| **Composite Pluto** | Return composite Pluto sign, house, aspects | Medium |
-
-### 8. Transit Triggers for Karmic Periods
-
-| Feature | Description | Priority |
-|---|---|---|
-| **Saturn transit to natal nodes** | Identify when transiting Saturn aspects the natal nodal axis | High |
-| **Pluto transit to natal nodes** | Identify when transiting Pluto aspects the nodal axis | Medium |
-| **Node return timing** | Calculate when the transiting nodal axis returns to natal position (~18.6 year cycle) | Medium |
-| **Eclipse activation of nodes** | Flag eclipses conjunct the natal nodal axis | Low |
-
-## Nice-to-Have
-
-| Feature | Description |
-|---|---|
-| **Part of Fortune calculation** | Arabic Part of Fortune — sign, house, aspects |
-| **Chiron position** | Chiron sign, house, aspects (wounded healer / deepest karmic wound) |
-| **Vertex axis** | Vertex sign/house (fated encounters) |
-| **Fixed star conjunctions** | Major fixed stars conjunct natal planets (karmic signatures) |
-| **Planetary nodes** | Nodes of Pluto, Saturn, etc. (Jeffrey Wolf Green's evolutionary astrology) |
-| **Karmic chart summary** | Auto-generated text summary of the main karmic themes in a natal chart |
-
-## API Design Notes
-
-- All features should accept a `person_id` from the database or raw birth data
-- Orb configuration should be customizable (default: 5° for personal planets, 3° for outer)
-- Return structured JSON with sign, house, degree, retrograde flag, aspect type, orb
-- Support house system parameter (Placidus, Equal, Whole Sign)

+ 0 - 153
mcp-wishlists/natal-astrology.md

@@ -1,153 +0,0 @@
-# Natal Astrology — Astro MCP Feature Wishlist
-
-> What the astro MCP should compute/support to enable natal chart interpretation by agents.
-
-## Already Supported (verify)
-
-- [x] Natal chart calculation (all planets, houses, angles, aspects)
-- [x] Planetary positions with sign, degree, retrograde flag
-- [x] House systems (Placidus, Equal, Whole Sign)
-- [x] Aspect calculation (conjunction, opposition, square, trine, sextile) with orbs
-- [x] Person database (store/retrieve birth data)
-- [x] Transit chart calculation
-- [x] Transit preview (daily aspect snapshots with significance scoring)
-- [x] Lunar nodes (North/South)
-- [x] Synastry chart
-- [x] Composite chart
-- [x] Multiple birth data formats (ISO 8601 with timezone offset)
-
-## Enhancements Needed for Natal Interpretation
-
-### 1. Chart Overview / Summary
-
-| Feature | Description | Priority |
-|---|---|---|
-| **Element balance report** | Count planets by element (fire/earth/air/water) with percentages | High |
-| **Modality balance report** | Count planets by modality (cardinal/fixed/mutable) with percentages | High |
-| **Hemisphere emphasis** | Report which hemisphere (upper/lower/east/west) has most planets | Medium |
-| **Chart shape detection** | Identify bundle, bowl, bucket, splash, locomotive, seesaw, splay patterns | Medium |
-| **Stellium detection** | Flag any sign or house with 3+ planets | High |
-| **Empty house list** | Report which houses have no planets | Low |
-
-### 2. Big Three Enhancement
-
-| Feature | Description | Priority |
-|---|---|---|
-| **Chart ruler identification** | Return the planet ruling the Ascendant sign, with its sign/house/aspects | High |
-| **Sun-Moon aspect** | Specifically return the aspect (if any) between Sun and Moon | Medium |
-| **Sun/Moon phase** | Return the lunar phase at birth (new moon, waxing, full, waning) | Low |
-
-### 3. Aspect Pattern Detection
-
-| Feature | Description | Priority |
-|---|---|---|
-| **T-square detection** | Identify T-square patterns: two planets in opposition, both squaring a third planet | High |
-| **Grand trine detection** | Identify grand trine patterns: three planets in trine, same element | High |
-| **Grand cross detection** | Identify grand cross patterns: four planets forming two oppositions and four squares | High |
-| **Yod detection** | Identify yod patterns: two sextile planets both quincunx a third | Medium |
-| **Aspect pattern summary** | Return all major aspect patterns found in the chart with involved planets/houses | High |
-
-### 4. Aspect Prioritization
-
-| Feature | Description | Priority |
-|---|---|---|
-| **Top aspects by orb** | Return the N tightest aspects (smallest orbs) in the chart | High |
-| **Aspects to Big Three** | Return all aspects involving Sun, Moon, or Ascendant | High |
-| **Aspects to chart ruler** | Return all aspects involving the chart ruler | Medium |
-| **Applying vs. separating** | Flag whether each aspect is applying or separating | Medium |
-
-### 5. House Analysis
-
-| Feature | Description | Priority |
-|---|---|---|
-| **House cusp signs** | Return the sign on each house cusp | High |
-| **House rulers** | For each house, return the ruling planet and its sign/house/condition | Medium |
-| **Planets in houses** | Group planets by house for quick house-themed reading | High |
-| **Angular/succedent/cadent count** | Count planets by house type | Medium |
-
-### 6. Sign Analysis
-
-| Feature | Description | Priority |
-|---|---|---|
-| **Planets in signs** | Group planets by sign for quick sign-themed reading | High |
-| **Sign dominance** | Identify which sign has the most planets (if any) | Low |
-
-### 7. Retrograde Report
-
-| Feature | Description | Priority |
-|---|---|---|
-| **Retrograde planet list** | Return all retrograde planets with sign/house | Medium |
-| **Retrograde emphasis** | Flag charts with 3+ retrograde planets | Low |
-
-### 8. Transit Integration for Natal Context
-
-| Feature | Description | Priority |
-|---|---|---|
-| **Current transits to natal** | Return current transiting aspects to natal planets with orbs and applying/separating | High |
-| **Upcoming major transits** | Return transits to natal Sun/Moon/Ascendant/chart ruler within next N days | Medium |
-| **Saturn return flag** | Flag if the person is currently in their Saturn return window (~28-30 years) | Medium |
-| **Jupiter return flag** | Flag if the person is currently in their Jupiter return window (~12 years) | Low |
-| **Eclipse proximity** | Flag if any natal planet is within 5° of the current eclipse axis | Low |
-
-### 9. Chart Synthesis Output (Nice-to-Have)
-
-| Feature | Description | Priority |
-|---|---|---|
-| **Auto narrative summary** | Generate a structured text summary of the natal chart's main themes | Low |
-| **Strength/challenge list** | Based on aspects, auto-generate a list of natural strengths and growth edges | Low |
-| **Interpretation prompts** | Return structured prompts an agent can use to build a reading | Medium |
-
-## API Design Notes
-
-### Chart Overview Output
-```json
-{
-  "element_balance": { "fire": 3, "earth": 2, "air": 4, "water": 3 },
-  "modality_balance": { "cardinal": 4, "fixed": 5, "mutable": 3 },
-  "hemisphere": { "upper": 7, "lower": 5, "east": 6, "west": 6 },
-  "chart_shape": "bowl",
-  "stelliums": [{"sign": "Scorpio", "planets": ["Sun", "Mercury", "Venus"]}],
-  "empty_houses": [3, 9]
-}
-```
-
-### Aspect Pattern Output
-```json
-{
-  "patterns": [
-    {
-      "type": "T-square",
-      "planets": ["Moon", "Mars", "Saturn"],
-      "apex": "Saturn",
-      "houses": [4, 7, 10],
-      "signs": ["Cancer", "Libra", "Capricorn"],
-      "modality": "cardinal"
-    }
-  ]
-}
-```
-
-### Top Aspects Output
-```json
-{
-  "top_aspects": [
-    {
-      "planet_a": "Sun",
-      "planet_b": "Moon",
-      "aspect": "trine",
-      "orb": 2.3,
-      "applying": true,
-      "sign_a": "Leo",
-      "sign_b": "Sagittarius",
-      "house_a": 5,
-      "house_b": 9
-    }
-  ]
-}
-```
-
-## House System Notes
-
-- Default: Placidus (most common in modern Western astrology)
-- Support: Equal House, Whole Sign (important for Vedic/traditional work)
-- All house-related features should respect the selected house system

+ 0 - 110
mcp-wishlists/relationship-astrology.md

@@ -1,110 +0,0 @@
-# Relationship Astrology — Astro MCP Feature Wishlist
-
-> What the astro MCP should compute/support to enable relationship chart interpretation by agents.
-
-## Already Supported (verify)
-
-- [x] Natal chart calculation
-- [x] Synastry chart (interchart aspects, house overlays)
-- [x] Composite chart calculation
-- [x] Person database
-- [x] Aspect calculation with configurable orbs
-- [x] House systems (Placidus, Equal, Whole Sign)
-- [x] Transit chart calculation
-
-## Needed for Relationship Interpretation
-
-### 1. Davison Chart
-
-| Feature | Description | Priority |
-|---|---|---|
-| **Davison chart calculation** | Calculate the midpoint-in-time-and-space chart for two people (average of birth dates, times, locations) | High |
-| **Davison planet positions** | Return all planetary positions, signs, houses, angles for the Davison chart | High |
-| **Davison aspects** | Calculate aspect patterns within the Davison chart | High |
-| **Davison transits** | Calculate transits to the Davison chart (for timing relationship events) | Medium |
-| **Davison progressions** | Calculate secondary progressions to the Davison chart | Low |
-
-### 2. Synastry Enhancements
-
-| Feature | Description | Priority |
-|---|---|---|
-| **Interchart aspects matrix** | Full matrix of aspects between all planets of Person A and Person B, with orbs | High |
-| **House overlay report** | For each planet in Chart A, report which house it falls in Chart B (and vice versa) | High |
-| **Top synastry aspects** | Return the 10-15 tightest/most significant interchart aspects, ranked by orb and significance | High |
-| **Relationship significator aspects** | Specifically identify Venus-Mars, Moon-Venus, Sun-Moon, Sun-Saturn interchart aspects | High |
-| **Saturn synastry aspects** | Specifically flag all Saturn interchart aspects to personal planets and nodes | High |
-| **Node synastry aspects** | Specifically flag all interchart aspects to the nodal axes | High |
-
-### 3. Composite Chart Enhanced Interpretation
-
-| Feature | Description | Priority |
-|---|---|---|
-| **Composite planet meanings** | Return structured data for each composite planet (sign, house, aspects) | Medium |
-| **Composite aspect list** | Return all aspects within the composite chart with orbs (tight orbs ≤3°) | Medium |
-| **Composite angular planets** | Flag planets conjunct the composite angles (AC, MC, DC, IC) | Medium |
-| **Composite chart ruler** | Identify and return the chart ruler's condition (sign, house, aspects) | Medium |
-| **Composite unaspected planets** | Flag planets with no major aspects (relationship blind spots) | Medium |
-| **Composite house emphasis** | Calculate which houses have the most planets (thematic emphasis) | Medium |
-| **Composite transit report** | Calculate transits to composite chart planets/angles for timing | Medium |
-
-### 4. Coalescent Chart (Nice-to-Have)
-
-| Feature | Description | Priority |
-|---|---|---|
-| **Coalescent chart calculation** | Implement Lawrence Grinnell's harmonic coalescent method | Low |
-| **Harmonic arc calculation** | Calculate shortest arcs between planet pairs and derive harmonic number | Low |
-| **Coalescent transit sensitivity** | Identify transits that strongly activate the coalescent chart | Low |
-
-### 5. Karmic Relationship Synthesis
-
-| Feature | Description | Priority |
-|---|---|---|
-| **Karmic synastry summary** | Combine synastry + composite + Davison karmic indicators into a structured report | Medium |
-| **Relationship type classifier** | Based on chart data, classify relationship type indicators (romantic, business, family, friendship, karmic) | Low |
-| **Compatibility score** | Generate a structured compatibility assessment (not a single number, but category scores: emotional, sexual, intellectual, commitment, spiritual) | Low |
-
-### 6. Relationship Timing
-
-| Feature | Description | Priority |
-|---|---|---|
-| **Composite transit preview** | Transit-to-composite chart aspect snapshots over a date range | Medium |
-| **Davison transit preview** | Transit-to-Davison chart aspect snapshots over a date range | Medium |
-| **Synthesis transit preview** | Combine transit-to-natal (both people) + transit-to-composite/Davison for relationship timing | Low |
-| **Eclipse activation of relationship charts** | Flag eclipses hitting composite/Davison angles or personal planets | Low |
-
-## API Design Notes
-
-### Davison Chart Input
-```
-person1_id: string (from database)
-person2_id: string (from database)
-  OR
-person1_datetime, person1_latitude, person1_longitude
-person2_datetime, person2_latitude, person2_longitude
-house_system: "placidus" | "equal" | "whole_sign"
-orb_limits: optional per-aspect-type orb configuration
-```
-
-### Synastry Enhancement Input
-```
-person1_id / person2_id (or raw birth data)
-include_house_overlays: boolean
-include_aspect_matrix: boolean
-max_orb: number (default: 5)
-significance_filter: optional minimum significance score
-```
-
-### Output Format
-- Structured JSON with sections: `interchart_aspects`, `house_overlays`, `composite_planets`, `composite_aspects`, `davison_planets`, `davison_aspects`
-- Each aspect: `{planet_a, planet_b, aspect_type, orb, applying/separating, significance}`
-- Each house overlay: `{planet, owner_chart, house, house_meaning}`
-- Summary fields: `top_aspects`, `saturn_contacts`, `node_contacts`, `venus_mars_contacts`
-
-## Relationship Reading Workflow (for agents)
-
-1. **Synastry** → interchart aspects + house overlays (interaction patterns, chemistry, friction)
-2. **Composite chart** → the relationship's identity, structure, public face
-3. **Davison chart** → the relationship's inner experience, emotional tone, long-term evolution
-4. **Karmic overlay** → Saturn/Pluto/Node contacts across all three layers
-5. **Transit timing** → transits to composite/Davison for relationship milestones
-6. **Synthesis** → repeated themes across all layers = core relationship narrative

+ 3 - 1
natal-astrology.md

@@ -1,4 +1,6 @@
-# Natal Astrology — A Comprehensive Reference
+# Natal Astrology — A Comprehensive Reference (astro-mcp v0.2.0)
+
+> Repository documentation context: astro-mcp v0.2.0. This is interpretation reference material, not an MCP capability specification.
 
 > Compiled from multiple online sources, June 2026.
 > Primary sources: Almanac.com, Perplexity AI synthesis, Cafe Astrology, Steven Forrest, industry-standard interpretation frameworks.

+ 3 - 1
relationship-astrology-techniques.md

@@ -1,4 +1,6 @@
-# Relationship Astrology Techniques: Synastry, Composite, Davison, and Coalescent Charts
+# Relationship Astrology Techniques: Synastry, Composite, Davison, and Coalescent Charts (astro-mcp v0.2.0)
+
+> Repository documentation context: astro-mcp v0.2.0. This is interpretation reference material, not an MCP capability specification.
 
 Source: <https://www.wildwitchwest.com/single-post/relationship-astrology-techniques-synastry-composite-davison-and-coalescent-charts>
 

+ 0 - 196
render_test_charts.py

@@ -1,196 +0,0 @@
-#!/usr/bin/env python3
-"""Test script for render_natal_chart_by_id via MCP client.
-
-Calls the astro-mcp server via MCP SSE to render test charts for
-celebrities stored in the database.
-
-Usage:
-    python3 render_test_charts.py [--format svg|png|jpg] [--size N]
-
-Requires:
-    - astro-mcp server running (e.g., on thinkcenter-2:7016)
-"""
-
-from __future__ import annotations
-
-import argparse
-import asyncio
-import json
-import os
-from datetime import timedelta
-from typing import Any
-
-from mcp import ClientSession
-from mcp.client.sse import sse_client
-
-ASTRO_MCP_URL = os.environ.get(
-    "ASTRO_MCP_URL", "http://192.168.0.249:7016/mcp/sse"
-)
-
-# Person nicknames in the database (as used in test_live_charts.py)
-PERSON_IDS = [
-    "trump",   # Donald Trump
-    "chaka",   # Chaka Khan
-    "lucky","grace"
-]
-
-
-def _payload_from_result(result: Any) -> dict[str, Any]:
-    """Extract a dict payload from an MCP CallToolResult."""
-    payload = getattr(result, "structuredContent", None)
-    if isinstance(payload, dict):
-        return payload
-
-    content_items = getattr(result, "content", []) or []
-    for item in content_items:
-        text = getattr(item, "text", None)
-        if not isinstance(text, str) or not text.strip():
-            continue
-        try:
-            decoded = json.loads(text)
-        except Exception:
-            continue
-        if isinstance(decoded, dict):
-            return decoded
-
-    error_texts = []
-    for item in content_items:
-        text = getattr(item, "text", None)
-        if isinstance(text, str) and text.strip():
-            error_texts.append(text.strip())
-    if error_texts:
-        return {"error": error_texts[0]}
-    return {}
-
-
-async def call_astro_tool(
-    session: ClientSession, tool_name: str, arguments: dict[str, Any]
-) -> dict[str, Any]:
-    """Call a tool on the astro-mcp server via MCP session."""
-    result = await session.call_tool(tool_name, arguments)
-    return _payload_from_result(result)
-
-
-async def render_person_chart(person_id: str, fmt: str, size: int, color_mode: str, timeout: float = 30.0) -> dict[str, Any] | None:
-    """Render a natal chart for one person by ID via MCP."""
-    url = ASTRO_MCP_URL
-    if not url.endswith("/mcp/sse"):
-        url = url.rstrip("/") + "/mcp/sse"
-
-    async with sse_client(url, timeout=timeout, sse_read_timeout=timeout) as streams:
-        async with ClientSession(
-            *streams, read_timeout_seconds=timedelta(seconds=timeout)
-        ) as session:
-            await session.initialize()
-            
-            # Get chart data first (for moon info)
-            chart_args = {"person_id": person_id}
-            chart_result = await call_astro_tool(session, "calculate_natal_chart_by_id", chart_args)
-            if "error" in chart_result:
-                print(f"  ERROR calculating {person_id}: {chart_result['error']}")
-                return None
-            
-            # Then render
-            render_args = {"person_id": person_id, "color_mode": color_mode, "size": size, "format": fmt}
-            render_result = await call_astro_tool(session, "render_natal_chart_by_id", render_args)
-            if "error" in render_result:
-                print(f"  ERROR rendering {person_id}: {render_result['error']}")
-                return None
-            
-            # Save to output directory
-            content = render_result.get("content", "")
-            ext = fmt
-            filename = f"{person_id}_natal.{ext}"
-            outdir = "/home/shared/astro"
-            os.makedirs(outdir, exist_ok=True)
-            outpath = os.path.join(outdir, filename)
-
-            mode = "w" if isinstance(content, str) else "wb"
-            with open(outpath, mode) as f:
-                f.write(content)
-
-            # Extract moon info from chart data
-            moon_info = None
-            for p in chart_result.get("planets", []):
-                if p.get("body") == "moon":
-                    moon_info = {
-                        "absolute_lon": p.get("absolute_lon"),
-                        "degree_within_sign": p.get("degree_within_sign"),
-                        "sign": p.get("sign"),
-                        "house": p.get("house"),
-                        "retrograde": p.get("retrograde"),
-                    }
-                    break
-
-            return {
-                "person_id": person_id,
-                "output": outpath,
-                "format": render_result.get("format"),
-                "content_type": render_result.get("content_type"),
-                "size_bytes": len(content) if isinstance(content, bytes) else len(content.encode("utf-8")),
-                "moon": moon_info,
-                "all_planets": [
-                    {"body": p.get("body"), "absolute_lon": p.get("absolute_lon"),
-                     "degree_within_sign": p.get("degree_within_sign"),
-                     "sign": p.get("sign"), "house": p.get("house"),
-                     "retrograde": p.get("retrograde")}
-                    for p in chart_result.get("planets", [])
-                ],
-                "houses_count": len(chart_result.get("houses", [])),
-                "aspects_count": len(chart_result.get("aspects", [])),
-            }
-
-
-async def main():
-    parser = argparse.ArgumentParser(description="Render test natal charts via MCP client")
-    parser.add_argument("--format", default="svg", choices=["svg", "png", "jpg"],
-                        help="Output format (default: svg)")
-    parser.add_argument("--size", type=int, default=600,
-                        help="Canvas size in pixels (default: 600)")
-    parser.add_argument("--color-mode", default="color", choices=["color", "bw", "dark"],
-                        help="Color theme (default: color)")
-    args = parser.parse_args()
-
-    print(f"Rendering test charts — format={args.format}, size={args.size}px, theme={args.color_mode}")
-    print(f"Output directory: /home/shared/astro/")
-    print(f"Persons: {', '.join(PERSON_IDS)}")
-    print()
-
-    results = []
-    for person_id in PERSON_IDS:
-        print(f"Processing {person_id}...")
-        result = await render_person_chart(person_id, args.format, args.size, args.color_mode)
-        if result:
-            results.append(result)
-            print(f"  Saved: {result['output']} ({result['size_bytes']:,} bytes)")
-            if result["moon"]:
-                m = result["moon"]
-                print(f"  MOON: {m['sign']} {m['degree_within_sign']:.1f}° (lon={m['absolute_lon']:.2f}°, house {m['house']})")
-            else:
-                print(f"  MOON: not found in data!")
-            print(f"  Planets: {len(result['all_planets'])}, Houses: {result['houses_count']}, Aspects: {result['aspects_count']}")
-        else:
-            print(f"  FAILED")
-        print()
-
-    # Summary comparison
-    print("=" * 60)
-    print("MOON COMPARISON")
-    print("=" * 60)
-    for r in results:
-        m = r.get("moon", {})
-        if m:
-            print(f"  {r['person_id']:20s} | Moon in {m.get('sign','?'):12s} {m.get('degree_within_sign',0):5.1f}° | House {m.get('house','?')} | Retrograde: {m.get('retrograde',False)}")
-        else:
-            print(f"  {r['person_id']:20s} | Moon data MISSING")
-    print()
-
-    # Save JSON summary
-    summary_path = "/home/shared/astro/test_charts_summary.json"
-    with open(summary_path, "w") as f:
-        json.dump(results, f, indent=2, default=str)
-    print(f"Summary saved to: {summary_path}")
-
-
-if __name__ == "__main__":
-    asyncio.run(main())

+ 1 - 1
src/astro_mcp/__init__.py

@@ -1,3 +1,3 @@
 """astro-mcp: MCP server for astrological chart calculations."""
 
-__version__ = "0.10.0"
+__version__ = "0.2.0"

+ 98 - 0
src/astro_mcp/chart_resources.py

@@ -0,0 +1,98 @@
+"""Chart artifact delivery through MCP resources and shared HTTP helpers."""
+
+from __future__ import annotations
+
+from typing import Any
+
+from fastmcp.resources import ResourceContent, ResourceResult
+
+from .server import mcp
+from .chart_renderer import render_natal_wheel
+
+_SUPPORTED_FORMATS = {"svg", "png", "jpg", "jpeg"}
+_CONTENT_TYPES = {
+    "svg": "image/svg+xml",
+    "png": "image/png",
+    "jpg": "image/jpeg",
+    "jpeg": "image/jpeg",
+}
+
+
+def _render_options(
+    *,
+    format: str = "svg",
+    size: int = 600,
+    style: str = "modern",
+    color_mode: str = "color",
+    table_position: str = "none",
+    include_planets: bool = False,
+    include_houses: bool = False,
+    title: str | None = None,
+) -> dict[str, Any]:
+    fmt = format.lower()
+    if fmt not in _SUPPORTED_FORMATS:
+        raise ValueError("unsupported chart format: use svg, png, jpg, or jpeg")
+    if size <= 0:
+        raise ValueError("chart size must be greater than zero")
+    return {
+        "style": style,
+        "color_mode": color_mode,
+        "size": size,
+        "table_position": table_position,
+        "include_planets": include_planets,
+        "include_houses": include_houses,
+        "title": title,
+        "format": fmt,
+    }
+
+
+async def render_natal_artifact(
+    person_id: str,
+    *,
+    format: str = "svg",
+    size: int = 600,
+    style: str = "modern",
+    color_mode: str = "color",
+    table_position: str = "none",
+    include_planets: bool = False,
+    include_houses: bool = False,
+    title: str | None = None,
+) -> tuple[bytes | str, str]:
+    """Calculate and render one database-backed natal chart."""
+    options = _render_options(
+        format=format,
+        size=size,
+        style=style,
+        color_mode=color_mode,
+        table_position=table_position,
+        include_planets=include_planets,
+        include_houses=include_houses,
+        title=title,
+    )
+    from .by_id_tools import calculate_natal_chart_by_id
+
+    chart_data = await calculate_natal_chart_by_id(person_id=person_id)
+    if "error" in chart_data:
+        raise LookupError(chart_data["error"])
+    result = render_natal_wheel(chart_data, **options)
+    return result["content"], result["content_type"]
+
+
+@mcp.resource(
+    "astro://charts/natal/{person_id}",
+    name="natal_chart",
+    description="Rendered natal chart for a person in the database.",
+    mime_type="image/svg+xml",
+)
+async def natal_chart_resource(person_id: str) -> ResourceResult:
+    """Return a database-backed natal chart as an MCP graphic resource."""
+    content, content_type = await render_natal_artifact(person_id)
+    return ResourceResult([ResourceContent(content, mime_type=content_type)])
+
+
+def content_type_for_format(format: str) -> str:
+    """Return the HTTP content type for a supported chart format."""
+    fmt = format.lower()
+    if fmt not in _SUPPORTED_FORMATS:
+        raise ValueError("unsupported chart format: use svg, png, jpg, or jpeg")
+    return _CONTENT_TYPES[fmt]

+ 0 - 836
src/astro_mcp/render_tools.py

@@ -1,836 +0,0 @@
-"""
-Chart rendering tools for astro-mcp.
-
-Render visual chart wheels (SVG/PNG/JPG) from calculated chart data.
-Combines calculation + rendering in one step.
-"""
-
-from __future__ import annotations
-
-from typing import Any
-
-from .server import mcp
-from .chart_renderer import (
-    render_natal_wheel,
-    render_transit_wheel,
-    render_synastry_wheel,
-)
-from .chart_tools import (
-    calculate_natal_chart,
-    calculate_transit_chart,
-    calculate_synastry_chart,
-    calculate_composite_chart,
-    calculate_davison_chart,
-)
-from .by_id_tools import (
-    calculate_natal_chart_by_id,
-    calculate_transit_chart_by_id,
-    calculate_synastry_chart_by_id,
-    calculate_composite_chart_by_id,
-    calculate_davison_chart_by_id,
-)
-
-# ═══════════════════════════════════════════════════════════════════════
-# CHART RENDERING TOOLS
-# ═══════════════════════════════════════════════════════════════════════
-# These tools render visual chart wheels from birth data.
-# They combine calculation + rendering in one step.
-# For data-only output, use the calculate_* tools instead.
-# ═══════════════════════════════════════════════════════════════════════
-
-# ── Shared render options (used by all render_* tools) ────────────────
-
-_RENDER_STYLE_HELP = (
-    "Chart visual style: 'modern' (clean, minimal), 'traditional' "
-    "(ornate, classical), or 'minimal' (bare bones)."
-)
-_RENDER_COLOR_HELP = (
-    "Color mode: 'color' (full color with element-themed zodiac ring), "
-    "'bw' (black/white, aspect lines distinguished by style), or "
-    "'dark' (dark background for web display)."
-)
-_RENDER_SIZE_HELP = "SVG width/height in pixels (default: 600)."
-_RENDER_TABLE_HELP = "Include an aspect table below the wheel."
-_RENDER_PLANETS_HELP = "Include a planet data table below the wheel."
-_RENDER_HOUSES_HELP = "Include a house cusp table below the wheel."
-_RENDER_TITLE_HELP = "Custom chart title. Auto-generated if not provided."
-
-
-# ── render_natal_chart ────────────────────────────────────────────────
-
-@mcp.tool()
-async def render_natal_chart(
-    # ── Birth data (same as calculate_natal_chart) ──────────────────
-    birth_datetime: str,
-    latitude: float,
-    longitude: float,
-    elevation: float = 0.0,
-    house_system: str = "placidus",
-    orb_limits: dict[str, float] | None = None,
-    top_n_aspects: int | None = None,
-    # ── Rendering options ───────────────────────────────────────────
-    style: str = "modern",
-    color_mode: str = "color",
-    size: int = 600,
-    table_position: str = "none",
-    include_planets: bool = False,
-    include_houses: bool = False,
-    title: str | None = None,
-    subtitle: str | None = None,
-    format: str = "svg",
-) -> dict[str, Any]:
-    """Render a natal chart wheel.
-
-    Calculates planetary positions and renders a visual zodiac wheel with
-    planets, houses, and aspect lines. Output as SVG or raster image (PNG/JPG).
-
-    BIRTH DATA (required):
-        birth_datetime: ISO 8601 datetime with timezone (e.g. "1990-05-15T10:30:00+01:00").
-        latitude: Birth latitude in decimal degrees (-90 to 90).
-        longitude: Birth longitude in decimal degrees (-180 to 180).
-
-    BIRTH DATA (optional):
-        elevation: Birth elevation in meters (default: 0).
-        house_system: "placidus" (default), "equal", or "whole_sign".
-        orb_limits: Per-aspect orb overrides, e.g. {"conjunction": 10}.
-        top_n_aspects: Limit aspects to the N tightest by orb.
-
-    RENDERING OPTIONS:
-        style: {_RENDER_STYLE_HELP}
-        color_mode: {_RENDER_COLOR_HELP}
-        size: {_RENDER_SIZE_HELP}
-        table_position: "none" (wheel only), "below" (portrait layout with tables
-            under the wheel), or "right" (landscape layout with tables to the right).
-        include_planets: Include a planet data table (requires table_position != "none").
-        include_houses: Include a house cusp table (requires table_position != "none").
-        title: Custom chart title. Auto-generated if not provided.
-        subtitle: Custom subtitle. Auto-generated from birth data if not provided.
-        format: Output format — "svg" (default), "png", or "jpg".
-
-    Returns:
-        Dict with "content", "format", "content_type", "width", "height",
-        and "included" (list of what's in the chart).
-    """
-    from .chart_renderer import render_natal_wheel
-
-    chart_data = await calculate_natal_chart(
-        birth_datetime=birth_datetime,
-        latitude=latitude,
-        longitude=longitude,
-        elevation=elevation,
-        house_system=house_system,
-        orb_limits=orb_limits,
-        top_n_aspects=top_n_aspects,
-    )
-    if "error" in chart_data:
-        return chart_data
-
-    result = render_natal_wheel(
-        chart_data,
-        style=style,
-        color_mode=color_mode,
-        size=size,
-        table_position=table_position,
-        include_planets=include_planets,
-        include_houses=include_houses,
-        title=title,
-        subtitle=subtitle,
-        format=format,
-    )
-    result["included"] = _included_list(table_position, include_planets, include_houses)
-    return result
-
-
-# ── render_natal_chart_by_id ──────────────────────────────────────────
-
-@mcp.tool()
-async def render_natal_chart_by_id(
-    # ── Person lookup (same as calculate_natal_chart_by_id) ─────────
-    person_id: str,
-    house_system: str = "placidus",
-    orb_limits: dict[str, float] | None = None,
-    top_n_aspects: int | None = None,
-    # ── Rendering options ───────────────────────────────────────────
-    style: str = "modern",
-    color_mode: str = "color",
-    size: int = 600,
-    table_position: str = "none",
-    include_planets: bool = False,
-    include_houses: bool = False,
-    title: str | None = None,
-    format: str = "svg",
-) -> dict[str, Any]:
-    """Render a natal chart wheel for a person from the database.
-
-    Looks up birth data by person_id or nickname, calculates the chart,
-    and renders it as a wheel. Output as SVG or raster image (PNG/JPG).
-
-    PERSON LOOKUP (required):
-        person_id: ID or nickname of a person in the persons database.
-
-    PERSON LOOKUP (optional):
-        house_system: "placidus" (default), "equal", or "whole_sign".
-        orb_limits: Per-aspect orb overrides.
-        top_n_aspects: Limit aspects to the N tightest by orb.
-
-    RENDERING OPTIONS:
-        style: {_RENDER_STYLE_HELP}
-        color_mode: {_RENDER_COLOR_HELP}
-        size: {_RENDER_SIZE_HELP}
-        table_position: "none" (wheel only), "below" (portrait), or "right" (landscape).
-        include_planets: {_RENDER_PLANETS_HELP}
-        include_houses: {_RENDER_HOUSES_HELP}
-        title: {_RENDER_TITLE_HELP}
-
-    Returns:
-        Dict with "svg" (SVG string), "format", "width", "height", "included".
-    """
-    from .chart_renderer import render_natal_wheel
-
-    chart_data = await calculate_natal_chart_by_id(
-        person_id=person_id,
-        house_system=house_system,
-        orb_limits=orb_limits,
-        top_n_aspects=top_n_aspects,
-    )
-    if "error" in chart_data:
-        return chart_data
-
-    result = render_natal_wheel(
-        chart_data,
-        style=style,
-        color_mode=color_mode,
-        size=size,
-        table_position=table_position,
-        include_planets=include_planets,
-        include_houses=include_houses,
-        title=title,
-        format=format,
-    )
-    result["included"] = _included_list(table_position, include_planets, include_houses)
-    return result
-
-
-# ── render_transit_chart ──────────────────────────────────────────────
-
-@mcp.tool()
-async def render_transit_chart(
-    # ── Birth data + transit date (same as calculate_transit_chart) ─
-    birth_datetime: str,
-    transit_datetime: str,
-    latitude: float,
-    longitude: float,
-    transit_latitude: float | None = None,
-    transit_longitude: float | None = None,
-    elevation: float = 0.0,
-    house_system: str = "placidus",
-    orb_limits: dict[str, float] | None = None,
-    # ── Rendering options ───────────────────────────────────────────
-    style: str = "modern",
-    color_mode: str = "color",
-    size: int = 600,
-    title: str | None = None,
-    format: str = "svg",
-) -> dict[str, Any]:
-    """Render a transit chart as a bi-wheel (natal inner, transit outer).
-
-    Calculates natal and transiting planet positions, then renders a bi-wheel
-    showing natal planets inside and transiting planets outside, with
-    transit-to-natal aspect lines. Output as SVG or raster image (PNG/JPG).
-
-    BIRTH DATA (required):
-        birth_datetime: ISO 8601 birth datetime with timezone.
-        transit_datetime: ISO 8601 transit datetime (the "now" or future date).
-        latitude: Birth latitude in decimal degrees.
-        longitude: Birth longitude in decimal degrees.
-
-    TRANSIT LOCATION (optional):
-        transit_latitude: Location latitude for transit calculation. Defaults to birth latitude.
-        transit_longitude: Location longitude for transit calculation. Defaults to birth longitude.
-
-    CHART OPTIONS:
-        elevation: Birth elevation in meters (default: 0).
-        house_system: "placidus" (default), "equal", or "whole_sign".
-        orb_limits: Per-aspect orb overrides.
-
-    RENDERING OPTIONS:
-        style: {_RENDER_STYLE_HELP}
-        color_mode: {_RENDER_COLOR_HELP}
-        size: {_RENDER_SIZE_HELP}
-        title: {_RENDER_TITLE_HELP}
-        format: Output format — "svg" (default), "png", or "jpg".
-
-    Returns:
-        Dict with "content", "format", "content_type", "width", "height", "included".
-    """
-    from .chart_renderer import render_transit_wheel
-
-    chart_data = await calculate_transit_chart(
-        birth_datetime=birth_datetime,
-        transit_datetime=transit_datetime,
-        latitude=latitude,
-        longitude=longitude,
-        transit_latitude=transit_latitude,
-        transit_longitude=transit_longitude,
-        elevation=elevation,
-        house_system=house_system,
-        orb_limits=orb_limits,
-    )
-    if "error" in chart_data:
-        return chart_data
-
-    result = render_transit_wheel(
-        chart_data,
-        style=style,
-        color_mode=color_mode,
-        size=size,
-        title=title,
-        format=format,
-    )
-    result["included"] = ["wheel"]
-    return result
-
-
-# ── render_transit_chart_by_id ────────────────────────────────────────
-
-@mcp.tool()
-async def render_transit_chart_by_id(
-    # ── Person lookup + transit date ────────────────────────────────
-    person_id: str,
-    transit_datetime: str,
-    transit_latitude: float | None = None,
-    transit_longitude: float | None = None,
-    house_system: str = "placidus",
-    orb_limits: dict[str, float] | None = None,
-    # ── Rendering options ───────────────────────────────────────────
-    style: str = "modern",
-    color_mode: str = "color",
-    size: int = 600,
-    title: str | None = None,
-    format: str = "svg",
-) -> dict[str, Any]:
-    """Render a transit bi-wheel for a person from the database.
-
-    Looks up birth data by person_id, calculates transits for the given
-    date, and renders a bi-wheel chart. Output as SVG or raster image (PNG/JPG).
-
-    PERSON LOOKUP (required):
-        person_id: ID or nickname of a person in the persons database.
-        transit_datetime: ISO 8601 transit datetime.
-
-    TRANSIT LOCATION (optional):
-        transit_latitude: Location latitude. Defaults to birth latitude.
-        transit_longitude: Location longitude. Defaults to birth longitude.
-
-    CHART OPTIONS:
-        house_system: "placidus" (default), "equal", or "whole_sign".
-        orb_limits: Per-aspect orb overrides.
-
-    RENDERING OPTIONS:
-        style: {_RENDER_STYLE_HELP}
-        color_mode: {_RENDER_COLOR_HELP}
-        size: {_RENDER_SIZE_HELP}
-        title: {_RENDER_TITLE_HELP}
-
-    Returns:
-        Dict with "svg", "format", "width", "height", "included".
-    """
-    from .chart_renderer import render_transit_wheel
-
-    chart_data = await calculate_transit_chart_by_id(
-        person_id=person_id,
-        transit_datetime=transit_datetime,
-        transit_latitude=transit_latitude,
-        transit_longitude=transit_longitude,
-        house_system=house_system,
-        orb_limits=orb_limits,
-    )
-    if "error" in chart_data:
-        return chart_data
-
-    result = render_transit_wheel(
-        chart_data,
-        style=style,
-        color_mode=color_mode,
-        size=size,
-        title=title,
-        format=format,
-    )
-    result["included"] = ["wheel"]
-    return result
-
-
-# ── render_synastry_chart ─────────────────────────────────────────────
-
-@mcp.tool()
-async def render_synastry_chart(
-    # ── Two people's birth data (same as calculate_synastry_chart) ──
-    person1_datetime: str,
-    person1_latitude: float,
-    person1_longitude: float,
-    person2_datetime: str,
-    person2_latitude: float,
-    person2_longitude: float,
-    elevation: float = 0.0,
-    house_system: str = "placidus",
-    orb_limits: dict[str, float] | None = None,
-    top_n_aspects: int | None = None,
-    # ── Rendering options ───────────────────────────────────────────
-    style: str = "modern",
-    color_mode: str = "color",
-    size: int = 800,
-    title: str | None = None,
-    format: str = "svg",
-) -> dict[str, Any]:
-    """Render a synastry (relationship) chart with two side-by-side wheels.
-
-    Calculates both natal charts and renders them side by side with
-    interaspect lines between the two charts. Output as SVG or raster image (PNG/JPG).
-
-    PERSON 1 (required):
-        person1_datetime: ISO 8601 birth datetime with timezone.
-        person1_latitude: Birth latitude in decimal degrees.
-        person1_longitude: Birth longitude in decimal degrees.
-
-    PERSON 2 (required):
-        person2_datetime: ISO 8601 birth datetime with timezone.
-        person2_latitude: Birth latitude in decimal degrees.
-        person2_longitude: Birth longitude in decimal degrees.
-
-    CHART OPTIONS:
-        elevation: Birth elevation in meters (default: 0).
-        house_system: "placidus" (default), "equal", or "whole_sign".
-        orb_limits: Per-aspect orb overrides.
-        top_n_aspects: Limit interaspects to the N tightest by orb.
-
-    RENDERING OPTIONS:
-        style: {_RENDER_STYLE_HELP}
-        color_mode: {_RENDER_COLOR_HELP}
-        size: {_RENDER_SIZE_HELP}
-        title: {_RENDER_TITLE_HELP}
-
-    Returns:
-        Dict with "svg", "format", "width", "height", "included".
-    """
-    from .chart_renderer import render_synastry_wheel
-
-    chart_data = await calculate_synastry_chart(
-        person1_datetime=person1_datetime,
-        person1_latitude=person1_latitude,
-        person1_longitude=person1_longitude,
-        person2_datetime=person2_datetime,
-        person2_latitude=person2_latitude,
-        person2_longitude=person2_longitude,
-        elevation=elevation,
-        house_system=house_system,
-        orb_limits=orb_limits,
-        top_n_aspects=top_n_aspects,
-    )
-    if "error" in chart_data:
-        return chart_data
-
-    result = render_synastry_wheel(
-        chart_data,
-        style=style,
-        color_mode=color_mode,
-        size=size,
-        title=title,
-        format=format,
-    )
-    result["included"] = ["wheel"]
-    return result
-
-
-# ── render_synastry_chart_by_id ───────────────────────────────────────
-
-@mcp.tool()
-async def render_synastry_chart_by_id(
-    # ── Two person IDs ──────────────────────────────────────────────
-    person1_id: str,
-    person2_id: str,
-    house_system: str = "placidus",
-    orb_limits: dict[str, float] | None = None,
-    top_n_aspects: int | None = None,
-    # ── Rendering options ───────────────────────────────────────────
-    style: str = "modern",
-    color_mode: str = "color",
-    size: int = 800,
-    title: str | None = None,
-    format: str = "svg",
-) -> dict[str, Any]:
-    """Render a synastry chart for two people from the database.
-
-    Looks up both persons by ID or nickname, calculates their synastry,
-    and renders side-by-side natal wheels with interaspect lines.
-    Output as SVG or raster image (PNG/JPG).
-
-    PERSON LOOKUP (required):
-        person1_id: ID or nickname of person 1 in the persons database.
-        person2_id: ID or nickname of person 2 in the persons database.
-
-    CHART OPTIONS:
-        house_system: "placidus" (default), "equal", or "whole_sign".
-        orb_limits: Per-aspect orb overrides.
-        top_n_aspects: Limit interaspects to the N tightest by orb.
-
-    RENDERING OPTIONS:
-        style: {_RENDER_STYLE_HELP}
-        color_mode: {_RENDER_COLOR_HELP}
-        size: {_RENDER_SIZE_HELP}
-        title: {_RENDER_TITLE_HELP}
-
-    Returns:
-        Dict with "svg", "format", "width", "height", "included".
-    """
-    from .chart_renderer import render_synastry_wheel
-
-    chart_data = await calculate_synastry_chart_by_id(
-        person1_id=person1_id,
-        person2_id=person2_id,
-        house_system=house_system,
-        orb_limits=orb_limits,
-        top_n_aspects=top_n_aspects,
-    )
-    if "error" in chart_data:
-        return chart_data
-
-    result = render_synastry_wheel(
-        chart_data,
-        style=style,
-        color_mode=color_mode,
-        size=size,
-        title=title,
-        format=format,
-    )
-    result["included"] = ["wheel"]
-    return result
-
-
-# ── render_composite_chart ────────────────────────────────────────────
-
-@mcp.tool()
-async def render_composite_chart(
-    # ── Two people's birth data (same as calculate_composite_chart) ─
-    person1_datetime: str,
-    person1_latitude: float,
-    person1_longitude: float,
-    person2_datetime: str,
-    person2_latitude: float,
-    person2_longitude: float,
-    elevation: float = 0.0,
-    house_system: str = "placidus",
-    orb_limits: dict[str, float] | None = None,
-    # ── Rendering options ───────────────────────────────────────────
-    style: str = "modern",
-    color_mode: str = "color",
-    size: int = 600,
-    table_position: str = "none",
-    include_planets: bool = False,
-    include_houses: bool = False,
-    title: str | None = None,
-    format: str = "svg",
-) -> dict[str, Any]:
-    """Render a composite chart (midpoint method) as a single wheel.
-
-    Calculates the composite chart from two people's birth data and renders
-    it as a standard natal-style wheel representing the relationship.
-    Output as SVG or raster image (PNG/JPG).
-
-    PERSON 1 (required):
-        person1_datetime: ISO 8601 birth datetime with timezone.
-        person1_latitude: Birth latitude in decimal degrees.
-        person1_longitude: Birth longitude in decimal degrees.
-
-    PERSON 2 (required):
-        person2_datetime: ISO 8601 birth datetime with timezone.
-        person2_latitude: Birth latitude in decimal degrees.
-        person2_longitude: Birth longitude in decimal degrees.
-
-    CHART OPTIONS:
-        elevation: Birth elevation in meters (default: 0).
-        house_system: "placidus" (default), "equal", or "whole_sign".
-        orb_limits: Per-aspect orb overrides.
-
-    RENDERING OPTIONS:
-        style: {_RENDER_STYLE_HELP}
-        color_mode: {_RENDER_COLOR_HELP}
-        size: {_RENDER_SIZE_HELP}
-        table_position: "none" (wheel only), "below" (portrait), or "right" (landscape).
-        include_planets: {_RENDER_PLANETS_HELP}
-        include_houses: {_RENDER_HOUSES_HELP}
-        title: {_RENDER_TITLE_HELP}
-
-    Returns:
-        Dict with "svg", "format", "width", "height", "included".
-    """
-    from .chart_renderer import render_natal_wheel
-
-    chart_data = await calculate_composite_chart(
-        person1_datetime=person1_datetime,
-        person1_latitude=person1_latitude,
-        person1_longitude=person1_longitude,
-        person2_datetime=person2_datetime,
-        person2_latitude=person2_latitude,
-        person2_longitude=person2_longitude,
-        elevation=elevation,
-        house_system=house_system,
-        orb_limits=orb_limits,
-    )
-    if "error" in chart_data:
-        return chart_data
-
-    result = render_natal_wheel(
-        chart_data,
-        style=style,
-        color_mode=color_mode,
-        size=size,
-        table_position=table_position,
-        include_planets=include_planets,
-        include_houses=include_houses,
-        title=title,
-        format=format,
-    )
-    result["included"] = _included_list(table_position, include_planets, include_houses)
-    return result
-
-
-# ── render_composite_chart_by_id ──────────────────────────────────────
-
-@mcp.tool()
-async def render_composite_chart_by_id(
-    # ── Two person IDs ──────────────────────────────────────────────
-    person1_id: str,
-    person2_id: str,
-    house_system: str = "placidus",
-    orb_limits: dict[str, float] | None = None,
-    # ── Rendering options ───────────────────────────────────────────
-    style: str = "modern",
-    color_mode: str = "color",
-    size: int = 600,
-    table_position: str = "none",
-    include_planets: bool = False,
-    include_houses: bool = False,
-    title: str | None = None,
-    format: str = "svg",
-) -> dict[str, Any]:
-    """Render a composite chart for two people from the database.
-
-    Looks up both persons by ID, calculates the composite chart, and
-    renders it as a single natal-style wheel. Output as SVG or raster image (PNG/JPG).
-
-    PERSON LOOKUP (required):
-        person1_id: ID or nickname of person 1 in the persons database.
-        person2_id: ID or nickname of person 2 in the persons database.
-
-    CHART OPTIONS:
-        house_system: "placidus" (default), "equal", or "whole_sign".
-        orb_limits: Per-aspect orb overrides.
-
-    RENDERING OPTIONS:
-        style: {_RENDER_STYLE_HELP}
-        color_mode: {_RENDER_COLOR_HELP}
-        size: {_RENDER_SIZE_HELP}
-        table_position: "none" (wheel only), "below" (portrait), or "right" (landscape).
-        include_planets: {_RENDER_PLANETS_HELP}
-        include_houses: {_RENDER_HOUSES_HELP}
-        title: {_RENDER_TITLE_HELP}
-        format: Output format — "svg" (default), "png", or "jpg".
-
-    Returns:
-        Dict with "content", "format", "content_type", "width", "height", "included".
-    """
-    from .chart_renderer import render_natal_wheel
-
-    chart_data = await calculate_composite_chart_by_id(
-        person1_id=person1_id,
-        person2_id=person2_id,
-        house_system=house_system,
-        orb_limits=orb_limits,
-    )
-    if "error" in chart_data:
-        return chart_data
-
-    result = render_natal_wheel(
-        chart_data,
-        style=style,
-        color_mode=color_mode,
-        size=size,
-        table_position=table_position,
-        include_planets=include_planets,
-        include_houses=include_houses,
-        title=title,
-        format=format,
-    )
-    result["included"] = _included_list(table_position, include_planets, include_houses)
-    return result
-
-
-# ── render_davison_chart ──────────────────────────────────────────────
-
-@mcp.tool()
-async def render_davison_chart(
-    # ── Two people's birth data (same as calculate_davison_chart) ───
-    person1_datetime: str,
-    person1_latitude: float,
-    person1_longitude: float,
-    person2_datetime: str,
-    person2_latitude: float,
-    person2_longitude: float,
-    elevation: float = 0.0,
-    house_system: str = "placidus",
-    orb_limits: dict[str, float] | None = None,
-    # ── Rendering options ───────────────────────────────────────────
-    style: str = "modern",
-    color_mode: str = "color",
-    size: int = 600,
-    table_position: str = "none",
-    include_planets: bool = False,
-    include_houses: bool = False,
-    title: str | None = None,
-    format: str = "svg",
-) -> dict[str, Any]:
-    """Render a Davison chart (midpoint in time and space) as a single wheel.
-
-    Calculates the Davison chart from two people's birth data and renders
-    it as a standard natal-style wheel. Output as SVG or raster image (PNG/JPG).
-
-    PERSON 1 (required):
-        person1_datetime: ISO 8601 birth datetime with timezone.
-        person1_latitude: Birth latitude in decimal degrees.
-        person1_longitude: Birth longitude in decimal degrees.
-
-    PERSON 2 (required):
-        person2_datetime: ISO 8601 birth datetime with timezone.
-        person2_latitude: Birth latitude in decimal degrees.
-        person2_longitude: Birth longitude in decimal degrees.
-
-    CHART OPTIONS:
-        elevation: Birth elevation in meters (default: 0).
-        house_system: "placidus" (default), "equal", or "whole_sign".
-        orb_limits: Per-aspect orb overrides.
-
-    RENDERING OPTIONS:
-        style: {_RENDER_STYLE_HELP}
-        color_mode: {_RENDER_COLOR_HELP}
-        size: {_RENDER_SIZE_HELP}
-        table_position: "none" (wheel only), "below" (portrait), or "right" (landscape).
-        include_planets: {_RENDER_PLANETS_HELP}
-        include_houses: {_RENDER_HOUSES_HELP}
-        title: {_RENDER_TITLE_HELP}
-
-    Returns:
-        Dict with "svg", "format", "width", "height", "included".
-    """
-    from .chart_renderer import render_natal_wheel
-
-    chart_data = await calculate_davison_chart(
-        person1_datetime=person1_datetime,
-        person1_latitude=person1_latitude,
-        person1_longitude=person1_longitude,
-        person2_datetime=person2_datetime,
-        person2_latitude=person2_latitude,
-        person2_longitude=person2_longitude,
-        elevation=elevation,
-        house_system=house_system,
-        orb_limits=orb_limits,
-    )
-    if "error" in chart_data:
-        return chart_data
-
-    result = render_natal_wheel(
-        chart_data,
-        style=style,
-        color_mode=color_mode,
-        size=size,
-        table_position=table_position,
-        include_planets=include_planets,
-        include_houses=include_houses,
-        title=title,
-        format=format,
-    )
-    result["included"] = _included_list(table_position, include_planets, include_houses)
-    return result
-
-
-# ── render_davison_chart_by_id ────────────────────────────────────────
-
-@mcp.tool()
-async def render_davison_chart_by_id(
-    # ── Two person IDs ──────────────────────────────────────────────
-    person1_id: str,
-    person2_id: str,
-    house_system: str = "placidus",
-    orb_limits: dict[str, float] | None = None,
-    # ── Rendering options ───────────────────────────────────────────
-    style: str = "modern",
-    color_mode: str = "color",
-    size: int = 600,
-    table_position: str = "none",
-    include_planets: bool = False,
-    include_houses: bool = False,
-    title: str | None = None,
-    format: str = "svg",
-) -> dict[str, Any]:
-    """Render a Davison chart for two people from the database.
-
-    Looks up both persons by ID, calculates the Davison chart, and
-    renders it as a single natal-style wheel. Output as SVG or raster image.
-
-    PERSON LOOKUP (required):
-        person1_id: ID or nickname of person 1 in the persons database.
-        person2_id: ID or nickname of person 2 in the persons database.
-
-    CHART OPTIONS:
-        house_system: "placidus" (default), "equal", or "whole_sign".
-        orb_limits: Per-aspect orb overrides.
-
-    RENDERING OPTIONS:
-        style: {_RENDER_STYLE_HELP}
-        color_mode: {_RENDER_COLOR_HELP}
-        size: {_RENDER_SIZE_HELP}
-        table_position: "none" (wheel only), "below" (portrait), or "right" (landscape).
-        include_planets: {_RENDER_PLANETS_HELP}
-        include_houses: {_RENDER_HOUSES_HELP}
-        title: {_RENDER_TITLE_HELP}
-
-    Returns:
-        Dict with "svg", "format", "width", "height", "included".
-    """
-    from .chart_renderer import render_natal_wheel
-
-    chart_data = await calculate_davison_chart_by_id(
-        person1_id=person1_id,
-        person2_id=person2_id,
-        house_system=house_system,
-        orb_limits=orb_limits,
-    )
-    if "error" in chart_data:
-        return chart_data
-
-    result = render_natal_wheel(
-        chart_data,
-        style=style,
-        color_mode=color_mode,
-        size=size,
-        table_position=table_position,
-        include_planets=include_planets,
-        include_houses=include_houses,
-        title=title,
-        format=format,
-    )
-    result["included"] = _included_list(table_position, include_planets, include_houses)
-    return result
-
-
-# ── Helper ────────────────────────────────────────────────────────────
-
-def _included_list(table_position: str, planets: bool, houses: bool) -> list[str]:
-    result = ["wheel"]
-    if table_position in ("below", "right"):
-        if planets:
-            result.append("planet_table")
-        if houses:
-            result.append("house_table")
-    return result
-
-

+ 14 - 12
src/astro_mcp/server.py

@@ -3,7 +3,8 @@ from __future__ import annotations
 import logging
 from pathlib import Path
 
-from fastapi import FastAPI
+from fastapi import FastAPI, HTTPException
+from fastapi.responses import Response
 from fastapi.staticfiles import StaticFiles
 from fastapi.templating import Jinja2Templates
 from mcp.server.fastmcp import FastMCP
@@ -24,6 +25,7 @@ mcp = FastMCP(
 
 # Import tools module to register all @mcp.tool() handlers
 from . import tools  # noqa: E402, F401
+from . import chart_resources  # noqa: E402, F401
 
 # Templates and static files
 TEMPLATES_DIR = Path(__file__).resolve().parent.parent.parent / "templates"
@@ -53,17 +55,6 @@ def _tool_names() -> list[str]:
         "calculate_composite_chart_by_id",
         "calculate_davison_chart_by_id",
         "get_transit_preview_by_id",
-        # Chart rendering tools
-        "render_natal_chart",
-        "render_natal_chart_by_id",
-        "render_transit_chart",
-        "render_transit_chart_by_id",
-        "render_synastry_chart",
-        "render_synastry_chart_by_id",
-        "render_composite_chart",
-        "render_composite_chart_by_id",
-        "render_davison_chart",
-        "render_davison_chart_by_id",
     ]
 
 
@@ -276,6 +267,17 @@ def create_app() -> FastAPI:
     def health() -> dict:
         return {"ok": True, "server": "astro-mcp", "version": __version__, "port": config.PORT}
 
+    @app.get("/charts/natal/{person_id}.{format}")
+    async def natal_chart_image(person_id: str, format: str, size: int = 600):
+        """Return a database-backed natal chart using the existing renderer."""
+        try:
+            content, content_type = await chart_resources.render_natal_artifact(
+                person_id, format=format, size=size,
+            )
+            return Response(content=content, media_type=content_type)
+        except (LookupError, ValueError) as exc:
+            raise HTTPException(status_code=404 if isinstance(exc, LookupError) else 400, detail=str(exc)) from exc
+
     @app.get("/")
     def root() -> dict:
         return {

+ 2 - 14
src/astro_mcp/tools.py

@@ -10,7 +10,7 @@ Tool modules:
     chart_tools   — direct-call chart calculation functions
     by_id_tools   — database-backed _byId chart tools
     person_tools  — person database CRUD + house system listing
-    render_tools  — chart rendering (SVG/PNG/JPG) tools
+
 """
 
 from __future__ import annotations
@@ -23,7 +23,7 @@ from .ephemeris_client import call_sky_state  # noqa: F401
 from . import chart_tools  # noqa: F401
 from . import by_id_tools  # noqa: F401
 from . import person_tools  # noqa: F401
-from . import render_tools  # noqa: F401
+
 
 # Re-export all public tools for backward compatibility.
 from .chart_tools import (  # noqa: F401
@@ -52,15 +52,3 @@ from .person_tools import (  # noqa: F401
     person_manage,
     list_house_systems,
 )
-from .render_tools import (  # noqa: F401
-    render_natal_chart,
-    render_natal_chart_by_id,
-    render_transit_chart,
-    render_transit_chart_by_id,
-    render_synastry_chart,
-    render_synastry_chart_by_id,
-    render_composite_chart,
-    render_composite_chart_by_id,
-    render_davison_chart,
-    render_davison_chart_by_id,
-)