@openephemeris/mcp-server 3.2.8 → 3.2.9
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.
|
@@ -6,7 +6,9 @@ registerTool({
|
|
|
6
6
|
"Returns a list of exact transit moments — when transiting planets form specified aspects " +
|
|
7
7
|
"to natal planet positions. Ideal for generating horoscope timelines, event forecasting, or " +
|
|
8
8
|
"finding optimal timing windows.\n\n" +
|
|
9
|
-
"
|
|
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
|
+
"CREDIT COST: 6 credits per call (1 for natal + 5 for transit search).\n\n" +
|
|
10
12
|
"EXAMPLE: Find all Saturn transits to the natal Sun/Moon for the next 6 months:\n" +
|
|
11
13
|
" natal_datetime='1990-04-15T14:30:00', natal_latitude=41.8781, natal_longitude=-87.6298,\n" +
|
|
12
14
|
" start_date='2026-01-01', end_date='2026-06-30', transiting_planets=['saturn']",
|
|
@@ -42,16 +44,7 @@ registerTool({
|
|
|
42
44
|
natal_points: {
|
|
43
45
|
type: "array",
|
|
44
46
|
items: { type: "string" },
|
|
45
|
-
description: "Natal point IDs
|
|
46
|
-
},
|
|
47
|
-
aspects: {
|
|
48
|
-
type: "array",
|
|
49
|
-
items: { type: "string", enum: ["conjunction", "opposition", "trine", "square", "sextile"] },
|
|
50
|
-
description: "Aspect types to include in the search. Defaults to all major aspects.",
|
|
51
|
-
},
|
|
52
|
-
orb: {
|
|
53
|
-
type: "number",
|
|
54
|
-
description: "Maximum orb in degrees (default: 1.0).",
|
|
47
|
+
description: "Natal point IDs whose exact longitudes should be targeted. E.g. ['sun', 'moon', 'asc', 'mc']. Omit for all core points.",
|
|
55
48
|
},
|
|
56
49
|
},
|
|
57
50
|
required: ["natal_datetime", "natal_latitude", "natal_longitude", "start_date", "end_date"],
|
|
@@ -59,6 +52,58 @@ registerTool({
|
|
|
59
52
|
},
|
|
60
53
|
handler: async (args) => {
|
|
61
54
|
validateRequired(args, ["natal_datetime", "natal_latitude", "natal_longitude", "start_date", "end_date"]);
|
|
55
|
+
// ── Step 1: Compute natal chart to extract real planetary longitudes ──
|
|
56
|
+
const natalBody = {
|
|
57
|
+
subject: {
|
|
58
|
+
name: "Transit Natal Subject",
|
|
59
|
+
birth_datetime: { iso: args.natal_datetime },
|
|
60
|
+
birth_location: {
|
|
61
|
+
latitude: { decimal: args.natal_latitude },
|
|
62
|
+
longitude: { decimal: args.natal_longitude }
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
const natalResult = await backendClient.post("/ephemeris/natal", natalBody);
|
|
67
|
+
// Extract ecliptic longitudes from natal chart planets
|
|
68
|
+
const targetDegrees = [];
|
|
69
|
+
const natalPositionMap = {};
|
|
70
|
+
// The natal response typically has a `planets` array or object
|
|
71
|
+
const planets = natalResult?.planets || natalResult?.data?.planets || [];
|
|
72
|
+
const planetArray = Array.isArray(planets) ? planets : Object.values(planets);
|
|
73
|
+
// Filter to requested natal points if specified
|
|
74
|
+
const wantedPoints = args.natal_points
|
|
75
|
+
? new Set(args.natal_points.map((p) => p.toLowerCase()))
|
|
76
|
+
: null;
|
|
77
|
+
for (const planet of planetArray) {
|
|
78
|
+
const name = (planet.name || planet.id || "").toLowerCase();
|
|
79
|
+
const lon = planet.longitude ?? planet.ecliptic_longitude ?? planet.lon;
|
|
80
|
+
if (typeof lon !== "number" || !isFinite(lon))
|
|
81
|
+
continue;
|
|
82
|
+
if (wantedPoints && !wantedPoints.has(name))
|
|
83
|
+
continue;
|
|
84
|
+
targetDegrees.push(Math.round(lon * 100) / 100); // 2 decimal places
|
|
85
|
+
natalPositionMap[name] = lon;
|
|
86
|
+
}
|
|
87
|
+
// Also extract angles (ASC, MC, DSC, IC) if present
|
|
88
|
+
const angles = natalResult?.angles || natalResult?.data?.angles;
|
|
89
|
+
if (angles) {
|
|
90
|
+
const angleEntries = Array.isArray(angles) ? angles : Object.entries(angles).map(([k, v]) => ({ name: k, ...(typeof v === 'object' ? v : { longitude: v }) }));
|
|
91
|
+
for (const angle of angleEntries) {
|
|
92
|
+
const name = (angle.name || angle.id || "").toLowerCase();
|
|
93
|
+
const lon = angle.longitude ?? angle.ecliptic_longitude ?? angle.lon;
|
|
94
|
+
if (typeof lon !== "number" || !isFinite(lon))
|
|
95
|
+
continue;
|
|
96
|
+
if (wantedPoints && !wantedPoints.has(name))
|
|
97
|
+
continue;
|
|
98
|
+
targetDegrees.push(Math.round(lon * 100) / 100);
|
|
99
|
+
natalPositionMap[name] = lon;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (targetDegrees.length === 0) {
|
|
103
|
+
throw new Error("Could not extract natal planet longitudes from the natal chart calculation. " +
|
|
104
|
+
"Please verify your natal_datetime, natal_latitude, and natal_longitude are correct.");
|
|
105
|
+
}
|
|
106
|
+
// ── Step 2: Search transits against those real natal longitudes ──
|
|
62
107
|
let startStr = args.start_date;
|
|
63
108
|
if (/^\d{4}-\d{2}-\d{2}$/.test(startStr)) {
|
|
64
109
|
startStr += "T00:00:00Z";
|
|
@@ -67,26 +112,19 @@ registerTool({
|
|
|
67
112
|
if (/^\d{4}-\d{2}-\d{2}$/.test(endStr)) {
|
|
68
113
|
endStr += "T23:59:59Z";
|
|
69
114
|
}
|
|
70
|
-
const
|
|
115
|
+
const transitBody = {
|
|
71
116
|
start_date: startStr,
|
|
72
117
|
end_date: endStr,
|
|
73
|
-
|
|
74
|
-
datetime: args.natal_datetime,
|
|
75
|
-
latitude: args.natal_latitude,
|
|
76
|
-
longitude: args.natal_longitude,
|
|
77
|
-
},
|
|
118
|
+
target_degrees: targetDegrees,
|
|
78
119
|
};
|
|
79
120
|
if (args.transiting_planets)
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if (Object.keys(searchCriteria).length > 0)
|
|
89
|
-
body.search_criteria = searchCriteria;
|
|
90
|
-
return await backendClient.request("POST", "/predictive/transits/search", { data: body });
|
|
121
|
+
transitBody.planet_names = args.transiting_planets;
|
|
122
|
+
const transitResult = await backendClient.request("POST", "/predictive/transits/search", { data: transitBody });
|
|
123
|
+
// Return combined context so the LLM knows what natal positions were targeted
|
|
124
|
+
return {
|
|
125
|
+
natal_positions: natalPositionMap,
|
|
126
|
+
target_degrees_used: targetDegrees,
|
|
127
|
+
transit_results: transitResult,
|
|
128
|
+
};
|
|
91
129
|
},
|
|
92
130
|
});
|