@openephemeris/mcp-server 4.18.0 → 4.20.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.
Files changed (35) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/index.js +8 -110
  3. package/dist/server-sse.js +7 -114
  4. package/dist/tools/apps/bazi-app.d.ts +1 -1
  5. package/dist/tools/apps/bazi-app.js +3 -2
  6. package/dist/tools/apps/bi-wheel-app.d.ts +1 -1
  7. package/dist/tools/apps/bi-wheel-app.js +3 -2
  8. package/dist/tools/apps/bodygraph-app.d.ts +7 -4
  9. package/dist/tools/apps/bodygraph-app.js +13 -5
  10. package/dist/tools/apps/chart-wheel-app.d.ts +1 -1
  11. package/dist/tools/apps/chart-wheel-app.js +3 -2
  12. package/dist/tools/apps/moon-phase-app.d.ts +1 -1
  13. package/dist/tools/apps/moon-phase-app.js +3 -2
  14. package/dist/tools/apps/transit-timeline-app.d.ts +1 -1
  15. package/dist/tools/apps/transit-timeline-app.js +3 -2
  16. package/dist/tools/apps/ui-resource.d.ts +43 -0
  17. package/dist/tools/apps/ui-resource.js +66 -0
  18. package/dist/tools/apps/ui-resources.d.ts +22 -0
  19. package/dist/tools/apps/ui-resources.js +105 -0
  20. package/dist/tools/apps/vedic-chart-app.d.ts +1 -1
  21. package/dist/tools/apps/vedic-chart-app.js +3 -2
  22. package/dist/tools/specialized/comparative.js +25 -23
  23. package/dist/tools/specialized/hd_cycles.js +22 -23
  24. package/dist/tools/specialized/progressed.js +5 -2
  25. package/dist/tools/specialized/relocation.js +5 -2
  26. package/dist/tools/visual.d.ts +15 -0
  27. package/dist/tools/visual.js +22 -0
  28. package/dist/ui/bazi.html +893 -856
  29. package/dist/ui/bi-wheel.html +555 -518
  30. package/dist/ui/bodygraph.html +433 -337
  31. package/dist/ui/chart-wheel.html +632 -595
  32. package/dist/ui/moon-phase.html +190 -155
  33. package/dist/ui/transit-timeline.html +865 -830
  34. package/dist/ui/vedic-chart.html +252 -215
  35. package/package.json +2 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,43 @@ Version numbering follows [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  ---
9
9
 
10
+ ## [4.20.0] — 2026-09-27
11
+
12
+ ### Added
13
+ - **Six more tools can show their chart as an image.** Pass `include_visual: true` (+2 credits) to
14
+ `ephemeris_progressed_chart` (natal inside, progressed outside), `ephemeris_relocation` (the
15
+ relocated wheel), `ephemeris_composite` (the Davison relationship wheel), `ephemeris_overlay` (both
16
+ charts as a bi-wheel), `hd_planetary_return` and `hd_opposition` (the bodygraph for that moment).
17
+ The picture appears in the conversation next to the data.
18
+
19
+ ### Fixed
20
+ - `ephemeris_composite` described itself as a planet-by-planet midpoint composite; it returns a
21
+ Davison relationship chart. For the midpoint method use `ephemeris_composite_midpoint`.
22
+
23
+ ## [4.19.0] — 2026-09-27
24
+
25
+ ### Changed
26
+ - **The Human Design bodygraph is easier to read.** Gate numbers use a clean sans-serif and are
27
+ noticeably larger at chat width; the Design and Personality planet columns sit close to the body
28
+ under their own headings; open centres use the theme's colour instead of glaring white; channels
29
+ defined from both sides are drawn as two parallel stripes (Design red beside Personality); the
30
+ mandala's body is about 1.3× larger inside its rings.
31
+ - **Select a single activation.** Clicking a planet row (for example Personality Moon) highlights
32
+ only that activation's gate, stub and channel, never its Design twin. The rendered SVG tags every
33
+ element by side and activation, so your own CSS can do the same.
34
+ - **Every chart widget fits the chat better.** Widgets size themselves from the host; scrolling the
35
+ conversation past a chart scrolls instead of zooming (zoom with Ctrl/Cmd + scroll or a pinch); the
36
+ fullscreen button only appears where the host supports fullscreen; moon phase and transit timeline
37
+ support picture-in-picture; fullscreen uses one shared layout that stacks into a single column on
38
+ phones; all seven widgets share one colour and font set, and the host draws the card border.
39
+
40
+ ### Fixed
41
+ - Changing theme no longer drops a widget out of fullscreen.
42
+ - On moon phase and transit timeline, the info button no longer covers the fullscreen button.
43
+ - Bodygraph hover highlights work again.
44
+ - Widget resources are versioned, so hosts that cache them (such as ChatGPT) pick up new widget
45
+ versions after an update without reinstalling the app.
46
+
10
47
  ## [4.18.0] — 2026-09-26
11
48
 
12
49
  ### Fixed
package/dist/index.js CHANGED
@@ -10,14 +10,8 @@ import { buildUiMeta } from "./tools/ui-meta.js";
10
10
  import { SERVER_INSTRUCTIONS } from "./instructions.js";
11
11
  import { captureEvent, captureFirstToolCallOnce, distinctIdFor } from "./analytics.js";
12
12
  import { listPrompts, getPromptContent } from "./prompts.js";
13
- // ── MCP App resource imports ────────────────────────────────────────────────
14
- import { CHART_WHEEL_RESOURCE_URI, CHART_WHEEL_MIME_TYPE, getChartWheelBundle, } from "./tools/apps/chart-wheel-app.js";
15
- import { BODYGRAPH_RESOURCE_URI, BODYGRAPH_MIME_TYPE, getBodygraphBundle, } from "./tools/apps/bodygraph-app.js";
16
- import { BI_WHEEL_RESOURCE_URI, BI_WHEEL_MIME_TYPE, getBiWheelBundle, } from "./tools/apps/bi-wheel-app.js";
17
- import { MOON_PHASE_RESOURCE_URI, MOON_PHASE_MIME_TYPE, getMoonPhaseBundle, } from "./tools/apps/moon-phase-app.js";
18
- import { TRANSIT_TIMELINE_RESOURCE_URI, TRANSIT_TIMELINE_MIME_TYPE, getTransitTimelineBundle, } from "./tools/apps/transit-timeline-app.js";
19
- import { VEDIC_CHART_RESOURCE_URI, VEDIC_CHART_MIME_TYPE, getVedicChartBundle, } from "./tools/apps/vedic-chart-app.js";
20
- import { BAZI_RESOURCE_URI, BAZI_MIME_TYPE, getBaziBundle, } from "./tools/apps/bazi-app.js";
13
+ // ── MCP App resources (list/read registry shared with server-sse.ts) ────────
14
+ import { listUiResources, readUiResource } from "./tools/apps/ui-resources.js";
21
15
  // ─────────────────────────────────────────────────────────────────────────────
22
16
  function resolveServerVersion() {
23
17
  try {
@@ -157,112 +151,16 @@ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
157
151
  ],
158
152
  };
159
153
  });
160
- // List available resources
161
154
  server.setRequestHandler(ListResourcesRequestSchema, async () => {
162
- const resources = [];
163
- if (getChartWheelBundle()) {
164
- resources.push({
165
- uri: CHART_WHEEL_RESOURCE_URI,
166
- name: "Chart Wheel Explorer",
167
- description: "Interactive natal chart wheel with clickable planets, houses, and aspects.",
168
- mimeType: CHART_WHEEL_MIME_TYPE,
169
- });
170
- }
171
- if (getBodygraphBundle()) {
172
- resources.push({
173
- uri: BODYGRAPH_RESOURCE_URI,
174
- name: "Human Design Bodygraph Explorer",
175
- description: "Interactive Human Design Bodygraph with clickable centers, gates, and channels.",
176
- mimeType: BODYGRAPH_MIME_TYPE,
177
- });
178
- }
179
- if (getBiWheelBundle()) {
180
- resources.push({
181
- uri: BI_WHEEL_RESOURCE_URI,
182
- name: "Bi-Wheel Explorer",
183
- description: "Interactive synastry or transit bi-wheel with cross-aspect lines and clickable planets.",
184
- mimeType: BI_WHEEL_MIME_TYPE,
185
- });
186
- }
187
- if (getMoonPhaseBundle()) {
188
- resources.push({
189
- uri: MOON_PHASE_RESOURCE_URI,
190
- name: "Moon Phase Explorer",
191
- description: "Interactive lunar phase dial showing illumination, sign, and void-of-course status.",
192
- mimeType: MOON_PHASE_MIME_TYPE,
193
- });
194
- }
195
- if (getTransitTimelineBundle()) {
196
- resources.push({
197
- uri: TRANSIT_TIMELINE_RESOURCE_URI,
198
- name: "Transit Timeline Explorer",
199
- description: "Interactive vertical timeline of upcoming transit hits grouped by month, with clickable events.",
200
- mimeType: TRANSIT_TIMELINE_MIME_TYPE,
201
- });
202
- }
203
- if (getVedicChartBundle()) {
204
- resources.push({
205
- uri: VEDIC_CHART_RESOURCE_URI,
206
- name: "Vedic Chart Explorer",
207
- description: "Interactive Vedic (Jyotish) South Indian Rashi grid with clickable sign placements.",
208
- mimeType: VEDIC_CHART_MIME_TYPE,
209
- });
210
- }
211
- if (getBaziBundle()) {
212
- resources.push({
213
- uri: BAZI_RESOURCE_URI,
214
- name: "BaZi Four Pillars Explorer",
215
- description: "Interactive BaZi (Four Pillars of Destiny) chart with clickable Year/Month/Day/Hour pillars.",
216
- mimeType: BAZI_MIME_TYPE,
217
- });
218
- }
219
- return { resources };
155
+ // One registry for both transports: versioned ui:// URIs + resource _meta
156
+ // (prefersBorder, CSP, widgetDescription). See tools/apps/ui-resources.ts.
157
+ return { resources: listUiResources() };
220
158
  });
221
- // Read resource
222
159
  server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
223
160
  const uri = request.params.uri;
224
- if (uri === CHART_WHEEL_RESOURCE_URI) {
225
- const bundle = getChartWheelBundle();
226
- if (!bundle)
227
- throw new Error("Chart Wheel UI bundle not found. Run `npm run build:ui` to build it.");
228
- return { contents: [{ uri: CHART_WHEEL_RESOURCE_URI, mimeType: CHART_WHEEL_MIME_TYPE, text: bundle }] };
229
- }
230
- if (uri === BODYGRAPH_RESOURCE_URI) {
231
- const bundle = getBodygraphBundle();
232
- if (!bundle)
233
- throw new Error("Bodygraph UI bundle not found. Run `npm run build:ui` to build it.");
234
- return { contents: [{ uri: BODYGRAPH_RESOURCE_URI, mimeType: BODYGRAPH_MIME_TYPE, text: bundle }] };
235
- }
236
- if (uri === BI_WHEEL_RESOURCE_URI) {
237
- const bundle = getBiWheelBundle();
238
- if (!bundle)
239
- throw new Error("Bi-Wheel UI bundle not found. Run `npm run build:ui` to build it.");
240
- return { contents: [{ uri: BI_WHEEL_RESOURCE_URI, mimeType: BI_WHEEL_MIME_TYPE, text: bundle }] };
241
- }
242
- if (uri === MOON_PHASE_RESOURCE_URI) {
243
- const bundle = getMoonPhaseBundle();
244
- if (!bundle)
245
- throw new Error("Moon Phase UI bundle not found. Run `npm run build:ui` to build it.");
246
- return { contents: [{ uri: MOON_PHASE_RESOURCE_URI, mimeType: MOON_PHASE_MIME_TYPE, text: bundle }] };
247
- }
248
- if (uri === TRANSIT_TIMELINE_RESOURCE_URI) {
249
- const bundle = getTransitTimelineBundle();
250
- if (!bundle)
251
- throw new Error("Transit Timeline UI bundle not found. Run `npm run build:ui` to build it.");
252
- return { contents: [{ uri: TRANSIT_TIMELINE_RESOURCE_URI, mimeType: TRANSIT_TIMELINE_MIME_TYPE, text: bundle }] };
253
- }
254
- if (uri === VEDIC_CHART_RESOURCE_URI) {
255
- const bundle = getVedicChartBundle();
256
- if (!bundle)
257
- throw new Error("Vedic Chart UI bundle not found. Run `npm run build:ui` to build it.");
258
- return { contents: [{ uri: VEDIC_CHART_RESOURCE_URI, mimeType: VEDIC_CHART_MIME_TYPE, text: bundle }] };
259
- }
260
- if (uri === BAZI_RESOURCE_URI) {
261
- const bundle = getBaziBundle();
262
- if (!bundle)
263
- throw new Error("BaZi UI bundle not found. Run `npm run build:ui` to build it.");
264
- return { contents: [{ uri: BAZI_RESOURCE_URI, mimeType: BAZI_MIME_TYPE, text: bundle }] };
265
- }
161
+ const result = readUiResource(uri);
162
+ if (result)
163
+ return result;
266
164
  throw new Error(`Unknown resource: ${uri}`);
267
165
  });
268
166
  // Handle tool calls
@@ -28,16 +28,7 @@ import { initTools, toolRegistry, formatToolResponse, formatToolError, modelVisi
28
28
  import { buildUiMeta } from "./tools/ui-meta.js";
29
29
  import { SERVER_INSTRUCTIONS } from "./instructions.js";
30
30
  import { BackendClient, backendClient, presentCredentialEnvVars, runWithClient } from "./backend/client.js";
31
- import { CHART_WHEEL_RESOURCE_URI, CHART_WHEEL_MIME_TYPE, getChartWheelBundle, } from "./tools/apps/chart-wheel-app.js";
32
- import { BODYGRAPH_RESOURCE_URI, BODYGRAPH_MIME_TYPE, getBodygraphBundle, } from "./tools/apps/bodygraph-app.js";
33
- import { BI_WHEEL_RESOURCE_URI, BI_WHEEL_MIME_TYPE, getBiWheelBundle, } from "./tools/apps/bi-wheel-app.js";
34
- // NOTE: bazi, vedic-chart imports removed — tools disabled.
35
- // NOTE: transit-timeline, bazi imports removed — tools disabled.
36
- // NOTE: transit-timeline, vedic-chart imports removed — tools disabled.
37
- import { MOON_PHASE_RESOURCE_URI, MOON_PHASE_MIME_TYPE, getMoonPhaseBundle, } from "./tools/apps/moon-phase-app.js";
38
- import { TRANSIT_TIMELINE_RESOURCE_URI, TRANSIT_TIMELINE_MIME_TYPE, getTransitTimelineBundle, } from "./tools/apps/transit-timeline-app.js";
39
- import { VEDIC_CHART_RESOURCE_URI, VEDIC_CHART_MIME_TYPE, getVedicChartBundle, } from "./tools/apps/vedic-chart-app.js";
40
- import { BAZI_RESOURCE_URI, BAZI_MIME_TYPE, getBaziBundle, } from "./tools/apps/bazi-app.js";
31
+ import { listUiResources, readUiResource } from "./tools/apps/ui-resources.js";
41
32
  import { oauthDiscoveryRouter, PROTECTED_RESOURCE_METADATA_URL } from "./oauth/discovery.js";
42
33
  import { oauthDcrRouter } from "./oauth/dcr.js";
43
34
  import { oauthTokenRouter } from "./oauth/token.js";
@@ -387,113 +378,15 @@ function createMcpServer(analyticsId = "anonymous", surface = "core", activity =
387
378
  });
388
379
  // --- Resource handlers ---
389
380
  server.setRequestHandler(ListResourcesRequestSchema, async () => {
390
- const resources = [];
391
- if (getChartWheelBundle()) {
392
- resources.push({
393
- uri: CHART_WHEEL_RESOURCE_URI,
394
- name: "Chart Wheel Explorer",
395
- description: "Interactive natal chart wheel with clickable planets, houses, and aspects.",
396
- mimeType: CHART_WHEEL_MIME_TYPE,
397
- });
398
- }
399
- if (getBodygraphBundle()) {
400
- resources.push({
401
- uri: BODYGRAPH_RESOURCE_URI,
402
- name: "Human Design Bodygraph Explorer",
403
- description: "Interactive Human Design Bodygraph with clickable centers, gates, and channels.",
404
- mimeType: BODYGRAPH_MIME_TYPE,
405
- });
406
- }
407
- if (getBiWheelBundle()) {
408
- resources.push({
409
- uri: BI_WHEEL_RESOURCE_URI,
410
- name: "Bi-Wheel Explorer",
411
- description: "Interactive synastry or transit bi-wheel with cross-aspect lines and clickable planets.",
412
- mimeType: BI_WHEEL_MIME_TYPE,
413
- });
414
- }
415
- // NOTE: bazi resources omitted — tools disabled.
416
- // NOTE: transit-timeline, vedic-chart resources omitted — tools disabled.
417
- if (getMoonPhaseBundle()) {
418
- resources.push({
419
- uri: MOON_PHASE_RESOURCE_URI,
420
- name: "Moon Phase Explorer",
421
- description: "Interactive lunar phase dial showing illumination, sign, and void-of-course status.",
422
- mimeType: MOON_PHASE_MIME_TYPE,
423
- });
424
- }
425
- if (getTransitTimelineBundle()) {
426
- resources.push({
427
- uri: TRANSIT_TIMELINE_RESOURCE_URI,
428
- name: "Transit Timeline Explorer",
429
- description: "Interactive vertical timeline of upcoming transit hits grouped by month, with clickable events.",
430
- mimeType: TRANSIT_TIMELINE_MIME_TYPE,
431
- });
432
- }
433
- if (getVedicChartBundle()) {
434
- resources.push({
435
- uri: VEDIC_CHART_RESOURCE_URI,
436
- name: "Vedic Chart Explorer",
437
- description: "Interactive Vedic (Jyotish) South Indian Rashi grid with clickable sign placements.",
438
- mimeType: VEDIC_CHART_MIME_TYPE,
439
- });
440
- }
441
- if (getBaziBundle()) {
442
- resources.push({
443
- uri: BAZI_RESOURCE_URI,
444
- name: "BaZi Four Pillars Explorer",
445
- description: "Interactive BaZi (Four Pillars of Destiny) chart with clickable Year/Month/Day/Hour pillars.",
446
- mimeType: BAZI_MIME_TYPE,
447
- });
448
- }
449
- return { resources };
381
+ // One registry for both transports: versioned ui:// URIs + resource _meta
382
+ // (prefersBorder, CSP, widgetDescription). See tools/apps/ui-resources.ts.
383
+ return { resources: listUiResources() };
450
384
  });
451
385
  server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
452
386
  const uri = request.params.uri;
453
- if (uri === CHART_WHEEL_RESOURCE_URI) {
454
- const bundle = getChartWheelBundle();
455
- if (!bundle)
456
- throw new Error("Chart Wheel UI bundle not found. Run `npm run build:ui` to build it.");
457
- return { contents: [{ uri: CHART_WHEEL_RESOURCE_URI, mimeType: CHART_WHEEL_MIME_TYPE, text: bundle }] };
458
- }
459
- if (uri === BODYGRAPH_RESOURCE_URI) {
460
- const bundle = getBodygraphBundle();
461
- if (!bundle)
462
- throw new Error("Bodygraph UI bundle not found. Run `npm run build:ui` to build it.");
463
- return { contents: [{ uri: BODYGRAPH_RESOURCE_URI, mimeType: BODYGRAPH_MIME_TYPE, text: bundle }] };
464
- }
465
- if (uri === BI_WHEEL_RESOURCE_URI) {
466
- const bundle = getBiWheelBundle();
467
- if (!bundle)
468
- throw new Error("Bi-Wheel UI bundle not found. Run `npm run build:ui` to build it.");
469
- return { contents: [{ uri: BI_WHEEL_RESOURCE_URI, mimeType: BI_WHEEL_MIME_TYPE, text: bundle }] };
470
- }
471
- // NOTE: bazi resources omitted — tools disabled.
472
- // NOTE: transit-timeline, vedic-chart ReadResource handlers removed.
473
- if (uri === MOON_PHASE_RESOURCE_URI) {
474
- const bundle = getMoonPhaseBundle();
475
- if (!bundle)
476
- throw new Error("Moon Phase UI bundle not found. Run `npm run build:ui` to build it.");
477
- return { contents: [{ uri: MOON_PHASE_RESOURCE_URI, mimeType: MOON_PHASE_MIME_TYPE, text: bundle }] };
478
- }
479
- if (uri === TRANSIT_TIMELINE_RESOURCE_URI) {
480
- const bundle = getTransitTimelineBundle();
481
- if (!bundle)
482
- throw new Error("Transit Timeline UI bundle not found. Run `npm run build:ui` to build it.");
483
- return { contents: [{ uri: TRANSIT_TIMELINE_RESOURCE_URI, mimeType: TRANSIT_TIMELINE_MIME_TYPE, text: bundle }] };
484
- }
485
- if (uri === VEDIC_CHART_RESOURCE_URI) {
486
- const bundle = getVedicChartBundle();
487
- if (!bundle)
488
- throw new Error("Vedic Chart UI bundle not found. Run `npm run build:ui` to build it.");
489
- return { contents: [{ uri: VEDIC_CHART_RESOURCE_URI, mimeType: VEDIC_CHART_MIME_TYPE, text: bundle }] };
490
- }
491
- if (uri === BAZI_RESOURCE_URI) {
492
- const bundle = getBaziBundle();
493
- if (!bundle)
494
- throw new Error("BaZi UI bundle not found. Run `npm run build:ui` to build it.");
495
- return { contents: [{ uri: BAZI_RESOURCE_URI, mimeType: BAZI_MIME_TYPE, text: bundle }] };
496
- }
387
+ const result = readUiResource(uri);
388
+ if (result)
389
+ return result;
497
390
  throw new Error(`Unknown resource: ${uri}`);
498
391
  });
499
392
  return server;
@@ -16,7 +16,7 @@
16
16
  * bazi_annual_pillar, bazi_compatibility, bazi_chart) already exist in
17
17
  * ../specialized/bazi.ts — this file is purely the interactive visual entry point.
18
18
  */
19
- export declare const BAZI_RESOURCE_URI = "ui://openephemeris/bazi";
19
+ export declare const BAZI_RESOURCE_URI: string;
20
20
  export declare const BAZI_MIME_TYPE = "text/html;profile=mcp-app";
21
21
  /** Read the pre-built HTML bundle. Returns null if not yet built. */
22
22
  export declare function getBaziBundle(): string | null;
@@ -20,6 +20,7 @@ import fs from "node:fs";
20
20
  import path from "node:path";
21
21
  import { fileURLToPath } from "node:url";
22
22
  import { registerTool, SERVER_VERSION } from "../index.js";
23
+ import { UI_RESOURCE_MIME_TYPE, uiResourceUri } from "./ui-resource.js";
23
24
  import { getActiveClient } from "../../backend/client.js";
24
25
  import { OUTPUT_SCHEMA_JSON } from "../output-schemas.js";
25
26
  // One BaZi component parser, shared with the specialized tools. The local copy
@@ -28,8 +29,8 @@ import { OUTPUT_SCHEMA_JSON } from "../output-schemas.js";
28
29
  import { parseBaziArgs, buildBaziConventionFields, CONVENTION_PROPERTIES } from "../specialized/bazi.js";
29
30
  import { RENDER_TOKEN_PROPERTY, renderTokenFromVisual, renderTokenHeaders } from "./_render-token.js";
30
31
  // ── Constants ─────────────────────────────────────────────────────────────────
31
- export const BAZI_RESOURCE_URI = "ui://openephemeris/bazi";
32
- export const BAZI_MIME_TYPE = "text/html;profile=mcp-app";
32
+ export const BAZI_RESOURCE_URI = uiResourceUri("bazi");
33
+ export const BAZI_MIME_TYPE = UI_RESOURCE_MIME_TYPE;
33
34
  const here = path.dirname(fileURLToPath(import.meta.url));
34
35
  const BUNDLE_PATHS = [
35
36
  path.resolve(here, "..", "..", "..", "dist", "ui", "bazi.html"),
@@ -21,7 +21,7 @@
21
21
  * The payload cross_aspects array is capped at 30 tightest-orb aspects.
22
22
  */
23
23
  import { getActiveClient } from "../../backend/client.js";
24
- export declare const BI_WHEEL_RESOURCE_URI = "ui://openephemeris/bi-wheel";
24
+ export declare const BI_WHEEL_RESOURCE_URI: string;
25
25
  export declare const BI_WHEEL_MIME_TYPE = "text/html;profile=mcp-app";
26
26
  /** Read the pre-built HTML bundle. Returns null if not yet built. */
27
27
  export declare function getBiWheelBundle(): string | null;
@@ -24,13 +24,14 @@ import fs from "node:fs";
24
24
  import path from "node:path";
25
25
  import { fileURLToPath } from "node:url";
26
26
  import { registerTool, SERVER_VERSION } from "../index.js";
27
+ import { UI_RESOURCE_MIME_TYPE, uiResourceUri } from "./ui-resource.js";
27
28
  import { getActiveClient } from "../../backend/client.js";
28
29
  import { OUTPUT_SCHEMA_JSON } from "../output-schemas.js";
29
30
  import { DATETIME_DESC, timezoneProperty, toDateTimeInputBody } from "../datetime.js";
30
31
  import { coordsFromArgsOrLocation } from "./_location-resolver.js";
31
32
  // ── Constants ─────────────────────────────────────────────────────────────────
32
- export const BI_WHEEL_RESOURCE_URI = "ui://openephemeris/bi-wheel";
33
- export const BI_WHEEL_MIME_TYPE = "text/html;profile=mcp-app";
33
+ export const BI_WHEEL_RESOURCE_URI = uiResourceUri("bi-wheel");
34
+ export const BI_WHEEL_MIME_TYPE = UI_RESOURCE_MIME_TYPE;
34
35
  const here = path.dirname(fileURLToPath(import.meta.url));
35
36
  const BUNDLE_PATH = path.resolve(here, "..", "..", "..", "dist", "ui", "bi-wheel.html");
36
37
  let cachedBiWheelBundle = null;
@@ -12,14 +12,17 @@
12
12
  * overlay SVG is returned inline by /human-design/transit-chart and
13
13
  * /human-design/composite (include_visual) rather than a separate render call.
14
14
  *
15
- * The bodygraph is rendered client-side from structured JSON — no SVG endpoint
16
- * is called, keeping latency at zero and the architecture consistent with
17
- * the chart-wheel-app approach.
15
+ * The bodygraph picture is the Go engine's SVG, rendered server-side
16
+ * (fetchGoBodygraphSVG, in parallel with the chart call; overlays inline as
17
+ * above) and returned as `_svg`.
18
+ * The iframe inlines that SVG and binds interactivity to its data-* attributes
19
+ * (packages/widget-ui/bodygraph/src/wire-go-svg.ts); it does not draw the
20
+ * graph itself.
18
21
  *
19
22
  * Also exports resource helpers (getBodygraphBundle, etc.) for use in
20
23
  * index.ts and server-sse.ts.
21
24
  */
22
- export declare const BODYGRAPH_RESOURCE_URI = "ui://openephemeris/bodygraph";
25
+ export declare const BODYGRAPH_RESOURCE_URI: string;
23
26
  export declare const BODYGRAPH_MIME_TYPE = "text/html;profile=mcp-app";
24
27
  /** Read the pre-built HTML bundle. Returns null if not yet built. */
25
28
  export declare function getBodygraphBundle(): string | null;
@@ -12,9 +12,12 @@
12
12
  * overlay SVG is returned inline by /human-design/transit-chart and
13
13
  * /human-design/composite (include_visual) rather than a separate render call.
14
14
  *
15
- * The bodygraph is rendered client-side from structured JSON — no SVG endpoint
16
- * is called, keeping latency at zero and the architecture consistent with
17
- * the chart-wheel-app approach.
15
+ * The bodygraph picture is the Go engine's SVG, rendered server-side
16
+ * (fetchGoBodygraphSVG, in parallel with the chart call; overlays inline as
17
+ * above) and returned as `_svg`.
18
+ * The iframe inlines that SVG and binds interactivity to its data-* attributes
19
+ * (packages/widget-ui/bodygraph/src/wire-go-svg.ts); it does not draw the
20
+ * graph itself.
18
21
  *
19
22
  * Also exports resource helpers (getBodygraphBundle, etc.) for use in
20
23
  * index.ts and server-sse.ts.
@@ -23,16 +26,17 @@ import fs from "node:fs";
23
26
  import path from "node:path";
24
27
  import { fileURLToPath } from "node:url";
25
28
  import { registerTool, SERVER_VERSION } from "../index.js";
29
+ import { UI_RESOURCE_MIME_TYPE, uiResourceUri } from "./ui-resource.js";
26
30
  import { getActiveClient, isBillingOrAuthError } from "../../backend/client.js";
27
31
  import { OUTPUT_SCHEMA_JSON } from "../output-schemas.js";
28
32
  import { localToUtcIsoHistorical } from "../datetime-historical.js";
29
33
  import { coordsFromArgsOrLocation } from "./_location-resolver.js";
30
34
  import { RENDER_TOKEN_PROPERTY, renderTokenFromSvg, renderTokenFromVisual, renderTokenHeaders, withoutRenderToken, } from "./_render-token.js";
31
35
  // ── Constants ─────────────────────────────────────────────────────────────
32
- export const BODYGRAPH_RESOURCE_URI = "ui://openephemeris/bodygraph";
36
+ export const BODYGRAPH_RESOURCE_URI = uiResourceUri("bodygraph");
33
37
  // Appended to every model-visible HD tool description — trademark hygiene.
34
38
  const HD_DISCLAIMER = " Human Design was originated by Ra Uru Hu; OpenEphemeris is an independent calculation service, not affiliated with Jovian Archive.";
35
- export const BODYGRAPH_MIME_TYPE = "text/html;profile=mcp-app";
39
+ export const BODYGRAPH_MIME_TYPE = UI_RESOURCE_MIME_TYPE;
36
40
  const here = path.dirname(fileURLToPath(import.meta.url));
37
41
  const BUNDLE_PATHS = [
38
42
  path.resolve(here, "..", "..", "..", "dist", "ui", "bodygraph.html"),
@@ -84,6 +88,10 @@ async function fetchGoBodygraphSVG(body, theme = "dark", view, renderToken) {
84
88
  // (handler_bodygraph.go) — only appended when the caller (the in-iframe
85
89
  // layout toggle) actually requests the mandala view.
86
90
  let qs = `format=svg&size=800&style=${theme}`;
91
+ // Planet columns stay ON (renderer default): they are the chart's
92
+ // activation list and are clickable in the iframe. The same variant serves
93
+ // inline AND fullscreen, so a display-mode switch never re-renders (the
94
+ // render-token waiver is 15 min / 12 uses, so a re-render is not always free).
87
95
  if (view?.layout)
88
96
  qs += `&layout=${encodeURIComponent(view.layout)}`;
89
97
  if (view?.rings !== undefined)
@@ -15,7 +15,7 @@
15
15
  * Also exports resource helpers (getChartWheelBundle, etc.) for use in
16
16
  * index.ts and server-sse.ts.
17
17
  */
18
- export declare const CHART_WHEEL_RESOURCE_URI = "ui://openephemeris/chart-wheel";
18
+ export declare const CHART_WHEEL_RESOURCE_URI: string;
19
19
  export declare const CHART_WHEEL_MIME_TYPE = "text/html;profile=mcp-app";
20
20
  /** Read the pre-built HTML bundle. Returns null if not yet built. */
21
21
  export declare function getChartWheelBundle(): string | null;
@@ -19,13 +19,14 @@ import fs from "node:fs";
19
19
  import path from "node:path";
20
20
  import { fileURLToPath } from "node:url";
21
21
  import { registerTool, SERVER_VERSION } from "../index.js";
22
+ import { UI_RESOURCE_MIME_TYPE, uiResourceUri } from "./ui-resource.js";
22
23
  import { getActiveClient } from "../../backend/client.js";
23
24
  import { OUTPUT_SCHEMA_JSON } from "../output-schemas.js";
24
25
  import { DATETIME_DESC, TIMEZONE_PROPERTY, toDateTimeInputBody } from "../datetime.js";
25
26
  import { coordsFromArgsOrLocation } from "./_location-resolver.js";
26
27
  // ── Constants ─────────────────────────────────────────────────────────────
27
- export const CHART_WHEEL_RESOURCE_URI = "ui://openephemeris/chart-wheel";
28
- export const CHART_WHEEL_MIME_TYPE = "text/html;profile=mcp-app";
28
+ export const CHART_WHEEL_RESOURCE_URI = uiResourceUri("chart-wheel");
29
+ export const CHART_WHEEL_MIME_TYPE = UI_RESOURCE_MIME_TYPE;
29
30
  // Bundle path: at runtime (compiled), __dirname = dist/tools/apps/
30
31
  // so ../../../dist/ui/ = dist/ui/ ✓
31
32
  // In dev (tsx), __dirname = src/tools/apps/
@@ -11,7 +11,7 @@
11
11
  * Also exports resource helpers (getMoonPhaseBundle, etc.) for use in
12
12
  * index.ts and server-sse.ts.
13
13
  */
14
- export declare const MOON_PHASE_RESOURCE_URI = "ui://openephemeris/moon-phase";
14
+ export declare const MOON_PHASE_RESOURCE_URI: string;
15
15
  export declare const MOON_PHASE_MIME_TYPE = "text/html;profile=mcp-app";
16
16
  /** Read the pre-built HTML bundle. Returns null if not yet built. */
17
17
  export declare function getMoonPhaseBundle(): string | null;
@@ -15,13 +15,14 @@ import fs from "node:fs";
15
15
  import path from "node:path";
16
16
  import { fileURLToPath } from "node:url";
17
17
  import { registerTool, validateCoordinates, SERVER_VERSION } from "../index.js";
18
+ import { UI_RESOURCE_MIME_TYPE, uiResourceUri } from "./ui-resource.js";
18
19
  import { getActiveClient, rethrowBillingOrAuth } from "../../backend/client.js";
19
20
  import { OUTPUT_SCHEMA_JSON } from "../output-schemas.js";
20
21
  import { TIMEZONE_PROPERTY } from "../datetime.js";
21
22
  import { localToUtcIsoHistorical } from "../datetime-historical.js";
22
23
  // ── Constants ─────────────────────────────────────────────────────────────────
23
- export const MOON_PHASE_RESOURCE_URI = "ui://openephemeris/moon-phase";
24
- export const MOON_PHASE_MIME_TYPE = "text/html;profile=mcp-app";
24
+ export const MOON_PHASE_RESOURCE_URI = uiResourceUri("moon-phase");
25
+ export const MOON_PHASE_MIME_TYPE = UI_RESOURCE_MIME_TYPE;
25
26
  const here = path.dirname(fileURLToPath(import.meta.url));
26
27
  const BUNDLE_PATHS = [
27
28
  path.resolve(here, "..", "..", "..", "dist", "ui", "moon-phase.html"),
@@ -15,7 +15,7 @@
15
15
  * Also exports resource helpers (getTransitTimelineBundle, etc.) for use in
16
16
  * index.ts and server-sse.ts.
17
17
  */
18
- export declare const TRANSIT_TIMELINE_RESOURCE_URI = "ui://openephemeris/transit-timeline";
18
+ export declare const TRANSIT_TIMELINE_RESOURCE_URI: string;
19
19
  export declare const TRANSIT_TIMELINE_MIME_TYPE = "text/html;profile=mcp-app";
20
20
  /** Read the pre-built HTML bundle. Returns null if not yet built. */
21
21
  export declare function getTransitTimelineBundle(): string | null;
@@ -19,12 +19,13 @@ import fs from "node:fs";
19
19
  import path from "node:path";
20
20
  import { fileURLToPath } from "node:url";
21
21
  import { registerTool, validateRequired, SERVER_VERSION } from "../index.js";
22
+ import { UI_RESOURCE_MIME_TYPE, uiResourceUri } from "./ui-resource.js";
22
23
  import { getActiveClient } from "../../backend/client.js";
23
24
  import { OUTPUT_SCHEMA_JSON } from "../output-schemas.js";
24
25
  import { DATETIME_DESC, WINDOW_DATE_DESC, timezoneProperty, toDateTimeInputBody } from "../datetime.js";
25
26
  // ── Constants ─────────────────────────────────────────────────────────────────
26
- export const TRANSIT_TIMELINE_RESOURCE_URI = "ui://openephemeris/transit-timeline";
27
- export const TRANSIT_TIMELINE_MIME_TYPE = "text/html;profile=mcp-app";
27
+ export const TRANSIT_TIMELINE_RESOURCE_URI = uiResourceUri("transit-timeline");
28
+ export const TRANSIT_TIMELINE_MIME_TYPE = UI_RESOURCE_MIME_TYPE;
28
29
  const here = path.dirname(fileURLToPath(import.meta.url));
29
30
  const BUNDLE_PATHS = [
30
31
  path.resolve(here, "..", "..", "..", "dist", "ui", "transit-timeline.html"),
@@ -0,0 +1,43 @@
1
+ export declare const UI_RESOURCE_MIME_TYPE = "text/html;profile=mcp-app";
2
+ /**
3
+ * Versioned resource URI: `ui://openephemeris/<slug>?v=<server version>`.
4
+ *
5
+ * ChatGPT caches a `ui://` resource per connector and never re-reads it for a
6
+ * URI it has seen — a deploy alone does not refresh the widget. Stamping the
7
+ * package version into the URI makes every release a new URI, so hosts fetch
8
+ * the new bundle on their own. Caveat: a widget change must ship with a
9
+ * version bump to bust the cache (same version ⇒ same URI).
10
+ *
11
+ * The SAME string is used in the tool `_meta` (ui.resourceUri and its aliases),
12
+ * in resources/list and in resources/read, on both transports.
13
+ */
14
+ export declare function uiResourceUri(slug: string): string;
15
+ /** The URI without its `?v=` query — what resources/read matches on. */
16
+ export declare function uiResourceBase(uri: string): string;
17
+ /**
18
+ * CSP the bundles actually need: none. Every bundle is a single self-contained
19
+ * HTML file (JS/CSS inlined, images and icons as data: URIs), and the apps talk
20
+ * to the host over postMessage only — no fetch, no remote script, style, image
21
+ * or font. Empty lists are the spec's secure default; declaring them explicitly
22
+ * documents that this is deliberate, not forgotten.
23
+ */
24
+ export declare const UI_RESOURCE_CSP: {
25
+ connectDomains: string[];
26
+ resourceDomains: string[];
27
+ };
28
+ /**
29
+ * `_meta` for a `ui://` resource (resources/list entry and resources/read
30
+ * content item). Two wire forms, same values:
31
+ *
32
+ * ui.* — MCP Apps (SEP-1865) McpUiResourceMeta.
33
+ * openai/* — ChatGPT's aliases for the same fields.
34
+ *
35
+ * prefersBorder: true — the host draws the card border and background; the apps
36
+ * therefore paint no card, border or glow of their own (shared/tokens.css).
37
+ *
38
+ * ui.domain / openai/widgetDomain are deliberately NOT set: their format is
39
+ * host-specific (a hash subdomain on Claude, a derived *.oaiusercontent.com on
40
+ * ChatGPT) and a wrong value can stop the widget rendering. Omitted = the
41
+ * host's default sandbox origin, which is what every live render uses today.
42
+ */
43
+ export declare function uiResourceMeta(widgetDescription: string): Record<string, unknown>;