@openephemeris/mcp-server 4.15.1 → 4.17.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 (43) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +26 -27
  3. package/config/dev-allowlist.json +31 -19
  4. package/dist/backend/client.d.ts +2 -0
  5. package/dist/backend/client.js +15 -10
  6. package/dist/instructions.d.ts +1 -1
  7. package/dist/instructions.js +5 -3
  8. package/dist/prompts.js +2 -2
  9. package/dist/tools/apps/bazi-app.js +10 -10
  10. package/dist/tools/apps/bi-wheel-app.js +14 -14
  11. package/dist/tools/apps/bodygraph-app.js +35 -29
  12. package/dist/tools/apps/chart-wheel-app.js +12 -9
  13. package/dist/tools/apps/location-tools.js +17 -4
  14. package/dist/tools/apps/moon-phase-app.js +12 -11
  15. package/dist/tools/apps/transit-timeline-app.d.ts +3 -0
  16. package/dist/tools/apps/transit-timeline-app.js +86 -18
  17. package/dist/tools/apps/vedic-chart-app.js +9 -8
  18. package/dist/tools/auth.js +14 -12
  19. package/dist/tools/dev.js +17 -29
  20. package/dist/tools/invocation-status.d.ts +25 -0
  21. package/dist/tools/invocation-status.js +69 -0
  22. package/dist/tools/specialized/account.js +6 -10
  23. package/dist/tools/specialized/acg.js +16 -21
  24. package/dist/tools/specialized/bazi.js +7 -12
  25. package/dist/tools/specialized/eclipse.js +6 -13
  26. package/dist/tools/specialized/electional.js +30 -37
  27. package/dist/tools/specialized/ephemeris_core.js +21 -17
  28. package/dist/tools/specialized/ephemeris_extended.js +22 -19
  29. package/dist/tools/specialized/human_design.js +11 -14
  30. package/dist/tools/specialized/moon.js +15 -26
  31. package/dist/tools/specialized/natal.js +9 -8
  32. package/dist/tools/specialized/progressed.js +6 -6
  33. package/dist/tools/specialized/relocation.js +6 -7
  34. package/dist/tools/specialized/returns.js +6 -8
  35. package/dist/tools/specialized/synastry.js +7 -8
  36. package/dist/tools/specialized/transits.js +15 -14
  37. package/dist/tools/specialized/vedic.js +7 -6
  38. package/dist/tools/ui-meta.d.ts +3 -28
  39. package/dist/tools/ui-meta.js +25 -16
  40. package/dist/ui/bi-wheel.html +468 -450
  41. package/dist/ui/chart-wheel.html +407 -389
  42. package/dist/ui/transit-timeline.html +2 -2
  43. package/package.json +3 -2
@@ -120,15 +120,18 @@ function buildNatalBody(datetime, lat, lon, houseSystem, timezone, additionalObj
120
120
  // ── Tool: explore_natal_chart ──────────────────────────────────────────────
121
121
  registerTool({
122
122
  name: "explore_natal_chart",
123
- description: "PRIMARY tool for any natal/birth-chart request. Renders an interactive, clickable chart " +
124
- "wheel — prefer this over ephemeris_natal_chart when the user wants to SEE a chart. " +
125
- "Returns an embedded visual chart explorer that lets you click any planet, house, or aspect " +
126
- "line for instant astrological interpretation.\n\n" +
127
- "CREDIT COST: 1 credit per call.\n\n" +
128
- "Supports house system switching (Placidus, Whole Sign, Equal, Koch). " +
129
- "The chart is computed using NASA JPL DE440 ephemerides for sub-arcsecond precision. " +
130
- "Renders interactively in MCP Apps-capable hosts (Claude and ChatGPT) and falls back to " +
131
- "static SVG in other hosts.",
123
+ description: "Use this when the user asks 'show me my birth chart', 'what's my rising sign', 'my big 3', " +
124
+ "'what's my sun, moon and rising', 'what sign is my Moon / Venus / Mars in', 'what does my chart " +
125
+ "say about my career / love', 'read my chart', or shares birth details and wants to explore them " +
126
+ "— the PRIMARY tool for any natal / birth-chart / astrology-chart request. Returns an interactive " +
127
+ "chart wheel rendered inline (Claude and ChatGPT; text summary elsewhere) where any planet, house " +
128
+ "or aspect line can be clicked for interpretation, with switchable house systems. Pass the " +
129
+ "birthplace as `location` (a place name — resolved server-side) or coordinates, plus the birth " +
130
+ "date and local time with its zone.\n\n" +
131
+ "CREDIT COST: 1 credit per call (+1 to resolve a place name).\n\n" +
132
+ "Do not use when the chart must be consumed as raw numbers for further computation — use " +
133
+ "ephemeris_natal_chart; for two charts together use explore_bi_wheel; for Human Design use " +
134
+ "explore_human_design; for Vedic use explore_vedic_chart.",
132
135
  inputSchema: {
133
136
  type: "object",
134
137
  properties: {
@@ -127,7 +127,16 @@ function offsetAtLocalNoon(timezone, date) {
127
127
  }
128
128
  registerTool({
129
129
  name: "location_search",
130
- description: "Resolve a place name to coordinates and IANA timezone — use this first whenever a user gives a birth city rather than latitude/longitude. NEVER recall coordinates from memory; always resolve them here. CREDIT COST: 1 credit per call. Returns display name, region (state/province), latitude, longitude, and IANA timezone. Pass the birth date as `date` to also get `utcOffsetAtDate`, the historically-correct UTC offset for that place on that date (1987 DST rules differ from today's). Post-1970 dates resolve locally and cost no extra credits; a pre-1970 date consults the API's historical correction overlay for the top match (1 extra credit) and returns its provenance — tzRuleSource, tzRuleCitation, tzOverlayVersion. When the result is `ambiguous` (several places share the name, e.g. \"portland\"), ASK the user which one they mean rather than assuming the first. Optional bias params (country/region/near) improve ranking; a trailing \"City, ST\" qualifier in the query is also honored.",
130
+ description: "Use this when the user gives a birth city or any place name — 'born in Chicago', 'Portland', " +
131
+ "'Mumbai, India' — and a chart needs coordinates and a timezone. Always resolve here; NEVER " +
132
+ "recall coordinates from memory. Returns display name, region (state/province), latitude, " +
133
+ "longitude and IANA timezone. Pass the birth date as `date` to also get `utcOffsetAtDate`, the " +
134
+ "historically-correct UTC offset for that place on that date; post-1970 dates resolve locally at " +
135
+ "no extra cost, pre-1970 dates consult the historical overlay for the top match (+1 credit) and " +
136
+ "return its provenance. When the result is `ambiguous` (several places share the name, e.g. " +
137
+ "\"portland\"), ASK the user which one they mean rather than assuming the first. A trailing \"City, " +
138
+ "ST\" qualifier in the query is honored. CREDIT COST: 1 credit per call. Do not use when you " +
139
+ "already have coordinates and only need the zone — use timezone_resolve.",
131
140
  inputSchema: {
132
141
  type: "object",
133
142
  properties: {
@@ -280,12 +289,16 @@ registerTool({
280
289
  });
281
290
  registerTool({
282
291
  name: "timezone_resolve",
283
- description: "Resolve the IANA timezone for a latitude/longitude pair. Use when you have coordinates but need the timezone to interpret a local birth time. Pass `date` to also get the historically-correct UTC offset that applied on that date. CREDIT COST: 1 credit per call. If you are starting from a place name rather than coordinates, use location_search instead — it returns the timezone too, in the same single call.",
292
+ description: "Use this when you have latitude/longitude but need the IANA timezone to interpret a local birth " +
293
+ "time, or the user asks 'what timezone is this location in' or 'what was the UTC offset there on " +
294
+ "that date'. Returns the IANA zone and, with `date`, the historically-correct UTC offset that " +
295
+ "applied on that date. CREDIT COST: 1 credit per call. Do not use when starting from a place name " +
296
+ "— use location_search, which returns the timezone too, in the same single call.",
284
297
  inputSchema: {
285
298
  type: "object",
286
299
  properties: {
287
- latitude: { type: "number" },
288
- longitude: { type: "number" },
300
+ latitude: { type: "number", description: "Latitude in decimal degrees, -90 to 90 (north positive)." },
301
+ longitude: { type: "number", description: "Longitude in decimal degrees, -180 to 180 (east positive)." },
289
302
  date: {
290
303
  type: "string",
291
304
  description: "Optional birth/event date 'YYYY-MM-DD'. Adds utcOffsetAtDate / utcOffsetMinutes / isDst / tzConfidence. Post-1970 resolves locally (free); pre-1970 consults the API's historical correction overlay (1 extra credit).",
@@ -193,18 +193,19 @@ async function computeMoonData(args) {
193
193
  // ── Tool: explore_moon_phase ─────────────────────────────────────────────────
194
194
  registerTool({
195
195
  name: "explore_moon_phase",
196
- description: "Generate an interactive Moon Phase dial showing the current lunar illumination, " +
197
- "phase name, zodiac sign, and void-of-course status as a beautiful circular visualization.\n\n" +
198
- "Returns a visual dial with:\n" +
199
- " • SVG crescent Moon showing real-time illumination percentage\n" +
200
- " • Phase name and waxing/waning indicator\n" +
201
- " • Current Moon sign with degree\n" +
202
- " • Void-of-Course status with timing details\n" +
203
- " • Lunar age (days in the synodic cycle)\n" +
204
- " • Upcoming New Moon and Full Moon dates\n\n" +
196
+ description: "Use this when the user asks 'what's the moon phase tonight', 'is tonight a full moon', 'show me " +
197
+ "the moon right now', 'what sign is the Moon in', 'is the Moon void of course' — or has no birth " +
198
+ "data and wants somewhere to start; it needs no arguments. Returns an interactive Moon dial " +
199
+ "(inline in Claude and ChatGPT; text summary elsewhere) with:\n" +
200
+ "• SVG crescent Moon showing real-time illumination percentage\n" +
201
+ "• Phase name and waxing/waning indicator\n" +
202
+ "• Current Moon sign with degree\n" +
203
+ "• Void-of-Course status with timing details\n" +
204
+ "• Lunar age (days in the synodic cycle) and days to the next New and Full Moon\n" +
205
+ "• The tightest applying lunar aspects\n\n" +
205
206
  "CREDIT COST: 3 credits per call (phase + void-of-course + aspects, 1 each).\n\n" +
206
- "Use this instead of ephemeris_moon_phase for a rich, interactive lunar phase experience in " +
207
- "MCP Apps-capable hosts (Claude and ChatGPT). Falls back to a text summary in other hosts.",
207
+ "Do not use when only the numbers are needed — use ephemeris_moon_phase; for the exact dates of " +
208
+ "upcoming phases use ephemeris_next_lunar_phase; for eclipses use ephemeris_next_eclipse.",
208
209
  inputSchema: {
209
210
  type: "object",
210
211
  properties: {
@@ -8,6 +8,9 @@
8
8
  * chart to extract real planetary longitudes, searches predictive transits
9
9
  * against those targets, then flattens the (planet@target → hits) result map
10
10
  * into a single chronological array the iframe renders as a vertical timeline.
11
+ * The search asks for orb windows (orb_deg) too, so every exact hit carries the
12
+ * in-orb window it belongs to — when the transit starts and ends, and which
13
+ * pass it is when a retrograde brings the planet back over the same point.
11
14
  *
12
15
  * Also exports resource helpers (getTransitTimelineBundle, etc.) for use in
13
16
  * index.ts and server-sse.ts.
@@ -8,6 +8,9 @@
8
8
  * chart to extract real planetary longitudes, searches predictive transits
9
9
  * against those targets, then flattens the (planet@target → hits) result map
10
10
  * into a single chronological array the iframe renders as a vertical timeline.
11
+ * The search asks for orb windows (orb_deg) too, so every exact hit carries the
12
+ * in-orb window it belongs to — when the transit starts and ends, and which
13
+ * pass it is when a retrograde brings the planet back over the same point.
11
14
  *
12
15
  * Also exports resource helpers (getTransitTimelineBundle, etc.) for use in
13
16
  * index.ts and server-sse.ts.
@@ -67,18 +70,32 @@ function normDeg(d) {
67
70
  return ((d % 360) + 360) % 360;
68
71
  }
69
72
  // ── Summary builder ──────────────────────────────────────────────────────────
70
- function buildSummary(transits, aspectLabel, win) {
73
+ function shortDate(iso) {
74
+ return iso ? new Date(iso).toISOString().slice(0, 10) : "?";
75
+ }
76
+ function buildSummary(transits, windows, aspectLabel, orbDeg, win) {
77
+ const active = windows.filter((w) => w.phase_at_start);
78
+ const activeLines = active.slice(0, 6).map((w) => " • " + w.planet_name + " " + (w.aspect ?? aspectLabel) + (w.natal_point ? " natal " + w.natal_point : "") +
79
+ " — " + w.phase_at_start + ", " + (Math.round((w.orb_at_start ?? 0) * 100) / 100) + "° from exact, in orb until " +
80
+ shortDate(w.leave));
81
+ const activeBlock = activeLines.length
82
+ ? "\n\nAlready in orb (" + orbDeg + "°) at the start:\n" + activeLines.join("\n")
83
+ : "";
71
84
  if (transits.length === 0) {
72
- return "**Transit Timeline** — no exact transit hits matched the search window and criteria.";
85
+ return "**Transit Timeline** — no exact transit hits matched the search window and criteria." + activeBlock;
73
86
  }
74
87
  const planets = [...new Set(transits.map((t) => t.planet_name).filter(Boolean))];
75
88
  const lines = transits.slice(0, 8).map((t) => {
76
89
  const asp = t.aspect ?? aspectLabel;
77
90
  const natal = t.natal_point ? " natal " + t.natal_point : "";
78
91
  const when = new Date(t.date).toISOString().slice(0, 10);
92
+ const passes = t.pass_count > 1 ? ", pass " + t.pass_index + " of " + t.pass_count : "";
93
+ const inOrb = t.window_start !== undefined
94
+ ? " — in orb " + shortDate(t.window_start) + " → " + shortDate(t.window_end) + passes
95
+ : "";
79
96
  return " • " + when + " — " + t.planet_name + " " + asp + natal +
80
97
  (t.zodiac_sign ? " (" + (t.zodiac_degree ?? 0) + "° " + t.zodiac_sign + ")" : "") +
81
- (t.is_retrograde ? " ℞" : "");
98
+ (t.is_retrograde ? " ℞" : "") + inOrb;
82
99
  });
83
100
  let summary = "**Transit Timeline** — " + transits.length + (transits.length === 1 ? " event" : " events") +
84
101
  " from " + win.start_date + " to " + win.end_date +
@@ -86,27 +103,29 @@ function buildSummary(transits, aspectLabel, win) {
86
103
  lines.join("\n");
87
104
  if (transits.length > 8)
88
105
  summary += "\n … and " + (transits.length - 8) + " more.";
106
+ summary += activeBlock;
89
107
  summary += "\n\nClick any transit for details and interpretation.";
90
108
  return summary;
91
109
  }
92
110
  // ── Tool: explore_transit_timeline ───────────────────────────────────────────
93
111
  registerTool({
94
112
  name: "explore_transit_timeline",
95
- description: "Generate an interactive Transit Timeline — a vertical, date-ordered list of " +
96
- "upcoming transit hits (transiting planets forming a chosen aspect to natal " +
97
- "chart positions) over a date range.\n\n" +
98
- "Returns a visual timeline with:\n" +
99
- " • Exact crossing dates grouped by month\n" +
100
- " • Transiting planet glyph, the natal point it contacts, and the aspect\n" +
101
- " • Zodiac position of each crossing and retrograde markers\n" +
102
- " • Click any transit for a focused interpretation\n\n" +
103
- "ASPECT ANGLES: 0 = conjunction/return (default), 180 = opposition, 90 = square, " +
104
- "120 = trine, 60 = sextile. EFFICIENCY: specify transiting_planets and natal_points " +
105
- "to keep compute fast. DEFAULT natal_points: sun, moon, mercury, venus, mars, jupiter, saturn. " +
106
- "SEARCH RANGE LIMITS: Explorer/PayG → 1 year; Pro → 5 years; Startup → 10 years.\n\n" +
113
+ description: "Use this when the user asks 'what transits are coming up for me', 'what's my forecast for the " +
114
+ "next year', 'when will Saturn hit my chart', 'show my upcoming transits' or 'what's happening " +
115
+ "astrologically for me in 2027' — a forecast they should SEE. Returns an interactive, " +
116
+ "date-ordered timeline (inline in Claude and ChatGPT; text summary elsewhere) of exact transit " +
117
+ "hits grouped by month — transiting planet glyph, the natal point it contacts, the aspect, zodiac " +
118
+ "position and retrograde markers — each clickable for a focused interpretation. Every hit carries its " +
119
+ "in-orb window (when the transit starts and ends, and which pass it is when a retrograde repeats it), " +
120
+ "and transits already in orb at the start are listed with applying/separating.\n\n" +
121
+ "ASPECT ANGLES: 0 = conjunction/return (default), 180 = opposition, 90 = square, 120 = trine, 60 " +
122
+ "= sextile. EFFICIENCY: specify transiting_planets and natal_points to keep compute fast. DEFAULT " +
123
+ "natal_points: sun, moon, mercury, venus, mars, jupiter, saturn. SEARCH RANGE LIMITS: " +
124
+ "Explorer/PayG → 1 year; Pro → 5 years; Startup → 10 years.\n\n" +
107
125
  "CREDIT COST: 6 credits per call (natal chart + predictive transit search).\n\n" +
108
- "Use this for a rich, interactive transit-forecast experience in MCP Apps-capable hosts " +
109
- "(Claude and ChatGPT). Falls back to a text summary in other hosts.",
126
+ "Do not use for transit data as JSON — use ephemeris_transits; for the sky today with no birth " +
127
+ "data use electional_moment_analysis; to see one date's transits drawn around the natal wheel use " +
128
+ "explore_bi_wheel (mode='transit').",
110
129
  inputSchema: {
111
130
  type: "object",
112
131
  properties: {
@@ -128,6 +147,10 @@ registerTool({
128
147
  type: "number",
129
148
  description: "Aspect angle in degrees. 0 = conjunction/return (default), 180 = opposition, 90 = square, 120 = trine, 60 = sextile.",
130
149
  },
150
+ orb_deg: {
151
+ type: "number",
152
+ description: "Orb in degrees that defines when a transit starts and ends (the in-orb window around each exact hit). Default 1. Greater than 0, at most 15.",
153
+ },
131
154
  },
132
155
  required: ["natal_datetime", "natal_latitude", "natal_longitude", "start_date", "end_date"],
133
156
  additionalProperties: false,
@@ -223,10 +246,12 @@ registerTool({
223
246
  let endStr = args.end_date;
224
247
  if (/^\d{4}-\d{2}-\d{2}$/.test(endStr))
225
248
  endStr += "T23:59:59Z";
249
+ const orbDeg = typeof args.orb_deg === "number" && args.orb_deg > 0 ? Math.min(args.orb_deg, 15) : 1;
226
250
  const transitBody = {
227
251
  start_date: startStr,
228
252
  end_date: endStr,
229
253
  target_degrees: effectiveTargets,
254
+ orb_deg: orbDeg,
230
255
  };
231
256
  if (args.transiting_planets)
232
257
  transitBody.planet_names = args.transiting_planets;
@@ -279,10 +304,53 @@ registerTool({
279
304
  }
280
305
  }
281
306
  transits.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
307
+ // ── Step 5: orb windows → attach each exact hit to its window ────────────
308
+ // A window groups every pass the planet makes without leaving the orb, so
309
+ // a retrograde triple pass is one window with three exacts. Hits are
310
+ // matched to windows by planet, target degree and exact time.
311
+ const rawWindows = Array.isArray(transitResult?.windows) ? transitResult.windows : [];
312
+ const windows = rawWindows.map((w) => {
313
+ const target = typeof w?.target_longitude === "number" ? w.target_longitude : null;
314
+ return {
315
+ planet: String(w?.planet_name ?? "").toLowerCase(),
316
+ planet_name: String(w?.planet_name ?? ""),
317
+ natal_point: target != null ? nearestPoint(target) : "",
318
+ aspect: aspectLabel,
319
+ target_longitude: target,
320
+ orb_deg: w?.orb_deg ?? orbDeg,
321
+ enter: w?.enter ?? null,
322
+ leave: w?.leave ?? null,
323
+ exacts: Array.isArray(w?.exacts)
324
+ ? w.exacts.map((e) => ({ time: e?.time, is_retrograde: Boolean(e?.is_retrograde) }))
325
+ : [],
326
+ duration_days: w?.duration_days ?? null,
327
+ phase_at_start: w?.phase_at_start ?? null,
328
+ orb_at_start: w?.orb_at_start ?? null,
329
+ };
330
+ });
331
+ for (const t of transits) {
332
+ const hitMs = new Date(t.date).getTime();
333
+ for (const w of windows) {
334
+ if (w.planet_name !== t.planet_name || w.target_longitude == null || t.target_longitude == null)
335
+ continue;
336
+ if (Math.abs(w.target_longitude - t.target_longitude) > 0.01)
337
+ continue;
338
+ const idx = w.exacts.findIndex((e) => Math.abs(new Date(e.time).getTime() - hitMs) <= 60_000);
339
+ if (idx < 0)
340
+ continue;
341
+ t.window_start = w.enter;
342
+ t.window_end = w.leave;
343
+ t.pass_index = idx + 1;
344
+ t.pass_count = w.exacts.length;
345
+ break;
346
+ }
347
+ }
282
348
  const win = { start_date: startStr.slice(0, 10), end_date: endStr.slice(0, 10) };
283
- const summary = buildSummary(transits, aspectLabel, win);
349
+ const summary = buildSummary(transits, windows, aspectLabel, orbDeg, win);
284
350
  const uiPayload = {
285
351
  transits,
352
+ windows,
353
+ orb_deg: orbDeg,
286
354
  natal_positions: natalPositionMap,
287
355
  aspect_angle: aspectAngle,
288
356
  aspect_label: aspectLabel,
@@ -96,14 +96,15 @@ function buildVedicSummary(payload, location) {
96
96
  // ── Tool: explore_vedic_chart ────────────────────────────────────────────────
97
97
  registerTool({
98
98
  name: "explore_vedic_chart",
99
- description: "Generate an interactive Vedic (Jyotish) birth chart as a South Indian fixed-sign Rashi grid, " +
100
- "with clickable rashis showing sidereal placements, nakshatras, and the Lagna.\n\n" +
101
- "CREDIT COST: 3 credits per call (chart calculation + visual render).\n\n" +
102
- "Returns an embedded visual explorer that lets you click any rashi cell for its themes and " +
103
- "any planets placed there. Shows sidereal (Lahiri by default) planet placements, nakshatra with " +
104
- "pada, navamsa, and the Lagna (Ascendant) rashi. Uses NASA JPL DE440 ephemerides. " +
105
- "Use this for a rich, interactive Jyotish experience in MCP Apps-capable hosts (Claude and ChatGPT). " +
106
- "Falls back to a text summary in other hosts.",
99
+ description: "Use this when the user asks 'show my Vedic chart', 'my Jyotish chart', 'what's my nakshatra', " +
100
+ "'my sidereal birth chart', 'my rashi chart' or 'what's my lagna' — the PRIMARY tool for a Vedic " +
101
+ "chart. Returns an interactive South Indian fixed-sign Rashi grid (inline in Claude and ChatGPT; " +
102
+ "text summary elsewhere) with sidereal (Lahiri by default) placements, nakshatra with pada, " +
103
+ "navamsa and the Lagna (Ascendant) rashi; click any rashi cell for its themes and the planets " +
104
+ "placed there. Birthplace may be a place name (`location`).\n\n" +
105
+ "CREDIT COST: 3 credits per call (chart + render; +1 to resolve a place name).\n\n" +
106
+ "Do not use when raw Jyotish data is enough — use vedic_chart (1 credit); for a Western tropical " +
107
+ "chart use explore_natal_chart.",
107
108
  inputSchema: {
108
109
  type: "object",
109
110
  properties: {
@@ -8,12 +8,12 @@ const credentialManager = new CredentialManager();
8
8
  // ────────────────────────────────────────────────────────
9
9
  registerTool({
10
10
  name: "auth_login",
11
- description: "Start the device authorization flow to connect this MCP server to your OpenEphemeris account " +
12
- "(free tier, no credit card needed). " +
13
- "Returns a verification URL and code for the user to enter in their browser. " +
14
- "The MCP server will then automatically receive credentials and all API calls will be " +
15
- "linked to the user's account (tier, credits, rate limits). " +
16
- "Only needed if no OPENEPHEMERIS_API_KEY env var is set and no cached credentials exist.",
11
+ description: "Use this when the user asks to 'connect', 'sign in', 'log in' or 'link my OpenEphemeris " +
12
+ "account', or when a call fails for lack of credentials and no OPENEPHEMERIS_API_KEY or cached " +
13
+ "login exists (free tier, no card needed). Returns a verification URL and code for the user to " +
14
+ "enter in their browser; the server then receives credentials automatically and every call is " +
15
+ "billed to that account (tier, credits, rate limits). Do not use to check whether you are already " +
16
+ "signed in — use auth_status; to disconnect use auth_logout.",
17
17
  inputSchema: {
18
18
  type: "object",
19
19
  properties: {},
@@ -96,9 +96,10 @@ registerTool({
96
96
  // ────────────────────────────────────────────────────────
97
97
  registerTool({
98
98
  name: "auth_status",
99
- description: "Check the current authentication status of this MCP server. " +
100
- "Shows whether the server is authenticated, which account it's linked to, " +
101
- "the authentication method (API key, JWT, device auth), and token expiry.",
99
+ description: "Use this when the user asks 'am I logged in', 'which account is this connected to', 'how am I " +
100
+ "authenticated' or 'when does my token expire'. Returns whether the server is authenticated, the " +
101
+ "linked account, the method (API key, JWT, device auth) and token expiry. Do not use for credits, " +
102
+ "plan or usage — use account_usage; to sign in use auth_login.",
102
103
  inputSchema: {
103
104
  type: "object",
104
105
  properties: {},
@@ -184,9 +185,10 @@ registerTool({
184
185
  // ────────────────────────────────────────────────────────
185
186
  registerTool({
186
187
  name: "auth_logout",
187
- description: "Disconnect this MCP server from your OpenEphemeris account by clearing " +
188
- "cached credentials. Does NOT revoke the API key if one is set via environment " +
189
- "variable — only clears device-auth cached credentials.",
188
+ description: "Use this when the user asks to 'log out', 'sign out', 'disconnect' or 'switch accounts' on this " +
189
+ "MCP server. Returns confirmation that cached device-auth credentials were cleared; an " +
190
+ "OPENEPHEMERIS_API_KEY set in the environment is not revoked. Do not use to check the current " +
191
+ "login — use auth_status.",
190
192
  inputSchema: {
191
193
  type: "object",
192
194
  properties: {},
package/dist/tools/dev.js CHANGED
@@ -44,25 +44,12 @@ function isAllowedOperation(method, pathname, allow) {
44
44
  // actually make.
45
45
  const DEV_API_REFERENCE = "Target API: Open Ephemeris REST API (https://api.openephemeris.com). " +
46
46
  "Call dev_list_allowed to see all currently available endpoint paths.\n\n" +
47
- "AUTH: Set OPENEPHEMERIS_API_KEY in your environment. See openephemeris.com/dashboard for active plan limits.\n\n" +
48
- "CREDIT COSTS:\n" +
49
- " • Standard chart math (natal, progressed, bazi, vedic, iching): 1 credit\n" +
50
- " • Human Design: 2 credits\n" +
51
- " • Visualization rendering (chart-wheel, bi-wheel, charts/*): 2 credits\n" +
52
- " • Comparative math (synastry, composite, overlay): 3 credits\n" +
53
- " • Predictive ops (transits/search, returns): 5 credits\n" +
54
- " • Predictive transit-chart: 1 credit\n" +
55
- " • ACG / astrocartography: 10 credits (acg/hits: 15 credits)\n" +
56
- " • Calendar endpoints: 10 credits\n" +
57
- " • Catalog / metadata / health endpoints: 0 credits\n" +
58
- " • Compute surcharge: requests > 30s add 1 credit per 30s (predictive, acg, calendar, electional)\n" +
59
- " • format=llm (token-optimized output): available on all tiers\n\n" +
60
- "BINARY RESPONSES:\n" +
61
- " • Binary/image endpoints return {content_type, content_length, encoding, data_base64}\n" +
62
- " so callers can decode bytes deterministically.\n\n" +
63
- "ECLIPSE NOTE: Eclipse endpoints accept format=llm via the query param like other endpoints.\n\n" +
64
- "format=llm NOTE: Add query: {format: 'llm'} to natal/synastry/composite/HD endpoints for " +
65
- "compact columnar output optimized for LLM token budgets (availability depends on your current plan).";
47
+ // Prices mirror go-sidecar/internal/api/auth/usage_meter.go; tiers mirror
48
+ // auth/middleware.go requiredTierForPath. /iching/* aliases /human-design/*.
49
+ "CREDIT COST: 1 for most GET calls; /calendar/* 10; /acg/* 10 (Pro tier); /electional/* 5 " +
50
+ "(find-window and aspect-search are Pro tier; moment-analysis and station-tracker are free); " +
51
+ "/human-design/* and /iching/* 2; catalogs/health 0; requests over 30s add 1 " +
52
+ "per 30s. Pass query {format: 'llm'} for compact output.\n\n";
66
53
  const READ_COMMON_CALLS = "COMMON CALLS:\n" +
67
54
  " GET /ephemeris/moon/phase — Current/queried moon phase\n" +
68
55
  " GET /ephemeris/moon/void-of-course — Next void-of-course period\n" +
@@ -196,11 +183,12 @@ function makeProxyHandler(allowedMethods, defaultMethod) {
196
183
  }
197
184
  registerTool({
198
185
  name: "dev_read_api",
199
- description: "Read from any allowlisted Open Ephemeris API endpoint via HTTP GET. This is the read-only " +
200
- "power-user escape hatch — use the typed tools (ephemeris_natal_chart, ephemeris_transits, etc.) " +
201
- "first for common operations. Endpoints that compute via POST (natal-chart, synastry, etc.) " +
202
- "are covered by the typed tools; the generic POST proxy is only exposed on the full tool " +
203
- "surface (`?profile=full`).\n\n" +
186
+ description: "Use this when the user asks for something no typed tool covers — 'is today a good biodynamic " +
187
+ "planting day', 'what's the tidal forcing', 'what Chinese zodiac animal is 2026', 'which bodies " +
188
+ "do you support' — or any other allowlisted GET endpoint. Returns the endpoint's JSON. Read-only " +
189
+ "HTTP GET; call dev_list_allowed to see every path. Do not use for birth charts, transits, moon " +
190
+ "phase, eclipses, Human Design, synastry or anything an ephemeris_* / explore_* tool already " +
191
+ "does. POST endpoints are only on the full tool surface (`?profile=full`).\n\n" +
204
192
  DEV_API_REFERENCE + "\n\n" + READ_COMMON_CALLS,
205
193
  inputSchema: makeProxyInputSchema(READ_METHODS),
206
194
  outputSchema: OUTPUT_SCHEMA_JSON,
@@ -233,11 +221,11 @@ registerTool({
233
221
  });
234
222
  registerTool({
235
223
  name: "dev_list_allowed",
236
- description: "List all API operations (method + path) that this MCP instance is authorized to call. " +
237
- "Returns endpoint entries grouped by method, plus the active deny rules. " +
238
- "Use this to discover what's available before calling dev_read_api, or to verify an endpoint path. " +
239
- "Typed shortcut tools (ephemeris_natal_chart, ephemeris_transits, etc.) cover the most common operations — " +
240
- "check those first before reaching for the generic proxies.",
224
+ description: "Use this when the user asks 'what endpoints can you call', 'what else can this server do', 'is " +
225
+ "/some/path available', or before a dev_read_api call whose path you are unsure of. Returns every " +
226
+ "allowlisted API operation (method + path) grouped by method, plus the active deny rules; read " +
227
+ "locally, no API call. Do not use to fetch data — use dev_read_api, or the typed ephemeris_* / " +
228
+ "explore_* tool when one exists.",
241
229
  inputSchema: {
242
230
  type: "object",
243
231
  properties: {},
@@ -0,0 +1,25 @@
1
+ /**
2
+ * invocation-status.ts — the status text ChatGPT shows while a tool runs and
3
+ * after it completes.
4
+ *
5
+ * Wire form (OpenAI Apps SDK reference, "Tool descriptor parameters"):
6
+ * _meta["openai/toolInvocation/invoking"] ≤ 64 chars, shown during the call
7
+ * _meta["openai/toolInvocation/invoked"] ≤ 64 chars, shown when it returns
8
+ * Both are ChatGPT-only; other hosts ignore the keys. Emitted by buildUiMeta
9
+ * (src/tools/ui-meta.ts), the one path both transports' tools/list share.
10
+ *
11
+ * Copy rules: name the thing being made in the user's words, so the wait reads
12
+ * as progress rather than plumbing. `explore_*` tools say "ready" (a thing to
13
+ * look at); `ephemeris_*` tools say "computed" / "found" (numbers) — the same
14
+ * picture-vs-data split the descriptions make. Never promise a render:
15
+ * `explore_*` fall back to text in hosts without MCP Apps.
16
+ *
17
+ * Every core tool must have an entry (test/invocation-status.test.ts); tools
18
+ * outside core may have one. Keep both strings under MAX_STATUS_CHARS.
19
+ */
20
+ export declare const MAX_STATUS_CHARS = 64;
21
+ export interface InvocationStatus {
22
+ readonly invoking: string;
23
+ readonly invoked: string;
24
+ }
25
+ export declare const INVOCATION_STATUS: Readonly<Record<string, InvocationStatus>>;
@@ -0,0 +1,69 @@
1
+ /**
2
+ * invocation-status.ts — the status text ChatGPT shows while a tool runs and
3
+ * after it completes.
4
+ *
5
+ * Wire form (OpenAI Apps SDK reference, "Tool descriptor parameters"):
6
+ * _meta["openai/toolInvocation/invoking"] ≤ 64 chars, shown during the call
7
+ * _meta["openai/toolInvocation/invoked"] ≤ 64 chars, shown when it returns
8
+ * Both are ChatGPT-only; other hosts ignore the keys. Emitted by buildUiMeta
9
+ * (src/tools/ui-meta.ts), the one path both transports' tools/list share.
10
+ *
11
+ * Copy rules: name the thing being made in the user's words, so the wait reads
12
+ * as progress rather than plumbing. `explore_*` tools say "ready" (a thing to
13
+ * look at); `ephemeris_*` tools say "computed" / "found" (numbers) — the same
14
+ * picture-vs-data split the descriptions make. Never promise a render:
15
+ * `explore_*` fall back to text in hosts without MCP Apps.
16
+ *
17
+ * Every core tool must have an entry (test/invocation-status.test.ts); tools
18
+ * outside core may have one. Keep both strings under MAX_STATUS_CHARS.
19
+ */
20
+ export const MAX_STATUS_CHARS = 64;
21
+ export const INVOCATION_STATUS = {
22
+ // Interactive apps
23
+ explore_natal_chart: { invoking: "Casting your birth chart…", invoked: "Birth chart ready" },
24
+ explore_bi_wheel: { invoking: "Comparing the two charts…", invoked: "Bi-wheel ready" },
25
+ explore_human_design: { invoking: "Drawing your bodygraph…", invoked: "Bodygraph ready" },
26
+ explore_human_design_transit: { invoking: "Overlaying the transits on your bodygraph…", invoked: "Transit overlay ready" },
27
+ explore_human_design_connection: { invoking: "Overlaying both bodygraphs…", invoked: "Connection chart ready" },
28
+ explore_moon_phase: { invoking: "Reading the Moon…", invoked: "Moon phase ready" },
29
+ explore_transit_timeline: { invoking: "Searching your upcoming transits…", invoked: "Transit timeline ready" },
30
+ explore_vedic_chart: { invoking: "Casting your Vedic chart…", invoked: "Vedic chart ready" },
31
+ explore_bazi_chart: { invoking: "Building your Four Pillars…", invoked: "Four Pillars ready" },
32
+ // Ephemeris data
33
+ ephemeris_natal_chart: { invoking: "Computing natal positions…", invoked: "Natal positions computed" },
34
+ ephemeris_planet_position: { invoking: "Locating the planet…", invoked: "Planet position computed" },
35
+ ephemeris_house_cusps: { invoking: "Computing house cusps…", invoked: "House cusps computed" },
36
+ ephemeris_angles_points: { invoking: "Computing chart angles…", invoked: "Chart angles computed" },
37
+ ephemeris_aspect_check: { invoking: "Measuring the aspect…", invoked: "Aspect measured" },
38
+ ephemeris_moon_phase: { invoking: "Checking the Moon…", invoked: "Moon phase found" },
39
+ ephemeris_next_lunar_phase: { invoking: "Finding the next lunar phase…", invoked: "Lunar phase dates found" },
40
+ ephemeris_next_eclipse: { invoking: "Finding the next eclipse…", invoked: "Eclipse found" },
41
+ ephemeris_retrograde_status: { invoking: "Checking retrograde motion…", invoked: "Retrograde status checked" },
42
+ ephemeris_transits: { invoking: "Searching for exact transit dates…", invoked: "Transit dates found" },
43
+ ephemeris_synastry: { invoking: "Computing synastry aspects…", invoked: "Synastry computed" },
44
+ ephemeris_relocation: { invoking: "Relocating your chart…", invoked: "Relocated chart computed" },
45
+ ephemeris_progressed_chart: { invoking: "Progressing your chart…", invoked: "Progressed chart computed" },
46
+ ephemeris_solar_return: { invoking: "Finding your solar return…", invoked: "Solar return computed" },
47
+ // Traditions
48
+ human_design_chart: { invoking: "Computing Human Design data…", invoked: "Human Design data computed" },
49
+ vedic_chart: { invoking: "Computing Vedic positions…", invoked: "Vedic positions computed" },
50
+ bazi_annual_pillar: { invoking: "Looking up the year pillar…", invoked: "Year pillar found" },
51
+ // Timing
52
+ ephemeris_electional: { invoking: "Scanning for the best timing windows…", invoked: "Timing windows found" },
53
+ electional_moment_analysis: { invoking: "Scoring this moment…", invoked: "Moment scored" },
54
+ electional_station_tracker: { invoking: "Finding retrograde and direct stations…", invoked: "Station dates found" },
55
+ // Astrocartography
56
+ acg_power_lines: { invoking: "Tracing your planetary lines…", invoked: "Planetary lines traced" },
57
+ acg_hits: { invoking: "Checking your lines at this location…", invoked: "Location lines found" },
58
+ // Geocoding
59
+ location_search: { invoking: "Looking up the place…", invoked: "Place found" },
60
+ timezone_resolve: { invoking: "Resolving the timezone…", invoked: "Timezone resolved" },
61
+ // Account + escape hatch
62
+ account_usage: { invoking: "Checking your account…", invoked: "Usage retrieved" },
63
+ dev_read_api: { invoking: "Calling the Open Ephemeris API…", invoked: "API response ready" },
64
+ dev_list_allowed: { invoking: "Listing available endpoints…", invoked: "Endpoints listed" },
65
+ // Auth (stdio only)
66
+ auth_login: { invoking: "Connecting your account…", invoked: "Account connected" },
67
+ auth_status: { invoking: "Checking the connection…", invoked: "Connection status ready" },
68
+ auth_logout: { invoking: "Disconnecting your account…", invoked: "Account disconnected" },
69
+ };
@@ -5,16 +5,12 @@ import { OUTPUT_SCHEMA_JSON } from "../output-schemas.js";
5
5
  const WALLET_TIERS = new Set(["explorer", "free", "payg", "wallet"]);
6
6
  registerTool({
7
7
  name: "account_usage",
8
- description: "Check the user's OpenEphemeris account usage and remaining credits. " +
9
- "Returns their plan tier, billing period, credits used / included / remaining, " +
10
- "percent of quota used, total API calls, and subscription status with renewal date.\n\n" +
11
- "✅ USE THIS TOOL FOR: 'How many credits do I have left?', 'What's my usage this month?', " +
12
- "'Am I close to my limit?', 'What plan am I on?', 'How do I upgrade or top up?'\n\n" +
13
- "CREDIT COST: Free (0 credits).\n\n" +
14
- "EXAMPLE: Check current usage:\n" +
15
- " (call with no arguments)\n\n" +
16
- "EXAMPLE: Check a past month:\n" +
17
- " month='2026-06'",
8
+ description: "Use this when the user asks 'how many credits do I have left', 'what's my usage this month', 'am " +
9
+ "I close to my limit', 'what plan am I on' or 'how do I upgrade or top up'. Returns plan tier, " +
10
+ "billing period, credits used / included / remaining, percent of quota used, total API calls, and " +
11
+ "subscription status with renewal date; pass month='YYYY-MM' for a past period.\n\n" +
12
+ "CREDIT COST: 0.\n\n" +
13
+ "Do not use to sign in or to check the login state — that is auth, not usage.",
18
14
  inputSchema: {
19
15
  type: "object",
20
16
  properties: {