@openephemeris/mcp-server 4.16.0 → 4.18.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 +46 -0
- package/README.md +15 -15
- package/config/dev-allowlist.json +31 -19
- package/dist/backend/client.d.ts +57 -1
- package/dist/backend/client.js +125 -15
- package/dist/prompts.js +26 -24
- package/dist/server-sse.d.ts +18 -0
- package/dist/server-sse.js +41 -6
- 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.js +36 -12
- package/dist/tools/apps/bi-wheel-app.d.ts +9 -2
- package/dist/tools/apps/bi-wheel-app.js +76 -117
- package/dist/tools/apps/bodygraph-app.js +88 -41
- package/dist/tools/apps/chart-wheel-app.js +56 -16
- package/dist/tools/apps/location-tools.js +10 -3
- package/dist/tools/apps/moon-phase-app.js +10 -2
- package/dist/tools/apps/transit-timeline-app.d.ts +3 -0
- package/dist/tools/apps/transit-timeline-app.js +77 -9
- package/dist/tools/apps/vedic-chart-app.js +10 -1
- 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 +1523 -1522
- package/dist/ui/bi-wheel.html +406 -374
- package/dist/ui/bodygraph.html +83 -79
- package/dist/ui/chart-wheel.html +394 -361
- package/dist/ui/transit-timeline.html +3 -3
- package/dist/ui/vedic-chart.html +818 -818
- package/package.json +1 -1
|
@@ -12,10 +12,10 @@
|
|
|
12
12
|
* Supported modes (BiWheelMode):
|
|
13
13
|
* synastry — two natal charts (person1 inner, person2 outer)
|
|
14
14
|
* transit — natal inner + transiting planets outer
|
|
15
|
-
* progressed — natal inner + secondary progressions outer (POST /ephemeris/progressed)
|
|
15
|
+
* progressed — natal inner + secondary progressions outer (POST /ephemeris/progressed, method=secondary)
|
|
16
16
|
* solar_return — natal inner + solar return chart outer (POST /predictive/returns/solar)
|
|
17
17
|
* lunar_return — natal inner + lunar return chart outer (POST /predictive/returns/lunar)
|
|
18
|
-
* solar_arc — natal inner + solar arc directions outer (
|
|
18
|
+
* solar_arc — natal inner + solar arc directions outer (POST /ephemeris/progressed, method=solar_arc)
|
|
19
19
|
*
|
|
20
20
|
* Cross-aspects are computed client-side (UI) AND server-side (for summary/fallback).
|
|
21
21
|
* The payload cross_aspects array is capped at 30 tightest-orb aspects.
|
|
@@ -26,7 +26,7 @@ import { fileURLToPath } from "node:url";
|
|
|
26
26
|
import { registerTool, SERVER_VERSION } from "../index.js";
|
|
27
27
|
import { getActiveClient } from "../../backend/client.js";
|
|
28
28
|
import { OUTPUT_SCHEMA_JSON } from "../output-schemas.js";
|
|
29
|
-
import { DATETIME_DESC, timezoneProperty } from "../datetime.js";
|
|
29
|
+
import { DATETIME_DESC, timezoneProperty, toDateTimeInputBody } from "../datetime.js";
|
|
30
30
|
import { coordsFromArgsOrLocation } from "./_location-resolver.js";
|
|
31
31
|
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
32
32
|
export const BI_WHEEL_RESOURCE_URI = "ui://openephemeris/bi-wheel";
|
|
@@ -130,120 +130,68 @@ function buildNatalBody(datetime, lat, lon, timezone, houseSystem = "placidus")
|
|
|
130
130
|
}
|
|
131
131
|
return body;
|
|
132
132
|
}
|
|
133
|
-
function
|
|
133
|
+
function locationBody(lat, lon, timezone) {
|
|
134
134
|
return {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
birth_datetime: { iso: natalDatetime },
|
|
138
|
-
birth_location: {
|
|
139
|
-
latitude: { decimal: lat ?? 0 },
|
|
140
|
-
longitude: { decimal: lon ?? 0 },
|
|
141
|
-
},
|
|
142
|
-
},
|
|
143
|
-
transit_datetime: { iso: transitDate },
|
|
135
|
+
latitude: { decimal: lat },
|
|
136
|
+
longitude: { decimal: lon },
|
|
144
137
|
...(timezone ? { timezone: { iana_name: timezone } } : {}),
|
|
145
|
-
configuration: { house_system: HOUSE_SYSTEM_MAP["placidus"] },
|
|
146
138
|
};
|
|
147
139
|
}
|
|
148
|
-
/**
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
},
|
|
159
|
-
configuration: {
|
|
160
|
-
target_datetime: { iso: targetDatetime },
|
|
161
|
-
method: "secondary",
|
|
162
|
-
house_system: HOUSE_SYSTEM_MAP["placidus"],
|
|
163
|
-
},
|
|
164
|
-
};
|
|
165
|
-
if (timezone) {
|
|
166
|
-
body.subject.birth_location.timezone = { iana_name: timezone };
|
|
167
|
-
}
|
|
140
|
+
/**
|
|
141
|
+
* POST /ephemeris/progressed. `method` picks secondary progressions or true
|
|
142
|
+
* solar arc directions — the server directs planets, angles AND house cusps by
|
|
143
|
+
* the Sun's real arc (ProgressedChartRequest; target_datetime is top-level,
|
|
144
|
+
* the method rides in progression_options).
|
|
145
|
+
*/
|
|
146
|
+
function buildProgressedBody(natalDatetime, lat, lon, timezone, targetDatetime, targetTimezone, method, houseSystem) {
|
|
147
|
+
const body = buildNatalBody(natalDatetime, lat, lon, timezone, houseSystem);
|
|
148
|
+
body.target_datetime = toDateTimeInputBody("person2_datetime", targetDatetime, targetTimezone);
|
|
149
|
+
body.progression_options = { method };
|
|
168
150
|
return body;
|
|
169
151
|
}
|
|
170
|
-
/**
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
latitude: { decimal: returnLat ?? natalLat ?? 0 },
|
|
183
|
-
longitude: { decimal: returnLon ?? natalLon ?? 0 },
|
|
184
|
-
},
|
|
185
|
-
target_datetime: { iso: targetDatetime },
|
|
186
|
-
configuration: { house_system: HOUSE_SYSTEM_MAP["placidus"] },
|
|
152
|
+
/**
|
|
153
|
+
* POST /predictive/returns/solar or /predictive/returns/lunar. These take a
|
|
154
|
+
* flat body (birth_datetime at the top level, no `subject`) — see
|
|
155
|
+
* SolarReturnRequest / LunarReturnRequest in openapi.json.
|
|
156
|
+
*/
|
|
157
|
+
function buildReturnBody(natalDatetime, natalLat, natalLon, timezone, targetDatetime, targetTimezone, returnLat, returnLon, houseSystem) {
|
|
158
|
+
return {
|
|
159
|
+
birth_datetime: toDateTimeInputBody("person1_datetime", natalDatetime, timezone, "person1_timezone"),
|
|
160
|
+
birth_location: locationBody(natalLat, natalLon, timezone),
|
|
161
|
+
return_location: locationBody(returnLat, returnLon),
|
|
162
|
+
target_datetime: toDateTimeInputBody("person2_datetime", targetDatetime, targetTimezone, "person2_timezone"),
|
|
163
|
+
house_system: HOUSE_SYSTEM_MAP[houseSystem] ?? HOUSE_SYSTEM_MAP["placidus"],
|
|
187
164
|
};
|
|
188
|
-
if (timezone) {
|
|
189
|
-
body.subject.birth_location.timezone = { iana_name: timezone };
|
|
190
|
-
}
|
|
191
|
-
return body;
|
|
192
165
|
}
|
|
193
166
|
/**
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
*
|
|
167
|
+
* Route outer-wheel fetch to the correct API endpoint based on mode, and hand
|
|
168
|
+
* back a chart in the natal-chart shape (top-level planets/houses/angles):
|
|
169
|
+
* /ephemeris/progressed nests it under `data`, the return endpoints under `chart`.
|
|
197
170
|
*/
|
|
198
|
-
function
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
const offsetPlanets = (raw) => {
|
|
205
|
-
if (Array.isArray(raw)) {
|
|
206
|
-
return raw.map((p) => ({
|
|
207
|
-
...p,
|
|
208
|
-
longitude: ((p.longitude ?? 0) + arcDegrees + 360) % 360,
|
|
209
|
-
}));
|
|
171
|
+
export async function fetchOuterChart(client, mode, dt1, lat1, lon1, tz1, dt2, lat2, lon2, tz2, houseSystem = "placidus") {
|
|
172
|
+
const post = (path, body) => client.post(path, body);
|
|
173
|
+
const unwrap = (resp, key) => {
|
|
174
|
+
const inner = resp?.[key];
|
|
175
|
+
if (!inner || typeof inner !== "object") {
|
|
176
|
+
throw new Error(`Unexpected ${mode} response: no \`${key}\` chart in the API reply.`);
|
|
210
177
|
}
|
|
211
|
-
|
|
212
|
-
const out = {};
|
|
213
|
-
for (const [k, v] of Object.entries(raw)) {
|
|
214
|
-
if (v && typeof v === "object" && "longitude" in v) {
|
|
215
|
-
out[k] = { ...v, longitude: ((v.longitude ?? 0) + arcDegrees + 360) % 360 };
|
|
216
|
-
}
|
|
217
|
-
else {
|
|
218
|
-
out[k] = v;
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
return out;
|
|
222
|
-
}
|
|
223
|
-
return raw;
|
|
178
|
+
return inner;
|
|
224
179
|
};
|
|
225
|
-
return {
|
|
226
|
-
...natalData,
|
|
227
|
-
planets: offsetPlanets(natalData.planets),
|
|
228
|
-
_solar_arc_degrees: Math.round(arcDegrees * 1000) / 1000,
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
/** Route outer-wheel fetch to the correct API endpoint based on mode. */
|
|
232
|
-
async function fetchOuterChart(client, mode, dt1, lat1, lon1, tz1, dt2, lat2, lon2, tz2) {
|
|
233
180
|
switch (mode) {
|
|
234
181
|
case "synastry":
|
|
235
|
-
return
|
|
182
|
+
return post("/ephemeris/natal-chart", buildNatalBody(dt2, lat2, lon2, tz2, houseSystem));
|
|
236
183
|
case "transit":
|
|
237
|
-
return
|
|
184
|
+
return post("/ephemeris/natal-chart", buildNatalBody(dt2, lat2 ?? lat1, lon2 ?? lon1, tz2 ?? tz1, houseSystem));
|
|
238
185
|
case "progressed":
|
|
239
|
-
|
|
186
|
+
case "solar_arc":
|
|
187
|
+
return unwrap(await post("/ephemeris/progressed", buildProgressedBody(dt1, lat1, lon1, tz1, dt2, tz2 ?? tz1, mode === "progressed" ? "secondary" : "solar_arc", houseSystem)), "data");
|
|
240
188
|
case "solar_return":
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
const
|
|
246
|
-
return
|
|
189
|
+
case "lunar_return": {
|
|
190
|
+
if (lat1 == null || lon1 == null) {
|
|
191
|
+
throw new Error(`Internal: ${mode} requires Person 1's latitude and longitude.`);
|
|
192
|
+
}
|
|
193
|
+
const path = mode === "solar_return" ? "/predictive/returns/solar" : "/predictive/returns/lunar";
|
|
194
|
+
return unwrap(await post(path, buildReturnBody(dt1, lat1, lon1, tz1, dt2, tz2 ?? tz1, lat2 ?? lat1, lon2 ?? lon1, houseSystem)), "chart");
|
|
247
195
|
}
|
|
248
196
|
}
|
|
249
197
|
}
|
|
@@ -292,25 +240,35 @@ export function computeCrossAspects(planets1, planets2) {
|
|
|
292
240
|
const CLASSICAL_PLANETS = new Set([
|
|
293
241
|
"sun", "moon", "mercury", "venus", "mars",
|
|
294
242
|
"jupiter", "saturn", "uranus", "neptune", "pluto",
|
|
295
|
-
"chiron", "
|
|
243
|
+
"chiron", "asc", "mc",
|
|
244
|
+
// The four lunar nodes keep the API's own names (lowercased) — Mean and True
|
|
245
|
+
// are different points and must never share a name.
|
|
246
|
+
"north node (mean)", "north node (true)", "south node (mean)", "south node (true)",
|
|
296
247
|
]);
|
|
297
248
|
/**
|
|
298
|
-
* Normalise the Go backend's verbose planet key
|
|
299
|
-
*
|
|
249
|
+
* Normalise the Go backend's verbose planet key to the canonical lowercase form
|
|
250
|
+
* the renderer expects. The lunar nodes keep their full names ("north node
|
|
251
|
+
* (mean)" etc.), matching packages/widget-ui/shared/lunar-nodes.ts.
|
|
300
252
|
*/
|
|
301
253
|
function canonicalizePlanetName(raw) {
|
|
302
254
|
const lower = raw.toLowerCase();
|
|
303
255
|
// Various node/chiron aliases the Go PlanetName() function emits.
|
|
304
|
-
// Keep mean vs true DISTINCT so the UI can offer a
|
|
305
|
-
//
|
|
256
|
+
// Keep mean vs true DISTINCT (North and South alike) so the UI can offer a
|
|
257
|
+
// node toggle and every label says which node it is.
|
|
306
258
|
if (lower === "north node (true)" || lower === "true node")
|
|
307
|
-
return "
|
|
259
|
+
return "north node (true)";
|
|
308
260
|
if (lower === "north node (mean)" || lower === "mean node" || lower === "north node")
|
|
309
|
-
return "
|
|
310
|
-
if (lower === "south node (
|
|
311
|
-
return "
|
|
312
|
-
if (lower === "
|
|
313
|
-
return "
|
|
261
|
+
return "north node (mean)";
|
|
262
|
+
if (lower === "south node (true)" || lower === "true south node")
|
|
263
|
+
return "south node (true)";
|
|
264
|
+
if (lower === "south node (mean)" || lower === "south node")
|
|
265
|
+
return "south node (mean)";
|
|
266
|
+
if (lower === "lilith (true)" || lower === "lilith_true" || lower === "true_lilith" || lower === "osculating apogee")
|
|
267
|
+
return "lilith (true)";
|
|
268
|
+
if (lower === "lilith (interpolated)" || lower === "lilith_interpolated" || lower === "lilith_natural" || lower === "interpolated apogee")
|
|
269
|
+
return "lilith (interpolated)";
|
|
270
|
+
if (lower === "lilith (mean)" || lower === "lilith" || lower === "mean_lilith" || lower === "mean apogee" || lower === "black moon lilith")
|
|
271
|
+
return "lilith (mean)";
|
|
314
272
|
// Strip parenthetical qualifiers for any other bodies just in case
|
|
315
273
|
return lower.replace(/\s*\(.*?\)/g, "").replace(/\s+/g, "_");
|
|
316
274
|
}
|
|
@@ -613,7 +571,7 @@ registerTool({
|
|
|
613
571
|
const loc2 = MODE_META[mode].outerLabel(dt2, args.person2_name);
|
|
614
572
|
const [innerData, outerData] = await Promise.all([
|
|
615
573
|
client.post("/ephemeris/natal-chart", buildNatalBody(dt1, lat1, lon1, tz1, houseSystem)),
|
|
616
|
-
fetchOuterChart(client, mode, dt1, lat1, lon1, tz1, dt2, lat2, lon2, tz2),
|
|
574
|
+
fetchOuterChart(client, mode, dt1, lat1, lon1, tz1, dt2, lat2, lon2, tz2, houseSystem),
|
|
617
575
|
]);
|
|
618
576
|
const params1 = { datetime: dt1, timezone: tz1 ?? null, latitude: lat1 ?? null, longitude: lon1 ?? null, location: loc1 };
|
|
619
577
|
const params2 = { datetime: dt2, timezone: tz2 ?? null, latitude: lat2 ?? null, longitude: lon2 ?? null, location: loc2 };
|
|
@@ -721,9 +679,10 @@ const PLANET_THEMES = {
|
|
|
721
679
|
neptune: "dreams, idealism, compassion, and dissolution",
|
|
722
680
|
pluto: "transformation, power dynamics, and profound change",
|
|
723
681
|
chiron: "the wounded healer — deep vulnerability, mastery through pain, and the gift of empathy forged in suffering",
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
682
|
+
"north node (mean)": "soul direction and karmic growth edge; the evolutionary path forward in this lifetime",
|
|
683
|
+
"north node (true)": "soul direction and karmic growth edge; the evolutionary path forward in this lifetime",
|
|
684
|
+
"south node (mean)": "accumulated karma and innate past-life gifts; the comfort zone that must be transcended",
|
|
685
|
+
"south node (true)": "accumulated karma and innate past-life gifts; the comfort zone that must be transcended",
|
|
727
686
|
asc: "the rising sign and outer personality mask; the social self, physical appearance, and the lens through which life is encountered",
|
|
728
687
|
mc: "public vocation, highest ambition, and the world's face; the pinnacle of achievement this person strives toward",
|
|
729
688
|
};
|
|
@@ -797,7 +756,7 @@ function interpretBiWheelPlanet(planet, sign, house, wheel, retrograde = false,
|
|
|
797
756
|
`Unlike transits, progressed planets move slowly — a sign ingress can take years to unfold. This position describes a chapter of inner development: the themes of ${capitalize(planet)} are now being expressed with the colouring of ${signQuality}. Aspects from this progressed position to natal points mark turning points in that developmental arc.`);
|
|
798
757
|
}
|
|
799
758
|
else if (mode === "solar_arc") {
|
|
800
|
-
return (`**Solar Arc ${capitalize(planet)}:** This directed position represents natal ${capitalize(planet)} advanced by
|
|
759
|
+
return (`**Solar Arc ${capitalize(planet)}:** This directed position represents natal ${capitalize(planet)} advanced by the solar arc — the distance the progressed Sun has travelled since birth (roughly 1° a year).${houseCtx}${retroCtx}\n\n` +
|
|
801
760
|
`Solar arc directions symbolise the symbolic maturation of each natal archetype. Where the directed ${capitalize(planet)} now falls — and especially which natal planets or angles it contacts — marks a significant chapter in the long development of ${theme}. Conjunctions, squares, and oppositions to natal points are the most potent activation signals.`);
|
|
802
761
|
}
|
|
803
762
|
else if (mode === "solar_return") {
|
|
@@ -990,7 +949,7 @@ registerTool({
|
|
|
990
949
|
progressed: `**Secondary Progressions Overview: ${label1} × ${label2}**\n\n` +
|
|
991
950
|
`This progression bi-wheel places the natal chart inside and secondary progressed positions in the outer ring. Progressed planets reflect the gradual inner maturation of natal archetypal themes using the day-for-a-year method. Cross-aspects from progressed to natal points mark the defining chapters of inner development.`,
|
|
992
951
|
solar_arc: `**Solar Arc Directions Overview: ${label1} × ${label2}**\n\n` +
|
|
993
|
-
`This solar arc bi-wheel shows natal positions inside and solar arc directed positions in the outer ring. Solar arc direction advances every natal planet by
|
|
952
|
+
`This solar arc bi-wheel shows natal positions inside and solar arc directed positions in the outer ring. Solar arc direction advances every natal planet, angle and house cusp by the same arc: the distance the progressed Sun has travelled since birth (roughly 1° a year). Contacts from directed positions to natal points — especially conjunctions — mark pivotal life moments.`,
|
|
994
953
|
solar_return: `**Solar Return Overview: ${label1} × ${label2}**\n\n` +
|
|
995
954
|
`This solar return bi-wheel compares the natal chart against the solar return for the target year. The outer ring shows where planets fall at the exact moment the Sun returns to its natal degree, describing the dominant themes and developmental focus of the coming solar year.`,
|
|
996
955
|
lunar_return: `**Lunar Return Overview: ${label1} × ${label2}**\n\n` +
|
|
@@ -23,10 +23,11 @@ import fs from "node:fs";
|
|
|
23
23
|
import path from "node:path";
|
|
24
24
|
import { fileURLToPath } from "node:url";
|
|
25
25
|
import { registerTool, SERVER_VERSION } from "../index.js";
|
|
26
|
-
import { getActiveClient } from "../../backend/client.js";
|
|
26
|
+
import { getActiveClient, isBillingOrAuthError } from "../../backend/client.js";
|
|
27
27
|
import { OUTPUT_SCHEMA_JSON } from "../output-schemas.js";
|
|
28
28
|
import { localToUtcIsoHistorical } from "../datetime-historical.js";
|
|
29
29
|
import { coordsFromArgsOrLocation } from "./_location-resolver.js";
|
|
30
|
+
import { RENDER_TOKEN_PROPERTY, renderTokenFromSvg, renderTokenFromVisual, renderTokenHeaders, withoutRenderToken, } from "./_render-token.js";
|
|
30
31
|
// ── Constants ─────────────────────────────────────────────────────────────
|
|
31
32
|
export const BODYGRAPH_RESOURCE_URI = "ui://openephemeris/bodygraph";
|
|
32
33
|
// Appended to every model-visible HD tool description — trademark hygiene.
|
|
@@ -67,7 +68,7 @@ export function clearBodygraphBundleCache() {
|
|
|
67
68
|
function capitalize(s) {
|
|
68
69
|
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
69
70
|
}
|
|
70
|
-
async function fetchGoBodygraphSVG(body, theme = "dark", view) {
|
|
71
|
+
async function fetchGoBodygraphSVG(body, theme = "dark", view, renderToken) {
|
|
71
72
|
try {
|
|
72
73
|
const client = getActiveClient();
|
|
73
74
|
// /visualization/bodygraph is registered as binary in BINARY_ENDPOINT_PREFIXES.
|
|
@@ -89,7 +90,14 @@ async function fetchGoBodygraphSVG(body, theme = "dark", view) {
|
|
|
89
90
|
qs += `&rings=${encodeURIComponent(view.rings)}`;
|
|
90
91
|
if (view?.houseSystem)
|
|
91
92
|
qs += `&house_system=${encodeURIComponent(view.houseSystem)}`;
|
|
92
|
-
|
|
93
|
+
// A re-render of a chart this account already paid for presents its render
|
|
94
|
+
// token; the API waives the charge only if it verifies for these inputs.
|
|
95
|
+
// The API returns the (new or echoed) token as data-oe-render-token on the
|
|
96
|
+
// root <svg> — read it back with renderTokenFromSvg.
|
|
97
|
+
const resp = await client.request("POST", `/visualization/bodygraph?${qs}`, {
|
|
98
|
+
data: body,
|
|
99
|
+
headers: renderTokenHeaders(renderToken),
|
|
100
|
+
});
|
|
93
101
|
if (typeof resp === "string")
|
|
94
102
|
return resp;
|
|
95
103
|
if (resp && typeof resp === "object") {
|
|
@@ -101,6 +109,10 @@ async function fetchGoBodygraphSVG(body, theme = "dark", view) {
|
|
|
101
109
|
return null;
|
|
102
110
|
}
|
|
103
111
|
catch (err) {
|
|
112
|
+
// Out of credits / signed out must reach the assistant with the credit-wall
|
|
113
|
+
// message and top-up link — never be papered over by the client render.
|
|
114
|
+
if (isBillingOrAuthError(err))
|
|
115
|
+
throw err;
|
|
104
116
|
console.warn("[bodygraph] Go SVG fetch failed, will fall back to client render:", err);
|
|
105
117
|
return null;
|
|
106
118
|
}
|
|
@@ -413,6 +425,9 @@ registerTool({
|
|
|
413
425
|
});
|
|
414
426
|
if (svg)
|
|
415
427
|
modelPayload._svg = svg;
|
|
428
|
+
const renderToken = renderTokenFromSvg(svg);
|
|
429
|
+
if (renderToken)
|
|
430
|
+
modelPayload._render_token = renderToken;
|
|
416
431
|
const summary = buildHdSummary(modelPayload, location);
|
|
417
432
|
if (bundleAvailable) {
|
|
418
433
|
// MCP Apps wire format: the UI is declared via `_meta.ui.resourceUri` and
|
|
@@ -468,6 +483,7 @@ registerTool({
|
|
|
468
483
|
type: "string",
|
|
469
484
|
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
485
|
},
|
|
486
|
+
render_token: RENDER_TOKEN_PROPERTY,
|
|
471
487
|
},
|
|
472
488
|
required: ["datetime"],
|
|
473
489
|
},
|
|
@@ -494,11 +510,15 @@ registerTool({
|
|
|
494
510
|
if (lon != null)
|
|
495
511
|
body.longitude = lon;
|
|
496
512
|
// Recalculate is iframe-only (visibility: ["app"]) — always co-fetch the SVG.
|
|
513
|
+
// Both halves present the render token: a theme / layout flip of the chart
|
|
514
|
+
// already on screen is then free (the house ring's 1-credit surcharge still
|
|
515
|
+
// applies when it renders); new birth data is charged as a new chart.
|
|
497
516
|
const [chartData, svg] = await Promise.all([
|
|
498
517
|
client.request("POST", "/human-design/chart", {
|
|
499
518
|
data: body,
|
|
519
|
+
headers: renderTokenHeaders(args.render_token),
|
|
500
520
|
}),
|
|
501
|
-
fetchGoBodygraphSVG(body, theme, { layout, rings, houseSystem }),
|
|
521
|
+
fetchGoBodygraphSVG(body, theme, { layout, rings, houseSystem }, args.render_token),
|
|
502
522
|
]);
|
|
503
523
|
const chart = chartData.chart ?? chartData;
|
|
504
524
|
const modelPayload = buildHdModelPayload(chart, {
|
|
@@ -510,6 +530,11 @@ registerTool({
|
|
|
510
530
|
});
|
|
511
531
|
if (svg)
|
|
512
532
|
modelPayload._svg = svg;
|
|
533
|
+
// The API echoes the presented token on a waived re-render, or mints a new
|
|
534
|
+
// one when this was charged as a new chart (e.g. edited birth data).
|
|
535
|
+
const renderToken = renderTokenFromSvg(svg);
|
|
536
|
+
if (renderToken)
|
|
537
|
+
modelPayload._render_token = renderToken;
|
|
513
538
|
return {
|
|
514
539
|
content: [{ type: "text", text: JSON.stringify({ ...modelPayload, server_version: SERVER_VERSION }) }],
|
|
515
540
|
};
|
|
@@ -1176,9 +1201,8 @@ registerTool({
|
|
|
1176
1201
|
"The Variable describes the four arrow directions attached to the Personality Sun/Earth " +
|
|
1177
1202
|
"and Design Sun/Earth positions, encoding the PHS (Primary Health System) — the optimal conditions " +
|
|
1178
1203
|
"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). " +
|
|
1204
|
+
"Returns an interpretation of Motivation (Personality Sun), Digestion (Design Sun), " +
|
|
1205
|
+
"Perspective (Personality Node) and Environment (Design Node). " +
|
|
1182
1206
|
"This tool is called automatically by the bodygraph UI; you do not need to call it directly.",
|
|
1183
1207
|
inputSchema: {
|
|
1184
1208
|
type: "object",
|
|
@@ -1224,13 +1248,13 @@ registerTool({
|
|
|
1224
1248
|
"and being drawn by appetite and desire rather than routine. " +
|
|
1225
1249
|
"Left Digestion types often thrive with more novelty, conversation, and engagement at the table — " +
|
|
1226
1250
|
"your body needs to be activated, not passively nourished.",
|
|
1227
|
-
|
|
1228
|
-
1:
|
|
1229
|
-
2:
|
|
1230
|
-
3: "
|
|
1231
|
-
4:
|
|
1232
|
-
5:
|
|
1233
|
-
6:
|
|
1251
|
+
colors: {
|
|
1252
|
+
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." },
|
|
1253
|
+
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." },
|
|
1254
|
+
3: { left: "Thirst, Hot: warm food, warm drinks, warm settings.", right: "Thirst, Cold: cool food, cool drinks, calm and cool settings." },
|
|
1255
|
+
4: { left: "Touch, Calm: eating in a quiet, settled atmosphere, often alone.", right: "Touch, Nervous: eating with some activity or buzz around." },
|
|
1256
|
+
5: { left: "Sound, High: eating with conversation, music or background sound.", right: "Sound, Low: eating in quiet." },
|
|
1257
|
+
6: { left: "Light, Direct: eating in daylight, earlier in the day.", right: "Light, Indirect: eating in softer light or later in the day." },
|
|
1234
1258
|
},
|
|
1235
1259
|
},
|
|
1236
1260
|
perspective: {
|
|
@@ -1251,13 +1275,13 @@ registerTool({
|
|
|
1251
1275
|
"stimulating environments. This is an Active environment variable: you are designed to encounter " +
|
|
1252
1276
|
"variety, new places, and fresh stimulation. Being too long in the same environment can lead to " +
|
|
1253
1277
|
"stagnation, restlessness, or decreased vitality.",
|
|
1254
|
-
|
|
1255
|
-
1:
|
|
1256
|
-
2:
|
|
1257
|
-
3: "
|
|
1258
|
-
4: "
|
|
1259
|
-
5:
|
|
1260
|
-
6:
|
|
1278
|
+
colors: {
|
|
1279
|
+
1: { left: "Caves, Exclusive: enclosed, protected spaces with few people.", right: "Caves, Blending: sheltered spaces shared with a trusted group." },
|
|
1280
|
+
2: { left: "Markets, Internal: places of exchange where you have a clear role inside.", right: "Markets, External: open places of exchange you move through." },
|
|
1281
|
+
3: { left: "Kitchens, Wet: hearth and gathering places with moisture and life.", right: "Kitchens, Dry: hearth and gathering places that are dry and warm." },
|
|
1282
|
+
4: { left: "Mountains, Active: high, open ground with movement.", right: "Mountains, Passive: high, open ground to rest in and overlook." },
|
|
1283
|
+
5: { left: "Valleys, Narrow: sheltered low ground with a close view.", right: "Valleys, Wide: sheltered low ground with a wide view." },
|
|
1284
|
+
6: { left: "Shores, Natural: edges where land meets water, in nature.", right: "Shores, Artificial: edges and thresholds in built places." },
|
|
1261
1285
|
},
|
|
1262
1286
|
},
|
|
1263
1287
|
motivation: {
|
|
@@ -1267,13 +1291,13 @@ registerTool({
|
|
|
1267
1291
|
left: "**Left Motivation** (Active) \u2014 Your decision-making is best guided by active, intentional pursuit " +
|
|
1268
1292
|
"of what aligns with your values and strategy. You are designed to initiate and drive toward what matters to you — " +
|
|
1269
1293
|
"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:
|
|
1294
|
+
colors: {
|
|
1295
|
+
1: { left: "Fear: the drive to understand the unknown and stay safe.", right: "Fear: the drive to understand the unknown and stay safe." },
|
|
1296
|
+
2: { left: "Hope: trust in a better possible outcome.", right: "Hope: trust in a better possible outcome." },
|
|
1297
|
+
3: { left: "Desire: the push to change or affect the world.", right: "Desire: the push to change or affect the world." },
|
|
1298
|
+
4: { left: "Need: identifying what is necessary.", right: "Need: identifying what is necessary." },
|
|
1299
|
+
5: { left: "Guilt: the drive to fix and improve.", right: "Guilt: the drive to fix and improve." },
|
|
1300
|
+
6: { left: "Innocence: acting without a personal agenda.", right: "Innocence: acting without a personal agenda." },
|
|
1277
1301
|
},
|
|
1278
1302
|
},
|
|
1279
1303
|
};
|
|
@@ -1284,11 +1308,8 @@ registerTool({
|
|
|
1284
1308
|
};
|
|
1285
1309
|
}
|
|
1286
1310
|
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.`
|
|
1311
|
+
const colorNote = color != null && info.colors?.[color]
|
|
1312
|
+
? `\n\n**Your Color ${color}${tone != null ? `, Tone ${tone}` : ""}:** ${info.colors[color][direction]}`
|
|
1292
1313
|
: "";
|
|
1293
1314
|
const label = `${capitalize(variable)} Arrow — ${capitalize(direction)} Variable`;
|
|
1294
1315
|
const icon = variable === "digestion" ? "🔵" : variable === "perspective" ? "🟢" : variable === "environment" ? "⚪" : "🔴";
|
|
@@ -1296,7 +1317,7 @@ registerTool({
|
|
|
1296
1317
|
content: [{
|
|
1297
1318
|
type: "text",
|
|
1298
1319
|
text: `**${label}** ${icon}\n\n` +
|
|
1299
|
-
main +
|
|
1320
|
+
main + colorNote +
|
|
1300
1321
|
"\n\n*The Variable is a refinement layer within the Primary Health System (PHS). " +
|
|
1301
1322
|
"It describes the optimal conditions for the body-brain to receive and process information correctly. " +
|
|
1302
1323
|
"Living according to your Variable is an advanced practice that supports deeper health, clarity, and cognitive accuracy.*",
|
|
@@ -1315,7 +1336,7 @@ registerTool({
|
|
|
1315
1336
|
"(inline in Claude and ChatGPT; text summary elsewhere) highlighting the channels the transit " +
|
|
1316
1337
|
"temporarily COMPLETES with the natal chart and any centers it newly defines, for now or a chosen " +
|
|
1317
1338
|
"datetime. Birthplace may be a place name (`location`).\n\n" +
|
|
1318
|
-
"CREDIT COST:
|
|
1339
|
+
"CREDIT COST: 5 credits per call (chart 3 + render 2; +1 per place name).\n\n" +
|
|
1319
1340
|
"Do not use for the person's own natal bodygraph — use explore_human_design; for another person's " +
|
|
1320
1341
|
"chart overlaid on theirs use explore_human_design_connection." +
|
|
1321
1342
|
HD_DISCLAIMER,
|
|
@@ -1349,6 +1370,7 @@ registerTool({
|
|
|
1349
1370
|
"concentric ring set the natal chart uses. Set by the in-iframe layout toggle, never by " +
|
|
1350
1371
|
"the model.",
|
|
1351
1372
|
},
|
|
1373
|
+
render_token: RENDER_TOKEN_PROPERTY,
|
|
1352
1374
|
},
|
|
1353
1375
|
required: ["datetime"],
|
|
1354
1376
|
},
|
|
@@ -1410,7 +1432,12 @@ registerTool({
|
|
|
1410
1432
|
body.include_visual = true;
|
|
1411
1433
|
body.visual_config = { theme, ...(layout ? { layout } : {}) };
|
|
1412
1434
|
}
|
|
1413
|
-
|
|
1435
|
+
// The iframe's theme / layout refetch presents the token of the overlay it
|
|
1436
|
+
// is showing; the API waives it only for the same account + inputs.
|
|
1437
|
+
const resp = await client.request("POST", "/human-design/transit-chart", {
|
|
1438
|
+
data: body,
|
|
1439
|
+
headers: renderTokenHeaders(args.render_token),
|
|
1440
|
+
});
|
|
1414
1441
|
const transit = (resp?.transit ?? {});
|
|
1415
1442
|
const completed = Array.isArray(transit.completed_channels) ? transit.completed_channels : [];
|
|
1416
1443
|
const newCenters = Array.isArray(transit.newly_defined_centers) ? transit.newly_defined_centers : [];
|
|
@@ -1428,8 +1455,15 @@ registerTool({
|
|
|
1428
1455
|
...resp,
|
|
1429
1456
|
_svg: svg,
|
|
1430
1457
|
_theme: theme,
|
|
1431
|
-
|
|
1432
|
-
|
|
1458
|
+
...(renderTokenFromVisual(resp?.visual) ? { _render_token: renderTokenFromVisual(resp?.visual) } : {}),
|
|
1459
|
+
// Lets the iframe re-request this overlay in the host's theme. The
|
|
1460
|
+
// resolved coordinates/zone/name are pinned so the refetch neither
|
|
1461
|
+
// re-geocodes (another credit) nor drifts to a different request body,
|
|
1462
|
+
// which would no longer match the render token.
|
|
1463
|
+
_refetch: {
|
|
1464
|
+
tool: "explore_human_design_transit",
|
|
1465
|
+
args: { ...withoutRenderToken(args), latitude: lat, longitude: lon, location, ...(timezone ? { timezone } : {}) },
|
|
1466
|
+
},
|
|
1433
1467
|
server_version: SERVER_VERSION,
|
|
1434
1468
|
};
|
|
1435
1469
|
if (bundleAvailable && svg) {
|
|
@@ -1453,7 +1487,7 @@ registerTool({
|
|
|
1453
1487
|
"text summary elsewhere) with every connected channel classified by HD connection theory: " +
|
|
1454
1488
|
"electromagnetic (attraction), companionship (sameness), dominance (one defines), compromise " +
|
|
1455
1489
|
"(friction).\n\n" +
|
|
1456
|
-
"CREDIT COST:
|
|
1490
|
+
"CREDIT COST: 5 credits per call (composite 3 + render 2).\n\n" +
|
|
1457
1491
|
"Do not use for one person's chart — use explore_human_design; for the sky's transit on one chart " +
|
|
1458
1492
|
"use explore_human_design_transit; for Western compatibility use explore_bi_wheel." +
|
|
1459
1493
|
HD_DISCLAIMER,
|
|
@@ -1498,6 +1532,7 @@ registerTool({
|
|
|
1498
1532
|
"concentric ring set the natal chart uses. Set by the in-iframe layout toggle, never by " +
|
|
1499
1533
|
"the model.",
|
|
1500
1534
|
},
|
|
1535
|
+
render_token: RENDER_TOKEN_PROPERTY,
|
|
1501
1536
|
},
|
|
1502
1537
|
required: ["person_a", "person_b"],
|
|
1503
1538
|
},
|
|
@@ -1529,12 +1564,15 @@ registerTool({
|
|
|
1529
1564
|
throw new Error(`${label} requires either \`latitude\` + \`longitude\` or a resolvable \`location\` name. ` +
|
|
1530
1565
|
"Human Design is sensitive to birth location; a chart at 0°N 0°E is silently wrong.");
|
|
1531
1566
|
}
|
|
1567
|
+
// Pin the resolved coordinates for the iframe's refetch (see _refetch).
|
|
1568
|
+
pinned[label] = { ...(p ?? {}), latitude: lat, longitude: lon, ...(tz ? { timezone: tz } : {}) };
|
|
1532
1569
|
return {
|
|
1533
1570
|
birth_datetime_utc: await localToUtcIsoHistorical(`${label}.datetime`, String(p?.datetime), tz, { latitude: lat, longitude: lon }, `${label}.timezone`),
|
|
1534
1571
|
latitude: lat,
|
|
1535
1572
|
longitude: lon,
|
|
1536
1573
|
};
|
|
1537
1574
|
};
|
|
1575
|
+
const pinned = {};
|
|
1538
1576
|
const body = {
|
|
1539
1577
|
subject_1: await toSubject(args.person_a, "person_a"),
|
|
1540
1578
|
subject_2: await toSubject(args.person_b, "person_b"),
|
|
@@ -1547,7 +1585,10 @@ registerTool({
|
|
|
1547
1585
|
body.include_visual = true;
|
|
1548
1586
|
body.visual_config = { theme, ...(layout ? { layout } : {}) };
|
|
1549
1587
|
}
|
|
1550
|
-
const resp = await client.request("POST", "/human-design/composite", {
|
|
1588
|
+
const resp = await client.request("POST", "/human-design/composite", {
|
|
1589
|
+
data: body,
|
|
1590
|
+
headers: renderTokenHeaders(args.render_token),
|
|
1591
|
+
});
|
|
1551
1592
|
const connections = Array.isArray(resp?.connections) ? resp.connections : [];
|
|
1552
1593
|
const svg = resp?.visual?.data;
|
|
1553
1594
|
const byType = (t) => connections.filter((c) => c?.type === t).map((c) => c.id);
|
|
@@ -1566,7 +1607,13 @@ registerTool({
|
|
|
1566
1607
|
...resp,
|
|
1567
1608
|
_svg: svg,
|
|
1568
1609
|
_theme: theme,
|
|
1569
|
-
|
|
1610
|
+
...(renderTokenFromVisual(resp?.visual) ? { _render_token: renderTokenFromVisual(resp?.visual) } : {}),
|
|
1611
|
+
// Resolved coordinates pinned so the refetch neither re-geocodes nor
|
|
1612
|
+
// drifts from the request the render token was issued for.
|
|
1613
|
+
_refetch: {
|
|
1614
|
+
tool: "explore_human_design_connection",
|
|
1615
|
+
args: { ...withoutRenderToken(args), person_a: pinned.person_a, person_b: pinned.person_b },
|
|
1616
|
+
},
|
|
1570
1617
|
server_version: SERVER_VERSION,
|
|
1571
1618
|
};
|
|
1572
1619
|
if (bundleAvailable && svg) {
|