@openephemeris/mcp-server 3.21.1 → 3.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,47 @@ Version numbering follows [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  ---
9
9
 
10
+ ## [3.23.0] — 2026-07-19
11
+
12
+ Coverage build-out (4 → 7 live iframe apps), tool-data bug fixes from live probing, and Human Design trademark hygiene. Registered tool count: 84 → 90.
13
+
14
+ ### Added
15
+ - **Three revived iframe apps** (all following current conventions — structuredContent, shared error/first-run/fullscreen chrome, theme reconciliation, harness + visual-check gated):
16
+ - **`explore_transit_timeline`** — vertical timeline of upcoming transit hits grouped by month, with retrograde markers and clickable events.
17
+ - **`explore_vedic_chart`** + `vedic_chart_recalculate` (app-only) — interactive Vedic (Jyotish) chart as a South Indian fixed-sign Rashi grid; Go renders the SVG server-side (`handler_vedic_visual.go`, now emitting `data-sign`/`data-lagna`/`data-planets` groups), iframe binds interactivity.
18
+ - **`explore_bazi_chart`** + `bazi_recalculate` (app-only) — BaZi Four Pillars grid from the Go visual renderer, with element coloring, Wu Xing balance bar, and click-to-explain pillars.
19
+ - **Moon-phase app grew up**: date control (`moon_phase_recalculate`, app-only), fullscreen toggle, and first-run coaching — it is no longer frozen on "now".
20
+ - **Copy-positions button** on the chart-wheel planet table (Markdown to clipboard, graceful fallback when the host denies clipboard access).
21
+ - **Credit-cost hints** on the iframe Recalculate controls, and per-theme SVG caching in the bodygraph so light↔dark host toggles no longer re-bill on every flip.
22
+ - **Go: Pholus rides a real kernel** — `pholus.bsp` fetched at build time (mirrors the Chiron pattern), boot self-test vs JPL Horizons truth, `BootFatal` in prod on mismatch. No body is served from silent Keplerian fallback.
23
+
24
+ ### Fixed
25
+ - **`ephemeris_natal_chart` now returns its promised aspect grid** (the tool omitted the endpoint's opt-in `include_aspects`).
26
+ - **`format=llm` `summary_lines` house annotations repaired** — a union-type formatting bug printed raw JSON bytes (`H{[56]}`) instead of the house number (`H8`).
27
+ - **`electional_moment_analysis` `format=llm`** top-level fields (aspects, day ruler, dignity, VOC, lunar phase) are now populated instead of `null`-with-values-hidden-under-`score_detail`.
28
+ - **Bi-wheel provenance dates render in the chart's timezone**, not the viewer's browser timezone.
29
+ - Cross-references added between every `explore_*` tool and its raw data sibling; credit-cost disclosure normalized to one consistent line per metered tool.
30
+
31
+ ### Changed
32
+ - **Human Design trademark hygiene**: affiliation disclaimers ("not affiliated with Jovian Archive") added to the HD marketing pages, the bodygraph iframe footer (with the AUP informational/entertainment line), and the `explore_human_design*` tool descriptions; the one "Rave I Ching" usage replaced with generic phrasing.
33
+
34
+ ## [3.22.0] — 2026-07-19
35
+
36
+ Mobile/dark QA sweep across the four live iframe apps (chart-wheel, bi-wheel, bodygraph, moon-phase), plus a design-parity pass bringing the wheels' colors in line with the web app's #239 engine redesign.
37
+
38
+ ### Added
39
+ - **Pinch-to-zoom + reliable touch pan** on chart-wheel, bi-wheel, and bodygraph — `touch-action: none` on the chart SVG stops mobile browsers from claiming drags for page scroll and pinches for page zoom; two-finger pinch zooms anchored at the gesture midpoint with simultaneous pan.
40
+ - **Midpoint aspect-type glyphs** on the natal chart wheel — every aspect line now shows its symbol (☌ ☍ △ □ ⚹) on a backing disc, matching the bi-wheel app and the Go engine's design.
41
+ - **Theme-aware HD transit/connection overlays** — `explore_human_design_transit` and `explore_human_design_connection` now pass `visual_config.theme` (previously always defaulted to light server-side) and re-fetch in the host's detected theme via the embedded app.
42
+
43
+ ### Fixed
44
+ - **HD transit/connection overlays no longer error-card** in MCP Apps hosts — both tools' payloads nest the chart under `natal` / `composite_chart` (with a `centers` map shape the app didn't handle), which the bodygraph app's validity gate rejected outright.
45
+ - **Chart-wheel and bi-wheel color palette** now matches the web app's #239 wheel redesign (element remap, vibrant dark-mode zodiac tints, warm/cool aspect semantics) — the iframe wheels had never received that pass and read muddy by comparison.
46
+ - Zodiac sign glyphs no longer occasionally render as the wrong emoji-presentation glyph (missing `U+FE0E` variation selector).
47
+ - Light-host theme detection no longer misreads as dark in some hosts (background-luminance probe used the wrong color space).
48
+ - **ASC/DSC angle labels** no longer clip at the edge of the wheel on full-bleed mobile hosts (a `viewBox` overflow needing container padding that phones don't provide) — coordinates are now clamped inside the frame.
49
+ - Mobile pan/zoom controls shrink to 32px with a translucent, blurred background and an icon-only Reset button, so the control stack stops covering the chart at phone widths.
50
+
10
51
  ## [3.21.1] — 2026-07-12
11
52
 
12
53
  ### Changed
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2025-2026 Open Ephemeris
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2025-2026 Open Ephemeris
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -359,8 +359,8 @@ Generated by `npm run sync:readme` from `config/dev-allowlist.json` and the live
359
359
 
360
360
  - Allowlisted operations: **28**
361
361
  - Methods: `GET=4`, `POST=24`, `PUT=0`, `PATCH=0`, `DELETE=0`
362
- - Registered tools (`OPENEPHEMERIS_PROFILE=dev`): **84**
363
- - Typed tools: `acg_hits`, `acg_power_lines`, `auth_login`, `auth_logout`, `auth_status`, `bazi_annual_pillar`, `bazi_chart`, `bazi_compatibility`, `bazi_element_balance`, `bazi_luck_pillars`, `bazi_ten_gods`, `bi_wheel_on_cross_aspect_click`, `bi_wheel_on_house_click`, `bi_wheel_on_planet_click`, `bi_wheel_recalculate`, `bi_wheel_synopsis`, `bodygraph_recalculate`, `chart_wheel_on_aspect_click`, `chart_wheel_on_house_click`, `chart_wheel_on_planet_click`, `chart_wheel_recalculate`, `chinese_bazi`, `dev_list_allowed`, `dev_read_api`, `dev_write_api`, `electional_aspect_search`, `electional_moment_analysis`, `electional_station_tracker`, `ephemeris_angles_points`, `ephemeris_aspect_check`, `ephemeris_bi_wheel`, `ephemeris_chart_wheel`, `ephemeris_composite`, `ephemeris_composite_midpoint`, `ephemeris_dignities`, `ephemeris_electional`, `ephemeris_fixed_stars`, `ephemeris_hermetic_lots`, `ephemeris_house_cusps`, `ephemeris_lunar_return`, `ephemeris_midpoints`, `ephemeris_moon_phase`, `ephemeris_natal_batch`, `ephemeris_natal_chart`, `ephemeris_natal_transits`, `ephemeris_next_eclipse`, `ephemeris_next_lunar_phase`, `ephemeris_overlay`, `ephemeris_planet_position`, `ephemeris_planetary_return`, `ephemeris_progressed_chart`, `ephemeris_relocation`, `ephemeris_retrograde_status`, `ephemeris_solar_return`, `ephemeris_synastry`, `ephemeris_transits`, `explore_bi_wheel`, `explore_human_design`, `explore_human_design_connection`, `explore_human_design_transit`, `explore_moon_phase`, `explore_natal_chart`, `hd_on_center_click`, `hd_on_channel_click`, `hd_on_connection_channel_click`, `hd_on_gate_click`, `hd_on_planet_click`, `hd_on_transit_channel_click`, `hd_on_variable_click`, `hd_opposition`, `hd_planetary_return`, `human_design_bodygraph`, `human_design_chart`, `human_design_composite`, `human_design_penta`, `location_search`, `timezone_resolve`, `vedic_chart`, `venus_eight_year_star`, `venus_elongations`, `venus_phase`, `venus_star_points`, `venus_star_points_conjunctions`, `venus_stations`
362
+ - Registered tools (`OPENEPHEMERIS_PROFILE=dev`): **90**
363
+ - Typed tools: `acg_hits`, `acg_power_lines`, `auth_login`, `auth_logout`, `auth_status`, `bazi_annual_pillar`, `bazi_chart`, `bazi_compatibility`, `bazi_element_balance`, `bazi_luck_pillars`, `bazi_recalculate`, `bazi_ten_gods`, `bi_wheel_on_cross_aspect_click`, `bi_wheel_on_house_click`, `bi_wheel_on_planet_click`, `bi_wheel_recalculate`, `bi_wheel_synopsis`, `bodygraph_recalculate`, `chart_wheel_on_aspect_click`, `chart_wheel_on_house_click`, `chart_wheel_on_planet_click`, `chart_wheel_recalculate`, `chinese_bazi`, `dev_list_allowed`, `dev_read_api`, `dev_write_api`, `electional_aspect_search`, `electional_moment_analysis`, `electional_station_tracker`, `ephemeris_angles_points`, `ephemeris_aspect_check`, `ephemeris_bi_wheel`, `ephemeris_chart_wheel`, `ephemeris_composite`, `ephemeris_composite_midpoint`, `ephemeris_dignities`, `ephemeris_electional`, `ephemeris_fixed_stars`, `ephemeris_hermetic_lots`, `ephemeris_house_cusps`, `ephemeris_lunar_return`, `ephemeris_midpoints`, `ephemeris_moon_phase`, `ephemeris_natal_batch`, `ephemeris_natal_chart`, `ephemeris_natal_transits`, `ephemeris_next_eclipse`, `ephemeris_next_lunar_phase`, `ephemeris_overlay`, `ephemeris_planet_position`, `ephemeris_planetary_return`, `ephemeris_progressed_chart`, `ephemeris_relocation`, `ephemeris_retrograde_status`, `ephemeris_solar_return`, `ephemeris_synastry`, `ephemeris_transits`, `explore_bazi_chart`, `explore_bi_wheel`, `explore_human_design`, `explore_human_design_connection`, `explore_human_design_transit`, `explore_moon_phase`, `explore_natal_chart`, `explore_transit_timeline`, `explore_vedic_chart`, `hd_on_center_click`, `hd_on_channel_click`, `hd_on_connection_channel_click`, `hd_on_gate_click`, `hd_on_planet_click`, `hd_on_transit_channel_click`, `hd_on_variable_click`, `hd_opposition`, `hd_planetary_return`, `human_design_bodygraph`, `human_design_chart`, `human_design_composite`, `human_design_penta`, `location_search`, `moon_phase_recalculate`, `timezone_resolve`, `vedic_chart`, `vedic_chart_recalculate`, `venus_eight_year_star`, `venus_elongations`, `venus_phase`, `venus_star_points`, `venus_star_points_conjunctions`, `venus_stations`
364
364
  - Generic tools:
365
365
 
366
366
  ### Allowlist Families
package/dist/index.js CHANGED
@@ -12,6 +12,9 @@ import { CHART_WHEEL_RESOURCE_URI, CHART_WHEEL_MIME_TYPE, getChartWheelBundle, }
12
12
  import { BODYGRAPH_RESOURCE_URI, BODYGRAPH_MIME_TYPE, getBodygraphBundle, } from "./tools/apps/bodygraph-app.js";
13
13
  import { BI_WHEEL_RESOURCE_URI, BI_WHEEL_MIME_TYPE, getBiWheelBundle, } from "./tools/apps/bi-wheel-app.js";
14
14
  import { MOON_PHASE_RESOURCE_URI, MOON_PHASE_MIME_TYPE, getMoonPhaseBundle, } from "./tools/apps/moon-phase-app.js";
15
+ import { TRANSIT_TIMELINE_RESOURCE_URI, TRANSIT_TIMELINE_MIME_TYPE, getTransitTimelineBundle, } from "./tools/apps/transit-timeline-app.js";
16
+ import { VEDIC_CHART_RESOURCE_URI, VEDIC_CHART_MIME_TYPE, getVedicChartBundle, } from "./tools/apps/vedic-chart-app.js";
17
+ import { BAZI_RESOURCE_URI, BAZI_MIME_TYPE, getBaziBundle, } from "./tools/apps/bazi-app.js";
15
18
  // ─────────────────────────────────────────────────────────────────────────────
16
19
  function resolveServerVersion() {
17
20
  try {
@@ -136,6 +139,30 @@ server.setRequestHandler(ListResourcesRequestSchema, async () => {
136
139
  mimeType: MOON_PHASE_MIME_TYPE,
137
140
  });
138
141
  }
142
+ if (getTransitTimelineBundle()) {
143
+ resources.push({
144
+ uri: TRANSIT_TIMELINE_RESOURCE_URI,
145
+ name: "Transit Timeline Explorer",
146
+ description: "Interactive vertical timeline of upcoming transit hits grouped by month, with clickable events.",
147
+ mimeType: TRANSIT_TIMELINE_MIME_TYPE,
148
+ });
149
+ }
150
+ if (getVedicChartBundle()) {
151
+ resources.push({
152
+ uri: VEDIC_CHART_RESOURCE_URI,
153
+ name: "Vedic Chart Explorer",
154
+ description: "Interactive Vedic (Jyotish) South Indian Rashi grid with clickable sign placements.",
155
+ mimeType: VEDIC_CHART_MIME_TYPE,
156
+ });
157
+ }
158
+ if (getBaziBundle()) {
159
+ resources.push({
160
+ uri: BAZI_RESOURCE_URI,
161
+ name: "BaZi Four Pillars Explorer",
162
+ description: "Interactive BaZi (Four Pillars of Destiny) chart with clickable Year/Month/Day/Hour pillars.",
163
+ mimeType: BAZI_MIME_TYPE,
164
+ });
165
+ }
139
166
  return { resources };
140
167
  });
141
168
  // Read resource
@@ -165,6 +192,24 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
165
192
  throw new Error("Moon Phase UI bundle not found. Run `npm run build:ui` to build it.");
166
193
  return { contents: [{ uri: MOON_PHASE_RESOURCE_URI, mimeType: MOON_PHASE_MIME_TYPE, text: bundle }] };
167
194
  }
195
+ if (uri === TRANSIT_TIMELINE_RESOURCE_URI) {
196
+ const bundle = getTransitTimelineBundle();
197
+ if (!bundle)
198
+ throw new Error("Transit Timeline UI bundle not found. Run `npm run build:ui` to build it.");
199
+ return { contents: [{ uri: TRANSIT_TIMELINE_RESOURCE_URI, mimeType: TRANSIT_TIMELINE_MIME_TYPE, text: bundle }] };
200
+ }
201
+ if (uri === VEDIC_CHART_RESOURCE_URI) {
202
+ const bundle = getVedicChartBundle();
203
+ if (!bundle)
204
+ throw new Error("Vedic Chart UI bundle not found. Run `npm run build:ui` to build it.");
205
+ return { contents: [{ uri: VEDIC_CHART_RESOURCE_URI, mimeType: VEDIC_CHART_MIME_TYPE, text: bundle }] };
206
+ }
207
+ if (uri === BAZI_RESOURCE_URI) {
208
+ const bundle = getBaziBundle();
209
+ if (!bundle)
210
+ throw new Error("BaZi UI bundle not found. Run `npm run build:ui` to build it.");
211
+ return { contents: [{ uri: BAZI_RESOURCE_URI, mimeType: BAZI_MIME_TYPE, text: bundle }] };
212
+ }
168
213
  throw new Error(`Unknown resource: ${uri}`);
169
214
  });
170
215
  // Handle tool calls
@@ -28,8 +28,13 @@ import { BackendClient, runWithClient } from "./backend/client.js";
28
28
  import { CHART_WHEEL_RESOURCE_URI, CHART_WHEEL_MIME_TYPE, getChartWheelBundle, } from "./tools/apps/chart-wheel-app.js";
29
29
  import { BODYGRAPH_RESOURCE_URI, BODYGRAPH_MIME_TYPE, getBodygraphBundle, } from "./tools/apps/bodygraph-app.js";
30
30
  import { BI_WHEEL_RESOURCE_URI, BI_WHEEL_MIME_TYPE, getBiWheelBundle, } from "./tools/apps/bi-wheel-app.js";
31
- // NOTE: transit-timeline, bazi, vedic-chart imports removed — tools disabled.
31
+ // NOTE: bazi, vedic-chart imports removed — tools disabled.
32
+ // NOTE: transit-timeline, bazi imports removed — tools disabled.
33
+ // NOTE: transit-timeline, vedic-chart imports removed — tools disabled.
32
34
  import { MOON_PHASE_RESOURCE_URI, MOON_PHASE_MIME_TYPE, getMoonPhaseBundle, } from "./tools/apps/moon-phase-app.js";
35
+ import { TRANSIT_TIMELINE_RESOURCE_URI, TRANSIT_TIMELINE_MIME_TYPE, getTransitTimelineBundle, } from "./tools/apps/transit-timeline-app.js";
36
+ import { VEDIC_CHART_RESOURCE_URI, VEDIC_CHART_MIME_TYPE, getVedicChartBundle, } from "./tools/apps/vedic-chart-app.js";
37
+ import { BAZI_RESOURCE_URI, BAZI_MIME_TYPE, getBaziBundle, } from "./tools/apps/bazi-app.js";
33
38
  import { oauthDiscoveryRouter, PROTECTED_RESOURCE_METADATA_URL } from "./oauth/discovery.js";
34
39
  import { oauthDcrRouter } from "./oauth/dcr.js";
35
40
  import { oauthTokenRouter } from "./oauth/token.js";
@@ -337,7 +342,8 @@ function createMcpServer(analyticsId = "anonymous") {
337
342
  mimeType: BI_WHEEL_MIME_TYPE,
338
343
  });
339
344
  }
340
- // NOTE: transit-timeline, bazi, vedic-chart resources omitted — tools disabled.
345
+ // NOTE: bazi resources omitted — tools disabled.
346
+ // NOTE: transit-timeline, vedic-chart resources omitted — tools disabled.
341
347
  if (getMoonPhaseBundle()) {
342
348
  resources.push({
343
349
  uri: MOON_PHASE_RESOURCE_URI,
@@ -346,6 +352,30 @@ function createMcpServer(analyticsId = "anonymous") {
346
352
  mimeType: MOON_PHASE_MIME_TYPE,
347
353
  });
348
354
  }
355
+ if (getTransitTimelineBundle()) {
356
+ resources.push({
357
+ uri: TRANSIT_TIMELINE_RESOURCE_URI,
358
+ name: "Transit Timeline Explorer",
359
+ description: "Interactive vertical timeline of upcoming transit hits grouped by month, with clickable events.",
360
+ mimeType: TRANSIT_TIMELINE_MIME_TYPE,
361
+ });
362
+ }
363
+ if (getVedicChartBundle()) {
364
+ resources.push({
365
+ uri: VEDIC_CHART_RESOURCE_URI,
366
+ name: "Vedic Chart Explorer",
367
+ description: "Interactive Vedic (Jyotish) South Indian Rashi grid with clickable sign placements.",
368
+ mimeType: VEDIC_CHART_MIME_TYPE,
369
+ });
370
+ }
371
+ if (getBaziBundle()) {
372
+ resources.push({
373
+ uri: BAZI_RESOURCE_URI,
374
+ name: "BaZi Four Pillars Explorer",
375
+ description: "Interactive BaZi (Four Pillars of Destiny) chart with clickable Year/Month/Day/Hour pillars.",
376
+ mimeType: BAZI_MIME_TYPE,
377
+ });
378
+ }
349
379
  return { resources };
350
380
  });
351
381
  server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
@@ -368,13 +398,32 @@ function createMcpServer(analyticsId = "anonymous") {
368
398
  throw new Error("Bi-Wheel UI bundle not found. Run `npm run build:ui` to build it.");
369
399
  return { contents: [{ uri: BI_WHEEL_RESOURCE_URI, mimeType: BI_WHEEL_MIME_TYPE, text: bundle }] };
370
400
  }
371
- // NOTE: transit-timeline, bazi, vedic-chart ReadResource handlers removed.
401
+ // NOTE: bazi resources omitted tools disabled.
402
+ // NOTE: transit-timeline, vedic-chart ReadResource handlers removed.
372
403
  if (uri === MOON_PHASE_RESOURCE_URI) {
373
404
  const bundle = getMoonPhaseBundle();
374
405
  if (!bundle)
375
406
  throw new Error("Moon Phase UI bundle not found. Run `npm run build:ui` to build it.");
376
407
  return { contents: [{ uri: MOON_PHASE_RESOURCE_URI, mimeType: MOON_PHASE_MIME_TYPE, text: bundle }] };
377
408
  }
409
+ if (uri === TRANSIT_TIMELINE_RESOURCE_URI) {
410
+ const bundle = getTransitTimelineBundle();
411
+ if (!bundle)
412
+ throw new Error("Transit Timeline UI bundle not found. Run `npm run build:ui` to build it.");
413
+ return { contents: [{ uri: TRANSIT_TIMELINE_RESOURCE_URI, mimeType: TRANSIT_TIMELINE_MIME_TYPE, text: bundle }] };
414
+ }
415
+ if (uri === VEDIC_CHART_RESOURCE_URI) {
416
+ const bundle = getVedicChartBundle();
417
+ if (!bundle)
418
+ throw new Error("Vedic Chart UI bundle not found. Run `npm run build:ui` to build it.");
419
+ return { contents: [{ uri: VEDIC_CHART_RESOURCE_URI, mimeType: VEDIC_CHART_MIME_TYPE, text: bundle }] };
420
+ }
421
+ if (uri === BAZI_RESOURCE_URI) {
422
+ const bundle = getBaziBundle();
423
+ if (!bundle)
424
+ throw new Error("BaZi UI bundle not found. Run `npm run build:ui` to build it.");
425
+ return { contents: [{ uri: BAZI_RESOURCE_URI, mimeType: BAZI_MIME_TYPE, text: bundle }] };
426
+ }
378
427
  throw new Error(`Unknown resource: ${uri}`);
379
428
  });
380
429
  return server;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * bazi-app.ts — MCP App tool registration for the BaZi Four Pillars Explorer.
3
+ *
4
+ * Registers 2 tools:
5
+ * • explore_bazi_chart — primary entry point, returns data + UI resource [model + app]
6
+ * • bazi_recalculate — re-render with new birth data or theme [app-only]
7
+ *
8
+ * The Go engine (internal/rendering/bazi) is the single source of truth for
9
+ * the visual — same pattern as bodygraph-app.ts (Phase 4 SVG unification).
10
+ * The include_visual intercept on POST /chinese/bazi (handler_bazi_visual_wrap.go)
11
+ * returns the structured pillar data AND the rendered SVG in one call, so a
12
+ * single request builds the whole model payload — no separate visualization
13
+ * fetch needed.
14
+ *
15
+ * Data tools (chinese_bazi, bazi_ten_gods, bazi_element_balance, bazi_luck_pillars,
16
+ * bazi_annual_pillar, bazi_compatibility, bazi_chart) already exist in
17
+ * ../specialized/bazi.ts — this file is purely the interactive visual entry point.
18
+ */
19
+ export declare const BAZI_RESOURCE_URI = "ui://openephemeris/bazi";
20
+ export declare const BAZI_MIME_TYPE = "text/html;profile=mcp-app";
21
+ /** Read the pre-built HTML bundle. Returns null if not yet built. */
22
+ export declare function getBaziBundle(): string | null;
23
+ /** Reset cache (useful in dev watch mode or tests). */
24
+ export declare function clearBaziBundleCache(): void;
@@ -0,0 +1,231 @@
1
+ /**
2
+ * bazi-app.ts — MCP App tool registration for the BaZi Four Pillars Explorer.
3
+ *
4
+ * Registers 2 tools:
5
+ * • explore_bazi_chart — primary entry point, returns data + UI resource [model + app]
6
+ * • bazi_recalculate — re-render with new birth data or theme [app-only]
7
+ *
8
+ * The Go engine (internal/rendering/bazi) is the single source of truth for
9
+ * the visual — same pattern as bodygraph-app.ts (Phase 4 SVG unification).
10
+ * The include_visual intercept on POST /chinese/bazi (handler_bazi_visual_wrap.go)
11
+ * returns the structured pillar data AND the rendered SVG in one call, so a
12
+ * single request builds the whole model payload — no separate visualization
13
+ * fetch needed.
14
+ *
15
+ * Data tools (chinese_bazi, bazi_ten_gods, bazi_element_balance, bazi_luck_pillars,
16
+ * bazi_annual_pillar, bazi_compatibility, bazi_chart) already exist in
17
+ * ../specialized/bazi.ts — this file is purely the interactive visual entry point.
18
+ */
19
+ import fs from "node:fs";
20
+ import path from "node:path";
21
+ import { fileURLToPath } from "node:url";
22
+ import { registerTool, SERVER_VERSION } from "../index.js";
23
+ import { getActiveClient } from "../../backend/client.js";
24
+ import { OUTPUT_SCHEMA_JSON } from "../output-schemas.js";
25
+ // ── Constants ─────────────────────────────────────────────────────────────────
26
+ export const BAZI_RESOURCE_URI = "ui://openephemeris/bazi";
27
+ export const BAZI_MIME_TYPE = "text/html;profile=mcp-app";
28
+ const here = path.dirname(fileURLToPath(import.meta.url));
29
+ const BUNDLE_PATHS = [
30
+ path.resolve(here, "..", "..", "..", "dist", "ui", "bazi.html"),
31
+ ];
32
+ function findBundlePath() {
33
+ for (const p of BUNDLE_PATHS) {
34
+ if (fs.existsSync(p))
35
+ return p;
36
+ }
37
+ return null;
38
+ }
39
+ let cachedBundle = null;
40
+ /** Read the pre-built HTML bundle. Returns null if not yet built. */
41
+ export function getBaziBundle() {
42
+ if (cachedBundle)
43
+ return cachedBundle;
44
+ const bundlePath = findBundlePath();
45
+ if (!bundlePath)
46
+ return null;
47
+ try {
48
+ cachedBundle = fs.readFileSync(bundlePath, "utf-8");
49
+ return cachedBundle;
50
+ }
51
+ catch {
52
+ return null;
53
+ }
54
+ }
55
+ /** Reset cache (useful in dev watch mode or tests). */
56
+ export function clearBaziBundleCache() {
57
+ cachedBundle = null;
58
+ }
59
+ function parseBaziArgs(args) {
60
+ let year = args.year;
61
+ let month = args.month;
62
+ let day = args.day;
63
+ let hour = args.hour;
64
+ if (args.datetime && (!year || !month || !day)) {
65
+ const dt = new Date(String(args.datetime));
66
+ if (!isNaN(dt.getTime())) {
67
+ year = dt.getUTCFullYear();
68
+ month = dt.getUTCMonth() + 1;
69
+ day = dt.getUTCDate();
70
+ if (hour == null)
71
+ hour = dt.getUTCHours();
72
+ }
73
+ }
74
+ if (!year || !month || !day) {
75
+ throw new Error("Provide year/month/day fields, or a datetime ISO string. " +
76
+ "Example: year=1987, month=7, day=15 OR datetime='1987-07-15T14:00:00'");
77
+ }
78
+ const out = { year, month, day };
79
+ if (hour != null)
80
+ out.hour = hour;
81
+ return out;
82
+ }
83
+ /**
84
+ * Fetch the BaZi chart via the include_visual intercept — one call returns
85
+ * both the structured pillar data (year/month/day/hour/day_master) and the
86
+ * Go-rendered SVG (bazi.RenderBaziChartSVG), same shape the strict
87
+ * /chinese/bazi handler returns with a `visual` key attached.
88
+ */
89
+ async function fetchBaziChart(components, theme = "dark") {
90
+ const client = getActiveClient();
91
+ const body = {
92
+ ...components,
93
+ include_visual: true,
94
+ visual_config: { format: "svg", theme, size: 800 },
95
+ };
96
+ return await client.request("POST", "/chinese/bazi", { data: body });
97
+ }
98
+ function buildModelPayload(data, components, theme) {
99
+ return {
100
+ year: data.year,
101
+ month: data.month,
102
+ day: data.day,
103
+ hour: data.hour,
104
+ day_master: data.day_master,
105
+ _svg: data.visual?.data,
106
+ _birth_params: components,
107
+ _theme: theme,
108
+ };
109
+ }
110
+ function buildSummary(payload) {
111
+ const dm = payload.day_master;
112
+ const dmLine = dm ? `Day Master: **${dm.stem} ${dm.element}**` : "";
113
+ const rows = ["year", "month", "day", "hour"]
114
+ .map((k) => {
115
+ const p = payload[k];
116
+ if (!p?.stem || !p?.branch)
117
+ return null;
118
+ const label = k.charAt(0).toUpperCase() + k.slice(1);
119
+ return `${label}: ${p.stem.name} ${p.stem.element} / ${p.branch.name} (${p.branch.animal})`;
120
+ })
121
+ .filter(Boolean)
122
+ .join("\n");
123
+ return ("**BaZi Four Pillars (四柱命盘)**\n\n" +
124
+ `${dmLine}\n\n${rows}\n\n` +
125
+ "Click any pillar on the chart for its interpretation.");
126
+ }
127
+ // ── Tool: explore_bazi_chart ─────────────────────────────────────────────────
128
+ registerTool({
129
+ name: "explore_bazi_chart",
130
+ description: "Generate an interactive BaZi (四柱命盘 Four Pillars of Destiny) chart with clickable pillars.\n\n" +
131
+ "Returns an embedded visual Four Pillars explorer showing the Year, Month, Day, and Hour " +
132
+ "pillars — each with its Heavenly Stem (天干) and Earthly Branch (地支), Ten God label, and " +
133
+ "element color — plus the Day Master identity and a Wu Xing (五行) element balance bar. " +
134
+ "Click any pillar for an instant interpretation of what it reveals about that life domain " +
135
+ "(Year = ancestry/early life, Month = parents/career, Day = self/spouse, Hour = children/later life).\n\n" +
136
+ "CREDIT COST: 3 credits per call (1 base + 2 visual render).\n\n" +
137
+ "Use this for a rich, interactive BaZi experience in MCP Apps-capable hosts (Claude Desktop). " +
138
+ "Falls back to a text summary in other hosts. For non-visual data lookups, use the dedicated " +
139
+ "chinese_bazi / bazi_ten_gods / bazi_element_balance / bazi_luck_pillars tools instead.",
140
+ inputSchema: {
141
+ type: "object",
142
+ properties: {
143
+ year: { type: "integer", description: "Gregorian birth year, e.g. 1987." },
144
+ month: { type: "integer", description: "Birth month (1–12)." },
145
+ day: { type: "integer", description: "Birth day of month (1–31)." },
146
+ hour: {
147
+ type: "integer",
148
+ description: "Birth hour (0–23). Optional, defaults to 12 (noon). " +
149
+ "Chinese shí hours are 2-hour blocks — precision within a 2-hour window is sufficient.",
150
+ },
151
+ datetime: {
152
+ type: "string",
153
+ description: "Alternative to year/month/day: ISO 8601 datetime (e.g. '1987-07-15T14:00:00'). " +
154
+ "year/month/day/hour are extracted automatically. Use this OR the individual fields.",
155
+ },
156
+ },
157
+ additionalProperties: false,
158
+ },
159
+ outputSchema: OUTPUT_SCHEMA_JSON,
160
+ annotations: {
161
+ title: "Interactive BaZi Four Pillars Explorer",
162
+ readOnlyHint: true,
163
+ destructiveHint: false,
164
+ idempotentHint: true,
165
+ openWorldHint: false,
166
+ },
167
+ _meta: {
168
+ ui: {
169
+ resourceUri: BAZI_RESOURCE_URI,
170
+ visibility: ["model", "app"],
171
+ },
172
+ },
173
+ handler: async (args) => {
174
+ const components = parseBaziArgs(args);
175
+ const bundleAvailable = Boolean(getBaziBundle());
176
+ const theme = "dark";
177
+ if (!bundleAvailable) {
178
+ // Text-fallback hosts have no use for the SVG — skip the visual surcharge.
179
+ const data = await getActiveClient().request("POST", "/chinese/bazi", {
180
+ data: components,
181
+ });
182
+ const payload = buildModelPayload(data, components, theme);
183
+ return { content: [{ type: "text", text: buildSummary(payload) }] };
184
+ }
185
+ const data = await fetchBaziChart(components, theme);
186
+ const payload = buildModelPayload(data, components, theme);
187
+ const summary = buildSummary(payload);
188
+ // MCP Apps wire format: the UI is declared via `_meta.ui.resourceUri` and
189
+ // delivered through resources/read — NOT as a content block.
190
+ return {
191
+ content: [{ type: "text", text: summary }],
192
+ structuredContent: { ...payload, server_version: SERVER_VERSION },
193
+ _meta: {
194
+ "ui/resourceUri": BAZI_RESOURCE_URI,
195
+ ui: { resourceUri: BAZI_RESOURCE_URI },
196
+ },
197
+ };
198
+ },
199
+ });
200
+ // ── Tool: bazi_recalculate [app-only] ────────────────────────────────────────
201
+ registerTool({
202
+ name: "bazi_recalculate",
203
+ description: "Recalculates a BaZi Four Pillars chart with new birth data or theme.",
204
+ inputSchema: {
205
+ type: "object",
206
+ properties: {
207
+ year: { type: "integer" },
208
+ month: { type: "integer" },
209
+ day: { type: "integer" },
210
+ hour: { type: "integer" },
211
+ theme: {
212
+ type: "string",
213
+ enum: ["light", "dark"],
214
+ description: "Render palette for the BaZi SVG. Mirrors the MCP host's light/dark color scheme.",
215
+ },
216
+ },
217
+ required: ["year", "month", "day"],
218
+ },
219
+ outputSchema: OUTPUT_SCHEMA_JSON,
220
+ annotations: { title: "Recalculate BaZi Chart", readOnlyHint: true, openWorldHint: false },
221
+ _meta: { ui: { resourceUri: BAZI_RESOURCE_URI, visibility: ["app"] } },
222
+ handler: async (args) => {
223
+ const components = parseBaziArgs(args);
224
+ const theme = args.theme === "light" ? "light" : "dark";
225
+ const data = await fetchBaziChart(components, theme);
226
+ const payload = buildModelPayload(data, components, theme);
227
+ return {
228
+ content: [{ type: "text", text: JSON.stringify({ ...payload, server_version: SERVER_VERSION }) }],
229
+ };
230
+ },
231
+ });
@@ -417,7 +417,9 @@ registerTool({
417
417
  "• lunar_return — Nearest lunar return chart (person2_datetime = target month). 5 credits.\n" +
418
418
  "• solar_arc — Solar arc directed positions (person2_datetime = target date). 1 credit.\n" +
419
419
  "Cross-aspects between both wheels are computed and displayed as coloured dashed lines. " +
420
- "Click any planet, aspect line, or house cusp for interpretation.",
420
+ "Click any planet, aspect line, or house cusp for interpretation. " +
421
+ "Use this instead of ephemeris_bi_wheel for a richer, interactive experience in " +
422
+ "MCP Apps-capable hosts (Claude Desktop). Falls back to static SVG in other hosts.",
421
423
  inputSchema: {
422
424
  type: "object",
423
425
  properties: {