@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
@@ -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 = {
@@ -46,7 +47,8 @@ registerTool({
46
47
  required: ["datetime", "latitude", "longitude"],
47
48
  additionalProperties: false,
48
49
  },
49
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
50
+ outputSchema: OUTPUT_SCHEMA_IMAGE,
51
+ annotations: { title: "Chart Wheel SVG", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
50
52
  handler: async (args) => {
51
53
  validateRequired(args, ["datetime", "latitude", "longitude"]);
52
54
  const body = {
@@ -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
  function buildSubject(name, datetime, lat, lon, timezone) {
4
5
  const subj = {
5
6
  name,
@@ -42,7 +43,8 @@ registerTool({
42
43
  ],
43
44
  additionalProperties: false,
44
45
  },
45
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
46
+ outputSchema: OUTPUT_SCHEMA_JSON,
47
+ annotations: { title: "Davison Relationship Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
46
48
  handler: async (args) => {
47
49
  validateRequired(args, [
48
50
  "person_a_datetime", "person_a_latitude", "person_a_longitude",
@@ -87,7 +89,8 @@ registerTool({
87
89
  ],
88
90
  additionalProperties: false,
89
91
  },
90
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
92
+ outputSchema: OUTPUT_SCHEMA_JSON,
93
+ annotations: { title: "Midpoint Composite Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
91
94
  handler: async (args) => {
92
95
  validateRequired(args, [
93
96
  "person_a_datetime", "person_a_latitude", "person_a_longitude",
@@ -132,7 +135,8 @@ registerTool({
132
135
  ],
133
136
  additionalProperties: false,
134
137
  },
135
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
138
+ outputSchema: OUTPUT_SCHEMA_JSON,
139
+ annotations: { title: "Planet Compatibility Score", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
136
140
  handler: async (args) => {
137
141
  validateRequired(args, [
138
142
  "person_a_datetime", "person_a_latitude", "person_a_longitude",
@@ -174,7 +178,8 @@ registerTool({
174
178
  required: ["natal_datetime", "natal_latitude", "natal_longitude"],
175
179
  additionalProperties: false,
176
180
  },
177
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
181
+ outputSchema: OUTPUT_SCHEMA_JSON,
182
+ annotations: { title: "Composite Chart Aspects", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
178
183
  handler: async (args) => {
179
184
  validateRequired(args, ["natal_datetime", "natal_latitude", "natal_longitude"]);
180
185
  // Backend expects: { subject: {...}, transit_datetime?: {...} }
@@ -1,5 +1,6 @@
1
1
  import { registerTool, validateRequired, validateCoordinates } from "../index.js";
2
2
  import { getActiveClient } from "../../backend/client.js";
3
+ import { OUTPUT_SCHEMA_JSON } from "../output-schemas.js";
3
4
  registerTool({
4
5
  name: "ephemeris_next_eclipse",
5
6
  description: "Find the next solar or lunar eclipse. " +
@@ -40,7 +41,8 @@ registerTool({
40
41
  required: ["eclipse_type"],
41
42
  additionalProperties: false,
42
43
  },
43
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
44
+ outputSchema: OUTPUT_SCHEMA_JSON,
45
+ annotations: { title: "Eclipse Finder", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
44
46
  handler: async (args) => {
45
47
  validateRequired(args, ["eclipse_type"]);
46
48
  validateCoordinates(args, "latitude", "longitude");
@@ -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
  // All electional endpoints are GET endpoints with query params.
4
5
  // GET /electional/find-window — already existed, keeping it
5
6
  registerTool({
@@ -56,7 +57,8 @@ registerTool({
56
57
  required: ["start_date", "end_date", "latitude", "longitude"],
57
58
  additionalProperties: false,
58
59
  },
59
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
60
+ outputSchema: OUTPUT_SCHEMA_JSON,
61
+ annotations: { title: "Electional Timing Window", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
60
62
  handler: async (args) => {
61
63
  validateRequired(args, ["start_date", "end_date", "latitude", "longitude"]);
62
64
  const query = {
@@ -106,7 +108,8 @@ registerTool({
106
108
  required: [],
107
109
  additionalProperties: false,
108
110
  },
109
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
111
+ outputSchema: OUTPUT_SCHEMA_JSON,
112
+ annotations: { title: "Moment Quality Analysis", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
110
113
  handler: async (args) => {
111
114
  const query = {};
112
115
  if (args.date)
@@ -157,7 +160,8 @@ registerTool({
157
160
  required: [],
158
161
  additionalProperties: false,
159
162
  },
160
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
163
+ outputSchema: OUTPUT_SCHEMA_JSON,
164
+ annotations: { title: "Retrograde Station Tracker", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
161
165
  handler: async (args) => {
162
166
  const query = {};
163
167
  // Map human-readable planet names to IDs if needed
@@ -216,7 +220,8 @@ registerTool({
216
220
  required: [],
217
221
  additionalProperties: false,
218
222
  },
219
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
223
+ outputSchema: OUTPUT_SCHEMA_JSON,
224
+ annotations: { title: "Active Aspects Search", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
220
225
  handler: async (args) => {
221
226
  const query = {};
222
227
  if (args.date)
@@ -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
  // POST /ephemeris/planet-position — OE-016
4
5
  registerTool({
5
6
  name: "ephemeris_planet_position",
@@ -34,7 +35,8 @@ registerTool({
34
35
  required: ["planet_id", "datetime"],
35
36
  additionalProperties: false,
36
37
  },
37
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
38
+ outputSchema: OUTPUT_SCHEMA_JSON,
39
+ annotations: { title: "Planet Position", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
38
40
  handler: async (args) => {
39
41
  validateRequired(args, ["planet_id", "datetime"]);
40
42
  const body = {
@@ -81,7 +83,8 @@ registerTool({
81
83
  required: ["datetime", "latitude", "longitude"],
82
84
  additionalProperties: false,
83
85
  },
84
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
86
+ outputSchema: OUTPUT_SCHEMA_JSON,
87
+ annotations: { title: "House Cusps", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
85
88
  handler: async (args) => {
86
89
  validateRequired(args, ["datetime", "latitude", "longitude"]);
87
90
  const body = {
@@ -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
  function buildSubject(name, datetime, lat, lon) {
4
5
  return {
5
6
  name,
@@ -42,7 +43,8 @@ registerTool({
42
43
  required: ["subjects"],
43
44
  additionalProperties: false,
44
45
  },
45
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
46
+ outputSchema: OUTPUT_SCHEMA_JSON,
47
+ annotations: { title: "Natal Batch Charts", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
46
48
  handler: async (args) => {
47
49
  validateRequired(args, ["subjects"]);
48
50
  const items = args.subjects.map((s) => ({
@@ -65,7 +67,8 @@ registerTool({
65
67
  required: ["datetime"],
66
68
  additionalProperties: false,
67
69
  },
68
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
70
+ outputSchema: OUTPUT_SCHEMA_JSON,
71
+ annotations: { title: "Essential Dignities", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
69
72
  handler: async (args) => {
70
73
  validateRequired(args, ["datetime"]);
71
74
  return await getActiveClient().post("/ephemeris/dignities", {
@@ -92,7 +95,8 @@ registerTool({
92
95
  required: ["datetime"],
93
96
  additionalProperties: false,
94
97
  },
95
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
98
+ outputSchema: OUTPUT_SCHEMA_JSON,
99
+ annotations: { title: "Retrograde Status", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
96
100
  handler: async (args) => {
97
101
  validateRequired(args, ["datetime"]);
98
102
  const client = getActiveClient();
@@ -141,7 +145,8 @@ registerTool({
141
145
  required: ["datetime", "latitude", "longitude"],
142
146
  additionalProperties: false,
143
147
  },
144
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
148
+ outputSchema: OUTPUT_SCHEMA_JSON,
149
+ annotations: { title: "Midpoints", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
145
150
  handler: async (args) => {
146
151
  validateRequired(args, ["datetime", "latitude", "longitude"]);
147
152
  return await getActiveClient().post("/ephemeris/midpoints", {
@@ -171,7 +176,8 @@ registerTool({
171
176
  required: ["datetime"],
172
177
  additionalProperties: false,
173
178
  },
174
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
179
+ outputSchema: OUTPUT_SCHEMA_JSON,
180
+ annotations: { title: "Fixed Stars", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
175
181
  handler: async (args) => {
176
182
  validateRequired(args, ["datetime"]);
177
183
  const body = {
@@ -231,7 +237,8 @@ registerTool({
231
237
  required: ["datetime", "latitude", "longitude"],
232
238
  additionalProperties: false,
233
239
  },
234
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
240
+ outputSchema: OUTPUT_SCHEMA_JSON,
241
+ annotations: { title: "Hermetic Lots", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
235
242
  handler: async (args) => {
236
243
  validateRequired(args, ["datetime", "latitude", "longitude"]);
237
244
  return await getActiveClient().post("/ephemeris/hermetic-lots", {
@@ -257,7 +264,8 @@ registerTool({
257
264
  required: ["datetime", "latitude", "longitude"],
258
265
  additionalProperties: false,
259
266
  },
260
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
267
+ outputSchema: OUTPUT_SCHEMA_JSON,
268
+ annotations: { title: "Angles & Points", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
261
269
  handler: async (args) => {
262
270
  validateRequired(args, ["datetime", "latitude", "longitude"]);
263
271
  return await getActiveClient().post("/ephemeris/angles-points", {
@@ -285,7 +293,8 @@ registerTool({
285
293
  required: ["longitude_1", "longitude_2"],
286
294
  additionalProperties: false,
287
295
  },
288
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
296
+ outputSchema: OUTPUT_SCHEMA_JSON,
297
+ annotations: { title: "Aspect Check", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
289
298
  handler: async (args) => {
290
299
  validateRequired(args, ["longitude_1", "longitude_2"]);
291
300
  // Backend field names are longitude1 / longitude2 (no underscore)
@@ -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
  /** Append 'Z' if no timezone offset is present in the datetime string. */
4
5
  function ensureTimezone(dt) {
5
6
  if (/[Zz]$/.test(dt) || /[+-]\d{2}:\d{2}$/.test(dt))
@@ -39,7 +40,8 @@ registerTool({
39
40
  required: ["datetime"],
40
41
  additionalProperties: false,
41
42
  },
42
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
43
+ outputSchema: OUTPUT_SCHEMA_JSON,
44
+ annotations: { title: "Human Design Bodygraph", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
43
45
  handler: async (args) => {
44
46
  validateRequired(args, ["datetime"]);
45
47
  const body = {
@@ -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
  /**
4
5
  * Ensure a datetime string has a timezone offset for Go's time.Time parsing.
5
6
  */
@@ -49,7 +50,8 @@ registerTool({
49
50
  required: ["planet", "datetime", "return_year"],
50
51
  additionalProperties: false,
51
52
  },
52
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
53
+ outputSchema: OUTPUT_SCHEMA_JSON,
54
+ annotations: { title: "HD Planetary Return", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
53
55
  handler: async (args) => {
54
56
  validateRequired(args, ["planet", "datetime", "return_year"]);
55
57
  return await getActiveClient().request("POST", "/human-design/cycles/return", {
@@ -100,7 +102,8 @@ registerTool({
100
102
  required: ["planet", "datetime", "target_year"],
101
103
  additionalProperties: false,
102
104
  },
103
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
105
+ outputSchema: OUTPUT_SCHEMA_JSON,
106
+ annotations: { title: "HD Opposition Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
104
107
  handler: async (args) => {
105
108
  validateRequired(args, ["planet", "datetime", "target_year"]);
106
109
  return await getActiveClient().request("POST", "/human-design/cycles/opposition", {
@@ -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
  // POST /human-design/composite — OE-027
4
5
  registerTool({
5
6
  name: "human_design_composite",
@@ -28,7 +29,8 @@ registerTool({
28
29
  required: ["person_a_datetime", "person_b_datetime"],
29
30
  additionalProperties: false,
30
31
  },
31
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
32
+ outputSchema: OUTPUT_SCHEMA_JSON,
33
+ annotations: { title: "HD Composite Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
32
34
  handler: async (args) => {
33
35
  validateRequired(args, ["person_a_datetime", "person_b_datetime"]);
34
36
  const body = {
@@ -88,7 +90,8 @@ registerTool({
88
90
  required: ["group_name", "members"],
89
91
  additionalProperties: false,
90
92
  },
91
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
93
+ outputSchema: OUTPUT_SCHEMA_JSON,
94
+ annotations: { title: "HD Penta Group Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
92
95
  handler: async (args) => {
93
96
  validateRequired(args, ["group_name", "members"]);
94
97
  const members = args.members.map((m) => ({
@@ -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_AND_JSON } from "../output-schemas.js";
3
4
  /**
4
5
  * Ensure a datetime string has a timezone offset for Go's time.Time parsing.
5
6
  * Go's encoding/json only accepts RFC 3339 (must have Z or +HH:MM offset).
@@ -64,7 +65,8 @@ registerTool({
64
65
  required: ["datetime"],
65
66
  additionalProperties: false,
66
67
  },
67
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
68
+ outputSchema: OUTPUT_SCHEMA_IMAGE_AND_JSON,
69
+ annotations: { title: "Human Design Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
68
70
  handler: async (args) => {
69
71
  validateRequired(args, ["datetime"]);
70
72
  const body = {
@@ -1,5 +1,6 @@
1
1
  import { registerTool, validateCoordinates } from "../index.js";
2
2
  import { getActiveClient } from "../../backend/client.js";
3
+ import { OUTPUT_SCHEMA_JSON } from "../output-schemas.js";
3
4
  registerTool({
4
5
  name: "ephemeris_moon_phase",
5
6
  description: "Get the Moon's current phase angle, illumination, and void-of-course status AT a specific " +
@@ -33,7 +34,8 @@ registerTool({
33
34
  required: [],
34
35
  additionalProperties: false,
35
36
  },
36
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
37
+ outputSchema: OUTPUT_SCHEMA_JSON,
38
+ annotations: { title: "Moon Phase", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
37
39
  handler: async (args) => {
38
40
  validateCoordinates(args, "latitude", "longitude");
39
41
  const params = {};
@@ -91,7 +93,8 @@ registerTool({
91
93
  required: ["phase"],
92
94
  additionalProperties: false,
93
95
  },
94
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
96
+ outputSchema: OUTPUT_SCHEMA_JSON,
97
+ annotations: { title: "Next Lunar Phase", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
95
98
  handler: async (args) => {
96
99
  // Map our clean enum to display names the calendar API returns
97
100
  const phaseNameMap = {
@@ -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_AND_JSON } 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
  /** Map human-readable house system names to standard single-letter codes */
@@ -73,7 +74,8 @@ registerTool({
73
74
  required: ["datetime", "latitude", "longitude"],
74
75
  additionalProperties: false,
75
76
  },
76
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
77
+ outputSchema: OUTPUT_SCHEMA_IMAGE_AND_JSON,
78
+ annotations: { title: "Natal Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
77
79
  handler: async (args) => {
78
80
  validateRequired(args, ["datetime", "latitude", "longitude"]);
79
81
  const body = {
@@ -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_AND_JSON } from "../output-schemas.js";
3
4
  registerTool({
4
5
  name: "ephemeris_progressed_chart",
5
6
  description: "Calculate a Secondary Progressed (or Solar Arc / Tertiary) chart. " +
@@ -56,7 +57,8 @@ registerTool({
56
57
  required: ["birth_datetime", "birth_latitude", "birth_longitude", "target_datetime"],
57
58
  additionalProperties: false,
58
59
  },
59
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
60
+ outputSchema: OUTPUT_SCHEMA_IMAGE_AND_JSON,
61
+ annotations: { title: "Progressed Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
60
62
  handler: async (args) => {
61
63
  validateRequired(args, ["birth_datetime", "birth_latitude", "birth_longitude", "target_datetime"]);
62
64
  const body = {
@@ -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
  registerTool({
4
5
  name: "ephemeris_relocation",
5
6
  description: "Calculate a relocation chart — the same natal planetary positions re-cast for a different " +
@@ -46,7 +47,8 @@ registerTool({
46
47
  required: ["natal_datetime", "natal_latitude", "natal_longitude", "relocation_latitude", "relocation_longitude"],
47
48
  additionalProperties: false,
48
49
  },
49
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
50
+ outputSchema: OUTPUT_SCHEMA_JSON,
51
+ annotations: { title: "Relocation Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
50
52
  handler: async (args) => {
51
53
  validateRequired(args, ["natal_datetime", "natal_latitude", "natal_longitude", "relocation_latitude", "relocation_longitude"]);
52
54
  const body = {
@@ -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_AND_JSON } from "../output-schemas.js";
3
4
  // POST /predictive/returns/solar
4
5
  registerTool({
5
6
  name: "ephemeris_solar_return",
@@ -59,7 +60,8 @@ registerTool({
59
60
  required: ["birth_datetime"],
60
61
  additionalProperties: false,
61
62
  },
62
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
63
+ outputSchema: OUTPUT_SCHEMA_IMAGE_AND_JSON,
64
+ annotations: { title: "Solar Return", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
63
65
  handler: async (args) => {
64
66
  validateRequired(args, ["birth_datetime"]);
65
67
  // Default target to current year if not provided
@@ -127,7 +129,8 @@ registerTool({
127
129
  required: ["birth_datetime"],
128
130
  additionalProperties: false,
129
131
  },
130
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
132
+ outputSchema: OUTPUT_SCHEMA_IMAGE_AND_JSON,
133
+ annotations: { title: "Lunar Return", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
131
134
  handler: async (args) => {
132
135
  validateRequired(args, ["birth_datetime"]);
133
136
  const targetDt = args.target_datetime ?? new Date().toISOString();
@@ -182,7 +185,8 @@ registerTool({
182
185
  required: ["body", "birth_datetime", "target_datetime"],
183
186
  additionalProperties: false,
184
187
  },
185
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
188
+ outputSchema: OUTPUT_SCHEMA_IMAGE_AND_JSON,
189
+ annotations: { title: "Planetary Return", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
186
190
  handler: async (args) => {
187
191
  validateRequired(args, ["body", "birth_datetime", "target_datetime"]);
188
192
  const requestBody = {
@@ -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_AND_JSON } from "../output-schemas.js";
3
4
  registerTool({
4
5
  name: "ephemeris_synastry",
5
6
  description: "Calculate a synastry chart comparing two people's natal charts. Returns inter-aspects " +
@@ -50,7 +51,8 @@ registerTool({
50
51
  ],
51
52
  additionalProperties: false,
52
53
  },
53
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
54
+ outputSchema: OUTPUT_SCHEMA_IMAGE_AND_JSON,
55
+ annotations: { title: "Synastry Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
54
56
  handler: async (args) => {
55
57
  validateRequired(args, [
56
58
  "person_a_datetime", "person_a_latitude", "person_a_longitude",
@@ -1,27 +1,22 @@
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
  registerTool({
4
5
  name: "ephemeris_transits",
5
6
  description: "Search for astrological transit events affecting a natal chart over a date range. " +
6
- "Returns a list of exact transit moments — when transiting planets form specified aspects " +
7
- "to natal planet positions. Ideal for generating horoscope timelines, event forecasting, or " +
8
- "finding optimal timing windows.\n\n" +
9
- "HOW IT WORKS: This tool first calculates the natal chart to extract your actual planetary " +
10
- "ecliptic longitudes, then searches for transiting planets crossing those exact degrees.\n\n" +
11
- "ASPECT ANGLES: Use aspect_angle to search for specific aspects to natal positions:\n" +
7
+ "Returns exact transit moments — when transiting planets form specified aspects " +
8
+ "to natal planet positions.\n\n" +
9
+ "EFFICIENCY TIP: Always specify both transiting_planets and natal_points to keep " +
10
+ "compute fast. Omitting both sends all outer planets against all core natal positions " +
11
+ " a much heavier computation.\n\n" +
12
+ "ASPECT ANGLES: Use aspect_angle to search for specific aspects:\n" +
12
13
  " 0 = conjunction/return (default), 180 = opposition, 90 = square, 120 = trine.\n" +
13
14
  " Example: Saturn Return → transiting_planets=['saturn'], natal_points=['saturn'], aspect_angle=0\n" +
14
15
  " Example: Uranus Opposition → transiting_planets=['uranus'], natal_points=['uranus'], aspect_angle=180\n" +
15
- " Example: Chiron Return → transiting_planets=['chiron'], natal_points=['chiron'], aspect_angle=0\n\n" +
16
- "SUPPORTED BODIES: Sun, Moon, Mercury, Venus, Mars, Jupiter, Saturn, Uranus, Neptune, " +
17
- "Pluto, Chiron, Pholus, Ceres, Pallas, Juno, Vesta, MeanNode.\n\n" +
18
- "CREDIT COST: 6 credits per call (1 for natal + 5 for transit search). " +
19
- "Requests exceeding 30 seconds incur 1 additional credit per 30s of compute time.\n\n" +
20
- "SEARCH RANGE LIMITS: Explorer tier is limited to 1-year windows; Developer to 5 years. " +
21
- "Ranges exceeding the limit are silently clamped.\n\n" +
22
- "EXAMPLE: Find all Saturn transits to the natal Sun/Moon for the next 6 months:\n" +
23
- " natal_datetime='1990-04-15T14:30:00', natal_latitude=41.8781, natal_longitude=-87.6298,\n" +
24
- " start_date='2026-01-01', end_date='2026-06-30', transiting_planets=['saturn']",
16
+ " Example: Outer planets to natal Sun/Moon → transiting_planets=['saturn','uranus','neptune','pluto'], natal_points=['sun','moon']\n\n" +
17
+ "DEFAULT natal_points (when omitted): sun, moon, mercury, venus, mars, jupiter, saturn.\n" +
18
+ "SEARCH RANGE LIMITS: Explorer/PayG tier 1 year max; Developer → 5 years; Startup → 10 years.\n" +
19
+ "CREDIT COST: 6 credits per call.",
25
20
  inputSchema: {
26
21
  type: "object",
27
22
  properties: {
@@ -66,7 +61,8 @@ registerTool({
66
61
  required: ["natal_datetime", "natal_latitude", "natal_longitude", "start_date", "end_date"],
67
62
  additionalProperties: false,
68
63
  },
69
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
64
+ outputSchema: OUTPUT_SCHEMA_JSON,
65
+ annotations: { title: "Transit Search", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
70
66
  handler: async (args) => {
71
67
  validateRequired(args, ["natal_datetime", "natal_latitude", "natal_longitude", "start_date", "end_date"]);
72
68
  // ── Step 1: Compute natal chart to extract real planetary longitudes ──
@@ -87,35 +83,49 @@ registerTool({
87
83
  // The natal response typically has a `planets` array or object
88
84
  const planets = natalResult?.planets || natalResult?.data?.planets || [];
89
85
  const planetArray = Array.isArray(planets) ? planets : Object.values(planets);
90
- // Filter to requested natal points if specified
86
+ // ── Core bodies used when no natal_points filter is given ────────────
87
+ // Defaulting to all planets creates 40+ serial SearchTransit calls on the
88
+ // backend (10+ targets × 4+ outer planets). Cap to the 7 classical bodies.
89
+ const CORE_BODIES_DEFAULT = new Set([
90
+ "sun", "moon", "mercury", "venus", "mars", "jupiter", "saturn",
91
+ ]);
91
92
  const wantedPoints = args.natal_points
92
93
  ? new Set(args.natal_points.map((p) => p.toLowerCase()))
93
- : null;
94
+ : CORE_BODIES_DEFAULT;
94
95
  for (const planet of planetArray) {
95
96
  const name = (planet.name || planet.id || "").toLowerCase();
96
97
  const lon = planet.longitude ?? planet.ecliptic_longitude ?? planet.lon;
97
98
  if (typeof lon !== "number" || !isFinite(lon))
98
99
  continue;
99
- if (wantedPoints && !wantedPoints.has(name))
100
+ if (!wantedPoints.has(name))
100
101
  continue;
101
- targetDegrees.push(Math.round(lon * 100) / 100); // 2 decimal places
102
+ targetDegrees.push(Math.round(lon * 100) / 100);
102
103
  natalPositionMap[name] = lon;
103
104
  }
104
- // Also extract angles (ASC, MC, DSC, IC) if present
105
- const angles = natalResult?.angles || natalResult?.data?.angles;
106
- if (angles) {
107
- const angleEntries = Array.isArray(angles) ? angles : Object.entries(angles).map(([k, v]) => ({ name: k, ...(typeof v === 'object' ? v : { longitude: v }) }));
108
- for (const angle of angleEntries) {
109
- const name = (angle.name || angle.id || "").toLowerCase();
110
- const lon = angle.longitude ?? angle.ecliptic_longitude ?? angle.lon;
111
- if (typeof lon !== "number" || !isFinite(lon))
112
- continue;
113
- if (wantedPoints && !wantedPoints.has(name))
114
- continue;
115
- targetDegrees.push(Math.round(lon * 100) / 100);
116
- natalPositionMap[name] = lon;
105
+ // Extract angles (ASC, MC) only when explicitly requested
106
+ if (args.natal_points) {
107
+ const angles = natalResult?.angles || natalResult?.data?.angles;
108
+ if (angles) {
109
+ const angleEntries = Array.isArray(angles)
110
+ ? angles
111
+ : Object.entries(angles).map(([k, v]) => ({ name: k, ...(typeof v === "object" ? v : { longitude: v }) }));
112
+ for (const angle of angleEntries) {
113
+ const name = (angle.name || angle.id || "").toLowerCase();
114
+ const lon = angle.longitude ?? angle.ecliptic_longitude ?? angle.lon;
115
+ if (typeof lon !== "number" || !isFinite(lon))
116
+ continue;
117
+ if (!wantedPoints.has(name))
118
+ continue;
119
+ targetDegrees.push(Math.round(lon * 100) / 100);
120
+ natalPositionMap[name] = lon;
121
+ }
117
122
  }
118
123
  }
124
+ // Hard cap: never send more than 12 target degrees
125
+ const MAX_TARGETS = 12;
126
+ if (targetDegrees.length > MAX_TARGETS) {
127
+ targetDegrees.splice(MAX_TARGETS);
128
+ }
119
129
  if (targetDegrees.length === 0) {
120
130
  throw new Error("Could not extract natal planet longitudes from the natal chart calculation. " +
121
131
  "Please verify your natal_datetime, natal_latitude, and natal_longitude are correct.");
@@ -129,23 +139,22 @@ registerTool({
129
139
  if (/^\d{4}-\d{2}-\d{2}$/.test(endStr)) {
130
140
  endStr += "T23:59:59Z";
131
141
  }
142
+ const aspectAngle = typeof args.aspect_angle === "number" ? args.aspect_angle : 0;
143
+ let effectiveTargets = targetDegrees;
144
+ if (aspectAngle !== 0) {
145
+ effectiveTargets = targetDegrees.map((deg) => ((deg + aspectAngle) % 360 + 360) % 360);
146
+ }
132
147
  const transitBody = {
133
148
  start_date: startStr,
134
149
  end_date: endStr,
135
- target_degrees: targetDegrees,
150
+ target_degrees: effectiveTargets,
136
151
  };
137
152
  if (args.transiting_planets)
138
153
  transitBody.planet_names = args.transiting_planets;
139
- // When aspect_angle is provided, apply offset to compute effective target degrees
140
- // (natal_lon + aspect_angle) mod 360 for each natal point
141
- let effectiveTargets = targetDegrees;
142
- if (typeof args.aspect_angle === "number" && args.aspect_angle !== 0) {
143
- effectiveTargets = targetDegrees.map((deg) => ((deg + args.aspect_angle) % 360 + 360) % 360);
144
- transitBody.target_degrees = effectiveTargets;
145
- // Also include aspect metadata for labeling in results
146
- transitBody.search_criteria = { aspect_angle: args.aspect_angle };
154
+ if (aspectAngle !== 0) {
155
+ transitBody.search_criteria = { aspect_angle: aspectAngle };
147
156
  }
148
- const transitResult = await getActiveClient().request("POST", "/predictive/transits/search", { data: transitBody });
157
+ const transitResult = await getActiveClient().request("POST", "/predictive/transits/search", { data: transitBody, timeoutMs: 90_000 });
149
158
  // Return combined context so the LLM knows what natal positions were targeted
150
159
  return {
151
160
  natal_positions: natalPositionMap,
@@ -1,5 +1,6 @@
1
1
  import { registerTool, validateRequired, validateCoordinates } from "../index.js";
2
2
  import { getActiveClient } from "../../backend/client.js";
3
+ import { OUTPUT_SCHEMA_JSON } from "../output-schemas.js";
3
4
  registerTool({
4
5
  name: "vedic_chart",
5
6
  description: "Calculate a Vedic (Jyotish) natal chart with sidereal positions. Returns planet placements " +
@@ -33,7 +34,8 @@ registerTool({
33
34
  required: ["datetime", "latitude", "longitude"],
34
35
  additionalProperties: false,
35
36
  },
36
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
37
+ outputSchema: OUTPUT_SCHEMA_JSON,
38
+ annotations: { title: "Vedic Natal Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
37
39
  handler: async (args) => {
38
40
  validateRequired(args, ["datetime", "latitude", "longitude"]);
39
41
  validateCoordinates(args, "latitude", "longitude");