@openephemeris/mcp-server 3.6.0 → 3.8.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 +24 -2
- package/README.md +7 -7
- package/config/dev-allowlist.json +15 -2
- package/dist/backend/client.js +7 -3
- package/dist/tools/index.js +2 -0
- package/dist/tools/specialized/acg.js +21 -1
- package/dist/tools/specialized/comparative.js +8 -5
- package/dist/tools/specialized/ephemeris_extended.js +68 -10
- package/dist/tools/specialized/hd_bodygraph.d.ts +1 -0
- package/dist/tools/specialized/hd_bodygraph.js +51 -0
- package/dist/tools/specialized/moon.js +20 -6
- package/dist/tools/specialized/returns.js +21 -4
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,28 @@ Version numbering follows [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [3.8.0] — 2026-04-21
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `human_design_bodygraph` — new dedicated tool for generating Human Design Bodygraph images
|
|
14
|
+
(PNG or SVG). Renders all 9 centers, 36 channels, and 64 gates with Personality/Design
|
|
15
|
+
color-coding. Supports `light`, `dark`, and `mono` styles. Developer tier, 2 credits.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
- **HD Bodygraph: missing 10-57 channel** — The "Channel of Perfected Form" (Gate 10 ↔ Gate 57)
|
|
19
|
+
was silently dropped from the visualization geometry while the math engine still reported it as
|
|
20
|
+
active. Charts with both gates activated now correctly render the channel.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## [3.7.0] — 2026-04-19
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
- **Personal Tier Removed**: The platform no longer offers the Personal subscription tier.
|
|
28
|
+
- `ephemeris_transits` and `ephemeris_natal_transits` moved from Personal to the free **Explorer** tier.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
10
32
|
## [3.6.0] — 2026-04-17
|
|
11
33
|
|
|
12
34
|
### Added
|
|
@@ -132,9 +154,9 @@ Version numbering follows [Semantic Versioning](https://semver.org/).
|
|
|
132
154
|
- `ephemeris_progressed_chart` — secondary progressions (day-for-a-year method)
|
|
133
155
|
|
|
134
156
|
### Changed
|
|
135
|
-
-
|
|
136
|
-
- Startup tier: 15,000 credits/month (formerly 'Pro')
|
|
157
|
+
- Explorer tier: transit endpoints now open at free tier (no subscription required)
|
|
137
158
|
- Developer tier: 75,000 credits/month
|
|
159
|
+
- Startup tier: 15,000 credits/month (formerly 'Pro')
|
|
138
160
|
|
|
139
161
|
---
|
|
140
162
|
|
package/README.md
CHANGED
|
@@ -110,8 +110,8 @@ This package runs as a local stdio MCP server. Remote-only clients require a hos
|
|
|
110
110
|
| Category | Tool | Tier |
|
|
111
111
|
|---|---|---|
|
|
112
112
|
| Natal chart | `ephemeris_natal_chart` | Explorer |
|
|
113
|
-
| Transit forecast | `ephemeris_transits` |
|
|
114
|
-
| Transit chart snapshot | `ephemeris_natal_transits` |
|
|
113
|
+
| Transit forecast | `ephemeris_transits` | Explorer |
|
|
114
|
+
| Transit chart snapshot | `ephemeris_natal_transits` | Explorer |
|
|
115
115
|
| Moon phase / VOC | `ephemeris_moon_phase` | Explorer |
|
|
116
116
|
| Eclipse next visible | `ephemeris_next_eclipse` | Explorer |
|
|
117
117
|
| Electional window | `ephemeris_electional` | Developer |
|
|
@@ -224,10 +224,10 @@ OpenEphemeris API
|
|
|
224
224
|
|
|
225
225
|
Generated by `npm run sync:readme` from `config/dev-allowlist.json` and the live tool registry.
|
|
226
226
|
|
|
227
|
-
- Allowlisted operations: **
|
|
228
|
-
- Methods: `GET=4`, `POST=
|
|
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_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`
|
|
227
|
+
- Allowlisted operations: **23**
|
|
228
|
+
- Methods: `GET=4`, `POST=19`, `PUT=0`, `PATCH=0`, `DELETE=0`
|
|
229
|
+
- Registered tools (`OPENEPHEMERIS_PROFILE=dev`): **52**
|
|
230
|
+
- Typed tools: `acg_hits`, `acg_power_lines`, `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_bodygraph`, `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
|
|
@@ -239,7 +239,7 @@ Generated by `npm run sync:readme` from `config/dev-allowlist.json` and the live
|
|
|
239
239
|
| `electional` | 4 | `GET /electional/aspect-search`, `GET /electional/find-window` |
|
|
240
240
|
| `ephemeris` | 1 | `POST /ephemeris/relocation` |
|
|
241
241
|
| `predictive` | 6 | `POST /predictive/returns`, `POST /predictive/returns/lunar` |
|
|
242
|
-
| `visualization` |
|
|
242
|
+
| `visualization` | 3 | `POST /visualization/bi-wheel`, `POST /visualization/bodygraph` |
|
|
243
243
|
<!-- GENERATED:RUNTIME_SNAPSHOT:END -->
|
|
244
244
|
|
|
245
245
|
## Why OpenEphemeris for AI Agents?
|
|
@@ -99,13 +99,17 @@
|
|
|
99
99
|
"method": "POST",
|
|
100
100
|
"path": "/visualization/bi-wheel"
|
|
101
101
|
},
|
|
102
|
+
{
|
|
103
|
+
"method": "POST",
|
|
104
|
+
"path": "/visualization/bodygraph"
|
|
105
|
+
},
|
|
102
106
|
{
|
|
103
107
|
"method": "POST",
|
|
104
108
|
"path": "/visualization/chart-wheel"
|
|
105
109
|
}
|
|
106
110
|
],
|
|
107
|
-
"last_generated_at": "2026-04-
|
|
108
|
-
"openapi_sha256": "
|
|
111
|
+
"last_generated_at": "2026-04-22T01:16:37.771Z",
|
|
112
|
+
"openapi_sha256": "c649b6a9ba32e1576f9f12d2d78f1706d5bc279d7733a29f1e69a8ca042024d6",
|
|
109
113
|
"candidates_get": [
|
|
110
114
|
{
|
|
111
115
|
"method": "GET",
|
|
@@ -973,6 +977,14 @@
|
|
|
973
977
|
],
|
|
974
978
|
"operationId": "generate_bi_wheel_visualization_bi_wheel_post"
|
|
975
979
|
},
|
|
980
|
+
{
|
|
981
|
+
"method": "POST",
|
|
982
|
+
"path": "/visualization/bodygraph",
|
|
983
|
+
"tags": [
|
|
984
|
+
"visualization"
|
|
985
|
+
],
|
|
986
|
+
"operationId": "GenerateBodygraphVisualization_visualization_bodygraph_post"
|
|
987
|
+
},
|
|
976
988
|
{
|
|
977
989
|
"method": "POST",
|
|
978
990
|
"path": "/visualization/chart-wheel",
|
|
@@ -1003,6 +1015,7 @@
|
|
|
1003
1015
|
"Non-GET allowlisted: POST /predictive/time-lords/profections",
|
|
1004
1016
|
"Non-GET allowlisted: POST /predictive/time-lords/zodiacal-releasing",
|
|
1005
1017
|
"Non-GET allowlisted: POST /visualization/bi-wheel",
|
|
1018
|
+
"Non-GET allowlisted: POST /visualization/bodygraph",
|
|
1006
1019
|
"Non-GET allowlisted: POST /visualization/chart-wheel"
|
|
1007
1020
|
]
|
|
1008
1021
|
}
|
package/dist/backend/client.js
CHANGED
|
@@ -8,6 +8,7 @@ const RETRYABLE_STATUSES = new Set([429, 502, 503, 504]);
|
|
|
8
8
|
const RETRYABLE_CODES = new Set(["ECONNRESET", "ETIMEDOUT", "ENOTFOUND", "EAI_AGAIN"]);
|
|
9
9
|
const BINARY_ENDPOINT_PREFIXES = [
|
|
10
10
|
"/visualization/bi-wheel",
|
|
11
|
+
"/visualization/bodygraph",
|
|
11
12
|
"/visualization/chart-wheel",
|
|
12
13
|
"/comparative/visualization/bi-wheel",
|
|
13
14
|
"/comparative/visualization/chart-wheel",
|
|
@@ -259,10 +260,13 @@ export class BackendClient {
|
|
|
259
260
|
`All chart positions, aspects, and timing data MUST come from the API.`, 402, "quota_exceeded", false, UPGRADE_URL);
|
|
260
261
|
}
|
|
261
262
|
if (status === 403) {
|
|
262
|
-
return new BackendError(
|
|
263
|
+
return new BackendError(`This feature is part of a higher-tier plan.\n\n` +
|
|
263
264
|
`${msg}\n\n` +
|
|
264
|
-
`
|
|
265
|
-
`
|
|
265
|
+
`Let the user know warmly: this particular capability is available on a ` +
|
|
266
|
+
`higher plan tier. Share the upgrade link (${UPGRADE_URL}) as a helpful suggestion, ` +
|
|
267
|
+
`not as a gate. Mention what they CAN do on their current plan, and frame the upgrade ` +
|
|
268
|
+
`as unlocking deeper possibilities — charts, visualizations, and advanced HD analysis ` +
|
|
269
|
+
`are all available with a plan upgrade.\n\n` +
|
|
266
270
|
`DO NOT attempt to generate astrological data from your own knowledge. ` +
|
|
267
271
|
`All chart positions, aspects, and timing data MUST come from the API.`, 403, "tier_required", false, UPGRADE_URL);
|
|
268
272
|
}
|
package/dist/tools/index.js
CHANGED
|
@@ -38,9 +38,11 @@ export async function initTools(profile) {
|
|
|
38
38
|
await import("./specialized/ephemeris_core.js");
|
|
39
39
|
await import("./specialized/comparative.js");
|
|
40
40
|
await import("./specialized/hd_group.js");
|
|
41
|
+
await import("./specialized/hd_bodygraph.js");
|
|
41
42
|
await import("./specialized/hd_cycles.js");
|
|
42
43
|
await import("./specialized/ephemeris_extended.js");
|
|
43
44
|
await import("./specialized/venus_star_points.js");
|
|
45
|
+
await import("./specialized/acg.js");
|
|
44
46
|
}
|
|
45
47
|
}
|
|
46
48
|
/**
|
|
@@ -71,7 +71,27 @@ registerTool({
|
|
|
71
71
|
body.query_lon = args.query_longitude;
|
|
72
72
|
if (args.radius_deg != null)
|
|
73
73
|
body.radius_deg = args.radius_deg;
|
|
74
|
-
|
|
74
|
+
const raw = await getActiveClient().post("/acg/power-lines", body);
|
|
75
|
+
// Strip coordinate geometry to prevent context-window blowouts.
|
|
76
|
+
// Full GeoJSON can be 200K+ tokens; condensed metadata is ~2K.
|
|
77
|
+
if (raw?.type === "FeatureCollection" && Array.isArray(raw.features)) {
|
|
78
|
+
const condensed = raw.features.map((f) => ({
|
|
79
|
+
planet: f.properties?.planet ?? f.properties?.body,
|
|
80
|
+
angle: f.properties?.angle ?? f.properties?.line_type,
|
|
81
|
+
...(f.properties?.label ? { label: f.properties.label } : {}),
|
|
82
|
+
...(f.properties?.zodiac_sign ? { zodiac_sign: f.properties.zodiac_sign } : {}),
|
|
83
|
+
...(f.properties?.longitude_at_equator != null
|
|
84
|
+
? { longitude_at_equator: f.properties.longitude_at_equator }
|
|
85
|
+
: {}),
|
|
86
|
+
}));
|
|
87
|
+
return {
|
|
88
|
+
type: "acg_power_lines_summary",
|
|
89
|
+
total_lines: condensed.length,
|
|
90
|
+
note: "Coordinate geometry stripped for LLM context efficiency. Use the full API or Ela Map for geographic rendering.",
|
|
91
|
+
lines: condensed,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
return raw;
|
|
75
95
|
},
|
|
76
96
|
});
|
|
77
97
|
// POST /acg/hits — OE-018
|
|
@@ -173,18 +173,21 @@ registerTool({
|
|
|
173
173
|
},
|
|
174
174
|
handler: async (args) => {
|
|
175
175
|
validateRequired(args, ["natal_datetime", "natal_latitude", "natal_longitude"]);
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
176
|
+
// Backend expects: { subject: {...}, transit_datetime?: {...} }
|
|
177
|
+
// NOT a subjects[] array — natal-transits is a single-subject endpoint
|
|
178
|
+
const body = {
|
|
179
|
+
subject: buildSubject("Natal", args.natal_datetime, args.natal_latitude, args.natal_longitude, args.natal_timezone),
|
|
180
|
+
};
|
|
179
181
|
if (args.transit_datetime) {
|
|
180
|
-
|
|
182
|
+
// transit_datetime is a DateTimeInput on the backend
|
|
183
|
+
body.transit_datetime = { iso: args.transit_datetime };
|
|
181
184
|
}
|
|
182
185
|
const query = {};
|
|
183
186
|
if (args.format)
|
|
184
187
|
query.format = args.format;
|
|
185
188
|
return await getActiveClient().request("POST", "/comparative/natal-transits", {
|
|
186
189
|
params: query,
|
|
187
|
-
data:
|
|
190
|
+
data: body,
|
|
188
191
|
});
|
|
189
192
|
},
|
|
190
193
|
});
|
|
@@ -72,11 +72,15 @@ registerTool({
|
|
|
72
72
|
},
|
|
73
73
|
});
|
|
74
74
|
// POST /ephemeris/retrograde-status
|
|
75
|
+
// NOTE: the backend handles one planet per call (planet_id is a non-nullable int
|
|
76
|
+
// that defaults to 0/Sun when omitted). We fan out to all 10 planets here so
|
|
77
|
+
// the documented "all planets" behaviour works correctly.
|
|
75
78
|
registerTool({
|
|
76
79
|
name: "ephemeris_retrograde_status",
|
|
77
80
|
description: "Get retrograde/direct status and speed for all planets at a given date/time. " +
|
|
78
|
-
"Returns is_retrograde flag, longitude speed, and station proximity.\n\n" +
|
|
79
|
-
"CREDIT COST: 1 credit per call
|
|
81
|
+
"Returns is_retrograde flag, longitude speed, and station proximity for every planet.\n\n" +
|
|
82
|
+
"CREDIT COST: 1 credit per call.\n\n" +
|
|
83
|
+
"Optionally pass planet_id (0-9) to query a single planet.",
|
|
80
84
|
inputSchema: {
|
|
81
85
|
type: "object",
|
|
82
86
|
properties: {
|
|
@@ -88,12 +92,34 @@ registerTool({
|
|
|
88
92
|
},
|
|
89
93
|
handler: async (args) => {
|
|
90
94
|
validateRequired(args, ["datetime"]);
|
|
91
|
-
const
|
|
95
|
+
const client = getActiveClient();
|
|
96
|
+
// If a specific planet is requested, delegate directly.
|
|
97
|
+
if (args.planet_id != null) {
|
|
98
|
+
return await client.post("/ephemeris/retrograde-status", {
|
|
99
|
+
date_time: { iso: args.datetime },
|
|
100
|
+
planet_id: args.planet_id,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
// Fan-out: the backend only handles one planet per call; query all 10 in parallel.
|
|
104
|
+
const PLANET_IDS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
|
|
105
|
+
const results = await Promise.all(PLANET_IDS.map((pid) => client.post("/ephemeris/retrograde-status", {
|
|
92
106
|
date_time: { iso: args.datetime },
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
107
|
+
planet_id: pid,
|
|
108
|
+
}).catch(() => null)));
|
|
109
|
+
// Merge into a keyed object: { planet_name: {...status} }
|
|
110
|
+
const merged = {};
|
|
111
|
+
for (const res of results) {
|
|
112
|
+
if (res && typeof res === "object" && res.planet_name) {
|
|
113
|
+
const r = res;
|
|
114
|
+
merged[r.planet_name] = {
|
|
115
|
+
planet_id: r.planet_id,
|
|
116
|
+
is_retrograde: r.is_retrograde,
|
|
117
|
+
longitude_speed: r.longitude_speed,
|
|
118
|
+
motion_type: r.motion_type,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return { planets: merged, datetime: args.datetime, success: true };
|
|
97
123
|
},
|
|
98
124
|
});
|
|
99
125
|
// POST /ephemeris/midpoints
|
|
@@ -150,7 +176,38 @@ registerTool({
|
|
|
150
176
|
body.star_names = args.star_names;
|
|
151
177
|
if (args.orb != null)
|
|
152
178
|
body.orb = args.orb;
|
|
153
|
-
|
|
179
|
+
const raw = await getActiveClient().post("/ephemeris/fixed-stars", body);
|
|
180
|
+
// Bug 6 workaround: backend returns longitude as degrees-within-sign (0–30),
|
|
181
|
+
// not full ecliptic longitude (0–360). Compute full_longitude from sign name.
|
|
182
|
+
const SIGN_ORDER = {
|
|
183
|
+
Aries: 0, Taurus: 1, Gemini: 2, Cancer: 3, Leo: 4, Virgo: 5,
|
|
184
|
+
Libra: 6, Scorpio: 7, Sagittarius: 8, Capricorn: 9, Aquarius: 10, Pisces: 11,
|
|
185
|
+
};
|
|
186
|
+
function enrichStar(star) {
|
|
187
|
+
if (!star || typeof star !== "object")
|
|
188
|
+
return star;
|
|
189
|
+
const signLon = typeof star.longitude === "number" ? star.longitude : 0;
|
|
190
|
+
const signName = star.sign ?? star.sign_name ?? "";
|
|
191
|
+
const signIndex = SIGN_ORDER[signName] ?? -1;
|
|
192
|
+
const fullLon = signIndex >= 0 ? signIndex * 30 + signLon : signLon;
|
|
193
|
+
return {
|
|
194
|
+
...star,
|
|
195
|
+
longitude: fullLon, // ecliptic longitude 0–360
|
|
196
|
+
sign_longitude: signLon, // original within-sign degrees preserved
|
|
197
|
+
sign: signName || undefined,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
// Normalise the response — backend may return array or { stars: [...] }
|
|
201
|
+
if (Array.isArray(raw)) {
|
|
202
|
+
return raw.map(enrichStar);
|
|
203
|
+
}
|
|
204
|
+
if (raw && typeof raw === "object") {
|
|
205
|
+
const starsKey = ["stars", "data", "fixed_stars", "results"].find((k) => Array.isArray(raw[k]));
|
|
206
|
+
if (starsKey) {
|
|
207
|
+
return { ...raw, [starsKey]: raw[starsKey].map(enrichStar) };
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return raw;
|
|
154
211
|
},
|
|
155
212
|
});
|
|
156
213
|
// POST /ephemeris/hermetic-lots
|
|
@@ -223,9 +280,10 @@ registerTool({
|
|
|
223
280
|
},
|
|
224
281
|
handler: async (args) => {
|
|
225
282
|
validateRequired(args, ["longitude_1", "longitude_2"]);
|
|
283
|
+
// Backend field names are longitude1 / longitude2 (no underscore)
|
|
226
284
|
const body = {
|
|
227
|
-
|
|
228
|
-
|
|
285
|
+
longitude1: args.longitude_1,
|
|
286
|
+
longitude2: args.longitude_2,
|
|
229
287
|
};
|
|
230
288
|
if (args.max_orb != null)
|
|
231
289
|
body.max_orb = args.max_orb;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { registerTool, validateRequired } from "../index.js";
|
|
2
|
+
import { getActiveClient } from "../../backend/client.js";
|
|
3
|
+
/** Append 'Z' if no timezone offset is present in the datetime string. */
|
|
4
|
+
function ensureTimezone(dt) {
|
|
5
|
+
if (/[Zz]$/.test(dt) || /[+-]\d{2}:\d{2}$/.test(dt))
|
|
6
|
+
return dt;
|
|
7
|
+
return dt + "Z";
|
|
8
|
+
}
|
|
9
|
+
registerTool({
|
|
10
|
+
name: "human_design_bodygraph",
|
|
11
|
+
description: "Generate a Human Design Bodygraph image (PNG or SVG) from a birth datetime (UTC). " +
|
|
12
|
+
"Renders the full bodygraph circuit board showing all 9 centers, 36 channels, and 64 gates " +
|
|
13
|
+
"with Personality (conscious) and Design (unconscious) activations color-coded. " +
|
|
14
|
+
"Defined centers are filled with their HD doctrine color; open/undefined centers remain muted. " +
|
|
15
|
+
"The tool returns a raw image (base64) which most Vision-enabled AI agents can display and visually read.\n\n" +
|
|
16
|
+
"CREDIT COST: 2 credits per call.\n\n" +
|
|
17
|
+
"EXAMPLE: Generate a bodygraph for someone born April 15, 1990 at 19:30 UTC:\n" +
|
|
18
|
+
" datetime='1990-04-15T19:30:00Z'",
|
|
19
|
+
inputSchema: {
|
|
20
|
+
type: "object",
|
|
21
|
+
properties: {
|
|
22
|
+
datetime: {
|
|
23
|
+
type: "string",
|
|
24
|
+
description: "ISO 8601 birth datetime in UTC, e.g. '1990-04-15T19:30:00Z'. " +
|
|
25
|
+
"Human Design chart calculation is time-sensitive — accuracy to the minute matters.",
|
|
26
|
+
},
|
|
27
|
+
style: {
|
|
28
|
+
type: "string",
|
|
29
|
+
enum: ["light", "dark", "mono"],
|
|
30
|
+
description: "Aesthetic style of the bodygraph: 'dark' (default, deep background), " +
|
|
31
|
+
"'light' (cream background), or 'mono' (black-and-white print-ready).",
|
|
32
|
+
},
|
|
33
|
+
format: {
|
|
34
|
+
type: "string",
|
|
35
|
+
enum: ["png", "svg"],
|
|
36
|
+
description: "Output format. Defaults to 'png'.",
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
required: ["datetime"],
|
|
40
|
+
additionalProperties: false,
|
|
41
|
+
},
|
|
42
|
+
handler: async (args) => {
|
|
43
|
+
validateRequired(args, ["datetime"]);
|
|
44
|
+
const body = {
|
|
45
|
+
birth_datetime_utc: ensureTimezone(args.datetime),
|
|
46
|
+
};
|
|
47
|
+
const format = args.format || "png";
|
|
48
|
+
const styleParam = args.style ? `&style=${args.style}` : "";
|
|
49
|
+
return await getActiveClient().post(`/visualization/bodygraph?format=${format}&size=800${styleParam}`, body);
|
|
50
|
+
},
|
|
51
|
+
});
|
|
@@ -36,8 +36,9 @@ registerTool({
|
|
|
36
36
|
handler: async (args) => {
|
|
37
37
|
validateCoordinates(args, "latitude", "longitude");
|
|
38
38
|
const params = {};
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
// Backend requires datetime even though the schema marks it optional.
|
|
40
|
+
// Default to current UTC time when caller omits it.
|
|
41
|
+
params.datetime = args.datetime ?? new Date().toISOString();
|
|
41
42
|
if (args.latitude != null)
|
|
42
43
|
params.latitude = args.latitude;
|
|
43
44
|
if (args.longitude != null)
|
|
@@ -109,10 +110,23 @@ registerTool({
|
|
|
109
110
|
const result = await getActiveClient().request("GET", "/calendar/astrology/moon-phases", {
|
|
110
111
|
params: { start_date: startDate, end_date: endDate },
|
|
111
112
|
});
|
|
112
|
-
// Normalise response shape —
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
113
|
+
// Normalise response shape — defensively handle every known API envelope shape.
|
|
114
|
+
// Crash "allPhases.filter is not a function" occurs when a property like
|
|
115
|
+
// result.data is truthy but is an object, not an array.
|
|
116
|
+
function extractArray(val) {
|
|
117
|
+
if (Array.isArray(val))
|
|
118
|
+
return val;
|
|
119
|
+
if (val && typeof val === "object") {
|
|
120
|
+
// one level deeper: { phases: [...] }, { data: [...] }, etc.
|
|
121
|
+
for (const key of ["phases", "data", "events", "moon_phases", "results", "items"]) {
|
|
122
|
+
const nested = val[key];
|
|
123
|
+
if (Array.isArray(nested))
|
|
124
|
+
return nested;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
const allPhases = extractArray(result) ?? [];
|
|
116
130
|
const matching = allPhases
|
|
117
131
|
.filter((p) => {
|
|
118
132
|
const name = (p.phase_name ?? p.name ?? p.phase ?? "").toLowerCase();
|
|
@@ -127,9 +127,9 @@ registerTool({
|
|
|
127
127
|
"Uranus returns (~84 years), etc.\n\n" +
|
|
128
128
|
"CREDIT COST: 5 credits per call.\n\n" +
|
|
129
129
|
"EXAMPLE: Saturn return for birth 1990-04-15 near year 2019:\n" +
|
|
130
|
-
" body='saturn', birth_datetime='1990-04-15T14:30:
|
|
130
|
+
" body='saturn', birth_datetime='1990-04-15T14:30:00Z', target_datetime='2019-01-01T00:00:00Z'\n" +
|
|
131
131
|
"EXAMPLE: Chiron return for birth 1975-03-10 near age 50:\n" +
|
|
132
|
-
" body='chiron', birth_datetime='1975-03-10T08:00:
|
|
132
|
+
" body='chiron', birth_datetime='1975-03-10T08:00:00Z', target_datetime='2025-01-01T00:00:00Z'",
|
|
133
133
|
inputSchema: {
|
|
134
134
|
type: "object",
|
|
135
135
|
properties: {
|
|
@@ -145,16 +145,33 @@ registerTool({
|
|
|
145
145
|
type: "string",
|
|
146
146
|
description: "Date/time near which to find the return (ISO 8601). MUST include offset or Z. Required.",
|
|
147
147
|
},
|
|
148
|
+
birth_latitude: {
|
|
149
|
+
type: "number",
|
|
150
|
+
description: "Birth latitude in decimal degrees. Required to get a full return chart (house cusps, angles).",
|
|
151
|
+
},
|
|
152
|
+
birth_longitude: {
|
|
153
|
+
type: "number",
|
|
154
|
+
description: "Birth longitude in decimal degrees. Required to get a full return chart (house cusps, angles).",
|
|
155
|
+
},
|
|
148
156
|
},
|
|
149
157
|
required: ["body", "birth_datetime", "target_datetime"],
|
|
150
158
|
additionalProperties: false,
|
|
151
159
|
},
|
|
152
160
|
handler: async (args) => {
|
|
153
161
|
validateRequired(args, ["body", "birth_datetime", "target_datetime"]);
|
|
154
|
-
|
|
162
|
+
const requestBody = {
|
|
155
163
|
body: args.body,
|
|
156
164
|
birth_datetime: { iso: args.birth_datetime },
|
|
157
165
|
target_datetime: { iso: args.target_datetime },
|
|
158
|
-
}
|
|
166
|
+
};
|
|
167
|
+
// Forward birth coordinates so the backend can compute the full return chart.
|
|
168
|
+
// Without these, computeReturnChartData sees {lat:0, lon:0} and returns nil.
|
|
169
|
+
if (args.birth_latitude != null && args.birth_longitude != null) {
|
|
170
|
+
requestBody.location = {
|
|
171
|
+
latitude: { decimal: args.birth_latitude },
|
|
172
|
+
longitude: { decimal: args.birth_longitude },
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
return await getActiveClient().post("/predictive/returns", requestBody);
|
|
159
176
|
},
|
|
160
177
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openephemeris/mcp-server",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.8.0",
|
|
4
4
|
"description": "Model Context Protocol server for the Open Ephemeris astronomical computation API",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"homepage": "https://openephemeris.com",
|
|
59
59
|
"repository": {
|
|
60
60
|
"type": "git",
|
|
61
|
-
"url": "https://github.com/
|
|
61
|
+
"url": "https://github.com/openephemeris/openephemeris-MCP.git"
|
|
62
62
|
},
|
|
63
63
|
"license": "MIT",
|
|
64
64
|
"engines": {
|