@openephemeris/mcp-server 4.17.0 → 4.19.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 +52 -0
- package/README.md +10 -10
- package/dist/backend/client.d.ts +57 -1
- package/dist/backend/client.js +125 -15
- package/dist/index.js +8 -110
- package/dist/prompts.js +26 -24
- package/dist/server-sse.d.ts +18 -0
- package/dist/server-sse.js +48 -120
- package/dist/tools/apps/_render-token.d.ts +34 -0
- package/dist/tools/apps/_render-token.js +50 -0
- package/dist/tools/apps/bazi-app.d.ts +1 -1
- package/dist/tools/apps/bazi-app.js +39 -14
- package/dist/tools/apps/bi-wheel-app.d.ts +10 -3
- package/dist/tools/apps/bi-wheel-app.js +79 -119
- package/dist/tools/apps/bodygraph-app.d.ts +7 -4
- package/dist/tools/apps/bodygraph-app.js +101 -46
- package/dist/tools/apps/chart-wheel-app.d.ts +1 -1
- package/dist/tools/apps/chart-wheel-app.js +59 -18
- package/dist/tools/apps/location-tools.js +10 -3
- package/dist/tools/apps/moon-phase-app.d.ts +1 -1
- package/dist/tools/apps/moon-phase-app.js +13 -4
- package/dist/tools/apps/transit-timeline-app.d.ts +1 -1
- package/dist/tools/apps/transit-timeline-app.js +6 -6
- package/dist/tools/apps/ui-resource.d.ts +43 -0
- package/dist/tools/apps/ui-resource.js +66 -0
- package/dist/tools/apps/ui-resources.d.ts +22 -0
- package/dist/tools/apps/ui-resources.js +105 -0
- package/dist/tools/apps/vedic-chart-app.d.ts +1 -1
- package/dist/tools/apps/vedic-chart-app.js +13 -3
- package/dist/tools/datetime-historical.js +7 -2
- package/dist/tools/datetime.js +2 -1
- package/dist/tools/dev.js +7 -6
- package/dist/tools/specialized/electional.js +4 -3
- package/dist/tools/specialized/ephemeris_extended.js +19 -4
- package/dist/tools/specialized/hd_group.js +2 -2
- package/dist/tools/specialized/moon.d.ts +1 -1
- package/dist/tools/specialized/moon.js +51 -43
- package/dist/tools/specialized/progressed.js +2 -25
- package/dist/tools/specialized/transits.js +5 -5
- package/dist/ui/bazi.html +1199 -1161
- package/dist/ui/bi-wheel.html +573 -504
- package/dist/ui/bodygraph.html +437 -337
- package/dist/ui/chart-wheel.html +654 -584
- package/dist/ui/moon-phase.html +190 -155
- package/dist/ui/transit-timeline.html +865 -830
- package/dist/ui/vedic-chart.html +971 -934
- package/package.json +2 -1
|
@@ -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
|
|
16
|
-
*
|
|
17
|
-
*
|
|
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,15 +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";
|
|
26
|
-
import {
|
|
29
|
+
import { UI_RESOURCE_MIME_TYPE, uiResourceUri } from "./ui-resource.js";
|
|
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";
|
|
34
|
+
import { RENDER_TOKEN_PROPERTY, renderTokenFromSvg, renderTokenFromVisual, renderTokenHeaders, withoutRenderToken, } from "./_render-token.js";
|
|
30
35
|
// ── Constants ─────────────────────────────────────────────────────────────
|
|
31
|
-
export const BODYGRAPH_RESOURCE_URI = "
|
|
36
|
+
export const BODYGRAPH_RESOURCE_URI = uiResourceUri("bodygraph");
|
|
32
37
|
// Appended to every model-visible HD tool description — trademark hygiene.
|
|
33
38
|
const HD_DISCLAIMER = " Human Design was originated by Ra Uru Hu; OpenEphemeris is an independent calculation service, not affiliated with Jovian Archive.";
|
|
34
|
-
export const BODYGRAPH_MIME_TYPE =
|
|
39
|
+
export const BODYGRAPH_MIME_TYPE = UI_RESOURCE_MIME_TYPE;
|
|
35
40
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
36
41
|
const BUNDLE_PATHS = [
|
|
37
42
|
path.resolve(here, "..", "..", "..", "dist", "ui", "bodygraph.html"),
|
|
@@ -67,7 +72,7 @@ export function clearBodygraphBundleCache() {
|
|
|
67
72
|
function capitalize(s) {
|
|
68
73
|
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
69
74
|
}
|
|
70
|
-
async function fetchGoBodygraphSVG(body, theme = "dark", view) {
|
|
75
|
+
async function fetchGoBodygraphSVG(body, theme = "dark", view, renderToken) {
|
|
71
76
|
try {
|
|
72
77
|
const client = getActiveClient();
|
|
73
78
|
// /visualization/bodygraph is registered as binary in BINARY_ENDPOINT_PREFIXES.
|
|
@@ -83,13 +88,24 @@ async function fetchGoBodygraphSVG(body, theme = "dark", view) {
|
|
|
83
88
|
// (handler_bodygraph.go) — only appended when the caller (the in-iframe
|
|
84
89
|
// layout toggle) actually requests the mandala view.
|
|
85
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).
|
|
86
95
|
if (view?.layout)
|
|
87
96
|
qs += `&layout=${encodeURIComponent(view.layout)}`;
|
|
88
97
|
if (view?.rings !== undefined)
|
|
89
98
|
qs += `&rings=${encodeURIComponent(view.rings)}`;
|
|
90
99
|
if (view?.houseSystem)
|
|
91
100
|
qs += `&house_system=${encodeURIComponent(view.houseSystem)}`;
|
|
92
|
-
|
|
101
|
+
// A re-render of a chart this account already paid for presents its render
|
|
102
|
+
// token; the API waives the charge only if it verifies for these inputs.
|
|
103
|
+
// The API returns the (new or echoed) token as data-oe-render-token on the
|
|
104
|
+
// root <svg> — read it back with renderTokenFromSvg.
|
|
105
|
+
const resp = await client.request("POST", `/visualization/bodygraph?${qs}`, {
|
|
106
|
+
data: body,
|
|
107
|
+
headers: renderTokenHeaders(renderToken),
|
|
108
|
+
});
|
|
93
109
|
if (typeof resp === "string")
|
|
94
110
|
return resp;
|
|
95
111
|
if (resp && typeof resp === "object") {
|
|
@@ -101,6 +117,10 @@ async function fetchGoBodygraphSVG(body, theme = "dark", view) {
|
|
|
101
117
|
return null;
|
|
102
118
|
}
|
|
103
119
|
catch (err) {
|
|
120
|
+
// Out of credits / signed out must reach the assistant with the credit-wall
|
|
121
|
+
// message and top-up link — never be papered over by the client render.
|
|
122
|
+
if (isBillingOrAuthError(err))
|
|
123
|
+
throw err;
|
|
104
124
|
console.warn("[bodygraph] Go SVG fetch failed, will fall back to client render:", err);
|
|
105
125
|
return null;
|
|
106
126
|
}
|
|
@@ -413,6 +433,9 @@ registerTool({
|
|
|
413
433
|
});
|
|
414
434
|
if (svg)
|
|
415
435
|
modelPayload._svg = svg;
|
|
436
|
+
const renderToken = renderTokenFromSvg(svg);
|
|
437
|
+
if (renderToken)
|
|
438
|
+
modelPayload._render_token = renderToken;
|
|
416
439
|
const summary = buildHdSummary(modelPayload, location);
|
|
417
440
|
if (bundleAvailable) {
|
|
418
441
|
// MCP Apps wire format: the UI is declared via `_meta.ui.resourceUri` and
|
|
@@ -468,6 +491,7 @@ registerTool({
|
|
|
468
491
|
type: "string",
|
|
469
492
|
description: "House system for the mandala house ring. Only used when layout=mandala and rings includes 'houses' with a birth location. Adds 1 credit when the house ring renders.",
|
|
470
493
|
},
|
|
494
|
+
render_token: RENDER_TOKEN_PROPERTY,
|
|
471
495
|
},
|
|
472
496
|
required: ["datetime"],
|
|
473
497
|
},
|
|
@@ -494,11 +518,15 @@ registerTool({
|
|
|
494
518
|
if (lon != null)
|
|
495
519
|
body.longitude = lon;
|
|
496
520
|
// Recalculate is iframe-only (visibility: ["app"]) — always co-fetch the SVG.
|
|
521
|
+
// Both halves present the render token: a theme / layout flip of the chart
|
|
522
|
+
// already on screen is then free (the house ring's 1-credit surcharge still
|
|
523
|
+
// applies when it renders); new birth data is charged as a new chart.
|
|
497
524
|
const [chartData, svg] = await Promise.all([
|
|
498
525
|
client.request("POST", "/human-design/chart", {
|
|
499
526
|
data: body,
|
|
527
|
+
headers: renderTokenHeaders(args.render_token),
|
|
500
528
|
}),
|
|
501
|
-
fetchGoBodygraphSVG(body, theme, { layout, rings, houseSystem }),
|
|
529
|
+
fetchGoBodygraphSVG(body, theme, { layout, rings, houseSystem }, args.render_token),
|
|
502
530
|
]);
|
|
503
531
|
const chart = chartData.chart ?? chartData;
|
|
504
532
|
const modelPayload = buildHdModelPayload(chart, {
|
|
@@ -510,6 +538,11 @@ registerTool({
|
|
|
510
538
|
});
|
|
511
539
|
if (svg)
|
|
512
540
|
modelPayload._svg = svg;
|
|
541
|
+
// The API echoes the presented token on a waived re-render, or mints a new
|
|
542
|
+
// one when this was charged as a new chart (e.g. edited birth data).
|
|
543
|
+
const renderToken = renderTokenFromSvg(svg);
|
|
544
|
+
if (renderToken)
|
|
545
|
+
modelPayload._render_token = renderToken;
|
|
513
546
|
return {
|
|
514
547
|
content: [{ type: "text", text: JSON.stringify({ ...modelPayload, server_version: SERVER_VERSION }) }],
|
|
515
548
|
};
|
|
@@ -1176,9 +1209,8 @@ registerTool({
|
|
|
1176
1209
|
"The Variable describes the four arrow directions attached to the Personality Sun/Earth " +
|
|
1177
1210
|
"and Design Sun/Earth positions, encoding the PHS (Primary Health System) — the optimal conditions " +
|
|
1178
1211
|
"for the body-mind to receive information and make decisions. " +
|
|
1179
|
-
"Returns an interpretation of
|
|
1180
|
-
"Perspective (
|
|
1181
|
-
"Environment (right/left Design Sun arrow), and Motivation (right/left Design Earth arrow). " +
|
|
1212
|
+
"Returns an interpretation of Motivation (Personality Sun), Digestion (Design Sun), " +
|
|
1213
|
+
"Perspective (Personality Node) and Environment (Design Node). " +
|
|
1182
1214
|
"This tool is called automatically by the bodygraph UI; you do not need to call it directly.",
|
|
1183
1215
|
inputSchema: {
|
|
1184
1216
|
type: "object",
|
|
@@ -1224,13 +1256,13 @@ registerTool({
|
|
|
1224
1256
|
"and being drawn by appetite and desire rather than routine. " +
|
|
1225
1257
|
"Left Digestion types often thrive with more novelty, conversation, and engagement at the table — " +
|
|
1226
1258
|
"your body needs to be activated, not passively nourished.",
|
|
1227
|
-
|
|
1228
|
-
1:
|
|
1229
|
-
2:
|
|
1230
|
-
3: "
|
|
1231
|
-
4:
|
|
1232
|
-
5:
|
|
1233
|
-
6:
|
|
1259
|
+
colors: {
|
|
1260
|
+
1: { left: "Appetite, Consecutive: orderly, one-at-a-time eating at regular times.", right: "Appetite, Alternating: grazing and switching between kinds of food across the day." },
|
|
1261
|
+
2: { left: "Taste, Open: sampling first; a bite tells the body what fits today.", right: "Taste, Closed: knowing what fits by sight or smell, without needing to try it." },
|
|
1262
|
+
3: { left: "Thirst, Hot: warm food, warm drinks, warm settings.", right: "Thirst, Cold: cool food, cool drinks, calm and cool settings." },
|
|
1263
|
+
4: { left: "Touch, Calm: eating in a quiet, settled atmosphere, often alone.", right: "Touch, Nervous: eating with some activity or buzz around." },
|
|
1264
|
+
5: { left: "Sound, High: eating with conversation, music or background sound.", right: "Sound, Low: eating in quiet." },
|
|
1265
|
+
6: { left: "Light, Direct: eating in daylight, earlier in the day.", right: "Light, Indirect: eating in softer light or later in the day." },
|
|
1234
1266
|
},
|
|
1235
1267
|
},
|
|
1236
1268
|
perspective: {
|
|
@@ -1251,13 +1283,13 @@ registerTool({
|
|
|
1251
1283
|
"stimulating environments. This is an Active environment variable: you are designed to encounter " +
|
|
1252
1284
|
"variety, new places, and fresh stimulation. Being too long in the same environment can lead to " +
|
|
1253
1285
|
"stagnation, restlessness, or decreased vitality.",
|
|
1254
|
-
|
|
1255
|
-
1:
|
|
1256
|
-
2:
|
|
1257
|
-
3: "
|
|
1258
|
-
4: "
|
|
1259
|
-
5:
|
|
1260
|
-
6:
|
|
1286
|
+
colors: {
|
|
1287
|
+
1: { left: "Caves, Exclusive: enclosed, protected spaces with few people.", right: "Caves, Blending: sheltered spaces shared with a trusted group." },
|
|
1288
|
+
2: { left: "Markets, Internal: places of exchange where you have a clear role inside.", right: "Markets, External: open places of exchange you move through." },
|
|
1289
|
+
3: { left: "Kitchens, Wet: hearth and gathering places with moisture and life.", right: "Kitchens, Dry: hearth and gathering places that are dry and warm." },
|
|
1290
|
+
4: { left: "Mountains, Active: high, open ground with movement.", right: "Mountains, Passive: high, open ground to rest in and overlook." },
|
|
1291
|
+
5: { left: "Valleys, Narrow: sheltered low ground with a close view.", right: "Valleys, Wide: sheltered low ground with a wide view." },
|
|
1292
|
+
6: { left: "Shores, Natural: edges where land meets water, in nature.", right: "Shores, Artificial: edges and thresholds in built places." },
|
|
1261
1293
|
},
|
|
1262
1294
|
},
|
|
1263
1295
|
motivation: {
|
|
@@ -1267,13 +1299,13 @@ registerTool({
|
|
|
1267
1299
|
left: "**Left Motivation** (Active) \u2014 Your decision-making is best guided by active, intentional pursuit " +
|
|
1268
1300
|
"of what aligns with your values and strategy. You are designed to initiate and drive toward what matters to you — " +
|
|
1269
1301
|
"though always filtered through your Authority to ensure the body’s wisdom is guiding the mind.",
|
|
1270
|
-
|
|
1271
|
-
1:
|
|
1272
|
-
2:
|
|
1273
|
-
3: "
|
|
1274
|
-
4:
|
|
1275
|
-
5:
|
|
1276
|
-
6:
|
|
1302
|
+
colors: {
|
|
1303
|
+
1: { left: "Fear: the drive to understand the unknown and stay safe.", right: "Fear: the drive to understand the unknown and stay safe." },
|
|
1304
|
+
2: { left: "Hope: trust in a better possible outcome.", right: "Hope: trust in a better possible outcome." },
|
|
1305
|
+
3: { left: "Desire: the push to change or affect the world.", right: "Desire: the push to change or affect the world." },
|
|
1306
|
+
4: { left: "Need: identifying what is necessary.", right: "Need: identifying what is necessary." },
|
|
1307
|
+
5: { left: "Guilt: the drive to fix and improve.", right: "Guilt: the drive to fix and improve." },
|
|
1308
|
+
6: { left: "Innocence: acting without a personal agenda.", right: "Innocence: acting without a personal agenda." },
|
|
1277
1309
|
},
|
|
1278
1310
|
},
|
|
1279
1311
|
};
|
|
@@ -1284,11 +1316,8 @@ registerTool({
|
|
|
1284
1316
|
};
|
|
1285
1317
|
}
|
|
1286
1318
|
const main = direction === "right" ? info.right : info.left;
|
|
1287
|
-
const
|
|
1288
|
-
? `\n\n**Your
|
|
1289
|
-
: "";
|
|
1290
|
-
const colorNote = color != null
|
|
1291
|
-
? `\n\n**Color ${color}** subtly modulates this Variable's expression — these nuances are covered in advanced PHS reading.`
|
|
1319
|
+
const colorNote = color != null && info.colors?.[color]
|
|
1320
|
+
? `\n\n**Your Color ${color}${tone != null ? `, Tone ${tone}` : ""}:** ${info.colors[color][direction]}`
|
|
1292
1321
|
: "";
|
|
1293
1322
|
const label = `${capitalize(variable)} Arrow — ${capitalize(direction)} Variable`;
|
|
1294
1323
|
const icon = variable === "digestion" ? "🔵" : variable === "perspective" ? "🟢" : variable === "environment" ? "⚪" : "🔴";
|
|
@@ -1296,7 +1325,7 @@ registerTool({
|
|
|
1296
1325
|
content: [{
|
|
1297
1326
|
type: "text",
|
|
1298
1327
|
text: `**${label}** ${icon}\n\n` +
|
|
1299
|
-
main +
|
|
1328
|
+
main + colorNote +
|
|
1300
1329
|
"\n\n*The Variable is a refinement layer within the Primary Health System (PHS). " +
|
|
1301
1330
|
"It describes the optimal conditions for the body-brain to receive and process information correctly. " +
|
|
1302
1331
|
"Living according to your Variable is an advanced practice that supports deeper health, clarity, and cognitive accuracy.*",
|
|
@@ -1315,7 +1344,7 @@ registerTool({
|
|
|
1315
1344
|
"(inline in Claude and ChatGPT; text summary elsewhere) highlighting the channels the transit " +
|
|
1316
1345
|
"temporarily COMPLETES with the natal chart and any centers it newly defines, for now or a chosen " +
|
|
1317
1346
|
"datetime. Birthplace may be a place name (`location`).\n\n" +
|
|
1318
|
-
"CREDIT COST:
|
|
1347
|
+
"CREDIT COST: 5 credits per call (chart 3 + render 2; +1 per place name).\n\n" +
|
|
1319
1348
|
"Do not use for the person's own natal bodygraph — use explore_human_design; for another person's " +
|
|
1320
1349
|
"chart overlaid on theirs use explore_human_design_connection." +
|
|
1321
1350
|
HD_DISCLAIMER,
|
|
@@ -1349,6 +1378,7 @@ registerTool({
|
|
|
1349
1378
|
"concentric ring set the natal chart uses. Set by the in-iframe layout toggle, never by " +
|
|
1350
1379
|
"the model.",
|
|
1351
1380
|
},
|
|
1381
|
+
render_token: RENDER_TOKEN_PROPERTY,
|
|
1352
1382
|
},
|
|
1353
1383
|
required: ["datetime"],
|
|
1354
1384
|
},
|
|
@@ -1410,7 +1440,12 @@ registerTool({
|
|
|
1410
1440
|
body.include_visual = true;
|
|
1411
1441
|
body.visual_config = { theme, ...(layout ? { layout } : {}) };
|
|
1412
1442
|
}
|
|
1413
|
-
|
|
1443
|
+
// The iframe's theme / layout refetch presents the token of the overlay it
|
|
1444
|
+
// is showing; the API waives it only for the same account + inputs.
|
|
1445
|
+
const resp = await client.request("POST", "/human-design/transit-chart", {
|
|
1446
|
+
data: body,
|
|
1447
|
+
headers: renderTokenHeaders(args.render_token),
|
|
1448
|
+
});
|
|
1414
1449
|
const transit = (resp?.transit ?? {});
|
|
1415
1450
|
const completed = Array.isArray(transit.completed_channels) ? transit.completed_channels : [];
|
|
1416
1451
|
const newCenters = Array.isArray(transit.newly_defined_centers) ? transit.newly_defined_centers : [];
|
|
@@ -1428,8 +1463,15 @@ registerTool({
|
|
|
1428
1463
|
...resp,
|
|
1429
1464
|
_svg: svg,
|
|
1430
1465
|
_theme: theme,
|
|
1431
|
-
|
|
1432
|
-
|
|
1466
|
+
...(renderTokenFromVisual(resp?.visual) ? { _render_token: renderTokenFromVisual(resp?.visual) } : {}),
|
|
1467
|
+
// Lets the iframe re-request this overlay in the host's theme. The
|
|
1468
|
+
// resolved coordinates/zone/name are pinned so the refetch neither
|
|
1469
|
+
// re-geocodes (another credit) nor drifts to a different request body,
|
|
1470
|
+
// which would no longer match the render token.
|
|
1471
|
+
_refetch: {
|
|
1472
|
+
tool: "explore_human_design_transit",
|
|
1473
|
+
args: { ...withoutRenderToken(args), latitude: lat, longitude: lon, location, ...(timezone ? { timezone } : {}) },
|
|
1474
|
+
},
|
|
1433
1475
|
server_version: SERVER_VERSION,
|
|
1434
1476
|
};
|
|
1435
1477
|
if (bundleAvailable && svg) {
|
|
@@ -1453,7 +1495,7 @@ registerTool({
|
|
|
1453
1495
|
"text summary elsewhere) with every connected channel classified by HD connection theory: " +
|
|
1454
1496
|
"electromagnetic (attraction), companionship (sameness), dominance (one defines), compromise " +
|
|
1455
1497
|
"(friction).\n\n" +
|
|
1456
|
-
"CREDIT COST:
|
|
1498
|
+
"CREDIT COST: 5 credits per call (composite 3 + render 2).\n\n" +
|
|
1457
1499
|
"Do not use for one person's chart — use explore_human_design; for the sky's transit on one chart " +
|
|
1458
1500
|
"use explore_human_design_transit; for Western compatibility use explore_bi_wheel." +
|
|
1459
1501
|
HD_DISCLAIMER,
|
|
@@ -1498,6 +1540,7 @@ registerTool({
|
|
|
1498
1540
|
"concentric ring set the natal chart uses. Set by the in-iframe layout toggle, never by " +
|
|
1499
1541
|
"the model.",
|
|
1500
1542
|
},
|
|
1543
|
+
render_token: RENDER_TOKEN_PROPERTY,
|
|
1501
1544
|
},
|
|
1502
1545
|
required: ["person_a", "person_b"],
|
|
1503
1546
|
},
|
|
@@ -1529,12 +1572,15 @@ registerTool({
|
|
|
1529
1572
|
throw new Error(`${label} requires either \`latitude\` + \`longitude\` or a resolvable \`location\` name. ` +
|
|
1530
1573
|
"Human Design is sensitive to birth location; a chart at 0°N 0°E is silently wrong.");
|
|
1531
1574
|
}
|
|
1575
|
+
// Pin the resolved coordinates for the iframe's refetch (see _refetch).
|
|
1576
|
+
pinned[label] = { ...(p ?? {}), latitude: lat, longitude: lon, ...(tz ? { timezone: tz } : {}) };
|
|
1532
1577
|
return {
|
|
1533
1578
|
birth_datetime_utc: await localToUtcIsoHistorical(`${label}.datetime`, String(p?.datetime), tz, { latitude: lat, longitude: lon }, `${label}.timezone`),
|
|
1534
1579
|
latitude: lat,
|
|
1535
1580
|
longitude: lon,
|
|
1536
1581
|
};
|
|
1537
1582
|
};
|
|
1583
|
+
const pinned = {};
|
|
1538
1584
|
const body = {
|
|
1539
1585
|
subject_1: await toSubject(args.person_a, "person_a"),
|
|
1540
1586
|
subject_2: await toSubject(args.person_b, "person_b"),
|
|
@@ -1547,7 +1593,10 @@ registerTool({
|
|
|
1547
1593
|
body.include_visual = true;
|
|
1548
1594
|
body.visual_config = { theme, ...(layout ? { layout } : {}) };
|
|
1549
1595
|
}
|
|
1550
|
-
const resp = await client.request("POST", "/human-design/composite", {
|
|
1596
|
+
const resp = await client.request("POST", "/human-design/composite", {
|
|
1597
|
+
data: body,
|
|
1598
|
+
headers: renderTokenHeaders(args.render_token),
|
|
1599
|
+
});
|
|
1551
1600
|
const connections = Array.isArray(resp?.connections) ? resp.connections : [];
|
|
1552
1601
|
const svg = resp?.visual?.data;
|
|
1553
1602
|
const byType = (t) => connections.filter((c) => c?.type === t).map((c) => c.id);
|
|
@@ -1566,7 +1615,13 @@ registerTool({
|
|
|
1566
1615
|
...resp,
|
|
1567
1616
|
_svg: svg,
|
|
1568
1617
|
_theme: theme,
|
|
1569
|
-
|
|
1618
|
+
...(renderTokenFromVisual(resp?.visual) ? { _render_token: renderTokenFromVisual(resp?.visual) } : {}),
|
|
1619
|
+
// Resolved coordinates pinned so the refetch neither re-geocodes nor
|
|
1620
|
+
// drifts from the request the render token was issued for.
|
|
1621
|
+
_refetch: {
|
|
1622
|
+
tool: "explore_human_design_connection",
|
|
1623
|
+
args: { ...withoutRenderToken(args), person_a: pinned.person_a, person_b: pinned.person_b },
|
|
1624
|
+
},
|
|
1570
1625
|
server_version: SERVER_VERSION,
|
|
1571
1626
|
};
|
|
1572
1627
|
if (bundleAvailable && svg) {
|
|
@@ -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
|
|
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
|
-
import { DATETIME_DESC, TIMEZONE_PROPERTY } from "../datetime.js";
|
|
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 = "
|
|
28
|
-
export const CHART_WHEEL_MIME_TYPE =
|
|
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/
|
|
@@ -159,9 +160,10 @@ registerTool({
|
|
|
159
160
|
items: { type: "string" },
|
|
160
161
|
description: "Optional list of body names to include. " +
|
|
161
162
|
"Defaults to 13 classical bodies (Sun through Pluto + Chiron + Nodes). " +
|
|
162
|
-
"Use 'all' as a single item to include every available body (
|
|
163
|
+
"Use 'all' as a single item to include every available body (the three Liliths — mean, true and interpolated — Ceres, Juno, Vesta, Pallas, Pholus, " +
|
|
163
164
|
"the 8 trans-Neptunian objects — eris, sedna, makemake, haumea, quaoar, orcus, ixion, varuna — and the " +
|
|
164
165
|
"8 Uranian/Hamburg-School points — cupido, hades, zeus, kronos, apollon, admetos, vulkanus, poseidon). " +
|
|
166
|
+
"Lilith slugs: 'lilith' (mean), 'lilith_true', 'lilith_interpolated'. " +
|
|
165
167
|
"Example: ['sun','moon','lilith','sedna'].",
|
|
166
168
|
},
|
|
167
169
|
},
|
|
@@ -410,19 +412,40 @@ registerTool({
|
|
|
410
412
|
const baseBody = buildNatalBody(datetime, lat, lon, houseSystem, args.timezone);
|
|
411
413
|
let endpoint = "/ephemeris/natal-chart";
|
|
412
414
|
let requestBody = baseBody;
|
|
415
|
+
// Where the chart sits in the reply: natal-chart returns it top-level,
|
|
416
|
+
// /ephemeris/progressed under `data`, the return endpoints under `chart`.
|
|
417
|
+
let chartKey = null;
|
|
418
|
+
const target = toDateTimeInputBody("target_datetime", String(args.target_datetime ?? datetime), args.timezone);
|
|
413
419
|
if (chartType === "solar_return") {
|
|
420
|
+
// SolarReturnRequest is flat — birth_datetime at the top level, no `subject`.
|
|
414
421
|
endpoint = "/predictive/returns/solar";
|
|
415
|
-
|
|
422
|
+
const subject = baseBody.subject;
|
|
423
|
+
requestBody = {
|
|
424
|
+
birth_datetime: toDateTimeInputBody("datetime", datetime, args.timezone),
|
|
425
|
+
birth_location: subject.birth_location,
|
|
426
|
+
target_datetime: target,
|
|
427
|
+
house_system: baseBody.configuration.house_system,
|
|
428
|
+
};
|
|
429
|
+
chartKey = "chart";
|
|
416
430
|
}
|
|
417
431
|
else if (chartType === "progressed") {
|
|
418
432
|
// openapi.json exposes /ephemeris/progressed — /predictive/progressed
|
|
419
433
|
// does not exist and 404'd every progressed recalculate in the iframe.
|
|
420
434
|
endpoint = "/ephemeris/progressed";
|
|
421
|
-
requestBody
|
|
422
|
-
|
|
435
|
+
requestBody = {
|
|
436
|
+
subject: baseBody.subject,
|
|
437
|
+
configuration: { house_system: baseBody.configuration.house_system },
|
|
438
|
+
target_datetime: target,
|
|
439
|
+
progression_options: { method: "secondary" },
|
|
440
|
+
};
|
|
441
|
+
chartKey = "data";
|
|
423
442
|
}
|
|
424
443
|
// Fetch updated chart JSON only — chart renders client-side in the iframe
|
|
425
|
-
const
|
|
444
|
+
const reply = await client.post(endpoint, requestBody);
|
|
445
|
+
const chartData = chartKey ? reply[chartKey] : reply;
|
|
446
|
+
if (!chartData || typeof chartData !== "object") {
|
|
447
|
+
throw new Error(`Unexpected ${chartType} response: no \`${chartKey}\` chart in the API reply.`);
|
|
448
|
+
}
|
|
426
449
|
// Normalise through buildModelPayload so house-switch results use the same
|
|
427
450
|
// format as the initial response.
|
|
428
451
|
const payload = buildModelPayload(chartData, {
|
|
@@ -457,7 +480,10 @@ const CLASSICAL_PLANETS = new Set([
|
|
|
457
480
|
"sun", "moon", "mercury", "venus", "mars",
|
|
458
481
|
"jupiter", "saturn", "uranus", "neptune", "pluto",
|
|
459
482
|
// Nodes use space form to match the renderer's glyph table (chart-renderer.ts).
|
|
460
|
-
"chiron", "
|
|
483
|
+
"chiron", "asc", "mc",
|
|
484
|
+
// The four lunar nodes keep the API's own names (lowercased): Mean and True
|
|
485
|
+
// are different points and never share a name.
|
|
486
|
+
"north node (mean)", "north node (true)", "south node (mean)", "south node (true)",
|
|
461
487
|
]);
|
|
462
488
|
/**
|
|
463
489
|
* Canonicalise the Go backend's verbose node keys ("North Node (Mean)" /
|
|
@@ -469,25 +495,40 @@ const CLASSICAL_PLANETS = new Set([
|
|
|
469
495
|
function canonicalizeBodyName(raw) {
|
|
470
496
|
const lower = raw.toLowerCase().trim();
|
|
471
497
|
if (lower === "north node (true)" || lower === "true node")
|
|
472
|
-
return "
|
|
498
|
+
return "north node (true)";
|
|
473
499
|
if (lower === "north node (mean)" || lower === "mean node" || lower === "north node")
|
|
474
|
-
return "north node";
|
|
475
|
-
if (lower === "south node (
|
|
476
|
-
return "south node";
|
|
500
|
+
return "north node (mean)";
|
|
501
|
+
if (lower === "south node (true)" || lower === "true south node")
|
|
502
|
+
return "south node (true)";
|
|
503
|
+
if (lower === "south node (mean)" || lower === "south node")
|
|
504
|
+
return "south node (mean)";
|
|
505
|
+
// Lilith: three different points (mean apogee 12, osculating 13, interpolated
|
|
506
|
+
// 40), each kept under the API's own name — never merged into one "lilith".
|
|
507
|
+
if (lower === "lilith (true)" || lower === "lilith_true" || lower === "true_lilith" || lower === "osculating apogee")
|
|
508
|
+
return "lilith (true)";
|
|
509
|
+
if (lower === "lilith (interpolated)" || lower === "lilith_interpolated" || lower === "lilith_natural" || lower === "interpolated apogee")
|
|
510
|
+
return "lilith (interpolated)";
|
|
511
|
+
if (lower === "lilith (mean)" || lower === "lilith" || lower === "mean_lilith" || lower === "mean apogee" || lower === "black moon lilith")
|
|
512
|
+
return "lilith (mean)";
|
|
477
513
|
return lower;
|
|
478
514
|
}
|
|
479
515
|
const EXTENDED_BODIES = new Set([
|
|
480
516
|
// Slugs match the Go API's ephemerisIDForSlug (handler_wheel_resolver.go) so
|
|
481
|
-
// additional_objects entries actually resolve server-side.
|
|
482
|
-
|
|
517
|
+
// additional_objects entries actually resolve server-side. Only ephemeris
|
|
518
|
+
// bodies belong here: vertex / part_of_fortune are not additional_objects and
|
|
519
|
+
// made the API reject every bodies:["all"] request.
|
|
520
|
+
"lilith", "mean_lilith", "lilith_true", "lilith_interpolated",
|
|
483
521
|
"ceres", "juno", "vesta", "pallas", "pholus",
|
|
484
|
-
"vertex", "part_of_fortune",
|
|
485
522
|
// Trans-Neptunian objects (all 8 — #603 catalog expansion)
|
|
486
523
|
"eris", "sedna", "makemake", "haumea", "quaoar", "orcus", "ixion", "varuna",
|
|
487
524
|
// Uranian / Hamburg School hypothetical points (all 8 — #603 catalog expansion)
|
|
488
525
|
"cupido", "hades", "zeus", "kronos", "apollon", "admetos", "vulkanus", "poseidon",
|
|
489
526
|
]);
|
|
490
|
-
|
|
527
|
+
// Names as they come back from the API (canonicalised), for display filtering.
|
|
528
|
+
const ALL_KNOWN_BODIES = new Set([
|
|
529
|
+
...CLASSICAL_PLANETS, ...EXTENDED_BODIES,
|
|
530
|
+
"lilith (mean)", "lilith (true)", "lilith (interpolated)",
|
|
531
|
+
]);
|
|
491
532
|
/**
|
|
492
533
|
* Build a lean, SVG-free payload for Claude's text context.
|
|
493
534
|
* Strips SVG, filters to classical planets only, and packages birth params.
|
|
@@ -617,7 +658,7 @@ function buildChartSummary(data, location, houseSystem) {
|
|
|
617
658
|
const CLASSICAL_ORDER = [
|
|
618
659
|
"sun", "moon", "mercury", "venus", "mars",
|
|
619
660
|
"jupiter", "saturn", "uranus", "neptune", "pluto",
|
|
620
|
-
"chiron", "
|
|
661
|
+
"chiron", "north node (mean)", "north node (true)", "south node (mean)", "south node (true)", "asc", "mc",
|
|
621
662
|
];
|
|
622
663
|
planetsArray.sort((a, b) => {
|
|
623
664
|
const ai = CLASSICAL_ORDER.indexOf(a.name.toLowerCase());
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { registerTool, validateRequired } from "../index.js";
|
|
2
|
-
import { getActiveClient } from "../../backend/client.js";
|
|
2
|
+
import { getActiveClient, isBillingOrAuthError } from "../../backend/client.js";
|
|
3
3
|
import { OUTPUT_SCHEMA_JSON } from "../output-schemas.js";
|
|
4
4
|
import { TZDATA_AUTHORITATIVE_FROM_YEAR } from "../datetime-historical.js";
|
|
5
5
|
import { isQualifiedForTopHit } from "./_location-resolver.js";
|
|
@@ -245,7 +245,11 @@ registerTool({
|
|
|
245
245
|
});
|
|
246
246
|
}
|
|
247
247
|
}
|
|
248
|
-
catch {
|
|
248
|
+
catch (err) {
|
|
249
|
+
// The credit wall / auth / tier gate is not "server unavailable":
|
|
250
|
+
// surface it with its top-up link instead of a quiet estimate.
|
|
251
|
+
if (isBillingOrAuthError(err))
|
|
252
|
+
throw err;
|
|
249
253
|
// Server path unavailable — the tzdata estimate stands, still
|
|
250
254
|
// labelled historical_estimate. Degrading to a labelled
|
|
251
255
|
// estimate is acceptable; failing the whole search is not.
|
|
@@ -349,7 +353,10 @@ registerTool({
|
|
|
349
353
|
};
|
|
350
354
|
}
|
|
351
355
|
}
|
|
352
|
-
catch {
|
|
356
|
+
catch (err) {
|
|
357
|
+
// Credit wall / auth / tier gate: surface it, don't degrade.
|
|
358
|
+
if (isBillingOrAuthError(err))
|
|
359
|
+
throw err;
|
|
353
360
|
// Server path unavailable — fall through to the local
|
|
354
361
|
// tzdata estimate below, flagged as such.
|
|
355
362
|
}
|
|
@@ -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
|
|
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 {
|
|
18
|
+
import { UI_RESOURCE_MIME_TYPE, uiResourceUri } from "./ui-resource.js";
|
|
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 = "
|
|
24
|
-
export const MOON_PHASE_MIME_TYPE =
|
|
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"),
|
|
@@ -111,8 +112,16 @@ async function computeMoonData(args) {
|
|
|
111
112
|
client.request("GET", "/ephemeris/moon/void-of-course", { params }),
|
|
112
113
|
client.request("GET", "/ephemeris/moon/aspects", { params }),
|
|
113
114
|
]);
|
|
115
|
+
// A credit wall (402), expired auth (401) or tier gate (403) on ANY of the
|
|
116
|
+
// three calls must surface as the thrown error — with its top-up / sign-in
|
|
117
|
+
// link — not as an "Unknown ?%" dial that reads like real data. Only
|
|
118
|
+
// non-billing partial failures (VOC or aspects down) degrade gracefully.
|
|
119
|
+
rethrowBillingOrAuth([phase, voc, aspects]);
|
|
120
|
+
// Without the phase there is no dial to draw; fail rather than render "Unknown".
|
|
121
|
+
if (phase.status === "rejected")
|
|
122
|
+
throw phase.reason;
|
|
114
123
|
const mergedData = {
|
|
115
|
-
phase: phase.
|
|
124
|
+
phase: phase.value,
|
|
116
125
|
void_of_course: voc.status === "fulfilled" ? voc.value : { error: voc.reason?.message },
|
|
117
126
|
aspects: aspects.status === "fulfilled" ? aspects.value : null,
|
|
118
127
|
};
|
|
@@ -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
|
|
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 = "
|
|
27
|
-
export const TRANSIT_TIMELINE_MIME_TYPE =
|
|
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"),
|
|
@@ -119,10 +120,9 @@ registerTool({
|
|
|
119
120
|
"in-orb window (when the transit starts and ends, and which pass it is when a retrograde repeats it), " +
|
|
120
121
|
"and transits already in orb at the start are listed with applying/separating.\n\n" +
|
|
121
122
|
"ASPECT ANGLES: 0 = conjunction/return (default), 180 = opposition, 90 = square, 120 = trine, 60 " +
|
|
122
|
-
"= sextile. EFFICIENCY: specify transiting_planets and natal_points to keep compute fast.
|
|
123
|
-
"
|
|
124
|
-
"
|
|
125
|
-
"CREDIT COST: 6 credits per call (natal chart + predictive transit search).\n\n" +
|
|
123
|
+
"= sextile. EFFICIENCY: specify transiting_planets and natal_points to keep compute fast. " +
|
|
124
|
+
"MAX SPAN and CREDIT COST as " +
|
|
125
|
+
"ephemeris_transits (6 for ≤1 year).\n\n" +
|
|
126
126
|
"Do not use for transit data as JSON — use ephemeris_transits; for the sky today with no birth " +
|
|
127
127
|
"data use electional_moment_analysis; to see one date's transits drawn around the natal wheel use " +
|
|
128
128
|
"explore_bi_wheel (mode='transit').",
|