@openephemeris/mcp-server 4.8.0 → 4.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/dist/tools/apps/bazi-app.js +14 -7
  3. package/dist/tools/apps/bi-wheel-app.js +5 -5
  4. package/dist/tools/apps/bodygraph-app.js +8 -8
  5. package/dist/tools/apps/chart-wheel-app.js +4 -4
  6. package/dist/tools/apps/location-tools.js +68 -6
  7. package/dist/tools/apps/moon-phase-app.js +1 -1
  8. package/dist/tools/apps/vedic-chart-app.js +1 -1
  9. package/dist/tools/auth.js +8 -0
  10. package/dist/tools/dev.js +15 -5
  11. package/dist/tools/specialized/account.js +5 -1
  12. package/dist/tools/specialized/acg.js +2 -2
  13. package/dist/tools/specialized/bazi.d.ts +38 -0
  14. package/dist/tools/specialized/bazi.js +126 -24
  15. package/dist/tools/specialized/bi_wheel.js +1 -1
  16. package/dist/tools/specialized/chart_wheel.js +1 -1
  17. package/dist/tools/specialized/comparative.js +4 -4
  18. package/dist/tools/specialized/eclipse.js +1 -1
  19. package/dist/tools/specialized/electional.js +5 -5
  20. package/dist/tools/specialized/ephemeris_core.js +2 -2
  21. package/dist/tools/specialized/ephemeris_extended.js +8 -8
  22. package/dist/tools/specialized/hd_bodygraph.js +1 -1
  23. package/dist/tools/specialized/hd_cycles.js +2 -2
  24. package/dist/tools/specialized/hd_group.js +2 -2
  25. package/dist/tools/specialized/human_design.js +1 -1
  26. package/dist/tools/specialized/moon.js +2 -2
  27. package/dist/tools/specialized/natal.js +1 -1
  28. package/dist/tools/specialized/progressed.js +1 -1
  29. package/dist/tools/specialized/relocation.js +1 -1
  30. package/dist/tools/specialized/returns.js +3 -3
  31. package/dist/tools/specialized/synastry.js +1 -1
  32. package/dist/tools/specialized/transits.js +1 -1
  33. package/dist/tools/specialized/vedic.js +1 -1
  34. package/dist/tools/specialized/venus_star_points.js +6 -6
  35. package/package.json +1 -1
@@ -2,7 +2,7 @@ import { registerTool } from "../index.js";
2
2
  import { getActiveClient } from "../../backend/client.js";
3
3
  import { OUTPUT_SCHEMA_IMAGE_AND_JSON } from "../output-schemas.js";
4
4
  export function parseBaziArgs(args, datetimeField = "datetime") {
5
- let { year, month, day, hour } = args;
5
+ let { year, month, day, hour, minute } = args;
6
6
  const datetime = args[datetimeField];
7
7
  if (datetime && (!year || !month || !day)) {
8
8
  const parts = baziLocalComponents(datetime, args.timezone, datetimeField);
@@ -11,6 +11,8 @@ export function parseBaziArgs(args, datetimeField = "datetime") {
11
11
  day = parts.day;
12
12
  if (hour == null)
13
13
  hour = parts.hour;
14
+ if (minute == null)
15
+ minute = parts.minute;
14
16
  }
15
17
  if (!year || !month || !day) {
16
18
  throw new Error("Provide year/month/day fields, or a datetime ISO string. " +
@@ -19,8 +21,48 @@ export function parseBaziArgs(args, datetimeField = "datetime") {
19
21
  const out = { year, month, day };
20
22
  if (hour != null)
21
23
  out.hour = hour;
24
+ if (minute != null)
25
+ out.minute = minute;
22
26
  return out;
23
27
  }
28
+ /**
29
+ * Build the optional BaZi convention fields (year_boundary, day_boundary,
30
+ * true_solar_time, latitude, longitude, timezone) to spread into a request
31
+ * body. `timezone` is wrapped to the API's `TimezoneInput` object shape.
32
+ *
33
+ * Kept separate from `parseBaziArgs`/`BaziComponents`: these fields frame
34
+ * which convention resolves the pillars rather than naming the birth moment
35
+ * itself, and bazi_compatibility needs the same set built twice under a
36
+ * `chart_a_`/`chart_b_` prefix.
37
+ */
38
+ export function buildBaziConventionFields(args) {
39
+ const fields = {};
40
+ if (args.year_boundary != null)
41
+ fields.year_boundary = args.year_boundary;
42
+ if (args.day_boundary != null)
43
+ fields.day_boundary = args.day_boundary;
44
+ if (args.true_solar_time != null)
45
+ fields.true_solar_time = args.true_solar_time;
46
+ if (args.latitude != null)
47
+ fields.latitude = args.latitude;
48
+ if (args.longitude != null)
49
+ fields.longitude = args.longitude;
50
+ if (typeof args.timezone === "string" && args.timezone.trim() !== "") {
51
+ fields.timezone = { iana_name: args.timezone };
52
+ }
53
+ return fields;
54
+ }
55
+ /** `buildBaziConventionFields`, reading `<prefix>_<field>` args — used by bazi_compatibility. */
56
+ export function buildBaziConventionFieldsPrefixed(args, prefix) {
57
+ return buildBaziConventionFields({
58
+ year_boundary: args[`${prefix}_year_boundary`],
59
+ day_boundary: args[`${prefix}_day_boundary`],
60
+ true_solar_time: args[`${prefix}_true_solar_time`],
61
+ latitude: args[`${prefix}_latitude`],
62
+ longitude: args[`${prefix}_longitude`],
63
+ timezone: args[`${prefix}_timezone`],
64
+ });
65
+ }
24
66
  /**
25
67
  * Extract the LOCAL calendar/clock components a BaZi chart is built from.
26
68
  *
@@ -50,7 +92,7 @@ function baziLocalComponents(datetime, timezone, field) {
50
92
  hour: "2-digit", minute: "2-digit",
51
93
  }).formatToParts(new Date(value));
52
94
  const get = (t) => Number(parts.find((p) => p.type === t)?.value);
53
- return { year: get("year"), month: get("month"), day: get("day"), hour: get("hour") % 24 };
95
+ return { year: get("year"), month: get("month"), day: get("day"), hour: get("hour") % 24, minute: get("minute") };
54
96
  }
55
97
  const m = /^(\d{4})-(\d{2})-(\d{2})(?:[T ](\d{2}):(\d{2}))?/.exec(value);
56
98
  if (!m) {
@@ -62,6 +104,7 @@ function baziLocalComponents(datetime, timezone, field) {
62
104
  month: Number(m[2]),
63
105
  day: Number(m[3]),
64
106
  hour: m[4] != null ? Number(m[4]) : undefined,
107
+ minute: m[5] != null ? Number(m[5]) : undefined,
65
108
  };
66
109
  }
67
110
  // Shared datetime input schema fragment — used across all BaZiRequest tools.
@@ -83,6 +126,10 @@ const DATETIME_PROPERTIES = {
83
126
  description: "Birth hour (0–23). Optional, defaults to 12 (noon). " +
84
127
  "Chinese shí hours are 2-hour blocks — precision within a 2-hour window is sufficient.",
85
128
  },
129
+ minute: {
130
+ type: "integer",
131
+ description: "Birth minute (0–59). Optional, defaults to 0. Only matters under true_solar_time or near a boundary.",
132
+ },
86
133
  datetime: {
87
134
  type: "string",
88
135
  description: "Alternative to year/month/day: ISO 8601 datetime read as LOCAL wall-clock time at the " +
@@ -93,11 +140,49 @@ const DATETIME_PROPERTIES = {
93
140
  },
94
141
  timezone: {
95
142
  type: "string",
96
- description: "IANA timezone name for the birth place, e.g. 'Asia/Shanghai'. Only needed when " +
97
- "`datetime` carries a 'Z' or ±HH:MM offset, to convert that instant back to the local " +
98
- "clock the pillars are built from.",
143
+ description: "IANA timezone name for the birth place, e.g. 'Asia/Shanghai'. Required to convert a " +
144
+ "zoned `datetime` back to local clock; also passed to the API to frame the year/month " +
145
+ "solar-term boundary at the true birth instant instead of naive local clock.",
146
+ },
147
+ };
148
+ // Optional convention fields, shared across every BaZiRequest-shaped tool —
149
+ // see BaZiRequest in the Go API. Kept terse: core tool-surface budget is
150
+ // near its ceiling (test/tool-surface-budget.test.ts).
151
+ export const CONVENTION_PROPERTIES = {
152
+ year_boundary: {
153
+ type: "string",
154
+ enum: ["lichun", "cny"],
155
+ description: "Year-pillar cutover convention. Default lichun.",
156
+ },
157
+ day_boundary: {
158
+ type: "string",
159
+ enum: ["zi_hour", "midnight"],
160
+ description: "Day-pillar cutover convention. Default zi_hour.",
161
+ },
162
+ true_solar_time: {
163
+ type: "boolean",
164
+ description: "Apply true solar time correction (needs latitude+longitude).",
165
+ },
166
+ latitude: {
167
+ type: "number",
168
+ description: "Birth latitude, decimal degrees (north+).",
169
+ },
170
+ longitude: {
171
+ type: "number",
172
+ description: "Birth longitude, decimal degrees (east+).",
99
173
  },
100
174
  };
175
+ /** CONVENTION_PROPERTIES (+ minute) under a `chart_a_`/`chart_b_` prefix — used by bazi_compatibility. */
176
+ function chartConventionProperties(label) {
177
+ return {
178
+ [`chart_${label.toLowerCase()}_minute`]: { type: "integer", description: `Chart ${label} minute (0–59).` },
179
+ [`chart_${label.toLowerCase()}_year_boundary`]: { type: "string", enum: ["lichun", "cny"], description: `Chart ${label} year-pillar cutover.` },
180
+ [`chart_${label.toLowerCase()}_day_boundary`]: { type: "string", enum: ["zi_hour", "midnight"], description: `Chart ${label} day-pillar cutover.` },
181
+ [`chart_${label.toLowerCase()}_true_solar_time`]: { type: "boolean", description: `Chart ${label}: true solar time correction.` },
182
+ [`chart_${label.toLowerCase()}_latitude`]: { type: "number", description: `Chart ${label} latitude (decimal degrees, north+).` },
183
+ [`chart_${label.toLowerCase()}_longitude`]: { type: "number", description: `Chart ${label} longitude (decimal degrees, east+).` },
184
+ };
185
+ }
101
186
  // Shared visual input schema fragment — mirrors natal.ts pattern.
102
187
  const VISUAL_PROPERTIES = {
103
188
  include_visual: {
@@ -153,14 +238,15 @@ registerTool({
153
238
  type: "object",
154
239
  properties: {
155
240
  ...DATETIME_PROPERTIES,
241
+ ...CONVENTION_PROPERTIES,
156
242
  ...VISUAL_PROPERTIES,
157
243
  },
158
244
  additionalProperties: false,
159
245
  },
160
246
  outputSchema: OUTPUT_SCHEMA_IMAGE_AND_JSON,
161
- annotations: { title: "BaZi Four Pillars Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
247
+ annotations: { title: "BaZi Four Pillars Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
162
248
  handler: async (args) => {
163
- const body = { ...parseBaziArgs(args) };
249
+ const body = { ...parseBaziArgs(args), ...buildBaziConventionFields(args) };
164
250
  applyVisualConfig(body, args);
165
251
  return await getActiveClient().request("POST", "/chinese/bazi", { data: body });
166
252
  },
@@ -190,14 +276,15 @@ registerTool({
190
276
  type: "object",
191
277
  properties: {
192
278
  ...DATETIME_PROPERTIES,
279
+ ...CONVENTION_PROPERTIES,
193
280
  ...VISUAL_PROPERTIES,
194
281
  },
195
282
  additionalProperties: false,
196
283
  },
197
284
  outputSchema: OUTPUT_SCHEMA_IMAGE_AND_JSON,
198
- annotations: { title: "BaZi Ten Gods", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
285
+ annotations: { title: "BaZi Ten Gods", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
199
286
  handler: async (args) => {
200
- const body = { ...parseBaziArgs(args) };
287
+ const body = { ...parseBaziArgs(args), ...buildBaziConventionFields(args) };
201
288
  applyVisualConfig(body, args);
202
289
  return await getActiveClient().request("POST", "/chinese/bazi/ten-gods", { data: body });
203
290
  },
@@ -225,14 +312,15 @@ registerTool({
225
312
  type: "object",
226
313
  properties: {
227
314
  ...DATETIME_PROPERTIES,
315
+ ...CONVENTION_PROPERTIES,
228
316
  ...VISUAL_PROPERTIES,
229
317
  },
230
318
  additionalProperties: false,
231
319
  },
232
320
  outputSchema: OUTPUT_SCHEMA_IMAGE_AND_JSON,
233
- annotations: { title: "BaZi Element Balance", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
321
+ annotations: { title: "BaZi Element Balance", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
234
322
  handler: async (args) => {
235
- const body = { ...parseBaziArgs(args) };
323
+ const body = { ...parseBaziArgs(args), ...buildBaziConventionFields(args) };
236
324
  applyVisualConfig(body, args);
237
325
  return await getActiveClient().request("POST", "/chinese/bazi/element-balance", { data: body });
238
326
  },
@@ -261,6 +349,7 @@ registerTool({
261
349
  type: "object",
262
350
  properties: {
263
351
  ...DATETIME_PROPERTIES,
352
+ ...CONVENTION_PROPERTIES,
264
353
  gender: {
265
354
  type: "string",
266
355
  enum: ["male", "female"],
@@ -273,16 +362,21 @@ registerTool({
273
362
  additionalProperties: false,
274
363
  },
275
364
  outputSchema: OUTPUT_SCHEMA_IMAGE_AND_JSON,
276
- annotations: { title: "BaZi Luck Pillars", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
365
+ annotations: { title: "BaZi Luck Pillars", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
277
366
  handler: async (args) => {
278
367
  if (!args.gender) {
279
368
  throw new Error("gender is required for luck pillar calculation. " +
280
369
  "Provide 'male' or 'female' — the direction of Da Yun depends on gender and year polarity.");
281
370
  }
282
- const { year, month, day, hour } = parseBaziArgs(args);
283
- const body = { year, month, day, gender: args.gender };
371
+ const { year, month, day, hour, minute } = parseBaziArgs(args);
372
+ const body = {
373
+ year, month, day, gender: args.gender,
374
+ ...buildBaziConventionFields(args),
375
+ };
284
376
  if (hour != null)
285
377
  body.hour = hour;
378
+ if (minute != null)
379
+ body.minute = minute;
286
380
  return await getActiveClient().request("POST", "/chinese/bazi/luck-pillars", { data: body });
287
381
  },
288
382
  });
@@ -316,7 +410,7 @@ registerTool({
316
410
  additionalProperties: false,
317
411
  },
318
412
  outputSchema: OUTPUT_SCHEMA_IMAGE_AND_JSON,
319
- annotations: { title: "BaZi Annual Pillar", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
413
+ annotations: { title: "BaZi Annual Pillar", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
320
414
  handler: async (args) => {
321
415
  if (!args.year) {
322
416
  throw new Error("year is required. Provide a Gregorian year (1–9999), e.g. year=2025.");
@@ -362,9 +456,10 @@ registerTool({
362
456
  },
363
457
  chart_a_timezone: {
364
458
  type: "string",
365
- description: "IANA timezone for Chart A's birth place, e.g. 'Asia/Shanghai'. Only needed when " +
366
- "chart_a_datetime carries a 'Z' or ±HH:MM offset.",
459
+ description: "IANA timezone for Chart A's birth place, e.g. 'Asia/Shanghai'. Needed when " +
460
+ "chart_a_datetime carries a 'Z' or ±HH:MM offset; also frames Chart A's year/month boundary.",
367
461
  },
462
+ ...chartConventionProperties("A"),
368
463
  // Chart B
369
464
  chart_b_year: { type: "integer", description: "Chart B birth year." },
370
465
  chart_b_month: { type: "integer", description: "Chart B birth month (1–12)." },
@@ -378,20 +473,22 @@ registerTool({
378
473
  },
379
474
  chart_b_timezone: {
380
475
  type: "string",
381
- description: "IANA timezone for Chart B's birth place, e.g. 'Asia/Shanghai'. Only needed when " +
382
- "chart_b_datetime carries a 'Z' or ±HH:MM offset.",
476
+ description: "IANA timezone for Chart B's birth place, e.g. 'Asia/Shanghai'. Needed when " +
477
+ "chart_b_datetime carries a 'Z' or ±HH:MM offset; also frames Chart B's year/month boundary.",
383
478
  },
479
+ ...chartConventionProperties("B"),
384
480
  },
385
481
  additionalProperties: false,
386
482
  },
387
483
  outputSchema: OUTPUT_SCHEMA_IMAGE_AND_JSON,
388
- annotations: { title: "BaZi Compatibility", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
484
+ annotations: { title: "BaZi Compatibility", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
389
485
  handler: async (args) => {
390
486
  const chartAComponents = parseBaziArgs({
391
487
  year: args.chart_a_year,
392
488
  month: args.chart_a_month,
393
489
  day: args.chart_a_day,
394
490
  hour: args.chart_a_hour,
491
+ minute: args.chart_a_minute,
395
492
  datetime: args.chart_a_datetime,
396
493
  timezone: args.chart_a_timezone,
397
494
  });
@@ -400,13 +497,14 @@ registerTool({
400
497
  month: args.chart_b_month,
401
498
  day: args.chart_b_day,
402
499
  hour: args.chart_b_hour,
500
+ minute: args.chart_b_minute,
403
501
  datetime: args.chart_b_datetime,
404
502
  timezone: args.chart_b_timezone,
405
503
  });
406
504
  return await getActiveClient().request("POST", "/chinese/bazi/compatibility", {
407
505
  data: {
408
- chart_a: chartAComponents,
409
- chart_b: chartBComponents,
506
+ chart_a: { ...chartAComponents, ...buildBaziConventionFieldsPrefixed(args, "chart_a") },
507
+ chart_b: { ...chartBComponents, ...buildBaziConventionFieldsPrefixed(args, "chart_b") },
410
508
  },
411
509
  });
412
510
  },
@@ -434,6 +532,7 @@ registerTool({
434
532
  type: "object",
435
533
  properties: {
436
534
  ...DATETIME_PROPERTIES,
535
+ ...CONVENTION_PROPERTIES,
437
536
  theme: {
438
537
  type: "string",
439
538
  enum: ["light", "dark", "mono"],
@@ -447,9 +546,9 @@ registerTool({
447
546
  additionalProperties: false,
448
547
  },
449
548
  outputSchema: OUTPUT_SCHEMA_IMAGE_AND_JSON,
450
- annotations: { title: "BaZi Chart (Visual)", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
549
+ annotations: { title: "BaZi Chart (Visual)", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
451
550
  handler: async (args) => {
452
- const { year, month, day, hour } = parseBaziArgs(args);
551
+ const { year, month, day, hour, minute } = parseBaziArgs(args);
453
552
  const body = {
454
553
  year, month, day,
455
554
  visual_config: {
@@ -457,9 +556,12 @@ registerTool({
457
556
  theme: args.theme ?? "light",
458
557
  size: args.size ?? 800,
459
558
  },
559
+ ...buildBaziConventionFields(args),
460
560
  };
461
561
  if (hour != null)
462
562
  body.hour = hour;
563
+ if (minute != null)
564
+ body.minute = minute;
463
565
  return await getActiveClient().request("POST", "/chinese/bazi/chart", { data: body });
464
566
  },
465
567
  });
@@ -40,7 +40,7 @@ registerTool({
40
40
  additionalProperties: false,
41
41
  },
42
42
  outputSchema: OUTPUT_SCHEMA_IMAGE,
43
- annotations: { title: "Bi-Wheel Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
43
+ annotations: { title: "Bi-Wheel Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
44
44
  handler: async (args) => {
45
45
  validateRequired(args, ["datetime_a", "latitude_a", "longitude_a", "datetime_b", "latitude_b", "longitude_b"]);
46
46
  assertZonedDatetime("datetime_a", args.datetime_a, args.timezone_a, "timezone_a");
@@ -45,7 +45,7 @@ registerTool({
45
45
  additionalProperties: false,
46
46
  },
47
47
  outputSchema: OUTPUT_SCHEMA_IMAGE,
48
- annotations: { title: "Chart Wheel SVG", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
48
+ annotations: { title: "Chart Wheel SVG", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
49
49
  handler: async (args) => {
50
50
  validateRequired(args, ["datetime", "latitude", "longitude"]);
51
51
  assertZonedDatetime("datetime", args.datetime, args.timezone);
@@ -51,7 +51,7 @@ registerTool({
51
51
  additionalProperties: false,
52
52
  },
53
53
  outputSchema: OUTPUT_SCHEMA_JSON,
54
- annotations: { title: "Davison Relationship Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
54
+ annotations: { title: "Davison Relationship Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
55
55
  handler: async (args) => {
56
56
  validateRequired(args, [
57
57
  "person_a_datetime", "person_a_latitude", "person_a_longitude",
@@ -99,7 +99,7 @@ registerTool({
99
99
  additionalProperties: false,
100
100
  },
101
101
  outputSchema: OUTPUT_SCHEMA_JSON,
102
- annotations: { title: "Midpoint Composite Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
102
+ annotations: { title: "Midpoint Composite Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
103
103
  handler: async (args) => {
104
104
  validateRequired(args, [
105
105
  "person_a_datetime", "person_a_latitude", "person_a_longitude",
@@ -147,7 +147,7 @@ registerTool({
147
147
  additionalProperties: false,
148
148
  },
149
149
  outputSchema: OUTPUT_SCHEMA_JSON,
150
- annotations: { title: "Synastry House Overlay", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
150
+ annotations: { title: "Synastry House Overlay", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
151
151
  handler: async (args) => {
152
152
  validateRequired(args, [
153
153
  "person_a_datetime", "person_a_latitude", "person_a_longitude",
@@ -193,7 +193,7 @@ registerTool({
193
193
  additionalProperties: false,
194
194
  },
195
195
  outputSchema: OUTPUT_SCHEMA_JSON,
196
- annotations: { title: "Transits to Natal Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
196
+ annotations: { title: "Transits to Natal Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
197
197
  handler: async (args) => {
198
198
  validateRequired(args, ["natal_datetime", "natal_latitude", "natal_longitude"]);
199
199
  assertPersonZoned("natal", args);
@@ -42,7 +42,7 @@ registerTool({
42
42
  additionalProperties: false,
43
43
  },
44
44
  outputSchema: OUTPUT_SCHEMA_JSON,
45
- annotations: { title: "Eclipse Finder", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
45
+ annotations: { title: "Eclipse Finder", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
46
46
  handler: async (args) => {
47
47
  validateRequired(args, ["eclipse_type"]);
48
48
  validateCoordinates(args, "latitude", "longitude");
@@ -59,7 +59,7 @@ registerTool({
59
59
  additionalProperties: false,
60
60
  },
61
61
  outputSchema: OUTPUT_SCHEMA_JSON,
62
- annotations: { title: "Electional Timing Window", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
62
+ annotations: { title: "Electional Timing Window", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
63
63
  handler: async (args) => {
64
64
  validateRequired(args, ["start_date", "end_date", "latitude", "longitude"]);
65
65
  const query = {
@@ -110,7 +110,7 @@ registerTool({
110
110
  additionalProperties: false,
111
111
  },
112
112
  outputSchema: OUTPUT_SCHEMA_JSON,
113
- annotations: { title: "Moment Quality Analysis", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
113
+ annotations: { title: "Moment Quality Analysis", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
114
114
  handler: async (args) => {
115
115
  const query = {};
116
116
  if (args.date)
@@ -164,7 +164,7 @@ registerTool({
164
164
  additionalProperties: false,
165
165
  },
166
166
  outputSchema: OUTPUT_SCHEMA_JSON,
167
- annotations: { title: "Retrograde Station Tracker", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
167
+ annotations: { title: "Retrograde Station Tracker", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
168
168
  handler: async (args) => {
169
169
  const query = {};
170
170
  // Map human-readable planet names to IDs if needed
@@ -224,7 +224,7 @@ registerTool({
224
224
  additionalProperties: false,
225
225
  },
226
226
  outputSchema: OUTPUT_SCHEMA_JSON,
227
- annotations: { title: "Active Aspects Search", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
227
+ annotations: { title: "Active Aspects Search", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
228
228
  handler: async (args) => {
229
229
  const query = {};
230
230
  if (args.date)
@@ -302,7 +302,7 @@ registerTool({
302
302
  additionalProperties: false,
303
303
  },
304
304
  outputSchema: OUTPUT_SCHEMA_JSON,
305
- annotations: { title: "Angle Crossing Search", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
305
+ annotations: { title: "Angle Crossing Search", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
306
306
  handler: async (args) => {
307
307
  validateRequired(args, ["latitude", "longitude"]);
308
308
  const query = {
@@ -40,7 +40,7 @@ registerTool({
40
40
  additionalProperties: false,
41
41
  },
42
42
  outputSchema: OUTPUT_SCHEMA_JSON,
43
- annotations: { title: "Planet Position", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
43
+ annotations: { title: "Planet Position", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
44
44
  handler: async (args) => {
45
45
  validateRequired(args, ["planet_id", "datetime"]);
46
46
  const body = {
@@ -89,7 +89,7 @@ registerTool({
89
89
  additionalProperties: false,
90
90
  },
91
91
  outputSchema: OUTPUT_SCHEMA_JSON,
92
- annotations: { title: "House Cusps", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
92
+ annotations: { title: "House Cusps", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
93
93
  handler: async (args) => {
94
94
  validateRequired(args, ["datetime", "latitude", "longitude"]);
95
95
  const body = {
@@ -56,7 +56,7 @@ registerTool({
56
56
  additionalProperties: false,
57
57
  },
58
58
  outputSchema: OUTPUT_SCHEMA_JSON,
59
- annotations: { title: "Natal Batch Charts", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
59
+ annotations: { title: "Natal Batch Charts", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
60
60
  handler: async (args) => {
61
61
  validateRequired(args, ["subjects"]);
62
62
  args.subjects.forEach((s, i) => assertZonedDatetime(`subjects[${i}].datetime`, s?.datetime, s?.timezone, `subjects[${i}].timezone`));
@@ -82,7 +82,7 @@ registerTool({
82
82
  additionalProperties: false,
83
83
  },
84
84
  outputSchema: OUTPUT_SCHEMA_JSON,
85
- annotations: { title: "Essential Dignities", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
85
+ annotations: { title: "Essential Dignities", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
86
86
  handler: async (args) => {
87
87
  validateRequired(args, ["datetime"]);
88
88
  return await getActiveClient().post("/ephemeris/dignities", {
@@ -122,7 +122,7 @@ registerTool({
122
122
  additionalProperties: false,
123
123
  },
124
124
  outputSchema: OUTPUT_SCHEMA_JSON,
125
- annotations: { title: "Retrograde Status", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
125
+ annotations: { title: "Retrograde Status", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
126
126
  handler: async (args) => {
127
127
  validateRequired(args, ["datetime"]);
128
128
  const client = getActiveClient();
@@ -173,7 +173,7 @@ registerTool({
173
173
  additionalProperties: false,
174
174
  },
175
175
  outputSchema: OUTPUT_SCHEMA_JSON,
176
- annotations: { title: "Midpoints", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
176
+ annotations: { title: "Midpoints", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
177
177
  handler: async (args) => {
178
178
  validateRequired(args, ["datetime", "latitude", "longitude"]);
179
179
  return await getActiveClient().post("/ephemeris/midpoints", {
@@ -219,7 +219,7 @@ registerTool({
219
219
  additionalProperties: false,
220
220
  },
221
221
  outputSchema: OUTPUT_SCHEMA_JSON,
222
- annotations: { title: "Fixed Stars", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
222
+ annotations: { title: "Fixed Stars", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
223
223
  handler: async (args) => {
224
224
  validateRequired(args, ["datetime"]);
225
225
  // A lone latitude would silently drop the angles from the scan, which is
@@ -262,7 +262,7 @@ registerTool({
262
262
  additionalProperties: false,
263
263
  },
264
264
  outputSchema: OUTPUT_SCHEMA_JSON,
265
- annotations: { title: "Hermetic Lots", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
265
+ annotations: { title: "Hermetic Lots", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
266
266
  handler: async (args) => {
267
267
  validateRequired(args, ["datetime", "latitude", "longitude"]);
268
268
  return await getActiveClient().post("/ephemeris/hermetic-lots", {
@@ -290,7 +290,7 @@ registerTool({
290
290
  additionalProperties: false,
291
291
  },
292
292
  outputSchema: OUTPUT_SCHEMA_JSON,
293
- annotations: { title: "Angles & Points", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
293
+ annotations: { title: "Angles & Points", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
294
294
  handler: async (args) => {
295
295
  validateRequired(args, ["datetime", "latitude", "longitude"]);
296
296
  return await getActiveClient().post("/ephemeris/angles-points", {
@@ -319,7 +319,7 @@ registerTool({
319
319
  additionalProperties: false,
320
320
  },
321
321
  outputSchema: OUTPUT_SCHEMA_JSON,
322
- annotations: { title: "Aspect Check", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
322
+ annotations: { title: "Aspect Check", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
323
323
  handler: async (args) => {
324
324
  validateRequired(args, ["longitude_1", "longitude_2"]);
325
325
  // Backend field names are longitude1 / longitude2 (no underscore)
@@ -49,7 +49,7 @@ registerTool({
49
49
  additionalProperties: false,
50
50
  },
51
51
  outputSchema: OUTPUT_SCHEMA_JSON,
52
- annotations: { title: "Human Design Bodygraph", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
52
+ annotations: { title: "Human Design Bodygraph", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
53
53
  handler: async (args) => {
54
54
  validateRequired(args, ["datetime"]);
55
55
  const body = {
@@ -58,7 +58,7 @@ registerTool({
58
58
  additionalProperties: false,
59
59
  },
60
60
  outputSchema: OUTPUT_SCHEMA_JSON,
61
- annotations: { title: "HD Planetary Return", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
61
+ annotations: { title: "HD Planetary Return", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
62
62
  handler: async (args) => {
63
63
  validateRequired(args, ["planet", "datetime", "return_year"]);
64
64
  return await getActiveClient().request("POST", "/human-design/cycles/return", {
@@ -112,7 +112,7 @@ registerTool({
112
112
  additionalProperties: false,
113
113
  },
114
114
  outputSchema: OUTPUT_SCHEMA_JSON,
115
- annotations: { title: "HD Opposition Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
115
+ annotations: { title: "HD Opposition Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
116
116
  handler: async (args) => {
117
117
  validateRequired(args, ["planet", "datetime", "target_year"]);
118
118
  return await getActiveClient().request("POST", "/human-design/cycles/opposition", {
@@ -53,7 +53,7 @@ registerTool({
53
53
  additionalProperties: false,
54
54
  },
55
55
  outputSchema: OUTPUT_SCHEMA_JSON,
56
- annotations: { title: "HD Composite Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
56
+ annotations: { title: "HD Composite Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
57
57
  handler: async (args) => {
58
58
  validateRequired(args, ["person_a_datetime", "person_b_datetime"]);
59
59
  const body = {
@@ -123,7 +123,7 @@ registerTool({
123
123
  additionalProperties: false,
124
124
  },
125
125
  outputSchema: OUTPUT_SCHEMA_JSON,
126
- annotations: { title: "HD Penta Group Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
126
+ annotations: { title: "HD Penta Group Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
127
127
  handler: async (args) => {
128
128
  validateRequired(args, ["group_name", "members"]);
129
129
  const members = await Promise.all(args.members.map(async (m, i) => ({
@@ -62,7 +62,7 @@ registerTool({
62
62
  additionalProperties: false,
63
63
  },
64
64
  outputSchema: OUTPUT_SCHEMA_IMAGE_AND_JSON,
65
- annotations: { title: "Human Design Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
65
+ annotations: { title: "Human Design Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
66
66
  handler: async (args) => {
67
67
  validateRequired(args, ["datetime"]);
68
68
  const body = {
@@ -39,7 +39,7 @@ registerTool({
39
39
  additionalProperties: false,
40
40
  },
41
41
  outputSchema: OUTPUT_SCHEMA_JSON,
42
- annotations: { title: "Moon Phase", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
42
+ annotations: { title: "Moon Phase", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
43
43
  handler: async (args) => {
44
44
  validateCoordinates(args, "latitude", "longitude");
45
45
  const params = {};
@@ -104,7 +104,7 @@ registerTool({
104
104
  additionalProperties: false,
105
105
  },
106
106
  outputSchema: OUTPUT_SCHEMA_JSON,
107
- annotations: { title: "Next Lunar Phase", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
107
+ annotations: { title: "Next Lunar Phase", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
108
108
  handler: async (args) => {
109
109
  const PHASE_LABELS = {
110
110
  new_moon: "New Moon",
@@ -73,7 +73,7 @@ registerTool({
73
73
  additionalProperties: false,
74
74
  },
75
75
  outputSchema: OUTPUT_SCHEMA_IMAGE_AND_JSON,
76
- annotations: { title: "Natal Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
76
+ annotations: { title: "Natal Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
77
77
  handler: async (args) => {
78
78
  validateRequired(args, ["datetime", "latitude", "longitude"]);
79
79
  assertZonedDatetime("datetime", args.datetime, args.timezone);
@@ -60,7 +60,7 @@ registerTool({
60
60
  additionalProperties: false,
61
61
  },
62
62
  outputSchema: OUTPUT_SCHEMA_IMAGE_AND_JSON,
63
- annotations: { title: "Progressed Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
63
+ annotations: { title: "Progressed Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
64
64
  handler: async (args) => {
65
65
  validateRequired(args, ["birth_datetime", "birth_latitude", "birth_longitude", "target_datetime"]);
66
66
  assertZonedDatetime("birth_datetime", args.birth_datetime, args.timezone);
@@ -51,7 +51,7 @@ registerTool({
51
51
  additionalProperties: false,
52
52
  },
53
53
  outputSchema: OUTPUT_SCHEMA_JSON,
54
- annotations: { title: "Relocation Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
54
+ annotations: { title: "Relocation Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
55
55
  handler: async (args) => {
56
56
  validateRequired(args, ["natal_datetime", "natal_latitude", "natal_longitude", "relocation_latitude", "relocation_longitude"]);
57
57
  assertZonedDatetime("natal_datetime", args.natal_datetime, args.timezone);