@openephemeris/mcp-server 3.5.3 → 3.6.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.
- package/CHANGELOG.md +28 -0
- package/README.md +2 -2
- package/config/dev-allowlist.json +2 -2
- package/dist/server-sse.js +1 -0
- package/dist/tools/specialized/acg.js +6 -0
- package/dist/tools/specialized/eclipse.js +37 -19
- package/dist/tools/specialized/electional.js +28 -9
- package/dist/tools/specialized/moon.js +92 -3
- package/dist/tools/specialized/returns.js +30 -17
- package/dist/tools/specialized/venus_star_points.js +6 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,34 @@ Version numbering follows [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [3.6.0] — 2026-04-17
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `ephemeris_next_lunar_phase` — new purpose-built tool for "when is the next new/full/quarter moon?" queries.
|
|
14
|
+
Replaces a 4–7 step multi-call chain with a single, credit-efficient call. Internally calculates a rolling
|
|
15
|
+
search window and filters the calendar API response so the LLM receives a clean, normalized answer.
|
|
16
|
+
|
|
17
|
+
### Changed (Tool Descriptions & LLM Routing)
|
|
18
|
+
- `ephemeris_moon_phase` — sharpened description to clarify it is point-in-time only and cannot answer
|
|
19
|
+
"upcoming date" questions. Adds explicit `→ use ephemeris_next_lunar_phase` redirect.
|
|
20
|
+
- `ephemeris_next_eclipse` — latitude/longitude are now **optional**. Omitting them triggers a global eclipse
|
|
21
|
+
search (`/eclipse/solar/global` or `/eclipse/lunar/global`), allowing Claude to answer "when is the next
|
|
22
|
+
total solar eclipse?" without forcing the user to specify a location.
|
|
23
|
+
- `acg_power_lines` — added explicit ❌/✅ routing guidance distinguishing it from `acg_hits`. This prevents
|
|
24
|
+
Claude from fetching full global GeoJSON geometry when the user's question is about a specific city.
|
|
25
|
+
- `acg_hits` — symmetric routing guidance added (mirrors `acg_power_lines` change above).
|
|
26
|
+
- `ephemeris_solar_return` — `target_datetime` is now optional; defaults to `new Date().toISOString()` so
|
|
27
|
+
Claude can answer "What does my solar return look like?" for the current year without stalling for input.
|
|
28
|
+
- `ephemeris_lunar_return` — same treatment; defaults to today so "what's my next lunar return?" resolves
|
|
29
|
+
in one call.
|
|
30
|
+
- `electional_station_tracker` — `planets` parameter now accepts human-readable names (`mercury,venus,mars`)
|
|
31
|
+
in addition to numeric IDs. Names are mapped to IDs in the handler. Adds `USE THIS TOOL FOR:` examples
|
|
32
|
+
for "Mercury retrograde" queries to aid routing.
|
|
33
|
+
- `venus_phase` — `date` is now optional (was incorrectly marked required despite the description
|
|
34
|
+
saying "Defaults to now"). Handler auto-fills today's date when omitted.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
10
38
|
## [3.5.3] — 2026-04-10
|
|
11
39
|
|
|
12
40
|
### Fixed
|
package/README.md
CHANGED
|
@@ -226,8 +226,8 @@ Generated by `npm run sync:readme` from `config/dev-allowlist.json` and the live
|
|
|
226
226
|
|
|
227
227
|
- Allowlisted operations: **22**
|
|
228
228
|
- Methods: `GET=4`, `POST=18`, `PUT=0`, `PATCH=0`, `DELETE=0`
|
|
229
|
-
- Registered tools (`OPENEPHEMERIS_PROFILE=dev`): **
|
|
230
|
-
- Typed tools: `auth_login`, `auth_logout`, `auth_status`, `chinese_bazi`, `dev_call`, `dev_list_allowed`, `electional_aspect_search`, `electional_moment_analysis`, `electional_station_tracker`, `ephemeris_angles_points`, `ephemeris_aspect_check`, `ephemeris_bi_wheel`, `ephemeris_chart_wheel`, `ephemeris_composite`, `ephemeris_composite_midpoint`, `ephemeris_dignities`, `ephemeris_electional`, `ephemeris_fixed_stars`, `ephemeris_hermetic_lots`, `ephemeris_house_cusps`, `ephemeris_lunar_return`, `ephemeris_midpoints`, `ephemeris_moon_phase`, `ephemeris_natal_batch`, `ephemeris_natal_chart`, `ephemeris_natal_transits`, `ephemeris_next_eclipse`, `ephemeris_overlay`, `ephemeris_planet_position`, `ephemeris_planetary_return`, `ephemeris_progressed_chart`, `ephemeris_relocation`, `ephemeris_retrograde_status`, `ephemeris_solar_return`, `ephemeris_synastry`, `ephemeris_transits`, `hd_opposition`, `hd_planetary_return`, `human_design_chart`, `human_design_composite`, `human_design_penta`, `vedic_chart`, `venus_eight_year_star`, `venus_elongations`, `venus_phase`, `venus_star_points`, `venus_star_points_conjunctions`, `venus_stations`
|
|
229
|
+
- Registered tools (`OPENEPHEMERIS_PROFILE=dev`): **49**
|
|
230
|
+
- Typed tools: `auth_login`, `auth_logout`, `auth_status`, `chinese_bazi`, `dev_call`, `dev_list_allowed`, `electional_aspect_search`, `electional_moment_analysis`, `electional_station_tracker`, `ephemeris_angles_points`, `ephemeris_aspect_check`, `ephemeris_bi_wheel`, `ephemeris_chart_wheel`, `ephemeris_composite`, `ephemeris_composite_midpoint`, `ephemeris_dignities`, `ephemeris_electional`, `ephemeris_fixed_stars`, `ephemeris_hermetic_lots`, `ephemeris_house_cusps`, `ephemeris_lunar_return`, `ephemeris_midpoints`, `ephemeris_moon_phase`, `ephemeris_natal_batch`, `ephemeris_natal_chart`, `ephemeris_natal_transits`, `ephemeris_next_eclipse`, `ephemeris_next_lunar_phase`, `ephemeris_overlay`, `ephemeris_planet_position`, `ephemeris_planetary_return`, `ephemeris_progressed_chart`, `ephemeris_relocation`, `ephemeris_retrograde_status`, `ephemeris_solar_return`, `ephemeris_synastry`, `ephemeris_transits`, `hd_opposition`, `hd_planetary_return`, `human_design_chart`, `human_design_composite`, `human_design_penta`, `vedic_chart`, `venus_eight_year_star`, `venus_elongations`, `venus_phase`, `venus_star_points`, `venus_star_points_conjunctions`, `venus_stations`
|
|
231
231
|
- Generic tools:
|
|
232
232
|
|
|
233
233
|
### Allowlist Families
|
|
@@ -104,8 +104,8 @@
|
|
|
104
104
|
"path": "/visualization/chart-wheel"
|
|
105
105
|
}
|
|
106
106
|
],
|
|
107
|
-
"last_generated_at": "2026-04-
|
|
108
|
-
"openapi_sha256": "
|
|
107
|
+
"last_generated_at": "2026-04-16T23:21:31.665Z",
|
|
108
|
+
"openapi_sha256": "2349bd8e3e36dad609b75cc61acb1722206990581085d986b0cdad79317296aa",
|
|
109
109
|
"candidates_get": [
|
|
110
110
|
{
|
|
111
111
|
"method": "GET",
|
package/dist/server-sse.js
CHANGED
|
@@ -61,6 +61,7 @@ async function validateApiKey(apiKey) {
|
|
|
61
61
|
try {
|
|
62
62
|
const resp = await axios.get(`${BACKEND_URL}/ephemeris/moon/phase`, {
|
|
63
63
|
headers: { "X-API-Key": apiKey },
|
|
64
|
+
params: { datetime: new Date().toISOString() }, // required param per OpenAPI spec
|
|
64
65
|
timeout: 5_000,
|
|
65
66
|
validateStatus: () => true, // don't throw on any status
|
|
66
67
|
});
|
|
@@ -11,6 +11,9 @@ registerTool({
|
|
|
11
11
|
"Returns GeoJSON LineStrings tracing each planetary angle line around the globe. " +
|
|
12
12
|
"These are the latitudinal lines where a planet was rising (AC), setting (DC), " +
|
|
13
13
|
"culminating (MC), or anti-culminating (IC) at birth.\n\n" +
|
|
14
|
+
"❌ NOT FOR: 'Is [city] good for me?' or 'What planets affect me in Tokyo?' " +
|
|
15
|
+
"→ For a specific city/location analysis, use acg_hits instead (faster and more relevant).\n" +
|
|
16
|
+
"✅ USE FOR: Getting the full global GeoJSON line geometry for map rendering or bulk geographic analysis.\n\n" +
|
|
14
17
|
"CREDIT COST: 10 credits per call.\n\n" +
|
|
15
18
|
"EXAMPLE: Saturn and Jupiter power lines for a chart born 1990-04-15 in Chicago:\n" +
|
|
16
19
|
" birth_datetime='1990-04-15T14:30:00', birth_latitude=41.8781, birth_longitude=-87.6298,\n" +
|
|
@@ -76,6 +79,9 @@ registerTool({
|
|
|
76
79
|
name: "acg_hits",
|
|
77
80
|
description: "Find all Astrocartography lines (power lines + aspect lines) passing near a specific location. " +
|
|
78
81
|
"Returns features sorted by distance, making it easy to interpret planetary influences at a place.\n\n" +
|
|
82
|
+
"✅ USE THIS TOOL FOR: 'Is [city] good for me?', 'What planets affect me in Tokyo?', " +
|
|
83
|
+
"'What ACG lines run through Paris for my chart?', 'Which cities are under my Jupiter line?'\n" +
|
|
84
|
+
"❌ NOT FOR: Full global map geometry → use acg_power_lines for that instead.\n\n" +
|
|
79
85
|
"CREDIT COST: 15 credits per call.\n\n" +
|
|
80
86
|
"EXAMPLE: All ACG lines within 3° of Paris for a chart born 1990-04-15 in Chicago:\n" +
|
|
81
87
|
" birth_datetime='1990-04-15T14:30:00', birth_latitude=41.8781, birth_longitude=-87.6298,\n" +
|
|
@@ -2,13 +2,19 @@ import { registerTool, validateRequired, validateCoordinates } from "../index.js
|
|
|
2
2
|
import { getActiveClient } from "../../backend/client.js";
|
|
3
3
|
registerTool({
|
|
4
4
|
name: "ephemeris_next_eclipse",
|
|
5
|
-
description: "Find the next solar or lunar eclipse
|
|
6
|
-
"Returns the eclipse type, date/time of maximum, magnitude, duration of totality (if any)
|
|
7
|
-
"
|
|
5
|
+
description: "Find the next solar or lunar eclipse. " +
|
|
6
|
+
"Returns the eclipse type, date/time of maximum, magnitude, and duration of totality (if any).\n\n" +
|
|
7
|
+
"📍 LOCATION OPTIONAL:\n" +
|
|
8
|
+
" • WITH latitude+longitude → returns local contact times and visibility for that specific location.\n" +
|
|
9
|
+
" • WITHOUT latitude+longitude → returns the next global eclipse of that type (no location needed).\n\n" +
|
|
10
|
+
"USE THIS TOOL FOR: 'When is the next solar eclipse?', 'When is the next total lunar eclipse?', " +
|
|
11
|
+
"'Will there be an eclipse visible from Tokyo?'\n\n" +
|
|
8
12
|
"CREDIT COST: 1 credit per call.\n\n" +
|
|
9
|
-
"EXAMPLE:
|
|
13
|
+
"EXAMPLE: Next solar eclipse globally (no location needed):\n" +
|
|
14
|
+
" eclipse_type='solar'\n\n" +
|
|
15
|
+
"EXAMPLE: Next solar eclipse visible from New York:\n" +
|
|
10
16
|
" eclipse_type='solar', latitude=40.7128, longitude=-74.006\n\n" +
|
|
11
|
-
"EXAMPLE:
|
|
17
|
+
"EXAMPLE: Next lunar eclipse from London:\n" +
|
|
12
18
|
" eclipse_type='lunar', latitude=51.5074, longitude=-0.1278",
|
|
13
19
|
inputSchema: {
|
|
14
20
|
type: "object",
|
|
@@ -16,35 +22,47 @@ registerTool({
|
|
|
16
22
|
eclipse_type: {
|
|
17
23
|
type: "string",
|
|
18
24
|
enum: ["solar", "lunar", "any"],
|
|
19
|
-
description: "Eclipse type to search for. Use 'any' for whichever comes first.",
|
|
25
|
+
description: "Eclipse type to search for. Use 'any' for whichever comes first (solar or lunar).",
|
|
20
26
|
},
|
|
21
27
|
latitude: {
|
|
22
28
|
type: "number",
|
|
23
|
-
description: "Observer latitude in decimal degrees (
|
|
29
|
+
description: "Observer latitude in decimal degrees (optional). When provided with longitude, returns local contact times and visibility.",
|
|
24
30
|
},
|
|
25
31
|
longitude: {
|
|
26
32
|
type: "number",
|
|
27
|
-
description: "Observer longitude in decimal degrees (
|
|
33
|
+
description: "Observer longitude in decimal degrees (optional, pair with latitude).",
|
|
28
34
|
},
|
|
29
35
|
after_date: {
|
|
30
36
|
type: "string",
|
|
31
37
|
description: "ISO 8601 date to search after (e.g. '2026-01-01'). Defaults to today if omitted.",
|
|
32
38
|
},
|
|
33
39
|
},
|
|
34
|
-
required: ["eclipse_type"
|
|
40
|
+
required: ["eclipse_type"],
|
|
35
41
|
additionalProperties: false,
|
|
36
42
|
},
|
|
37
43
|
handler: async (args) => {
|
|
38
|
-
validateRequired(args, ["eclipse_type"
|
|
44
|
+
validateRequired(args, ["eclipse_type"]);
|
|
39
45
|
validateCoordinates(args, "latitude", "longitude");
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
const hasLocation = args.latitude != null && args.longitude != null;
|
|
47
|
+
if (hasLocation) {
|
|
48
|
+
// Local visibility query
|
|
49
|
+
const params = {
|
|
50
|
+
lat: args.latitude,
|
|
51
|
+
lon: args.longitude,
|
|
52
|
+
};
|
|
53
|
+
if (args.after_date)
|
|
54
|
+
params.date = args.after_date;
|
|
55
|
+
if (args.eclipse_type && args.eclipse_type !== "any")
|
|
56
|
+
params.type = args.eclipse_type;
|
|
57
|
+
return await getActiveClient().request("GET", "/eclipse/next-visible", { params, timeoutMs: 60_000 });
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
// Global query — route to global endpoint
|
|
61
|
+
const params = {};
|
|
62
|
+
if (args.after_date)
|
|
63
|
+
params.date = args.after_date;
|
|
64
|
+
const endpoint = args.eclipse_type === "lunar" ? "/eclipse/lunar/global" : "/eclipse/solar/global";
|
|
65
|
+
return await getActiveClient().request("GET", endpoint, { params, timeoutMs: 60_000 });
|
|
66
|
+
}
|
|
49
67
|
},
|
|
50
68
|
});
|
|
@@ -117,21 +117,24 @@ registerTool({
|
|
|
117
117
|
});
|
|
118
118
|
},
|
|
119
119
|
});
|
|
120
|
-
// GET /electional/station-tracker
|
|
121
120
|
registerTool({
|
|
122
121
|
name: "electional_station_tracker",
|
|
123
122
|
description: "Find all upcoming retrograde and direct stations for planets in a date range. " +
|
|
124
|
-
"Returns exact station times, longitudes, and signs
|
|
125
|
-
"Mercury retrograde, Venus retrograde,
|
|
123
|
+
"Returns exact station times, longitudes, and signs.\n\n" +
|
|
124
|
+
"USE THIS TOOL FOR: 'When does Mercury go retrograde?', 'Is Venus retrograde this year?', " +
|
|
125
|
+
"'What planets station this month?', 'When does Mars go direct?'\n\n" +
|
|
126
|
+
"All required fields have smart defaults (searches the next 90 days from today).\n\n" +
|
|
126
127
|
"CREDIT COST: 3 credits per call.\n\n" +
|
|
127
|
-
"EXAMPLE:
|
|
128
|
-
"
|
|
128
|
+
"EXAMPLE: Mercury and Venus stations in the next 3 months (all defaults):\n" +
|
|
129
|
+
" (no args required, will auto-scan next 90 days for all inner planets)\n\n" +
|
|
130
|
+
"EXAMPLE: Outer planet stations in 2026:\n" +
|
|
131
|
+
" start_date='2026-01-01', end_date='2026-12-31', planets='jupiter,saturn,uranus,neptune,pluto'",
|
|
129
132
|
inputSchema: {
|
|
130
133
|
type: "object",
|
|
131
134
|
properties: {
|
|
132
135
|
start_date: {
|
|
133
136
|
type: "string",
|
|
134
|
-
description: "Start date (ISO 8601). Defaults to
|
|
137
|
+
description: "Start date (ISO 8601). Defaults to today.",
|
|
135
138
|
},
|
|
136
139
|
end_date: {
|
|
137
140
|
type: "string",
|
|
@@ -139,7 +142,9 @@ registerTool({
|
|
|
139
142
|
},
|
|
140
143
|
planets: {
|
|
141
144
|
type: "string",
|
|
142
|
-
description: "Comma-separated planet
|
|
145
|
+
description: "Comma-separated planet names to track. " +
|
|
146
|
+
"E.g. 'mercury,venus,mars' or 'jupiter,saturn,uranus,neptune,pluto'. " +
|
|
147
|
+
"Defaults to Mercury through Saturn (inner + classical planets).",
|
|
143
148
|
},
|
|
144
149
|
format: {
|
|
145
150
|
type: "string",
|
|
@@ -152,12 +157,26 @@ registerTool({
|
|
|
152
157
|
},
|
|
153
158
|
handler: async (args) => {
|
|
154
159
|
const query = {};
|
|
160
|
+
// Map human-readable planet names to IDs if needed
|
|
161
|
+
const PLANET_NAME_MAP = {
|
|
162
|
+
mercury: "2", venus: "3", mars: "4",
|
|
163
|
+
jupiter: "5", saturn: "6", uranus: "7",
|
|
164
|
+
neptune: "8", pluto: "9",
|
|
165
|
+
};
|
|
155
166
|
if (args.start_date)
|
|
156
167
|
query.start_date = args.start_date;
|
|
157
168
|
if (args.end_date)
|
|
158
169
|
query.end_date = args.end_date;
|
|
159
|
-
if (args.planets)
|
|
160
|
-
|
|
170
|
+
if (args.planets) {
|
|
171
|
+
// Accept both names ('mercury,venus') and numeric IDs ('2,3')
|
|
172
|
+
query.planets = args.planets
|
|
173
|
+
.split(",")
|
|
174
|
+
.map((p) => {
|
|
175
|
+
const name = p.trim().toLowerCase();
|
|
176
|
+
return PLANET_NAME_MAP[name] ?? p.trim();
|
|
177
|
+
})
|
|
178
|
+
.join(",");
|
|
179
|
+
}
|
|
161
180
|
if (args.format)
|
|
162
181
|
query.format = args.format;
|
|
163
182
|
return await getActiveClient().request("GET", "/electional/station-tracker", {
|
|
@@ -2,9 +2,12 @@ import { registerTool, validateCoordinates } from "../index.js";
|
|
|
2
2
|
import { getActiveClient } from "../../backend/client.js";
|
|
3
3
|
registerTool({
|
|
4
4
|
name: "ephemeris_moon_phase",
|
|
5
|
-
description: "Get the current
|
|
6
|
-
"(New, Waxing Crescent, First Quarter,
|
|
7
|
-
"
|
|
5
|
+
description: "Get the Moon's current phase angle, illumination, and void-of-course status AT a specific " +
|
|
6
|
+
"point in time. Returns phase name (New, Waxing Crescent, First Quarter, etc.), exact angle, " +
|
|
7
|
+
"illumination %, and next void-of-course period.\n\n" +
|
|
8
|
+
"⚠️ THIS TOOL ANSWERS: 'What phase is the moon in right now (or at a given datetime)?'\n" +
|
|
9
|
+
"❌ THIS TOOL DOES NOT ANSWER: 'When is the next new moon / full moon?'\n" +
|
|
10
|
+
"→ For upcoming phase DATES use ephemeris_next_lunar_phase instead.\n\n" +
|
|
8
11
|
"CREDIT COST: 1 credit per call.\n\n" +
|
|
9
12
|
"If no datetime is provided, returns the current (live) moon phase.\n\n" +
|
|
10
13
|
"EXAMPLE: Get moon phase for a specific date/time:\n" +
|
|
@@ -49,3 +52,89 @@ registerTool({
|
|
|
49
52
|
};
|
|
50
53
|
},
|
|
51
54
|
});
|
|
55
|
+
registerTool({
|
|
56
|
+
name: "ephemeris_next_lunar_phase",
|
|
57
|
+
description: "Find the next occurrence of a specific Moon phase after a given date. " +
|
|
58
|
+
"Returns the exact UTC datetime, zodiac sign, and degree.\n\n" +
|
|
59
|
+
"✅ USE THIS TOOL FOR: 'When is the next new moon?', 'When is the next full moon?', " +
|
|
60
|
+
"'What date is the next quarter moon?', or any question about UPCOMING phase dates.\n\n" +
|
|
61
|
+
"CREDIT COST: 1 credit per call.\n\n" +
|
|
62
|
+
"EXAMPLE: Find the next new moon:\n" +
|
|
63
|
+
" phase='new_moon'\n\n" +
|
|
64
|
+
"EXAMPLE: Find the next full moon after a specific date:\n" +
|
|
65
|
+
" phase='full_moon', after_date='2026-06-01'\n\n" +
|
|
66
|
+
"EXAMPLE: Find the next 3 full moons:\n" +
|
|
67
|
+
" phase='full_moon', count=3",
|
|
68
|
+
inputSchema: {
|
|
69
|
+
type: "object",
|
|
70
|
+
properties: {
|
|
71
|
+
phase: {
|
|
72
|
+
type: "string",
|
|
73
|
+
enum: ["new_moon", "full_moon", "first_quarter", "last_quarter"],
|
|
74
|
+
description: "The lunar phase to find. " +
|
|
75
|
+
"'new_moon' = conjunction (0° Sun-Moon, start of cycle). " +
|
|
76
|
+
"'full_moon' = opposition (180°). " +
|
|
77
|
+
"'first_quarter' = 90° waxing. " +
|
|
78
|
+
"'last_quarter' = 270° waning.",
|
|
79
|
+
},
|
|
80
|
+
after_date: {
|
|
81
|
+
type: "string",
|
|
82
|
+
description: "ISO 8601 date to search from (e.g. '2026-06-01'). Defaults to today (UTC) if omitted.",
|
|
83
|
+
},
|
|
84
|
+
count: {
|
|
85
|
+
type: "integer",
|
|
86
|
+
description: "Number of upcoming occurrences to return (1-12). Default 1.",
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
required: ["phase"],
|
|
90
|
+
additionalProperties: false,
|
|
91
|
+
},
|
|
92
|
+
handler: async (args) => {
|
|
93
|
+
// Map our clean enum to display names the calendar API returns
|
|
94
|
+
const phaseNameMap = {
|
|
95
|
+
new_moon: "New Moon",
|
|
96
|
+
full_moon: "Full Moon",
|
|
97
|
+
first_quarter: "First Quarter",
|
|
98
|
+
last_quarter: "Last Quarter",
|
|
99
|
+
};
|
|
100
|
+
const phaseLabel = phaseNameMap[args.phase] ?? args.phase;
|
|
101
|
+
const count = Math.max(1, Math.min(12, args.count ?? 1));
|
|
102
|
+
// Build a search window wide enough to capture `count` occurrences.
|
|
103
|
+
// Lunar cycle is ~29.5 days, so count * 35 days guarantees coverage.
|
|
104
|
+
const startDate = args.after_date
|
|
105
|
+
? args.after_date.substring(0, 10)
|
|
106
|
+
: new Date().toISOString().substring(0, 10);
|
|
107
|
+
const endMs = new Date(startDate + "T00:00:00Z").getTime() + count * 35 * 24 * 60 * 60 * 1000;
|
|
108
|
+
const endDate = new Date(endMs).toISOString().substring(0, 10);
|
|
109
|
+
const result = await getActiveClient().request("GET", "/calendar/astrology/moon-phases", {
|
|
110
|
+
params: { start_date: startDate, end_date: endDate },
|
|
111
|
+
});
|
|
112
|
+
// Normalise response shape — API may return array, or { phases: [...] }, or { data: [...] }
|
|
113
|
+
const allPhases = Array.isArray(result)
|
|
114
|
+
? result
|
|
115
|
+
: result?.phases ?? result?.data ?? result?.events ?? [];
|
|
116
|
+
const matching = allPhases
|
|
117
|
+
.filter((p) => {
|
|
118
|
+
const name = (p.phase_name ?? p.name ?? p.phase ?? "").toLowerCase();
|
|
119
|
+
return (name === phaseLabel.toLowerCase() ||
|
|
120
|
+
name === args.phase.replace("_", " ").toLowerCase());
|
|
121
|
+
})
|
|
122
|
+
.slice(0, count);
|
|
123
|
+
if (matching.length === 0) {
|
|
124
|
+
// Return full calendar as fallback so the LLM can inspect it
|
|
125
|
+
return {
|
|
126
|
+
requested_phase: phaseLabel,
|
|
127
|
+
search_window: { start_date: startDate, end_date: endDate },
|
|
128
|
+
result_count: 0,
|
|
129
|
+
note: "No matching phase found in window. Full calendar returned for inspection.",
|
|
130
|
+
full_calendar: allPhases,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
requested_phase: phaseLabel,
|
|
135
|
+
after_date: startDate,
|
|
136
|
+
results: matching,
|
|
137
|
+
result_count: matching.length,
|
|
138
|
+
};
|
|
139
|
+
},
|
|
140
|
+
});
|
|
@@ -3,21 +3,26 @@ import { getActiveClient } from "../../backend/client.js";
|
|
|
3
3
|
// POST /predictive/returns/solar
|
|
4
4
|
registerTool({
|
|
5
5
|
name: "ephemeris_solar_return",
|
|
6
|
-
description: "Calculate the exact Solar Return date/time — when the Sun returns to its natal ecliptic longitude
|
|
7
|
-
"
|
|
6
|
+
description: "Calculate the exact Solar Return date/time — when the Sun returns to its natal ecliptic longitude " +
|
|
7
|
+
"(happens once per year near the birthday). Returns the Solar Return chart for the year ahead.\n\n" +
|
|
8
8
|
"CREDIT COST: 5 credits per call.\n\n" +
|
|
9
|
-
"
|
|
10
|
-
"
|
|
9
|
+
"TARGET DATE: Provide target_datetime near the desired birthday year. " +
|
|
10
|
+
"If omitted, defaults to the current year's solar return.\n\n" +
|
|
11
|
+
"EXAMPLE: Solar return for someone born 1985-06-21 (current year):\n" +
|
|
12
|
+
" birth_datetime='1985-06-21T14:00:00Z'\n\n" +
|
|
13
|
+
"EXAMPLE: Solar return for 2027 specifically:\n" +
|
|
14
|
+
" birth_datetime='1985-06-21T14:00:00Z', target_datetime='2027-01-01T00:00:00Z'",
|
|
11
15
|
inputSchema: {
|
|
12
16
|
type: "object",
|
|
13
17
|
properties: {
|
|
14
18
|
birth_datetime: {
|
|
15
19
|
type: "string",
|
|
16
|
-
description: "ISO 8601 birth date/time.
|
|
20
|
+
description: "ISO 8601 birth date/time. Include offset or Z (e.g. '1985-06-21T14:00:00-05:00').",
|
|
17
21
|
},
|
|
18
22
|
target_datetime: {
|
|
19
23
|
type: "string",
|
|
20
|
-
description: "Date/time near which to find the Solar Return (ISO 8601
|
|
24
|
+
description: "Date/time near which to find the Solar Return (ISO 8601 with offset or Z). " +
|
|
25
|
+
"Defaults to approximately 1 year from now if omitted.",
|
|
21
26
|
},
|
|
22
27
|
birth_latitude: {
|
|
23
28
|
type: "number",
|
|
@@ -29,21 +34,23 @@ registerTool({
|
|
|
29
34
|
},
|
|
30
35
|
return_latitude: {
|
|
31
36
|
type: "number",
|
|
32
|
-
description: "Location latitude for the return chart (optional).",
|
|
37
|
+
description: "Location latitude for the return chart (optional, if different from birth location).",
|
|
33
38
|
},
|
|
34
39
|
return_longitude: {
|
|
35
40
|
type: "number",
|
|
36
41
|
description: "Location longitude for the return chart (optional).",
|
|
37
42
|
},
|
|
38
43
|
},
|
|
39
|
-
required: ["birth_datetime"
|
|
44
|
+
required: ["birth_datetime"],
|
|
40
45
|
additionalProperties: false,
|
|
41
46
|
},
|
|
42
47
|
handler: async (args) => {
|
|
43
|
-
validateRequired(args, ["birth_datetime"
|
|
48
|
+
validateRequired(args, ["birth_datetime"]);
|
|
49
|
+
// Default target to current year if not provided
|
|
50
|
+
const targetDt = args.target_datetime ?? new Date().toISOString();
|
|
44
51
|
const body = {
|
|
45
52
|
birth_datetime: { iso: args.birth_datetime },
|
|
46
|
-
target_datetime: { iso:
|
|
53
|
+
target_datetime: { iso: targetDt },
|
|
47
54
|
};
|
|
48
55
|
if (args.birth_latitude != null && args.birth_longitude != null) {
|
|
49
56
|
body.location = {
|
|
@@ -66,18 +73,23 @@ registerTool({
|
|
|
66
73
|
description: "Calculate the exact Lunar Return date/time — when the Moon returns to its natal ecliptic longitude (~every 27.3 days). " +
|
|
67
74
|
"Used to cast the monthly Lunar Return chart.\n\n" +
|
|
68
75
|
"CREDIT COST: 5 credits per call.\n\n" +
|
|
69
|
-
"
|
|
70
|
-
"
|
|
76
|
+
"TARGET DATE: Provide target_datetime near the month you want. " +
|
|
77
|
+
"If omitted, defaults to the next upcoming lunar return from today.\n\n" +
|
|
78
|
+
"EXAMPLE: Next lunar return after today for someone born 1990-04-15:\n" +
|
|
79
|
+
" birth_datetime='1990-04-15T14:30:00Z'\n\n" +
|
|
80
|
+
"EXAMPLE: Lunar return nearest March 2026:\n" +
|
|
81
|
+
" birth_datetime='1990-04-15T14:30:00Z', target_datetime='2026-03-15T00:00:00Z'",
|
|
71
82
|
inputSchema: {
|
|
72
83
|
type: "object",
|
|
73
84
|
properties: {
|
|
74
85
|
birth_datetime: {
|
|
75
86
|
type: "string",
|
|
76
|
-
description: "ISO 8601 birth date/time.
|
|
87
|
+
description: "ISO 8601 birth date/time. Include offset or Z.",
|
|
77
88
|
},
|
|
78
89
|
target_datetime: {
|
|
79
90
|
type: "string",
|
|
80
|
-
description: "Date/time near which to find the Lunar Return (ISO 8601
|
|
91
|
+
description: "Date/time near which to find the Lunar Return (ISO 8601 with offset or Z). " +
|
|
92
|
+
"Defaults to today if omitted (finds next return).",
|
|
81
93
|
},
|
|
82
94
|
birth_latitude: {
|
|
83
95
|
type: "number",
|
|
@@ -88,14 +100,15 @@ registerTool({
|
|
|
88
100
|
description: "Birth longitude in decimal degrees (optional).",
|
|
89
101
|
},
|
|
90
102
|
},
|
|
91
|
-
required: ["birth_datetime"
|
|
103
|
+
required: ["birth_datetime"],
|
|
92
104
|
additionalProperties: false,
|
|
93
105
|
},
|
|
94
106
|
handler: async (args) => {
|
|
95
|
-
validateRequired(args, ["birth_datetime"
|
|
107
|
+
validateRequired(args, ["birth_datetime"]);
|
|
108
|
+
const targetDt = args.target_datetime ?? new Date().toISOString();
|
|
96
109
|
const body = {
|
|
97
110
|
birth_datetime: { iso: args.birth_datetime },
|
|
98
|
-
target_datetime: { iso:
|
|
111
|
+
target_datetime: { iso: targetDt },
|
|
99
112
|
};
|
|
100
113
|
if (args.birth_latitude != null && args.birth_longitude != null) {
|
|
101
114
|
body.location = {
|
|
@@ -104,24 +104,25 @@ registerTool({
|
|
|
104
104
|
description: "Get the current Venus phase — morning star, evening star, or combust. Returns Venus " +
|
|
105
105
|
"longitude, Sun longitude, elongation, retrograde/cazimi status.\n\n" +
|
|
106
106
|
"CREDIT COST: 1 credit per call.\n\n" +
|
|
107
|
-
"EXAMPLE: Venus phase right now:\n" +
|
|
107
|
+
"EXAMPLE: Venus phase right now (omit date):\n" +
|
|
108
|
+
" (no args required)\n\n" +
|
|
109
|
+
"EXAMPLE: Venus phase on a specific date:\n" +
|
|
108
110
|
" date='2026-03-20'",
|
|
109
111
|
inputSchema: {
|
|
110
112
|
type: "object",
|
|
111
113
|
properties: {
|
|
112
|
-
date: { type: "string", description: "Date to check (ISO 8601).
|
|
114
|
+
date: { type: "string", description: "Date to check (ISO 8601). Omit for today's phase." },
|
|
113
115
|
format: { type: "string", enum: ["json", "llm"], description: "Output format." },
|
|
114
116
|
},
|
|
115
|
-
required: [
|
|
117
|
+
required: [],
|
|
116
118
|
additionalProperties: false,
|
|
117
119
|
},
|
|
118
120
|
handler: async (args) => {
|
|
119
|
-
validateRequired(args, ["date"]);
|
|
120
121
|
const query = {};
|
|
121
122
|
if (args.format)
|
|
122
123
|
query.format = args.format;
|
|
123
124
|
return await getActiveClient().request("POST", "/ephemeris/venus-star-points/phase", {
|
|
124
|
-
data: { date: args.date },
|
|
125
|
+
data: { date: args.date ?? new Date().toISOString().split("T")[0] },
|
|
125
126
|
params: query,
|
|
126
127
|
});
|
|
127
128
|
},
|