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