@openephemeris/mcp-server 3.22.0 → 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,30 @@ 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
+
10
34
  ## [3.22.0] — 2026-07-19
11
35
 
12
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.
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: {
@@ -27,6 +27,8 @@ import { getActiveClient } from "../../backend/client.js";
27
27
  import { OUTPUT_SCHEMA_JSON } from "../output-schemas.js";
28
28
  // ── Constants ─────────────────────────────────────────────────────────────
29
29
  export const BODYGRAPH_RESOURCE_URI = "ui://openephemeris/bodygraph";
30
+ // Appended to every model-visible HD tool description — trademark hygiene.
31
+ const HD_DISCLAIMER = " Human Design was originated by Ra Uru Hu; OpenEphemeris is an independent calculation service, not affiliated with Jovian Archive.";
30
32
  export const BODYGRAPH_MIME_TYPE = "text/html;profile=mcp-app";
31
33
  const here = path.dirname(fileURLToPath(import.meta.url));
32
34
  const BUNDLE_PATHS = [
@@ -346,11 +348,13 @@ function buildHdModelPayload(data, birthParams) {
346
348
  function buildHdSummary(payload, location) {
347
349
  const definedList = payload.centers.filter((c) => c.defined).map((c) => c.name);
348
350
  const undefinedList = payload.centers.filter((c) => !c.defined).map((c) => c.name);
351
+ const channelList = payload.active_channels.length > 0 ? payload.active_channels.join(", ") : "None";
349
352
  return (`**Human Design Chart \u2014 ${location}**\n\n` +
350
353
  `Type: **${payload.type}** | Profile: **${payload.profile}** | Authority: **${payload.authority}**\n` +
351
354
  `Definition: ${payload.definition} | Cross: ${payload.incarnation_cross}\n\n` +
352
355
  `Defined Centers: ${definedList.length > 0 ? definedList.join(", ") : "None"}\n` +
353
- `Undefined Centers: ${undefinedList.length > 0 ? undefinedList.join(", ") : "None"}\n\n` +
356
+ `Undefined Centers: ${undefinedList.length > 0 ? undefinedList.join(", ") : "None"}\n` +
357
+ `Channels (${payload.active_channels.length}): ${channelList}\n\n` +
354
358
  "Click any center, gate, or channel on the bodygraph for interpretation.");
355
359
  }
356
360
  // ── Tool: explore_human_design ────────────────────────────────────────────
@@ -363,8 +367,10 @@ registerTool({
363
367
  "Shows defined/undefined centers, active gates, channels, Type, Profile, Authority, " +
364
368
  "and Incarnation Cross. " +
365
369
  "The chart is calculated using NASA JPL DE440 ephemerides for both Personality and Design positions. " +
366
- "Use this for a rich, interactive HD experience in MCP Apps-capable hosts (Claude Desktop). " +
367
- "Falls back to a text summary in other hosts.",
370
+ "Use this instead of human_design_chart or human_design_bodygraph for a richer, interactive HD " +
371
+ "experience in MCP Apps-capable hosts (Claude Desktop). " +
372
+ "Falls back to a text summary in other hosts." +
373
+ HD_DISCLAIMER,
368
374
  inputSchema: {
369
375
  type: "object",
370
376
  properties: {
@@ -1331,7 +1337,8 @@ registerTool({
1331
1337
  "Highlights the channels a transit temporarily COMPLETES with the natal chart and any centers it " +
1332
1338
  "newly defines — the core of a Human Design transit reading. " +
1333
1339
  "Returns an interactive overlay bodygraph in MCP Apps-capable hosts (Claude Desktop), with a text " +
1334
- "summary fallback elsewhere. Premium (Pro tier). Calculated with NASA JPL DE440 ephemerides.",
1340
+ "summary fallback elsewhere. Premium (Pro tier). Calculated with NASA JPL DE440 ephemerides." +
1341
+ HD_DISCLAIMER,
1335
1342
  inputSchema: {
1336
1343
  type: "object",
1337
1344
  properties: {
@@ -1436,10 +1443,13 @@ registerTool({
1436
1443
  registerTool({
1437
1444
  name: "explore_human_design_connection",
1438
1445
  description: "Compare two people's Human Design charts and classify every connected channel by HD connection " +
1439
- "theory. CREDIT COST: 3 credits per call. Classifications: " +
1446
+ "theory.\n\n" +
1447
+ "CREDIT COST: 3 credits per call.\n\n" +
1448
+ "Classifications: " +
1440
1449
  "electromagnetic (attraction), companionship (sameness), dominance (one defines), and " +
1441
1450
  "compromise (friction). Returns an interactive two-person overlay bodygraph in MCP Apps-capable " +
1442
- "hosts, with a text summary fallback elsewhere. Premium (Pro tier). NASA JPL DE440 ephemerides.",
1451
+ "hosts, with a text summary fallback elsewhere. Premium (Pro tier). NASA JPL DE440 ephemerides." +
1452
+ HD_DISCLAIMER,
1443
1453
  inputSchema: {
1444
1454
  type: "object",
1445
1455
  properties: {