@openephemeris/mcp-server 3.13.9 → 3.13.10

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 (61) hide show
  1. package/README.md +2 -2
  2. package/config/dev-allowlist.json +28 -4
  3. package/dist/index.js +45 -0
  4. package/dist/oauth/dcr.d.ts +11 -0
  5. package/dist/oauth/dcr.js +49 -0
  6. package/dist/oauth/discovery.d.ts +11 -0
  7. package/dist/oauth/discovery.js +40 -0
  8. package/dist/oauth/pkce.d.ts +18 -0
  9. package/dist/oauth/pkce.js +34 -0
  10. package/dist/oauth/rate-limit.d.ts +20 -0
  11. package/dist/oauth/rate-limit.js +65 -0
  12. package/dist/oauth/store.d.ts +68 -0
  13. package/dist/oauth/store.js +247 -0
  14. package/dist/oauth/supabase-jwt.d.ts +39 -0
  15. package/dist/oauth/supabase-jwt.js +194 -0
  16. package/dist/oauth/token.d.ts +21 -0
  17. package/dist/oauth/token.js +213 -0
  18. package/dist/prompts.js +171 -159
  19. package/dist/server-sse.js +134 -36
  20. package/dist/tools/apps/bazi-app.d.ts +23 -0
  21. package/dist/tools/apps/bazi-app.js +224 -0
  22. package/dist/tools/apps/bi-wheel-app.d.ts +14 -7
  23. package/dist/tools/apps/bi-wheel-app.js +293 -108
  24. package/dist/tools/apps/bodygraph-app.js +38 -3
  25. package/dist/tools/apps/moon-phase-app.d.ts +19 -0
  26. package/dist/tools/apps/moon-phase-app.js +190 -0
  27. package/dist/tools/apps/transit-timeline-app.d.ts +20 -0
  28. package/dist/tools/apps/transit-timeline-app.js +243 -0
  29. package/dist/tools/apps/vedic-chart-app.d.ts +17 -0
  30. package/dist/tools/apps/vedic-chart-app.js +226 -0
  31. package/dist/tools/index.js +4 -0
  32. package/dist/tools/specialized/acg.js +2 -2
  33. package/dist/tools/specialized/bazi.js +367 -44
  34. package/dist/tools/specialized/bi_wheel.js +1 -1
  35. package/dist/tools/specialized/chart_wheel.js +1 -1
  36. package/dist/tools/specialized/comparative.js +4 -4
  37. package/dist/tools/specialized/eclipse.js +1 -1
  38. package/dist/tools/specialized/electional.js +4 -4
  39. package/dist/tools/specialized/ephemeris_core.js +2 -2
  40. package/dist/tools/specialized/ephemeris_extended.js +8 -8
  41. package/dist/tools/specialized/hd_bodygraph.js +1 -1
  42. package/dist/tools/specialized/hd_cycles.js +2 -2
  43. package/dist/tools/specialized/hd_group.js +2 -2
  44. package/dist/tools/specialized/human_design.js +1 -1
  45. package/dist/tools/specialized/moon.js +2 -2
  46. package/dist/tools/specialized/natal.js +1 -1
  47. package/dist/tools/specialized/progressed.js +1 -1
  48. package/dist/tools/specialized/relocation.js +1 -1
  49. package/dist/tools/specialized/returns.js +3 -3
  50. package/dist/tools/specialized/synastry.js +1 -1
  51. package/dist/tools/specialized/transits.js +1 -1
  52. package/dist/tools/specialized/vedic.js +1 -1
  53. package/dist/tools/specialized/venus_star_points.js +6 -6
  54. package/dist/ui/bazi.html +213 -0
  55. package/dist/ui/bi-wheel.html +762 -489
  56. package/dist/ui/bodygraph.html +1788 -1649
  57. package/dist/ui/chart-wheel.html +1772 -1593
  58. package/dist/ui/moon-phase.html +6758 -0
  59. package/dist/ui/transit-timeline.html +6835 -0
  60. package/dist/ui/vedic-chart.html +210 -0
  61. package/package.json +2 -2
@@ -1,68 +1,391 @@
1
1
  import { registerTool } from "../index.js";
2
2
  import { getActiveClient } from "../../backend/client.js";
3
+ function parseBaziArgs(args) {
4
+ let { year, month, day, hour } = args;
5
+ if (args.datetime && (!year || !month || !day)) {
6
+ const dt = new Date(args.datetime);
7
+ if (!isNaN(dt.getTime())) {
8
+ year = dt.getUTCFullYear();
9
+ month = dt.getUTCMonth() + 1;
10
+ day = dt.getUTCDate();
11
+ if (hour == null)
12
+ hour = dt.getUTCHours();
13
+ }
14
+ }
15
+ if (!year || !month || !day) {
16
+ throw new Error("Provide year/month/day fields, or a datetime ISO string. " +
17
+ "Example: year=1987, month=7, day=15 OR datetime='1987-07-15T14:00:00'");
18
+ }
19
+ const out = { year, month, day };
20
+ if (hour != null)
21
+ out.hour = hour;
22
+ return out;
23
+ }
24
+ // Shared datetime input schema fragment — used across all BaZiRequest tools.
25
+ const DATETIME_PROPERTIES = {
26
+ year: {
27
+ type: "integer",
28
+ description: "Gregorian birth year, e.g. 1987.",
29
+ },
30
+ month: {
31
+ type: "integer",
32
+ description: "Birth month (1–12).",
33
+ },
34
+ day: {
35
+ type: "integer",
36
+ description: "Birth day of month (1–31).",
37
+ },
38
+ hour: {
39
+ type: "integer",
40
+ description: "Birth hour (0–23). Optional, defaults to 12 (noon). " +
41
+ "Chinese shí hours are 2-hour blocks — precision within a 2-hour window is sufficient.",
42
+ },
43
+ datetime: {
44
+ type: "string",
45
+ description: "Alternative to year/month/day: ISO 8601 datetime (e.g. '1987-07-15T14:00:00'). " +
46
+ "year/month/day/hour are extracted automatically. Use this OR the individual fields.",
47
+ },
48
+ };
49
+ // Shared visual input schema fragment — mirrors natal.ts pattern.
50
+ const VISUAL_PROPERTIES = {
51
+ include_visual: {
52
+ type: "boolean",
53
+ description: "When true, the response includes a rendered SVG chart of the Four Pillars. " +
54
+ "Costs an extra 2 credits (total 3 credits). Ideal for delivering a visual alongside text analysis.",
55
+ },
56
+ visual_config: {
57
+ type: "object",
58
+ description: "Optional rendering preferences. Only relevant when include_visual=true.",
59
+ properties: {
60
+ theme: {
61
+ type: "string",
62
+ enum: ["light", "dark", "mono"],
63
+ description: "Chart color theme. Defaults to 'light'.",
64
+ },
65
+ size: {
66
+ type: "integer",
67
+ description: "Chart width in pixels (400–1600). Defaults to 800.",
68
+ },
69
+ },
70
+ additionalProperties: false,
71
+ },
72
+ };
73
+ // buildVisualBody appends include_visual and visual_config to a request body
74
+ // when the caller passes include_visual=true.
75
+ function applyVisualConfig(body, args) {
76
+ if (args.include_visual) {
77
+ body.include_visual = true;
78
+ body.visual_config = {
79
+ format: "svg", // always SVG for MCP — no resvg dependency in prod
80
+ theme: args.visual_config?.theme ?? "light",
81
+ size: args.visual_config?.size ?? 800,
82
+ };
83
+ }
84
+ }
85
+ // ─────────────────────────────────────────────────────────────────────────────
86
+ // chinese_bazi — base Four Pillars chart
87
+ // ─────────────────────────────────────────────────────────────────────────────
3
88
  registerTool({
4
89
  name: "chinese_bazi",
5
- description: "Calculate a Chinese Ba Zi (Four Pillars of Destiny) chart. Returns the Year, Month, Day, " +
6
- "and Hour pillars, each containing a Heavenly Stem and Earthly Branch pair. Also includes " +
7
- "the Day Master element, Wu Xing element breakdown, and basic interpretation context.\n\n" +
8
- "CREDIT COST: 1 credit per call.\n\n" +
9
- "You can provide either a datetime string (which will be decomposed automatically) " +
10
- "or explicit year/month/day/hour values.\n\n" +
11
- "EXAMPLE: Ba Zi for someone born July 15, 1987 at 2:00 PM:\n" +
90
+ description: "Calculate a Chinese BaZi (四柱命盘 Four Pillars of Destiny) chart. Returns the Year, Month, Day, " +
91
+ "and Hour pillars, each with a Heavenly Stem (天干) and Earthly Branch (地支) pair. " +
92
+ "Includes the Day Master element and basic metadata.\n\n" +
93
+ "CREDIT COST: 1 credit (3 credits when include_visual=true).\n\n" +
94
+ "Set include_visual=true to receive a rendered SVG chart alongside the text data.\n\n" +
95
+ "For deep analysis, follow up with:\n" +
96
+ " bazi_ten_gods() — Ten Gods (十神) per pillar including hidden stems\n" +
97
+ " • bazi_element_balance() — Weighted Wu Xing (五行) element scores + Yong Shen\n" +
98
+ " • bazi_luck_pillars() — 8 Da Yun 10-year luck cycles\n\n" +
99
+ "EXAMPLE: BaZi chart for someone born July 15, 1987 at 2 PM:\n" +
100
+ " year=1987, month=7, day=15, hour=14",
101
+ inputSchema: {
102
+ type: "object",
103
+ properties: {
104
+ ...DATETIME_PROPERTIES,
105
+ ...VISUAL_PROPERTIES,
106
+ },
107
+ additionalProperties: false,
108
+ },
109
+ annotations: { title: "BaZi Four Pillars Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
110
+ handler: async (args) => {
111
+ const body = { ...parseBaziArgs(args) };
112
+ applyVisualConfig(body, args);
113
+ return await getActiveClient().request("POST", "/chinese/bazi", { data: body });
114
+ },
115
+ });
116
+ // ─────────────────────────────────────────────────────────────────────────────
117
+ // bazi_ten_gods — Ten Gods (十神) analysis
118
+ // ─────────────────────────────────────────────────────────────────────────────
119
+ registerTool({
120
+ name: "bazi_ten_gods",
121
+ description: "Calculate the Ten Gods (十神 Shí Shén) for a BaZi chart. Each of the 10 gods describes " +
122
+ "the relationship between the Day Master (日主, the person's core identity element) and " +
123
+ "every other Heavenly Stem in the chart — including visible stems and the hidden stems " +
124
+ "stored within each Earthly Branch.\n\n" +
125
+ "The 10 gods and their categories:\n" +
126
+ " COMPANION: Friend (比肩), Rob Wealth (劫财)\n" +
127
+ " OUTPUT: Eating God (食神), Hurting Officer (伤官)\n" +
128
+ " WEALTH: Indirect Wealth (偏财), Direct Wealth (正财)\n" +
129
+ " POWER: Seven Killings (七杀), Direct Officer (正官)\n" +
130
+ " RESOURCE: Indirect Resource (偏印), Direct Resource (正印)\n\n" +
131
+ "The Day Pillar itself has no Ten God (it IS the Day Master). " +
132
+ "Hidden stems in the Day Branch still receive Ten God labels.\n\n" +
133
+ "CREDIT COST: 1 credit (3 credits when include_visual=true).\n\n" +
134
+ "Set include_visual=true to receive an SVG Four Pillars chart alongside the Ten Gods data.\n\n" +
135
+ "EXAMPLE: Ten Gods for someone born July 15, 1987 at 2 PM:\n" +
12
136
  " year=1987, month=7, day=15, hour=14",
137
+ inputSchema: {
138
+ type: "object",
139
+ properties: {
140
+ ...DATETIME_PROPERTIES,
141
+ ...VISUAL_PROPERTIES,
142
+ },
143
+ additionalProperties: false,
144
+ },
145
+ annotations: { title: "BaZi Day Master Analysis", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
146
+ handler: async (args) => {
147
+ const body = { ...parseBaziArgs(args) };
148
+ applyVisualConfig(body, args);
149
+ return await getActiveClient().request("POST", "/chinese/bazi/ten-gods", { data: body });
150
+ },
151
+ });
152
+ // ─────────────────────────────────────────────────────────────────────────────
153
+ // bazi_element_balance — Wu Xing (五行) weighted element analysis
154
+ // ─────────────────────────────────────────────────────────────────────────────
155
+ registerTool({
156
+ name: "bazi_element_balance",
157
+ description: "Calculate the weighted Wu Xing (五行 Five Elements) distribution for a BaZi chart. " +
158
+ "Returns percentage scores for Wood, Fire, Earth, Metal, and Water across all chart positions.\n\n" +
159
+ "Scoring is weighted — hidden stems inside Earthly Branches count toward element totals:\n" +
160
+ " • Visible Heavenly Stems: 1.0 weight each (4 positions)\n" +
161
+ " • Hidden Stem main (本气): 1.0 weight\n" +
162
+ " • Hidden Stem secondary (中气): 0.6 weight\n" +
163
+ " • Hidden Stem residual (余气): 0.3 weight\n\n" +
164
+ "Also returns:\n" +
165
+ " • Day Master strength: 'strong' (旺, ≥50% own+resource elements) or 'weak' (弱)\n" +
166
+ " • Yong Shen (用神): the favorable element — what the chart needs most\n\n" +
167
+ "CREDIT COST: 1 credit (3 credits when include_visual=true).\n\n" +
168
+ "Set include_visual=true to receive an SVG Four Pillars chart alongside the element data.\n\n" +
169
+ "EXAMPLE: Element balance for 1987-07-15 at 14:00:\n" +
170
+ " year=1987, month=7, day=15, hour=14",
171
+ inputSchema: {
172
+ type: "object",
173
+ properties: {
174
+ ...DATETIME_PROPERTIES,
175
+ ...VISUAL_PROPERTIES,
176
+ },
177
+ additionalProperties: false,
178
+ },
179
+ annotations: { title: "BaZi Clash & Combination", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
180
+ handler: async (args) => {
181
+ const body = { ...parseBaziArgs(args) };
182
+ applyVisualConfig(body, args);
183
+ return await getActiveClient().request("POST", "/chinese/bazi/element-balance", { data: body });
184
+ },
185
+ });
186
+ // ─────────────────────────────────────────────────────────────────────────────
187
+ // bazi_luck_pillars — Da Yun (大运) 10-year luck cycles
188
+ // ─────────────────────────────────────────────────────────────────────────────
189
+ registerTool({
190
+ name: "bazi_luck_pillars",
191
+ description: "Calculate the 8 Da Yun (大运) 10-year luck pillar cycles for a BaZi chart. " +
192
+ "Luck pillars sequence through the sexagenary cycle starting from a person's " +
193
+ "'starting age' (起运岁数), calculated by counting days from birth to the nearest " +
194
+ "solar term boundary (Jié 节). Each 3 days = 1 year of starting age (Zi Ping rule).\n\n" +
195
+ "Direction (forward/backward) depends on gender and year polarity:\n" +
196
+ " • Male in Yang year → forward\n" +
197
+ " • Male in Yin year → backward\n" +
198
+ " • Female in Yang year → backward\n" +
199
+ " • Female in Yin year → forward\n\n" +
200
+ "gender is REQUIRED — the direction of luck pillars is gender-dependent.\n\n" +
201
+ "Returns: starting_age, direction, direction_reason, and 8 pillars each with:\n" +
202
+ " stem, branch, Chinese characters, element, start_age, end_age\n\n" +
203
+ "CREDIT COST: 1 credit per call.\n\n" +
204
+ "EXAMPLE: Luck pillars for a female born July 15, 1987 at 2 PM:\n" +
205
+ " year=1987, month=7, day=15, hour=14, gender='female'",
206
+ inputSchema: {
207
+ type: "object",
208
+ properties: {
209
+ ...DATETIME_PROPERTIES,
210
+ gender: {
211
+ type: "string",
212
+ enum: ["male", "female"],
213
+ description: "Required. Determines the direction of Da Yun progression. " +
214
+ "Male in Yang year / Female in Yin year → forward. " +
215
+ "Male in Yin year / Female in Yang year → backward.",
216
+ },
217
+ },
218
+ required: ["gender"],
219
+ additionalProperties: false,
220
+ },
221
+ annotations: { title: "BaZi Ten Gods", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
222
+ handler: async (args) => {
223
+ if (!args.gender) {
224
+ throw new Error("gender is required for luck pillar calculation. " +
225
+ "Provide 'male' or 'female' — the direction of Da Yun depends on gender and year polarity.");
226
+ }
227
+ const { year, month, day, hour } = parseBaziArgs(args);
228
+ const body = { year, month, day, gender: args.gender };
229
+ if (hour != null)
230
+ body.hour = hour;
231
+ return await getActiveClient().request("POST", "/chinese/bazi/luck-pillars", { data: body });
232
+ },
233
+ });
234
+ // ─────────────────────────────────────────────────────────────────────────────
235
+ // bazi_annual_pillar — sexagenary year pillar + NaYin
236
+ // ─────────────────────────────────────────────────────────────────────────────
237
+ registerTool({
238
+ name: "bazi_annual_pillar",
239
+ description: "Look up the sexagenary pillar for any Gregorian year (1–9999). Returns the Heavenly Stem, " +
240
+ "Earthly Branch, Chinese characters, zodiac animal, element, polarity, and NaYin (纳音) " +
241
+ "poetic resonance image.\n\n" +
242
+ "NaYin maps each pair in the 60-cycle sexagenary sequence to one of 30 elemental images " +
243
+ "(e.g. '海中金 Metal in the Sea', '炉中火 Fire in the Furnace'). It is traditionally " +
244
+ "applied to the Year and Day pillars to reveal deeper elemental character.\n\n" +
245
+ "Use this to:\n" +
246
+ " • Identify the energetic quality of any given year\n" +
247
+ " • Determine a person's birth year pillar for compatibility context\n" +
248
+ " • Find the NaYin element for year or day interpretations\n\n" +
249
+ "CREDIT COST: 1 credit per call.\n\n" +
250
+ "EXAMPLE: Year pillar for 2025:\n" +
251
+ " year=2025",
13
252
  inputSchema: {
14
253
  type: "object",
15
254
  properties: {
16
255
  year: {
17
256
  type: "integer",
18
- description: "Gregorian birth year, e.g. 1987.",
257
+ description: "Gregorian year (1–9999). e.g. 2025.",
19
258
  },
20
- month: {
21
- type: "integer",
22
- description: "Birth month (1-12).",
259
+ },
260
+ required: ["year"],
261
+ additionalProperties: false,
262
+ },
263
+ annotations: { title: "BaZi Luck Pillars", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
264
+ handler: async (args) => {
265
+ if (!args.year) {
266
+ throw new Error("year is required. Provide a Gregorian year (1–9999), e.g. year=2025.");
267
+ }
268
+ return await getActiveClient().request("POST", "/chinese/bazi/annual-pillar", {
269
+ data: { year: args.year },
270
+ });
271
+ },
272
+ });
273
+ // ─────────────────────────────────────────────────────────────────────────────
274
+ // bazi_compatibility — pair chart scoring
275
+ // ─────────────────────────────────────────────────────────────────────────────
276
+ registerTool({
277
+ name: "bazi_compatibility",
278
+ description: "Calculate BaZi compatibility between two people by comparing their Four Pillars charts. " +
279
+ "Returns a score 0–100, an assessment, and a breakdown of the harmony/clash/penalty " +
280
+ "factors found between the two charts.\n\n" +
281
+ "Scoring factors include:\n" +
282
+ " +5 Six Harmony (六合 Liù Hé) — branch pair bonds\n" +
283
+ " +8 Three Harmonies (三合 Sān Hé) — branch trinity formations\n" +
284
+ " -8 Six Clash (六冲 Liù Chōng) — opposing branch pairs\n" +
285
+ " -5 Six Harm (六害 Liù Hài) — branch interference pairs\n" +
286
+ " -6 Three Penalties (三刑 Sān Xíng) — branch penalty formations\n\n" +
287
+ "Assessment grades:\n" +
288
+ " 90–100: excellent | 70–89: good | 50–69: moderate | below 50: challenging\n\n" +
289
+ "CREDIT COST: 2 credits per call.\n\n" +
290
+ "EXAMPLE: Compatibility between two people:\n" +
291
+ " chart_a_year=1987, chart_a_month=7, chart_a_day=15, chart_a_hour=14\n" +
292
+ " chart_b_year=1990, chart_b_month=3, chart_b_day=22, chart_b_hour=8",
293
+ inputSchema: {
294
+ type: "object",
295
+ properties: {
296
+ // Chart A
297
+ chart_a_year: { type: "integer", description: "Chart A birth year." },
298
+ chart_a_month: { type: "integer", description: "Chart A birth month (1–12)." },
299
+ chart_a_day: { type: "integer", description: "Chart A birth day (1–31)." },
300
+ chart_a_hour: { type: "integer", description: "Chart A birth hour (0–23). Optional." },
301
+ chart_a_datetime: {
302
+ type: "string",
303
+ description: "Chart A alternative: ISO 8601 datetime. Extracts year/month/day/hour automatically.",
23
304
  },
24
- day: {
25
- type: "integer",
26
- description: "Birth day of month (1-31).",
305
+ // Chart B
306
+ chart_b_year: { type: "integer", description: "Chart B birth year." },
307
+ chart_b_month: { type: "integer", description: "Chart B birth month (1–12)." },
308
+ chart_b_day: { type: "integer", description: "Chart B birth day (1–31)." },
309
+ chart_b_hour: { type: "integer", description: "Chart B birth hour (0–23). Optional." },
310
+ chart_b_datetime: {
311
+ type: "string",
312
+ description: "Chart B alternative: ISO 8601 datetime. Extracts year/month/day/hour automatically.",
27
313
  },
28
- hour: {
29
- type: "integer",
30
- description: "Birth hour (0-23). Optional, defaults to 12 (noon). " +
31
- "Chinese hours (shí) are 2-hour blocks, so precision within a 2-hour window is sufficient.",
314
+ },
315
+ additionalProperties: false,
316
+ },
317
+ annotations: { title: "BaZi Annual Pillars", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
318
+ handler: async (args) => {
319
+ const chartAComponents = parseBaziArgs({
320
+ year: args.chart_a_year,
321
+ month: args.chart_a_month,
322
+ day: args.chart_a_day,
323
+ hour: args.chart_a_hour,
324
+ datetime: args.chart_a_datetime,
325
+ });
326
+ const chartBComponents = parseBaziArgs({
327
+ year: args.chart_b_year,
328
+ month: args.chart_b_month,
329
+ day: args.chart_b_day,
330
+ hour: args.chart_b_hour,
331
+ datetime: args.chart_b_datetime,
332
+ });
333
+ return await getActiveClient().request("POST", "/chinese/bazi/compatibility", {
334
+ data: {
335
+ chart_a: chartAComponents,
336
+ chart_b: chartBComponents,
32
337
  },
33
- datetime: {
338
+ });
339
+ },
340
+ });
341
+ // ─────────────────────────────────────────────────────────────────────────────
342
+ // bazi_chart — dedicated Four Pillars SVG chart visual
343
+ // ─────────────────────────────────────────────────────────────────────────────
344
+ registerTool({
345
+ name: "bazi_chart",
346
+ description: "Render a self-contained SVG chart of a BaZi Four Pillars chart. " +
347
+ "Returns a production-quality visualization showing:\n" +
348
+ " • All four pillars (Year, Month, Day, Hour) with Heavenly Stems and Earthly Branches\n" +
349
+ " • Ten Gods (十神) labels for each non-Day pillar\n" +
350
+ " • Wu Xing (五行) element balance bar chart\n" +
351
+ " • Day Master identity with element and polarity\n\n" +
352
+ "The chart is rendered in the Open Ephemeris design system (OKLCH color tokens) " +
353
+ "and is fully self-contained — suitable for embedding or display without dependencies.\n\n" +
354
+ "Theme options: 'light' (default), 'dark', 'mono'.\n\n" +
355
+ "CREDIT COST: 3 credits per call (1 base + 2 visual render).\n\n" +
356
+ "Use this tool when the user asks to 'show', 'visualize', 'draw', or 'render' " +
357
+ "a BaZi / Four Pillars chart. For text-only data, use chinese_bazi() instead.\n\n" +
358
+ "EXAMPLE: Render a chart for someone born July 15, 1987 at 2 PM:\n" +
359
+ " year=1987, month=7, day=15, hour=14",
360
+ inputSchema: {
361
+ type: "object",
362
+ properties: {
363
+ ...DATETIME_PROPERTIES,
364
+ theme: {
34
365
  type: "string",
35
- description: "Alternative: ISO 8601 datetime (e.g. '1987-07-15T14:00:00'). " +
36
- "If provided, year/month/day/hour are extracted automatically. " +
37
- "Use this OR the individual year/month/day fields, not both.",
366
+ enum: ["light", "dark", "mono"],
367
+ description: "Chart color theme. Defaults to 'light'.",
368
+ },
369
+ size: {
370
+ type: "integer",
371
+ description: "Chart width in pixels (400–1600). Defaults to 800.",
38
372
  },
39
373
  },
40
374
  additionalProperties: false,
41
375
  },
42
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
376
+ annotations: { title: "BaZi Compatibility", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
43
377
  handler: async (args) => {
44
- let year = args.year;
45
- let month = args.month;
46
- let day = args.day;
47
- let hour = args.hour;
48
- // If datetime provided, extract components
49
- if (args.datetime && (!year || !month || !day)) {
50
- const dt = new Date(args.datetime);
51
- if (!isNaN(dt.getTime())) {
52
- year = dt.getUTCFullYear();
53
- month = dt.getUTCMonth() + 1;
54
- day = dt.getUTCDate();
55
- if (hour == null)
56
- hour = dt.getUTCHours();
57
- }
58
- }
59
- if (!year || !month || !day) {
60
- throw new Error("Either provide year/month/day fields, or a datetime string. " +
61
- "Example: year=1987, month=7, day=15 OR datetime='1987-07-15T14:00:00'");
62
- }
63
- const body = { year, month, day };
378
+ const { year, month, day, hour } = parseBaziArgs(args);
379
+ const body = {
380
+ year, month, day,
381
+ visual_config: {
382
+ format: "svg",
383
+ theme: args.theme ?? "light",
384
+ size: args.size ?? 800,
385
+ },
386
+ };
64
387
  if (hour != null)
65
388
  body.hour = hour;
66
- return await getActiveClient().request("POST", "/chinese/bazi", { data: body });
389
+ return await getActiveClient().request("POST", "/chinese/bazi/chart", { data: body });
67
390
  },
68
391
  });
@@ -38,7 +38,7 @@ registerTool({
38
38
  required: ["datetime_a", "latitude_a", "longitude_a", "datetime_b", "latitude_b", "longitude_b"],
39
39
  additionalProperties: false,
40
40
  },
41
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
41
+ annotations: { title: "Bi-Wheel Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
42
42
  handler: async (args) => {
43
43
  validateRequired(args, ["datetime_a", "latitude_a", "longitude_a", "datetime_b", "latitude_b", "longitude_b"]);
44
44
  const body = {
@@ -46,7 +46,7 @@ registerTool({
46
46
  required: ["datetime", "latitude", "longitude"],
47
47
  additionalProperties: false,
48
48
  },
49
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
49
+ annotations: { title: "Chart Wheel SVG", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
50
50
  handler: async (args) => {
51
51
  validateRequired(args, ["datetime", "latitude", "longitude"]);
52
52
  const body = {
@@ -42,7 +42,7 @@ registerTool({
42
42
  ],
43
43
  additionalProperties: false,
44
44
  },
45
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
45
+ annotations: { title: "Davison Relationship Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
46
46
  handler: async (args) => {
47
47
  validateRequired(args, [
48
48
  "person_a_datetime", "person_a_latitude", "person_a_longitude",
@@ -87,7 +87,7 @@ registerTool({
87
87
  ],
88
88
  additionalProperties: false,
89
89
  },
90
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
90
+ annotations: { title: "Midpoint Composite Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
91
91
  handler: async (args) => {
92
92
  validateRequired(args, [
93
93
  "person_a_datetime", "person_a_latitude", "person_a_longitude",
@@ -132,7 +132,7 @@ registerTool({
132
132
  ],
133
133
  additionalProperties: false,
134
134
  },
135
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
135
+ annotations: { title: "Planet Compatibility Score", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
136
136
  handler: async (args) => {
137
137
  validateRequired(args, [
138
138
  "person_a_datetime", "person_a_latitude", "person_a_longitude",
@@ -174,7 +174,7 @@ registerTool({
174
174
  required: ["natal_datetime", "natal_latitude", "natal_longitude"],
175
175
  additionalProperties: false,
176
176
  },
177
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
177
+ annotations: { title: "Composite Chart Aspects", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
178
178
  handler: async (args) => {
179
179
  validateRequired(args, ["natal_datetime", "natal_latitude", "natal_longitude"]);
180
180
  // Backend expects: { subject: {...}, transit_datetime?: {...} }
@@ -40,7 +40,7 @@ registerTool({
40
40
  required: ["eclipse_type"],
41
41
  additionalProperties: false,
42
42
  },
43
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
43
+ annotations: { title: "Eclipse Finder", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
44
44
  handler: async (args) => {
45
45
  validateRequired(args, ["eclipse_type"]);
46
46
  validateCoordinates(args, "latitude", "longitude");
@@ -56,7 +56,7 @@ registerTool({
56
56
  required: ["start_date", "end_date", "latitude", "longitude"],
57
57
  additionalProperties: false,
58
58
  },
59
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
59
+ annotations: { title: "Electional Timing Window", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
60
60
  handler: async (args) => {
61
61
  validateRequired(args, ["start_date", "end_date", "latitude", "longitude"]);
62
62
  const query = {
@@ -106,7 +106,7 @@ registerTool({
106
106
  required: [],
107
107
  additionalProperties: false,
108
108
  },
109
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
109
+ annotations: { title: "Moment Quality Analysis", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
110
110
  handler: async (args) => {
111
111
  const query = {};
112
112
  if (args.date)
@@ -157,7 +157,7 @@ registerTool({
157
157
  required: [],
158
158
  additionalProperties: false,
159
159
  },
160
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
160
+ annotations: { title: "Retrograde Station Tracker", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
161
161
  handler: async (args) => {
162
162
  const query = {};
163
163
  // Map human-readable planet names to IDs if needed
@@ -216,7 +216,7 @@ registerTool({
216
216
  required: [],
217
217
  additionalProperties: false,
218
218
  },
219
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
219
+ annotations: { title: "Active Aspects Search", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
220
220
  handler: async (args) => {
221
221
  const query = {};
222
222
  if (args.date)
@@ -34,7 +34,7 @@ registerTool({
34
34
  required: ["planet_id", "datetime"],
35
35
  additionalProperties: false,
36
36
  },
37
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
37
+ annotations: { title: "Planet Position", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
38
38
  handler: async (args) => {
39
39
  validateRequired(args, ["planet_id", "datetime"]);
40
40
  const body = {
@@ -81,7 +81,7 @@ registerTool({
81
81
  required: ["datetime", "latitude", "longitude"],
82
82
  additionalProperties: false,
83
83
  },
84
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
84
+ annotations: { title: "House Cusps", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
85
85
  handler: async (args) => {
86
86
  validateRequired(args, ["datetime", "latitude", "longitude"]);
87
87
  const body = {
@@ -42,7 +42,7 @@ registerTool({
42
42
  required: ["subjects"],
43
43
  additionalProperties: false,
44
44
  },
45
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
45
+ annotations: { title: "Natal Batch Charts", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
46
46
  handler: async (args) => {
47
47
  validateRequired(args, ["subjects"]);
48
48
  const items = args.subjects.map((s) => ({
@@ -65,7 +65,7 @@ registerTool({
65
65
  required: ["datetime"],
66
66
  additionalProperties: false,
67
67
  },
68
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
68
+ annotations: { title: "Essential Dignities", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
69
69
  handler: async (args) => {
70
70
  validateRequired(args, ["datetime"]);
71
71
  return await getActiveClient().post("/ephemeris/dignities", {
@@ -92,7 +92,7 @@ registerTool({
92
92
  required: ["datetime"],
93
93
  additionalProperties: false,
94
94
  },
95
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
95
+ annotations: { title: "Retrograde Status", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
96
96
  handler: async (args) => {
97
97
  validateRequired(args, ["datetime"]);
98
98
  const client = getActiveClient();
@@ -141,7 +141,7 @@ registerTool({
141
141
  required: ["datetime", "latitude", "longitude"],
142
142
  additionalProperties: false,
143
143
  },
144
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
144
+ annotations: { title: "Midpoints", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
145
145
  handler: async (args) => {
146
146
  validateRequired(args, ["datetime", "latitude", "longitude"]);
147
147
  return await getActiveClient().post("/ephemeris/midpoints", {
@@ -171,7 +171,7 @@ registerTool({
171
171
  required: ["datetime"],
172
172
  additionalProperties: false,
173
173
  },
174
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
174
+ annotations: { title: "Fixed Stars", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
175
175
  handler: async (args) => {
176
176
  validateRequired(args, ["datetime"]);
177
177
  const body = {
@@ -231,7 +231,7 @@ registerTool({
231
231
  required: ["datetime", "latitude", "longitude"],
232
232
  additionalProperties: false,
233
233
  },
234
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
234
+ annotations: { title: "Hermetic Lots", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
235
235
  handler: async (args) => {
236
236
  validateRequired(args, ["datetime", "latitude", "longitude"]);
237
237
  return await getActiveClient().post("/ephemeris/hermetic-lots", {
@@ -257,7 +257,7 @@ registerTool({
257
257
  required: ["datetime", "latitude", "longitude"],
258
258
  additionalProperties: false,
259
259
  },
260
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
260
+ annotations: { title: "Angles & Points", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
261
261
  handler: async (args) => {
262
262
  validateRequired(args, ["datetime", "latitude", "longitude"]);
263
263
  return await getActiveClient().post("/ephemeris/angles-points", {
@@ -285,7 +285,7 @@ registerTool({
285
285
  required: ["longitude_1", "longitude_2"],
286
286
  additionalProperties: false,
287
287
  },
288
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
288
+ annotations: { title: "Aspect Check", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
289
289
  handler: async (args) => {
290
290
  validateRequired(args, ["longitude_1", "longitude_2"]);
291
291
  // Backend field names are longitude1 / longitude2 (no underscore)
@@ -39,7 +39,7 @@ registerTool({
39
39
  required: ["datetime"],
40
40
  additionalProperties: false,
41
41
  },
42
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
42
+ annotations: { title: "Human Design Bodygraph", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
43
43
  handler: async (args) => {
44
44
  validateRequired(args, ["datetime"]);
45
45
  const body = {