@openephemeris/mcp-server 3.24.0 → 4.1.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 +173 -0
- package/LICENSE +21 -21
- package/README.md +75 -3
- package/dist/analytics.js +37 -5
- package/dist/backend/client.d.ts +7 -0
- package/dist/backend/client.js +39 -38
- package/dist/index.js +64 -2
- package/dist/oauth/session-utils.d.ts +42 -18
- package/dist/oauth/session-utils.js +79 -0
- package/dist/prompts.js +55 -42
- package/dist/server-sse.js +114 -14
- package/dist/tools/apps/bazi-app.js +15 -28
- package/dist/tools/apps/bi-wheel-app.js +14 -11
- package/dist/tools/apps/bodygraph-app.d.ts +5 -5
- package/dist/tools/apps/bodygraph-app.js +159 -212
- package/dist/tools/apps/chart-wheel-app.js +21 -20
- package/dist/tools/apps/location-tools.js +167 -18
- package/dist/tools/apps/moon-phase-app.js +10 -3
- package/dist/tools/apps/transit-timeline-app.js +6 -4
- package/dist/tools/apps/vedic-chart-app.js +15 -49
- package/dist/tools/datetime.d.ts +65 -0
- package/dist/tools/datetime.js +153 -0
- package/dist/tools/dev.js +4 -3
- package/dist/tools/index.d.ts +45 -2
- package/dist/tools/index.js +81 -2
- package/dist/tools/specialized/account.d.ts +1 -0
- package/dist/tools/specialized/account.js +100 -0
- package/dist/tools/specialized/acg.js +16 -14
- package/dist/tools/specialized/bazi.d.ts +7 -1
- package/dist/tools/specialized/bazi.js +89 -23
- package/dist/tools/specialized/bi_wheel.js +5 -4
- package/dist/tools/specialized/chart_wheel.js +5 -8
- package/dist/tools/specialized/comparative.js +40 -21
- package/dist/tools/specialized/electional.js +13 -10
- package/dist/tools/specialized/ephemeris_core.js +13 -8
- package/dist/tools/specialized/ephemeris_extended.js +60 -53
- package/dist/tools/specialized/hd_bodygraph.js +8 -10
- package/dist/tools/specialized/hd_cycles.js +7 -14
- package/dist/tools/specialized/hd_group.js +20 -13
- package/dist/tools/specialized/human_design.js +11 -17
- package/dist/tools/specialized/moon.js +14 -6
- package/dist/tools/specialized/natal.js +7 -9
- package/dist/tools/specialized/progressed.js +12 -8
- package/dist/tools/specialized/relocation.js +9 -3
- package/dist/tools/specialized/returns.js +23 -11
- package/dist/tools/specialized/synastry.js +17 -6
- package/dist/tools/specialized/transits.js +9 -5
- package/dist/tools/specialized/vedic.js +5 -3
- package/dist/tools/specialized/venus_star_points.js +14 -9
- package/dist/ui/bazi.html +1063 -1049
- package/dist/ui/bi-wheel.html +4188 -4128
- package/dist/ui/bodygraph.html +3673 -3616
- package/dist/ui/chart-wheel.html +3769 -3713
- package/dist/ui/moon-phase.html +3219 -3153
- package/dist/ui/transit-timeline.html +199 -170
- package/dist/ui/vedic-chart.html +1116 -1098
- package/package.json +3 -2
- package/smithery.yaml +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { registerTool, validateRequired } from "../index.js";
|
|
1
|
+
import { registerTool, validateRequired, validateCoordinates } from "../index.js";
|
|
2
2
|
import { getActiveClient } from "../../backend/client.js";
|
|
3
3
|
import { OUTPUT_SCHEMA_JSON } from "../output-schemas.js";
|
|
4
|
+
import { DATETIME_DESC, TIMEZONE_PROPERTY, assertZonedDatetime, localToUtcIso } from "../datetime.js";
|
|
4
5
|
function buildSubject(name, datetime, lat, lon) {
|
|
5
6
|
return {
|
|
6
7
|
name,
|
|
@@ -21,8 +22,8 @@ registerTool({
|
|
|
21
22
|
"(100 subjects = 100 credits). Confirm with the user before large batches.\n\n" +
|
|
22
23
|
"EXAMPLE: Two people batch:\n" +
|
|
23
24
|
" subjects: [\n" +
|
|
24
|
-
" { name: 'Alice', datetime: '1990-04-15T14:30:00', latitude: 41.88, longitude: -87.63 },\n" +
|
|
25
|
-
" { name: 'Bob', datetime: '1988-09-
|
|
25
|
+
" { name: 'Alice', datetime: '1990-04-15T14:30:00', timezone: 'America/Chicago', latitude: 41.88, longitude: -87.63 },\n" +
|
|
26
|
+
" { name: 'Bob', datetime: '1988-09-22T15:15:00Z', latitude: 34.05, longitude: -118.24 }\n" +
|
|
26
27
|
" ]",
|
|
27
28
|
inputSchema: {
|
|
28
29
|
type: "object",
|
|
@@ -33,7 +34,8 @@ registerTool({
|
|
|
33
34
|
type: "object",
|
|
34
35
|
properties: {
|
|
35
36
|
name: { type: "string", description: "Subject name." },
|
|
36
|
-
datetime: { type: "string", description:
|
|
37
|
+
datetime: { type: "string", description: DATETIME_DESC },
|
|
38
|
+
timezone: TIMEZONE_PROPERTY,
|
|
37
39
|
latitude: { type: "number", description: "Birth latitude." },
|
|
38
40
|
longitude: { type: "number", description: "Birth longitude." },
|
|
39
41
|
},
|
|
@@ -49,6 +51,7 @@ registerTool({
|
|
|
49
51
|
annotations: { title: "Natal Batch Charts", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
50
52
|
handler: async (args) => {
|
|
51
53
|
validateRequired(args, ["subjects"]);
|
|
54
|
+
args.subjects.forEach((s, i) => assertZonedDatetime(`subjects[${i}].datetime`, s?.datetime, s?.timezone, `subjects[${i}].timezone`));
|
|
52
55
|
const items = args.subjects.map((s) => ({
|
|
53
56
|
subject: buildSubject(s.name, s.datetime, s.latitude, s.longitude),
|
|
54
57
|
}));
|
|
@@ -64,7 +67,8 @@ registerTool({
|
|
|
64
67
|
inputSchema: {
|
|
65
68
|
type: "object",
|
|
66
69
|
properties: {
|
|
67
|
-
datetime: { type: "string", description:
|
|
70
|
+
datetime: { type: "string", description: DATETIME_DESC },
|
|
71
|
+
timezone: TIMEZONE_PROPERTY,
|
|
68
72
|
},
|
|
69
73
|
required: ["datetime"],
|
|
70
74
|
additionalProperties: false,
|
|
@@ -74,7 +78,7 @@ registerTool({
|
|
|
74
78
|
handler: async (args) => {
|
|
75
79
|
validateRequired(args, ["datetime"]);
|
|
76
80
|
return await getActiveClient().post("/ephemeris/dignities", {
|
|
77
|
-
date_time: { iso: args.datetime },
|
|
81
|
+
date_time: { iso: localToUtcIso("datetime", args.datetime, args.timezone) },
|
|
78
82
|
});
|
|
79
83
|
},
|
|
80
84
|
});
|
|
@@ -86,12 +90,16 @@ registerTool({
|
|
|
86
90
|
name: "ephemeris_retrograde_status",
|
|
87
91
|
description: "Get retrograde/direct status and speed for all planets at a given date/time. " +
|
|
88
92
|
"Returns is_retrograde flag, longitude speed, and station proximity for every planet.\n\n" +
|
|
89
|
-
"
|
|
90
|
-
"
|
|
93
|
+
"✅ Answers 'is X retrograde?' at ONE instant. For WHEN a planet turns retrograde or " +
|
|
94
|
+
"direct, or whether it stations anywhere in a date range, use electional_station_tracker.\n\n" +
|
|
95
|
+
"CREDIT COST: for a single planet — the common case — pass planet_id (0-9, e.g. 2 for " +
|
|
96
|
+
"Mercury): 1 credit. Omitting planet_id runs the all-planets sweep: 10 credits (the " +
|
|
97
|
+
"backend bills one credit per body and this fans out to 10).",
|
|
91
98
|
inputSchema: {
|
|
92
99
|
type: "object",
|
|
93
100
|
properties: {
|
|
94
|
-
datetime: { type: "string", description:
|
|
101
|
+
datetime: { type: "string", description: DATETIME_DESC },
|
|
102
|
+
timezone: TIMEZONE_PROPERTY,
|
|
95
103
|
planet_id: { type: "integer", description: "Optional single planet ID (0-9). Omit for all planets." },
|
|
96
104
|
},
|
|
97
105
|
required: ["datetime"],
|
|
@@ -105,14 +113,14 @@ registerTool({
|
|
|
105
113
|
// If a specific planet is requested, delegate directly.
|
|
106
114
|
if (args.planet_id != null) {
|
|
107
115
|
return await client.post("/ephemeris/retrograde-status", {
|
|
108
|
-
date_time: { iso: args.datetime },
|
|
116
|
+
date_time: { iso: localToUtcIso("datetime", args.datetime, args.timezone) },
|
|
109
117
|
planet_id: args.planet_id,
|
|
110
118
|
});
|
|
111
119
|
}
|
|
112
120
|
// Fan-out: the backend only handles one planet per call; query all 10 in parallel.
|
|
113
121
|
const PLANET_IDS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
|
|
114
122
|
const results = await Promise.all(PLANET_IDS.map((pid) => client.post("/ephemeris/retrograde-status", {
|
|
115
|
-
date_time: { iso: args.datetime },
|
|
123
|
+
date_time: { iso: localToUtcIso("datetime", args.datetime, args.timezone) },
|
|
116
124
|
planet_id: pid,
|
|
117
125
|
}).catch(() => null)));
|
|
118
126
|
// Merge into a keyed object: { planet_name: {...status} }
|
|
@@ -140,7 +148,8 @@ registerTool({
|
|
|
140
148
|
inputSchema: {
|
|
141
149
|
type: "object",
|
|
142
150
|
properties: {
|
|
143
|
-
datetime: { type: "string", description:
|
|
151
|
+
datetime: { type: "string", description: DATETIME_DESC },
|
|
152
|
+
timezone: TIMEZONE_PROPERTY,
|
|
144
153
|
latitude: { type: "number", description: "Observer latitude." },
|
|
145
154
|
longitude: { type: "number", description: "Observer longitude." },
|
|
146
155
|
},
|
|
@@ -152,7 +161,7 @@ registerTool({
|
|
|
152
161
|
handler: async (args) => {
|
|
153
162
|
validateRequired(args, ["datetime", "latitude", "longitude"]);
|
|
154
163
|
return await getActiveClient().post("/ephemeris/midpoints", {
|
|
155
|
-
date_time: { iso: args.datetime },
|
|
164
|
+
date_time: { iso: localToUtcIso("datetime", args.datetime, args.timezone) },
|
|
156
165
|
latitude: args.latitude,
|
|
157
166
|
longitude: args.longitude,
|
|
158
167
|
});
|
|
@@ -161,13 +170,28 @@ registerTool({
|
|
|
161
170
|
// POST /ephemeris/fixed-stars
|
|
162
171
|
registerTool({
|
|
163
172
|
name: "ephemeris_fixed_stars",
|
|
164
|
-
description: "
|
|
165
|
-
"
|
|
173
|
+
description: "Fixed star positions (ecliptic longitude, magnitude) and the chart points conjunct them. " +
|
|
174
|
+
"Always scans the ten traditional planets.\n\n" +
|
|
175
|
+
"⚠️ ANGLES REQUIRE A LOCATION: 'what stars are on my Ascendant?' is only answerable with " +
|
|
176
|
+
"latitude AND longitude — ASC/MC/DSC/IC depend on place, not just time. Supply both and the " +
|
|
177
|
+
"four angles join the scan; omit them and the result covers PLANETS ONLY. Each conjunction " +
|
|
178
|
+
"is tagged point_type 'planet' or 'angle', and the angle longitudes used come back in " +
|
|
179
|
+
"`angles`, so never report a planetary hit as an Ascendant hit.\n\n" +
|
|
166
180
|
"CREDIT COST: 1 credit per call.",
|
|
167
181
|
inputSchema: {
|
|
168
182
|
type: "object",
|
|
169
183
|
properties: {
|
|
170
|
-
datetime: { type: "string", description:
|
|
184
|
+
datetime: { type: "string", description: DATETIME_DESC },
|
|
185
|
+
timezone: TIMEZONE_PROPERTY,
|
|
186
|
+
latitude: {
|
|
187
|
+
type: "number",
|
|
188
|
+
description: "Observer latitude in decimal degrees. Required (with longitude) to scan the angles. " +
|
|
189
|
+
"Resolve from a place name with location_search; never recall coordinates from memory.",
|
|
190
|
+
},
|
|
191
|
+
longitude: {
|
|
192
|
+
type: "number",
|
|
193
|
+
description: "Observer longitude in decimal degrees. Required (with latitude) to scan the angles.",
|
|
194
|
+
},
|
|
171
195
|
star_names: {
|
|
172
196
|
type: "array",
|
|
173
197
|
items: { type: "string" },
|
|
@@ -182,45 +206,26 @@ registerTool({
|
|
|
182
206
|
annotations: { title: "Fixed Stars", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
183
207
|
handler: async (args) => {
|
|
184
208
|
validateRequired(args, ["datetime"]);
|
|
209
|
+
// A lone latitude would silently drop the angles from the scan, which is
|
|
210
|
+
// the exact failure this tool exists to avoid — reject it instead.
|
|
211
|
+
validateCoordinates(args, "latitude", "longitude");
|
|
185
212
|
const body = {
|
|
186
|
-
date_time: { iso: args.datetime },
|
|
213
|
+
date_time: { iso: localToUtcIso("datetime", args.datetime, args.timezone) },
|
|
187
214
|
};
|
|
215
|
+
if (args.latitude != null)
|
|
216
|
+
body.latitude = args.latitude;
|
|
217
|
+
if (args.longitude != null)
|
|
218
|
+
body.longitude = args.longitude;
|
|
188
219
|
if (args.star_names)
|
|
189
220
|
body.star_names = args.star_names;
|
|
190
221
|
if (args.orb != null)
|
|
191
222
|
body.orb = args.orb;
|
|
192
|
-
|
|
193
|
-
//
|
|
194
|
-
//
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
};
|
|
199
|
-
function enrichStar(star) {
|
|
200
|
-
if (!star || typeof star !== "object")
|
|
201
|
-
return star;
|
|
202
|
-
const signLon = typeof star.longitude === "number" ? star.longitude : 0;
|
|
203
|
-
const signName = star.sign ?? star.sign_name ?? "";
|
|
204
|
-
const signIndex = SIGN_ORDER[signName] ?? -1;
|
|
205
|
-
const fullLon = signIndex >= 0 ? signIndex * 30 + signLon : signLon;
|
|
206
|
-
return {
|
|
207
|
-
...star,
|
|
208
|
-
longitude: fullLon, // ecliptic longitude 0–360
|
|
209
|
-
sign_longitude: signLon, // original within-sign degrees preserved
|
|
210
|
-
sign: signName || undefined,
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
// Normalise the response — backend may return array or { stars: [...] }
|
|
214
|
-
if (Array.isArray(raw)) {
|
|
215
|
-
return raw.map(enrichStar);
|
|
216
|
-
}
|
|
217
|
-
if (raw && typeof raw === "object") {
|
|
218
|
-
const starsKey = ["stars", "data", "fixed_stars", "results"].find((k) => Array.isArray(raw[k]));
|
|
219
|
-
if (starsKey) {
|
|
220
|
-
return { ...raw, [starsKey]: raw[starsKey].map(enrichStar) };
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
return raw;
|
|
223
|
+
// No response rewriting: /ephemeris/fixed-stars returns full ecliptic
|
|
224
|
+
// longitude (0–360) directly. It previously returned degrees-within-sign,
|
|
225
|
+
// and the workaround here reconstructed the full value from a `sign`
|
|
226
|
+
// field the endpoint never actually sent — so it was inert. Both the
|
|
227
|
+
// backend bug and the dead workaround are gone.
|
|
228
|
+
return await getActiveClient().post("/ephemeris/fixed-stars", body);
|
|
224
229
|
},
|
|
225
230
|
});
|
|
226
231
|
// POST /ephemeris/hermetic-lots
|
|
@@ -232,7 +237,8 @@ registerTool({
|
|
|
232
237
|
inputSchema: {
|
|
233
238
|
type: "object",
|
|
234
239
|
properties: {
|
|
235
|
-
datetime: { type: "string", description:
|
|
240
|
+
datetime: { type: "string", description: DATETIME_DESC },
|
|
241
|
+
timezone: TIMEZONE_PROPERTY,
|
|
236
242
|
latitude: { type: "number", description: "Observer latitude." },
|
|
237
243
|
longitude: { type: "number", description: "Observer longitude." },
|
|
238
244
|
},
|
|
@@ -244,7 +250,7 @@ registerTool({
|
|
|
244
250
|
handler: async (args) => {
|
|
245
251
|
validateRequired(args, ["datetime", "latitude", "longitude"]);
|
|
246
252
|
return await getActiveClient().post("/ephemeris/hermetic-lots", {
|
|
247
|
-
date_time: { iso: args.datetime },
|
|
253
|
+
date_time: { iso: localToUtcIso("datetime", args.datetime, args.timezone) },
|
|
248
254
|
latitude: args.latitude,
|
|
249
255
|
longitude: args.longitude,
|
|
250
256
|
});
|
|
@@ -259,7 +265,8 @@ registerTool({
|
|
|
259
265
|
inputSchema: {
|
|
260
266
|
type: "object",
|
|
261
267
|
properties: {
|
|
262
|
-
datetime: { type: "string", description:
|
|
268
|
+
datetime: { type: "string", description: DATETIME_DESC },
|
|
269
|
+
timezone: TIMEZONE_PROPERTY,
|
|
263
270
|
latitude: { type: "number", description: "Observer latitude." },
|
|
264
271
|
longitude: { type: "number", description: "Observer longitude." },
|
|
265
272
|
},
|
|
@@ -271,7 +278,7 @@ registerTool({
|
|
|
271
278
|
handler: async (args) => {
|
|
272
279
|
validateRequired(args, ["datetime", "latitude", "longitude"]);
|
|
273
280
|
return await getActiveClient().post("/ephemeris/angles-points", {
|
|
274
|
-
date_time: { iso: args.datetime },
|
|
281
|
+
date_time: { iso: localToUtcIso("datetime", args.datetime, args.timezone) },
|
|
275
282
|
latitude: args.latitude,
|
|
276
283
|
longitude: args.longitude,
|
|
277
284
|
});
|
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
import { registerTool, validateRequired, pickEnum } from "../index.js";
|
|
2
2
|
import { getActiveClient } from "../../backend/client.js";
|
|
3
3
|
import { OUTPUT_SCHEMA_JSON } from "../output-schemas.js";
|
|
4
|
-
|
|
5
|
-
function ensureTimezone(dt) {
|
|
6
|
-
if (/[Zz]$/.test(dt) || /[+-]\d{2}:\d{2}$/.test(dt))
|
|
7
|
-
return dt;
|
|
8
|
-
return dt + "Z";
|
|
9
|
-
}
|
|
4
|
+
import { DATETIME_DESC, TIMEZONE_PROPERTY, localToUtcIso } from "../datetime.js";
|
|
10
5
|
registerTool({
|
|
11
6
|
name: "human_design_bodygraph",
|
|
12
7
|
description: "Generate a Human Design Bodygraph image (SVG) from a birth datetime (UTC). " +
|
|
@@ -16,16 +11,19 @@ registerTool({
|
|
|
16
11
|
"Returns a native SVG that Claude displays inline. Use format='png' to opt into raster output (requires server-side rasterizer).\n\n" +
|
|
17
12
|
"For a user-facing interactive bodygraph explorer, use explore_human_design instead.\n\n" +
|
|
18
13
|
"CREDIT COST: 2 credits per call.\n\n" +
|
|
19
|
-
"EXAMPLE
|
|
14
|
+
"EXAMPLE (local birth time + zone): born 15 April 1990 at 14:30 in Chicago:\n" +
|
|
15
|
+
" datetime='1990-04-15T14:30:00', timezone='America/Chicago'\n" +
|
|
16
|
+
"EXAMPLE (already in UTC):\n" +
|
|
20
17
|
" datetime='1990-04-15T19:30:00Z'",
|
|
21
18
|
inputSchema: {
|
|
22
19
|
type: "object",
|
|
23
20
|
properties: {
|
|
24
21
|
datetime: {
|
|
25
22
|
type: "string",
|
|
26
|
-
description:
|
|
27
|
-
"Human Design
|
|
23
|
+
description: DATETIME_DESC +
|
|
24
|
+
" Human Design is time-sensitive — accuracy to the minute matters.",
|
|
28
25
|
},
|
|
26
|
+
timezone: TIMEZONE_PROPERTY,
|
|
29
27
|
style: {
|
|
30
28
|
type: "string",
|
|
31
29
|
enum: ["light", "dark", "mono"],
|
|
@@ -46,7 +44,7 @@ registerTool({
|
|
|
46
44
|
handler: async (args) => {
|
|
47
45
|
validateRequired(args, ["datetime"]);
|
|
48
46
|
const body = {
|
|
49
|
-
birth_datetime_utc:
|
|
47
|
+
birth_datetime_utc: localToUtcIso("datetime", args.datetime, args.timezone),
|
|
50
48
|
};
|
|
51
49
|
const format = pickEnum(args.format, ["svg", "png"]) || "svg";
|
|
52
50
|
const style = pickEnum(args.style, ["light", "dark", "mono"]);
|
|
@@ -1,16 +1,7 @@
|
|
|
1
1
|
import { registerTool, validateRequired } from "../index.js";
|
|
2
2
|
import { getActiveClient } from "../../backend/client.js";
|
|
3
3
|
import { OUTPUT_SCHEMA_JSON } from "../output-schemas.js";
|
|
4
|
-
|
|
5
|
-
* Ensure a datetime string has a timezone offset for Go's time.Time parsing.
|
|
6
|
-
*/
|
|
7
|
-
function ensureTimezone(dt) {
|
|
8
|
-
if (!dt)
|
|
9
|
-
return dt;
|
|
10
|
-
if (/[Zz]$/.test(dt) || /[+-]\d{2}:\d{2}$/.test(dt) || /[+-]\d{4}$/.test(dt))
|
|
11
|
-
return dt;
|
|
12
|
-
return dt + "Z";
|
|
13
|
-
}
|
|
4
|
+
import { DATETIME_DESC, TIMEZONE_PROPERTY, localToUtcIso } from "../datetime.js";
|
|
14
5
|
registerTool({
|
|
15
6
|
name: "hd_planetary_return",
|
|
16
7
|
description: "Calculate a Human Design planetary return chart — full HD chart " +
|
|
@@ -35,8 +26,9 @@ registerTool({
|
|
|
35
26
|
},
|
|
36
27
|
datetime: {
|
|
37
28
|
type: "string",
|
|
38
|
-
description:
|
|
29
|
+
description: DATETIME_DESC,
|
|
39
30
|
},
|
|
31
|
+
timezone: TIMEZONE_PROPERTY,
|
|
40
32
|
return_year: {
|
|
41
33
|
type: "integer",
|
|
42
34
|
description: "Year to find the return near (e.g. 2020 for a Saturn return at ~age 29).",
|
|
@@ -57,7 +49,7 @@ registerTool({
|
|
|
57
49
|
return await getActiveClient().request("POST", "/human-design/cycles/return", {
|
|
58
50
|
data: {
|
|
59
51
|
planet: args.planet,
|
|
60
|
-
birth_datetime_utc:
|
|
52
|
+
birth_datetime_utc: localToUtcIso("datetime", args.datetime, args.timezone),
|
|
61
53
|
return_year: args.return_year,
|
|
62
54
|
format: args.format,
|
|
63
55
|
include_chiron: args.include_chiron,
|
|
@@ -87,8 +79,9 @@ registerTool({
|
|
|
87
79
|
},
|
|
88
80
|
datetime: {
|
|
89
81
|
type: "string",
|
|
90
|
-
description:
|
|
82
|
+
description: DATETIME_DESC,
|
|
91
83
|
},
|
|
84
|
+
timezone: TIMEZONE_PROPERTY,
|
|
92
85
|
target_year: {
|
|
93
86
|
type: "integer",
|
|
94
87
|
description: "Year to search for the opposition near.",
|
|
@@ -109,7 +102,7 @@ registerTool({
|
|
|
109
102
|
return await getActiveClient().request("POST", "/human-design/cycles/opposition", {
|
|
110
103
|
data: {
|
|
111
104
|
planet: args.planet,
|
|
112
|
-
birth_datetime_utc:
|
|
105
|
+
birth_datetime_utc: localToUtcIso("datetime", args.datetime, args.timezone),
|
|
113
106
|
target_year: args.target_year,
|
|
114
107
|
format: args.format,
|
|
115
108
|
include_chiron: args.include_chiron,
|
|
@@ -1,25 +1,31 @@
|
|
|
1
1
|
import { registerTool, validateRequired } from "../index.js";
|
|
2
2
|
import { getActiveClient } from "../../backend/client.js";
|
|
3
3
|
import { OUTPUT_SCHEMA_JSON } from "../output-schemas.js";
|
|
4
|
+
import { DATETIME_DESC, localToUtcIso, timezoneProperty } from "../datetime.js";
|
|
4
5
|
// POST /human-design/composite — OE-027
|
|
5
6
|
registerTool({
|
|
6
7
|
name: "human_design_composite",
|
|
7
8
|
description: "Calculate a Human Design composite chart for two people. Merges both bodygraphs " +
|
|
8
9
|
"to show shared channels, authority dynamics, and relationship type.\n\n" +
|
|
9
10
|
"CREDIT COST: 4 credits per call.\n\n" +
|
|
10
|
-
"EXAMPLE:\n" +
|
|
11
|
-
" person_a_datetime='1990-04-15T14:30:
|
|
11
|
+
"EXAMPLE (local times + zones):\n" +
|
|
12
|
+
" person_a_datetime='1990-04-15T14:30:00', person_a_timezone='America/Chicago',\n" +
|
|
13
|
+
" person_b_datetime='1988-09-22T08:15:00', person_b_timezone='America/Los_Angeles'\n" +
|
|
14
|
+
"EXAMPLE (already in UTC):\n" +
|
|
15
|
+
" person_a_datetime='1990-04-15T19:30:00Z', person_b_datetime='1988-09-22T15:15:00Z'",
|
|
12
16
|
inputSchema: {
|
|
13
17
|
type: "object",
|
|
14
18
|
properties: {
|
|
15
19
|
person_a_datetime: {
|
|
16
20
|
type: "string",
|
|
17
|
-
description:
|
|
21
|
+
description: DATETIME_DESC,
|
|
18
22
|
},
|
|
23
|
+
person_a_timezone: timezoneProperty("Person A's birth location"),
|
|
19
24
|
person_b_datetime: {
|
|
20
25
|
type: "string",
|
|
21
|
-
description:
|
|
26
|
+
description: DATETIME_DESC,
|
|
22
27
|
},
|
|
28
|
+
person_b_timezone: timezoneProperty("Person B's birth location", "America/Los_Angeles"),
|
|
23
29
|
format: {
|
|
24
30
|
type: "string",
|
|
25
31
|
enum: ["json", "llm"],
|
|
@@ -35,10 +41,10 @@ registerTool({
|
|
|
35
41
|
validateRequired(args, ["person_a_datetime", "person_b_datetime"]);
|
|
36
42
|
const body = {
|
|
37
43
|
subject_1: {
|
|
38
|
-
birth_datetime_utc: args.person_a_datetime,
|
|
44
|
+
birth_datetime_utc: localToUtcIso("person_a_datetime", args.person_a_datetime, args.person_a_timezone, "person_a_timezone"),
|
|
39
45
|
},
|
|
40
46
|
subject_2: {
|
|
41
|
-
birth_datetime_utc: args.person_b_datetime,
|
|
47
|
+
birth_datetime_utc: localToUtcIso("person_b_datetime", args.person_b_datetime, args.person_b_timezone, "person_b_timezone"),
|
|
42
48
|
},
|
|
43
49
|
};
|
|
44
50
|
if (args.format)
|
|
@@ -52,12 +58,12 @@ registerTool({
|
|
|
52
58
|
description: "Calculate a Human Design Penta (group) chart for 3-5 people. Shows functional " +
|
|
53
59
|
"attributes, leadership dynamics, channels, redundancies, and a group stability score.\n\n" +
|
|
54
60
|
"CREDIT COST: 6 credits per call.\n\n" +
|
|
55
|
-
"EXAMPLE (3 people):\n" +
|
|
61
|
+
"EXAMPLE (3 people — each datetime states its zone):\n" +
|
|
56
62
|
" group_name='Team Alpha',\n" +
|
|
57
63
|
" members=[\n" +
|
|
58
|
-
" {id: 'alice', name: 'Alice', datetime: '1990-04-15T14:30:
|
|
59
|
-
" {id: 'bob', name: 'Bob', datetime: '1988-09-
|
|
60
|
-
" {id: 'carol', name: 'Carol', datetime: '1995-01-10T11:00:
|
|
64
|
+
" {id: 'alice', name: 'Alice', datetime: '1990-04-15T14:30:00', timezone: 'America/Chicago'},\n" +
|
|
65
|
+
" {id: 'bob', name: 'Bob', datetime: '1988-09-22T15:15:00Z'},\n" +
|
|
66
|
+
" {id: 'carol', name: 'Carol', datetime: '1995-01-10T11:00:00+00:00'}\n" +
|
|
61
67
|
" ]",
|
|
62
68
|
inputSchema: {
|
|
63
69
|
type: "object",
|
|
@@ -73,7 +79,8 @@ registerTool({
|
|
|
73
79
|
properties: {
|
|
74
80
|
id: { type: "string", description: "Unique member ID." },
|
|
75
81
|
name: { type: "string", description: "Member's name." },
|
|
76
|
-
datetime: { type: "string", description:
|
|
82
|
+
datetime: { type: "string", description: DATETIME_DESC },
|
|
83
|
+
timezone: timezoneProperty("this member's birth location"),
|
|
77
84
|
},
|
|
78
85
|
required: ["id", "name", "datetime"],
|
|
79
86
|
},
|
|
@@ -94,11 +101,11 @@ registerTool({
|
|
|
94
101
|
annotations: { title: "HD Penta Group Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
95
102
|
handler: async (args) => {
|
|
96
103
|
validateRequired(args, ["group_name", "members"]);
|
|
97
|
-
const members = args.members.map((m) => ({
|
|
104
|
+
const members = args.members.map((m, i) => ({
|
|
98
105
|
id: m.id,
|
|
99
106
|
name: m.name,
|
|
100
107
|
birth_data: {
|
|
101
|
-
birth_datetime_utc: m.datetime,
|
|
108
|
+
birth_datetime_utc: localToUtcIso(`members[${i}].datetime`, m.datetime, m.timezone, `members[${i}].timezone`),
|
|
102
109
|
},
|
|
103
110
|
}));
|
|
104
111
|
const body = {
|
|
@@ -1,18 +1,7 @@
|
|
|
1
1
|
import { registerTool, validateRequired } from "../index.js";
|
|
2
2
|
import { getActiveClient } from "../../backend/client.js";
|
|
3
3
|
import { OUTPUT_SCHEMA_IMAGE_AND_JSON } from "../output-schemas.js";
|
|
4
|
-
|
|
5
|
-
* Ensure a datetime string has a timezone offset for Go's time.Time parsing.
|
|
6
|
-
* Go's encoding/json only accepts RFC 3339 (must have Z or +HH:MM offset).
|
|
7
|
-
*/
|
|
8
|
-
function ensureTimezone(dt) {
|
|
9
|
-
if (!dt)
|
|
10
|
-
return dt;
|
|
11
|
-
// Already has timezone indicator
|
|
12
|
-
if (/[Zz]$/.test(dt) || /[+-]\d{2}:\d{2}$/.test(dt) || /[+-]\d{4}$/.test(dt))
|
|
13
|
-
return dt;
|
|
14
|
-
return dt + "Z";
|
|
15
|
-
}
|
|
4
|
+
import { DATETIME_DESC, TIMEZONE_PROPERTY, localToUtcIso } from "../datetime.js";
|
|
16
5
|
registerTool({
|
|
17
6
|
name: "human_design_chart",
|
|
18
7
|
description: "Calculate a full Human Design I Ching hexagram chart from birth data. Returns the person's Type " +
|
|
@@ -23,19 +12,24 @@ registerTool({
|
|
|
23
12
|
"CREDIT COST: 2 credits per call.\n\n" +
|
|
24
13
|
"Human Design uses two calculation moments: the birth time (Personality) and ~88° of Sun motion " +
|
|
25
14
|
"before birth (~3 months prior, the Design calculation). The API handles this automatically.\n\n" +
|
|
26
|
-
"
|
|
27
|
-
"
|
|
15
|
+
"The datetime must state its zone — pass an offset/'Z', or pass local time plus timezone. " +
|
|
16
|
+
"A zone-less datetime is rejected rather than assumed to be UTC: Human Design is minute-sensitive, " +
|
|
17
|
+
"and an hour of error changes the Profile and the Design Sun line.\n\n" +
|
|
18
|
+
"EXAMPLE (local birth time + zone): someone born 15 April 1990 at 14:30 in Chicago:\n" +
|
|
19
|
+
" datetime='1990-04-15T14:30:00', timezone='America/Chicago', latitude=41.8781, longitude=-87.6298\n" +
|
|
20
|
+
"EXAMPLE (already in UTC):\n" +
|
|
28
21
|
" datetime='1990-04-15T19:30:00Z', latitude=41.8781, longitude=-87.6298",
|
|
29
22
|
inputSchema: {
|
|
30
23
|
type: "object",
|
|
31
24
|
properties: {
|
|
32
25
|
datetime: {
|
|
33
26
|
type: "string",
|
|
34
|
-
description:
|
|
27
|
+
description: DATETIME_DESC,
|
|
35
28
|
},
|
|
29
|
+
timezone: TIMEZONE_PROPERTY,
|
|
36
30
|
latitude: {
|
|
37
31
|
type: "number",
|
|
38
|
-
description: "Latitude of birth location in decimal degrees (positive = North).",
|
|
32
|
+
description: "Latitude of birth location in decimal degrees (positive = North). Resolve from a place name with location_search; never recall coordinates from memory.",
|
|
39
33
|
},
|
|
40
34
|
longitude: {
|
|
41
35
|
type: "number",
|
|
@@ -71,7 +65,7 @@ registerTool({
|
|
|
71
65
|
handler: async (args) => {
|
|
72
66
|
validateRequired(args, ["datetime"]);
|
|
73
67
|
const body = {
|
|
74
|
-
birth_datetime_utc:
|
|
68
|
+
birth_datetime_utc: localToUtcIso("datetime", args.datetime, args.timezone),
|
|
75
69
|
};
|
|
76
70
|
if (args.latitude != null)
|
|
77
71
|
body.latitude = args.latitude;
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { registerTool, validateCoordinates } from "../index.js";
|
|
2
2
|
import { getActiveClient } from "../../backend/client.js";
|
|
3
3
|
import { OUTPUT_SCHEMA_JSON } from "../output-schemas.js";
|
|
4
|
+
import { TIMEZONE_PROPERTY, localToUtcIso } from "../datetime.js";
|
|
4
5
|
registerTool({
|
|
5
6
|
name: "ephemeris_moon_phase",
|
|
6
|
-
description: "Get the Moon's current phase angle, illumination, and void-of-course status AT a
|
|
7
|
-
"point in time. Returns phase name (New, Waxing Crescent, First Quarter, etc.), exact
|
|
8
|
-
"illumination %, and next void-of-course
|
|
9
|
-
"
|
|
7
|
+
description: "Get the Moon's current phase angle, illumination, sign and void-of-course status AT a " +
|
|
8
|
+
"specific point in time. Returns phase name (New, Waxing Crescent, First Quarter, etc.), exact " +
|
|
9
|
+
"angle, illumination %, the zodiac sign and degree the Moon occupies, and next void-of-course " +
|
|
10
|
+
"period.\n\n" +
|
|
11
|
+
"⚠️ THIS TOOL ANSWERS: 'What phase is the moon in right now (or at a given datetime)?' and " +
|
|
12
|
+
"'What sign is the moon in?'\n" +
|
|
10
13
|
"❌ THIS TOOL DOES NOT ANSWER: 'When is the next new moon / full moon?'\n" +
|
|
11
14
|
"→ For upcoming phase DATES use ephemeris_next_lunar_phase instead.\n\n" +
|
|
12
15
|
"For a user-facing interactive moon-phase dial, use explore_moon_phase instead.\n\n" +
|
|
@@ -21,8 +24,11 @@ registerTool({
|
|
|
21
24
|
properties: {
|
|
22
25
|
datetime: {
|
|
23
26
|
type: "string",
|
|
24
|
-
description: "ISO 8601 datetime to query
|
|
27
|
+
description: "ISO 8601 datetime to query, stating its zone ('2026-03-20T12:00:00Z' or " +
|
|
28
|
+
"'2026-03-20T08:00:00-04:00'), or a local time together with `timezone`. " +
|
|
29
|
+
"If omitted, returns the current live moon phase (UTC now).",
|
|
25
30
|
},
|
|
31
|
+
timezone: TIMEZONE_PROPERTY,
|
|
26
32
|
latitude: {
|
|
27
33
|
type: "number",
|
|
28
34
|
description: "Observer latitude (optional, used for local void-of-course calculations).",
|
|
@@ -42,7 +48,9 @@ registerTool({
|
|
|
42
48
|
const params = {};
|
|
43
49
|
// Backend requires datetime even though the schema marks it optional.
|
|
44
50
|
// Default to current UTC time when caller omits it.
|
|
45
|
-
params.datetime = args.datetime
|
|
51
|
+
params.datetime = args.datetime
|
|
52
|
+
? localToUtcIso("datetime", args.datetime, args.timezone)
|
|
53
|
+
: new Date().toISOString();
|
|
46
54
|
if (args.latitude != null)
|
|
47
55
|
params.latitude = args.latitude;
|
|
48
56
|
if (args.longitude != null)
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { registerTool, validateRequired } from "../index.js";
|
|
2
2
|
import { getActiveClient } from "../../backend/client.js";
|
|
3
3
|
import { OUTPUT_SCHEMA_IMAGE_AND_JSON } from "../output-schemas.js";
|
|
4
|
-
|
|
5
|
-
"Include timezone offset if known, e.g. '1990-04-15T14:30:00-05:00'.";
|
|
4
|
+
import { DATETIME_DESC, TIMEZONE_PROPERTY, assertZonedDatetime } from "../datetime.js";
|
|
6
5
|
/** Map human-readable house system names to standard single-letter codes */
|
|
7
6
|
const HOUSE_SYSTEM_MAP = {
|
|
8
7
|
placidus: "P", whole_sign: "W", equal: "E", koch: "K",
|
|
@@ -17,8 +16,9 @@ registerTool({
|
|
|
17
16
|
"essential dignities, retrograde status, house system data, and major aspect grid. Asteroids are automatically included. " +
|
|
18
17
|
"Returns raw JSON. For a user-facing visual chart, use explore_natal_chart instead.\n\n" +
|
|
19
18
|
"CREDIT COST: 1 credit per call.\n\n" +
|
|
20
|
-
"EXAMPLE:
|
|
21
|
-
" datetime='1990-04-15T14:30:00', latitude=41.8781, longitude=-87.6298"
|
|
19
|
+
"EXAMPLE: born 15 April 1990 at 2:30 PM local time in Chicago — pass the local time and name the zone:\n" +
|
|
20
|
+
" datetime='1990-04-15T14:30:00', timezone='America/Chicago', latitude=41.8781, longitude=-87.6298\n" +
|
|
21
|
+
"Equivalently, put the zone on the datetime: datetime='1990-04-15T14:30:00-05:00' (or the UTC instant '1990-04-15T19:30:00Z').",
|
|
22
22
|
inputSchema: {
|
|
23
23
|
type: "object",
|
|
24
24
|
properties: {
|
|
@@ -26,13 +26,10 @@ registerTool({
|
|
|
26
26
|
type: "string",
|
|
27
27
|
description: DATETIME_DESC,
|
|
28
28
|
},
|
|
29
|
-
timezone:
|
|
30
|
-
type: "string",
|
|
31
|
-
description: "IANA timezone name, e.g. 'America/Denver'. Use this if passing local time without a UTC offset in the datetime string.",
|
|
32
|
-
},
|
|
29
|
+
timezone: TIMEZONE_PROPERTY,
|
|
33
30
|
latitude: {
|
|
34
31
|
type: "number",
|
|
35
|
-
description: "Geographic latitude of birth location in decimal degrees (positive = North).",
|
|
32
|
+
description: "Geographic latitude of birth location in decimal degrees (positive = North). Resolve from a place name with location_search; never recall coordinates from memory.",
|
|
36
33
|
},
|
|
37
34
|
longitude: {
|
|
38
35
|
type: "number",
|
|
@@ -79,6 +76,7 @@ registerTool({
|
|
|
79
76
|
annotations: { title: "Natal Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
80
77
|
handler: async (args) => {
|
|
81
78
|
validateRequired(args, ["datetime", "latitude", "longitude"]);
|
|
79
|
+
assertZonedDatetime("datetime", args.datetime, args.timezone);
|
|
82
80
|
const body = {
|
|
83
81
|
subject: {
|
|
84
82
|
name: "MCP Request",
|
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
import { registerTool, validateRequired } from "../index.js";
|
|
2
2
|
import { getActiveClient } from "../../backend/client.js";
|
|
3
3
|
import { OUTPUT_SCHEMA_IMAGE_AND_JSON } from "../output-schemas.js";
|
|
4
|
+
import { DATETIME_DESC, TIMEZONE_PROPERTY, WINDOW_DATE_DESC, assertZonedDatetime } from "../datetime.js";
|
|
4
5
|
registerTool({
|
|
5
6
|
name: "ephemeris_progressed_chart",
|
|
6
7
|
description: "Calculate a Secondary Progressed (or Solar Arc / Tertiary) chart. " +
|
|
7
8
|
"Advances the natal chart symbolically — 1 day = 1 year (secondary), or using solar arc motion. " +
|
|
8
9
|
"Returns progressed planet positions, house cusps, aspects, and retrograde status.\n\n" +
|
|
9
10
|
"CREDIT COST: 1 credit per call.\n\n" +
|
|
10
|
-
"EXAMPLE: Secondary progressions for someone born 1985-06-21, progressed to 2026-01-01:\n" +
|
|
11
|
-
" birth_datetime='1985-06-21T14:00:00',
|
|
12
|
-
" target_datetime='2026-01-01', method='secondary'",
|
|
11
|
+
"EXAMPLE: Secondary progressions for someone born 1985-06-21 at 2:00 PM in London, progressed to 2026-01-01:\n" +
|
|
12
|
+
" birth_datetime='1985-06-21T14:00:00', timezone='Europe/London',\n" +
|
|
13
|
+
" birth_latitude=51.5, birth_longitude=-0.12, target_datetime='2026-01-01', method='secondary'",
|
|
13
14
|
inputSchema: {
|
|
14
15
|
type: "object",
|
|
15
16
|
properties: {
|
|
16
17
|
birth_datetime: {
|
|
17
18
|
type: "string",
|
|
18
|
-
description:
|
|
19
|
-
"e.g. '1990-05-15T14:30:00-05:00'. A naive datetime (no offset) is " +
|
|
20
|
-
"interpreted as UTC and will shift the progressed positions.",
|
|
19
|
+
description: DATETIME_DESC,
|
|
21
20
|
},
|
|
21
|
+
timezone: TIMEZONE_PROPERTY,
|
|
22
22
|
birth_latitude: {
|
|
23
23
|
type: "number",
|
|
24
|
-
description: "Birth latitude in decimal degrees.",
|
|
24
|
+
description: "Birth latitude in decimal degrees. Resolve from a place name with location_search; never recall coordinates from memory.",
|
|
25
25
|
},
|
|
26
26
|
birth_longitude: {
|
|
27
27
|
type: "number",
|
|
@@ -29,7 +29,7 @@ registerTool({
|
|
|
29
29
|
},
|
|
30
30
|
target_datetime: {
|
|
31
31
|
type: "string",
|
|
32
|
-
description: "
|
|
32
|
+
description: "Date to progress the chart to. " + WINDOW_DATE_DESC,
|
|
33
33
|
},
|
|
34
34
|
method: {
|
|
35
35
|
type: "string",
|
|
@@ -63,6 +63,8 @@ registerTool({
|
|
|
63
63
|
annotations: { title: "Progressed Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
64
64
|
handler: async (args) => {
|
|
65
65
|
validateRequired(args, ["birth_datetime", "birth_latitude", "birth_longitude", "target_datetime"]);
|
|
66
|
+
assertZonedDatetime("birth_datetime", args.birth_datetime, args.timezone);
|
|
67
|
+
assertZonedDatetime("target_datetime", args.target_datetime);
|
|
66
68
|
const body = {
|
|
67
69
|
subject: {
|
|
68
70
|
name: "Progressed Subject",
|
|
@@ -70,6 +72,8 @@ registerTool({
|
|
|
70
72
|
birth_location: {
|
|
71
73
|
latitude: { decimal: args.birth_latitude },
|
|
72
74
|
longitude: { decimal: args.birth_longitude },
|
|
75
|
+
// The server resolves the IANA zone, so historical DST stays correct.
|
|
76
|
+
...(args.timezone ? { timezone: { iana_name: args.timezone } } : {}),
|
|
73
77
|
},
|
|
74
78
|
},
|
|
75
79
|
target_datetime: { iso: args.target_datetime },
|