@openephemeris/mcp-server 3.2.0 → 3.2.2
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/README.md +22 -2
- package/config/dev-allowlist.json +1262 -1318
- package/dist/scripts/test-client.d.ts +1 -0
- package/dist/scripts/test-client.js +69 -0
- package/dist/scripts/test-sse-client.d.ts +1 -0
- package/dist/scripts/test-sse-client.js +221 -0
- package/dist/src/auth/device-auth.js +3 -0
- package/dist/src/index.js +6 -0
- package/dist/src/server-sse.d.ts +1 -0
- package/dist/src/server-sse.js +264 -0
- package/dist/src/tools/auth.js +6 -6
- package/dist/src/tools/dev.js +26 -18
- package/dist/src/tools/index.d.ts +8 -0
- package/dist/src/tools/index.js +23 -0
- package/dist/src/tools/specialized/eclipse.js +11 -14
- package/dist/src/tools/specialized/electional.js +3 -2
- package/dist/src/tools/specialized/human_design.js +4 -3
- package/dist/src/tools/specialized/moon.js +3 -2
- package/dist/src/tools/specialized/natal.js +22 -13
- package/dist/src/tools/specialized/relocation.js +14 -9
- package/dist/src/tools/specialized/synastry.js +23 -11
- package/dist/src/tools/specialized/transits.js +20 -13
- package/dist/test/allowlist-and-tools.test.js +13 -13
- package/dist/test/backend-client.test.js +1 -3
- package/package.json +7 -1
package/dist/src/tools/dev.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { registerTool } from "./index.js";
|
|
1
|
+
import { registerTool, validateRequired } from "./index.js";
|
|
2
2
|
import { backendClient } from "../backend/client.js";
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
@@ -10,8 +10,10 @@ function getAllowlistPath() {
|
|
|
10
10
|
// dist lives at mcp-server/dist; src lives at mcp-server/src.
|
|
11
11
|
// We resolve relative to current file location for both dev+prod.
|
|
12
12
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
let mcpServerRoot = path.resolve(here, "..", "..");
|
|
14
|
+
if (path.basename(mcpServerRoot) === "dist") {
|
|
15
|
+
mcpServerRoot = path.resolve(mcpServerRoot, "..");
|
|
16
|
+
}
|
|
15
17
|
return path.join(mcpServerRoot, "config", "dev-allowlist.json");
|
|
16
18
|
}
|
|
17
19
|
function loadAllowlist() {
|
|
@@ -30,10 +32,10 @@ function isAllowedOperation(method, pathname, allow) {
|
|
|
30
32
|
return allow.some((e) => e.method === method && e.path === pathname);
|
|
31
33
|
}
|
|
32
34
|
registerTool({
|
|
33
|
-
name: "
|
|
35
|
+
name: "dev_call",
|
|
34
36
|
description: "Call any allowlisted Open Ephemeris API endpoint directly. This is the power-user escape hatch " +
|
|
35
|
-
"— use the typed tools (
|
|
36
|
-
"Call
|
|
37
|
+
"— use the typed tools (ephemeris_natal_chart, ephemeris_transits, etc.) first for common operations. " +
|
|
38
|
+
"Call dev_list_allowed to see all currently available endpoint paths.\n\n" +
|
|
37
39
|
"AUTH: Set OPENEPHEMERIS_API_KEY in your environment. See openephemeris.com/dashboard for active plan limits.\n\n" +
|
|
38
40
|
"CREDIT COSTS:\n" +
|
|
39
41
|
" • Standard chart math (natal, progressed, bazi, HD): 1 credit\n" +
|
|
@@ -44,7 +46,7 @@ registerTool({
|
|
|
44
46
|
" • Catalog / metadata / health endpoints: 0 credits\n" +
|
|
45
47
|
" • format=llm (token-optimized output): available on all tiers\n\n" +
|
|
46
48
|
"COMMON CALLS:\n" +
|
|
47
|
-
" POST /ephemeris/natal-chart — Full natal chart (body: {
|
|
49
|
+
" POST /ephemeris/natal-chart — Full natal chart (body: {subject: {name: 'Name', birth_datetime: {iso: '1990-04-15T14:30:00-05:00'}, birth_location: {latitude: {decimal: 40.0}, longitude: {decimal: -70.0}, timezone: {}}}})\n" +
|
|
48
50
|
" POST /ephemeris/natal/batch — Up to 50 natal charts in one request\n" +
|
|
49
51
|
" POST /ephemeris/relocation — Relocated chart (same natal, new location)\n" +
|
|
50
52
|
" POST /predictive/transits/search — Transit event search over a date range\n" +
|
|
@@ -52,15 +54,16 @@ registerTool({
|
|
|
52
54
|
" POST /predictive/returns/lunar — Lunar return chart\n" +
|
|
53
55
|
" POST /comparative/synastry — Two-person synastry chart\n" +
|
|
54
56
|
" POST /comparative/composite — Composite (midpoint) chart\n" +
|
|
55
|
-
" POST /human-design/chart — Full HD bodygraph\n" +
|
|
57
|
+
" POST /human-design/chart — Full HD bodygraph (body: {birth_datetime_utc: '1990-04-15T19:30:00Z'}) — lat/lon optional\n" +
|
|
58
|
+
" POST /time/julian-day — Convert date to JD (body: {year: 1987, month: 7, day: 15, hour: 14, minute: 1})\n" +
|
|
56
59
|
" GET /ephemeris/moon/phase — Current/queried moon phase\n" +
|
|
57
60
|
" GET /ephemeris/moon/void-of-course — Next void-of-course period\n" +
|
|
58
61
|
" GET /ephemeris/agro/daily — Biodynamic farming day quality\n" +
|
|
59
62
|
" GET /ephemeris/agro/calendar — Multi-day biodynamic calendar\n" +
|
|
60
63
|
" GET /ephemeris/agro/void-of-course — Biodynamic VoC periods\n" +
|
|
64
|
+
" GET /eclipse/next-visible — Next eclipse visible from a location (query: lat, lon, type=solar|lunar)\n" +
|
|
61
65
|
" GET /eclipse/solar/global — Next global solar eclipse (query: date=YYYY-MM-DD)\n" +
|
|
62
|
-
" GET /eclipse/solar/local — Local solar eclipse (query:
|
|
63
|
-
" GET /eclipse/next-visible — Next eclipse visible from a location\n" +
|
|
66
|
+
" GET /eclipse/solar/local — Local solar eclipse (query: lat, lon)\n" +
|
|
64
67
|
" GET /tidal/forcing — Gravitational tidal forcing index\n" +
|
|
65
68
|
" GET /tidal/forcing/deep-time — Extended tidal deep-time analysis\n" +
|
|
66
69
|
" POST /acg/power-lines — Astrocartography power lines (lat/lon GeoJSON)\n" +
|
|
@@ -68,9 +71,9 @@ registerTool({
|
|
|
68
71
|
" GET /calendar/astrology/moon-phases — Moon phase calendar for a date range\n" +
|
|
69
72
|
" GET /location/autocomplete — Geocode a place name (query: q=City Name)\n" +
|
|
70
73
|
" POST /timezone/lookup — Resolve timezone + UTC offset for a location\n" +
|
|
71
|
-
" POST /chinese/bazi — Chinese Ba Zi (Four Pillars) chart\n" +
|
|
74
|
+
" POST /chinese/bazi — Chinese Ba Zi (Four Pillars) chart (body: {year, month, day, hour})\n" +
|
|
72
75
|
" GET /chinese/zodiac — Chinese zodiac year element/animal\n" +
|
|
73
|
-
" POST /vedic/chart — Vedic (Jyotish) natal chart\n" +
|
|
76
|
+
" POST /vedic/chart — Vedic (Jyotish) natal chart (body: {datetime_utc, latitude, longitude})\n" +
|
|
74
77
|
" GET /catalogs/bodies — List all supported celestial bodies\n\n" +
|
|
75
78
|
"BINARY RESPONSES:\n" +
|
|
76
79
|
" • Binary/image endpoints return {content_type, content_length, encoding, data_base64}\n" +
|
|
@@ -118,8 +121,13 @@ registerTool({
|
|
|
118
121
|
additionalProperties: false,
|
|
119
122
|
},
|
|
120
123
|
handler: async (args) => {
|
|
121
|
-
|
|
122
|
-
const
|
|
124
|
+
validateRequired(args, ["path"]);
|
|
125
|
+
const methodStr = String(args.method || "GET").toUpperCase();
|
|
126
|
+
if (!["GET", "POST", "PUT", "PATCH", "DELETE"].includes(methodStr)) {
|
|
127
|
+
throw new Error(`Invalid HTTP method: ${methodStr}`);
|
|
128
|
+
}
|
|
129
|
+
const method = methodStr;
|
|
130
|
+
const pathname = String(args.path);
|
|
123
131
|
if (!pathname.startsWith("/")) {
|
|
124
132
|
throw new Error("path must start with '/'");
|
|
125
133
|
}
|
|
@@ -161,12 +169,12 @@ registerTool({
|
|
|
161
169
|
},
|
|
162
170
|
});
|
|
163
171
|
registerTool({
|
|
164
|
-
name: "
|
|
172
|
+
name: "dev_list_allowed",
|
|
165
173
|
description: "List all API operations (method + path) that this MCP instance is authorized to call. " +
|
|
166
174
|
"Returns endpoint entries grouped by method, plus the active deny rules. " +
|
|
167
|
-
"Use this to discover what's available before calling
|
|
168
|
-
"Typed shortcut tools (
|
|
169
|
-
"check those first before reaching for
|
|
175
|
+
"Use this to discover what's available before calling dev_call, or to verify an endpoint path. " +
|
|
176
|
+
"Typed shortcut tools (ephemeris_natal_chart, ephemeris_transits, etc.) cover the most common operations — " +
|
|
177
|
+
"check those first before reaching for dev_call.",
|
|
170
178
|
inputSchema: {
|
|
171
179
|
type: "object",
|
|
172
180
|
properties: {},
|
|
@@ -23,3 +23,11 @@ export type ToolProfile = "dev" | "legacy";
|
|
|
23
23
|
* - `legacy`: registers only the generic tools (back-compat).
|
|
24
24
|
*/
|
|
25
25
|
export declare function initTools(profile?: ToolProfile): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Throws an error if any of the required keys are missing or empty.
|
|
28
|
+
*/
|
|
29
|
+
export declare function validateRequired(args: any, requiredKeys: string[]): void;
|
|
30
|
+
/**
|
|
31
|
+
* Throws an error if only one of the coordinate pair is provided.
|
|
32
|
+
*/
|
|
33
|
+
export declare function validateCoordinates(args: any, latKey: string, lonKey: string): void;
|
package/dist/src/tools/index.js
CHANGED
|
@@ -31,3 +31,26 @@ export async function initTools(profile) {
|
|
|
31
31
|
await import("./specialized/electional.js");
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Throws an error if any of the required keys are missing or empty.
|
|
36
|
+
*/
|
|
37
|
+
export function validateRequired(args, requiredKeys) {
|
|
38
|
+
if (!args)
|
|
39
|
+
throw new Error("Missing arguments object.");
|
|
40
|
+
const missing = requiredKeys.filter((k) => args[k] == null || args[k] === "");
|
|
41
|
+
if (missing.length > 0) {
|
|
42
|
+
throw new Error(`Missing required arguments: ${missing.join(", ")}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Throws an error if only one of the coordinate pair is provided.
|
|
47
|
+
*/
|
|
48
|
+
export function validateCoordinates(args, latKey, lonKey) {
|
|
49
|
+
if (!args)
|
|
50
|
+
return;
|
|
51
|
+
const hasLat = args[latKey] != null && args[latKey] !== "";
|
|
52
|
+
const hasLon = args[lonKey] != null && args[lonKey] !== "";
|
|
53
|
+
if (hasLat !== hasLon) {
|
|
54
|
+
throw new Error(`Both ${latKey} and ${lonKey} must be provided together, or both omitted.`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { registerTool } from "../index.js";
|
|
1
|
+
import { registerTool, validateRequired, validateCoordinates } from "../index.js";
|
|
2
2
|
import { backendClient } from "../../backend/client.js";
|
|
3
3
|
registerTool({
|
|
4
|
-
name: "
|
|
4
|
+
name: "ephemeris_next_eclipse",
|
|
5
5
|
description: "Find the next solar or lunar eclipse visible from a given location (or globally). " +
|
|
6
6
|
"Returns the eclipse type, date/time of maximum, magnitude, duration of totality (if any), " +
|
|
7
7
|
"and local contact times if coordinates are provided.\n\n" +
|
|
@@ -35,22 +35,19 @@ registerTool({
|
|
|
35
35
|
additionalProperties: false,
|
|
36
36
|
},
|
|
37
37
|
handler: async (args) => {
|
|
38
|
+
validateRequired(args, ["eclipse_type"]);
|
|
39
|
+
validateCoordinates(args, "latitude", "longitude");
|
|
38
40
|
const params = {};
|
|
39
41
|
if (args.latitude != null)
|
|
40
|
-
params.
|
|
42
|
+
params.lat = args.latitude;
|
|
41
43
|
if (args.longitude != null)
|
|
42
|
-
params.
|
|
44
|
+
params.lon = args.longitude;
|
|
43
45
|
if (args.after_date)
|
|
44
46
|
params.date = args.after_date;
|
|
45
|
-
if (args.eclipse_type
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
return await backendClient.request("GET", "/eclipse/solar/global", { params, timeoutMs: 60_000 });
|
|
51
|
-
}
|
|
52
|
-
else {
|
|
53
|
-
return await backendClient.request("GET", "/eclipse/lunar/global", { params, timeoutMs: 60_000 });
|
|
54
|
-
}
|
|
47
|
+
if (args.eclipse_type)
|
|
48
|
+
params.type = args.eclipse_type;
|
|
49
|
+
// Use /eclipse/next-visible which is the unified endpoint
|
|
50
|
+
// that accepts lat/lon and type filters
|
|
51
|
+
return await backendClient.request("GET", "/eclipse/next-visible", { params, timeoutMs: 60_000 });
|
|
55
52
|
},
|
|
56
53
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { registerTool } from "../index.js";
|
|
1
|
+
import { registerTool, validateRequired } from "../index.js";
|
|
2
2
|
import { backendClient } from "../../backend/client.js";
|
|
3
3
|
registerTool({
|
|
4
|
-
name: "
|
|
4
|
+
name: "ephemeris_electional",
|
|
5
5
|
description: "Find optimal planetary timing windows (electional astrology). Scans a date range to find the " +
|
|
6
6
|
"best times for an event based on essential dignity, aspect quality, sect, and void-of-course " +
|
|
7
7
|
"moon penalties. Evaluates every hour and clusters the best continuous windows.\n\n" +
|
|
@@ -55,6 +55,7 @@ registerTool({
|
|
|
55
55
|
additionalProperties: false,
|
|
56
56
|
},
|
|
57
57
|
handler: async (args) => {
|
|
58
|
+
validateRequired(args, ["start_date", "end_date", "latitude", "longitude"]);
|
|
58
59
|
const query = {
|
|
59
60
|
start_date: args.start_date,
|
|
60
61
|
end_date: args.end_date,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { registerTool } from "../index.js";
|
|
1
|
+
import { registerTool, validateRequired } from "../index.js";
|
|
2
2
|
import { backendClient } from "../../backend/client.js";
|
|
3
3
|
registerTool({
|
|
4
|
-
name: "
|
|
4
|
+
name: "human_design_chart",
|
|
5
5
|
description: "Calculate a full Human Design bodygraph chart from birth data. Returns the person's Type " +
|
|
6
6
|
"(Generator, Manifesting Generator, Projector, Manifestor, Reflector), Strategy, Authority, " +
|
|
7
7
|
"Profile (e.g. 1/3, 2/4), defined and undefined Centers, activated Gates and Channels, " +
|
|
@@ -37,8 +37,9 @@ registerTool({
|
|
|
37
37
|
additionalProperties: false,
|
|
38
38
|
},
|
|
39
39
|
handler: async (args) => {
|
|
40
|
+
validateRequired(args, ["datetime", "latitude", "longitude"]);
|
|
40
41
|
const body = {
|
|
41
|
-
|
|
42
|
+
birth_datetime_utc: args.datetime,
|
|
42
43
|
latitude: args.latitude,
|
|
43
44
|
longitude: args.longitude,
|
|
44
45
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { registerTool } from "../index.js";
|
|
1
|
+
import { registerTool, validateCoordinates } from "../index.js";
|
|
2
2
|
import { backendClient } from "../../backend/client.js";
|
|
3
3
|
registerTool({
|
|
4
|
-
name: "
|
|
4
|
+
name: "ephemeris_moon_phase",
|
|
5
5
|
description: "Get the current Moon phase and void-of-course status. Returns the Moon's sign, phase name " +
|
|
6
6
|
"(New, Waxing Crescent, First Quarter, Waxing Gibbous, Full, Waning Gibbous, Last Quarter, " +
|
|
7
7
|
"Waning Crescent), exact phase angle, illumination percentage, and the next void-of-course period.\n\n" +
|
|
@@ -31,6 +31,7 @@ registerTool({
|
|
|
31
31
|
additionalProperties: false,
|
|
32
32
|
},
|
|
33
33
|
handler: async (args) => {
|
|
34
|
+
validateCoordinates(args, "latitude", "longitude");
|
|
34
35
|
const params = {};
|
|
35
36
|
if (args.datetime)
|
|
36
37
|
params.datetime = args.datetime;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { registerTool } from "../index.js";
|
|
1
|
+
import { registerTool, validateRequired } from "../index.js";
|
|
2
2
|
import { backendClient } from "../../backend/client.js";
|
|
3
3
|
const DATETIME_DESC = "ISO 8601 datetime string, e.g. '1990-04-15T14:30:00' (local time at birth location). " +
|
|
4
4
|
"Include timezone offset if known, e.g. '1990-04-15T14:30:00-05:00'.";
|
|
5
5
|
registerTool({
|
|
6
|
-
name: "
|
|
6
|
+
name: "ephemeris_natal_chart",
|
|
7
7
|
description: "Calculate a full natal (birth) chart for a person. Returns planetary positions, house cusps, " +
|
|
8
8
|
"aspects, and chart patterns. Use format='llm' for a compact, token-efficient output ideal for " +
|
|
9
9
|
"interpretation (available on all tiers). The result includes all major planets, angles (ASC/MC/DSC/IC), " +
|
|
@@ -38,28 +38,37 @@ registerTool({
|
|
|
38
38
|
},
|
|
39
39
|
include_arabic_parts: {
|
|
40
40
|
type: "boolean",
|
|
41
|
-
description: "
|
|
41
|
+
description: "Reserved for future use. Hermetic Lots / Arabic Parts are currently available via the dedicated /ephemeris/hermetic-lots endpoint.",
|
|
42
42
|
},
|
|
43
43
|
include_fixed_stars: {
|
|
44
44
|
type: "boolean",
|
|
45
|
-
description: "
|
|
45
|
+
description: "Reserved for future use. Fixed star positions are currently available via the dedicated /ephemeris/fixed-stars endpoint.",
|
|
46
46
|
},
|
|
47
47
|
},
|
|
48
48
|
required: ["datetime", "latitude", "longitude"],
|
|
49
49
|
additionalProperties: false,
|
|
50
50
|
},
|
|
51
51
|
handler: async (args) => {
|
|
52
|
+
validateRequired(args, ["datetime", "latitude", "longitude"]);
|
|
52
53
|
const body = {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
subject: {
|
|
55
|
+
name: "MCP Request",
|
|
56
|
+
birth_datetime: { iso: args.datetime },
|
|
57
|
+
birth_location: {
|
|
58
|
+
latitude: { decimal: args.latitude },
|
|
59
|
+
longitude: { decimal: args.longitude }
|
|
60
|
+
},
|
|
61
|
+
}
|
|
56
62
|
};
|
|
57
|
-
if (args.house_system)
|
|
58
|
-
body.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
+
if (args.house_system) {
|
|
64
|
+
body.configuration = { house_system: args.house_system };
|
|
65
|
+
}
|
|
66
|
+
if (args.include_arabic_parts || args.include_fixed_stars) {
|
|
67
|
+
body.options = {
|
|
68
|
+
...(args.include_arabic_parts && { include_arabic_parts: args.include_arabic_parts }),
|
|
69
|
+
...(args.include_fixed_stars && { include_fixed_stars: args.include_fixed_stars }),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
63
72
|
const query = {};
|
|
64
73
|
if (args.format)
|
|
65
74
|
query.format = args.format;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { registerTool } from "../index.js";
|
|
1
|
+
import { registerTool, validateRequired } from "../index.js";
|
|
2
2
|
import { backendClient } from "../../backend/client.js";
|
|
3
3
|
registerTool({
|
|
4
|
-
name: "
|
|
4
|
+
name: "ephemeris_relocation",
|
|
5
5
|
description: "Calculate a relocation chart — the same natal planetary positions re-cast for a different " +
|
|
6
6
|
"geographic location. Used to understand how living in a different city shifts house placements " +
|
|
7
7
|
"and angles, without changing the planetary longitudes in the chart.\n\n" +
|
|
@@ -47,16 +47,21 @@ registerTool({
|
|
|
47
47
|
additionalProperties: false,
|
|
48
48
|
},
|
|
49
49
|
handler: async (args) => {
|
|
50
|
+
validateRequired(args, ["natal_datetime", "natal_latitude", "natal_longitude", "relocation_latitude", "relocation_longitude"]);
|
|
50
51
|
const body = {
|
|
51
52
|
natal: {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
53
|
+
subject: {
|
|
54
|
+
name: "MCP Request",
|
|
55
|
+
birth_datetime: { iso: args.natal_datetime },
|
|
56
|
+
birth_location: {
|
|
57
|
+
latitude: { decimal: args.natal_latitude },
|
|
58
|
+
longitude: { decimal: args.natal_longitude },
|
|
59
|
+
timezone: {},
|
|
60
|
+
},
|
|
61
|
+
},
|
|
59
62
|
},
|
|
63
|
+
relocation_lat: args.relocation_latitude,
|
|
64
|
+
relocation_lon: args.relocation_longitude,
|
|
60
65
|
};
|
|
61
66
|
if (args.house_system)
|
|
62
67
|
body.house_system = args.house_system;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { registerTool } from "../index.js";
|
|
1
|
+
import { registerTool, validateRequired } from "../index.js";
|
|
2
2
|
import { backendClient } from "../../backend/client.js";
|
|
3
3
|
registerTool({
|
|
4
|
-
name: "
|
|
4
|
+
name: "ephemeris_synastry",
|
|
5
5
|
description: "Calculate a synastry chart comparing two people's natal charts. Returns inter-aspects " +
|
|
6
6
|
"(planetary connections between the two charts), composite points, and relationship indicators. " +
|
|
7
7
|
"Use this for compatibility analysis, relationship timing, or partnership insights.\n\n" +
|
|
8
|
-
"CREDIT COST:
|
|
8
|
+
"CREDIT COST: 3 credits per call.\n\n" +
|
|
9
9
|
"EXAMPLE: Compare two people's charts:\n" +
|
|
10
10
|
" person_a_datetime='1990-04-15T14:30:00', person_a_latitude=41.8781, person_a_longitude=-87.6298,\n" +
|
|
11
11
|
" person_b_datetime='1988-09-22T08:15:00', person_b_latitude=34.0522, person_b_longitude=-118.2437",
|
|
@@ -36,16 +36,28 @@ registerTool({
|
|
|
36
36
|
additionalProperties: false,
|
|
37
37
|
},
|
|
38
38
|
handler: async (args) => {
|
|
39
|
+
validateRequired(args, [
|
|
40
|
+
"person_a_datetime", "person_a_latitude", "person_a_longitude",
|
|
41
|
+
"person_b_datetime", "person_b_latitude", "person_b_longitude",
|
|
42
|
+
]);
|
|
39
43
|
const body = {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
+
subject_a: {
|
|
45
|
+
name: "Person A",
|
|
46
|
+
birth_datetime: { iso: args.person_a_datetime },
|
|
47
|
+
birth_location: {
|
|
48
|
+
latitude: { decimal: args.person_a_latitude },
|
|
49
|
+
longitude: { decimal: args.person_a_longitude },
|
|
50
|
+
timezone: {},
|
|
51
|
+
},
|
|
44
52
|
},
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
53
|
+
subject_b: {
|
|
54
|
+
name: "Person B",
|
|
55
|
+
birth_datetime: { iso: args.person_b_datetime },
|
|
56
|
+
birth_location: {
|
|
57
|
+
latitude: { decimal: args.person_b_latitude },
|
|
58
|
+
longitude: { decimal: args.person_b_longitude },
|
|
59
|
+
timezone: {},
|
|
60
|
+
},
|
|
49
61
|
},
|
|
50
62
|
};
|
|
51
63
|
if (args.house_system)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { registerTool } from "../index.js";
|
|
1
|
+
import { registerTool, validateRequired } from "../index.js";
|
|
2
2
|
import { backendClient } from "../../backend/client.js";
|
|
3
3
|
registerTool({
|
|
4
|
-
name: "
|
|
4
|
+
name: "ephemeris_transits",
|
|
5
5
|
description: "Search for astrological transit events affecting a natal chart over a date range. " +
|
|
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 " +
|
|
@@ -58,23 +58,30 @@ registerTool({
|
|
|
58
58
|
additionalProperties: false,
|
|
59
59
|
},
|
|
60
60
|
handler: async (args) => {
|
|
61
|
+
validateRequired(args, ["natal_datetime", "natal_latitude", "natal_longitude", "start_date", "end_date"]);
|
|
62
|
+
let startStr = args.start_date;
|
|
63
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(startStr)) {
|
|
64
|
+
startStr += "T00:00:00Z";
|
|
65
|
+
}
|
|
66
|
+
let endStr = args.end_date;
|
|
67
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(endStr)) {
|
|
68
|
+
endStr += "T23:59:59Z";
|
|
69
|
+
}
|
|
61
70
|
const body = {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
latitude: args.natal_latitude,
|
|
65
|
-
longitude: args.natal_longitude,
|
|
66
|
-
},
|
|
67
|
-
start_date: args.start_date,
|
|
68
|
-
end_date: args.end_date,
|
|
71
|
+
start_date: startStr,
|
|
72
|
+
end_date: endStr,
|
|
69
73
|
};
|
|
70
74
|
if (args.transiting_planets)
|
|
71
|
-
body.
|
|
75
|
+
body.planet_names = args.transiting_planets;
|
|
76
|
+
const searchCriteria = {};
|
|
72
77
|
if (args.natal_points)
|
|
73
|
-
|
|
78
|
+
searchCriteria.natal_points = args.natal_points;
|
|
74
79
|
if (args.aspects)
|
|
75
|
-
|
|
80
|
+
searchCriteria.aspects = args.aspects;
|
|
76
81
|
if (args.orb != null)
|
|
77
|
-
|
|
82
|
+
searchCriteria.orb = args.orb;
|
|
83
|
+
if (Object.keys(searchCriteria).length > 0)
|
|
84
|
+
body.search_criteria = searchCriteria;
|
|
78
85
|
return await backendClient.request("POST", "/predictive/transits/search", { data: body });
|
|
79
86
|
},
|
|
80
87
|
});
|
|
@@ -66,19 +66,19 @@ describe("toolRegistry", () => {
|
|
|
66
66
|
});
|
|
67
67
|
it("registers all expected tools", () => {
|
|
68
68
|
const expectedTools = [
|
|
69
|
-
"
|
|
70
|
-
"
|
|
71
|
-
"
|
|
72
|
-
"
|
|
73
|
-
"
|
|
74
|
-
"
|
|
75
|
-
"
|
|
76
|
-
"
|
|
77
|
-
"
|
|
78
|
-
"
|
|
79
|
-
"
|
|
80
|
-
"
|
|
81
|
-
"
|
|
69
|
+
"auth_login",
|
|
70
|
+
"auth_status",
|
|
71
|
+
"auth_logout",
|
|
72
|
+
"dev_call",
|
|
73
|
+
"dev_list_allowed",
|
|
74
|
+
"ephemeris_natal_chart",
|
|
75
|
+
"ephemeris_transits",
|
|
76
|
+
"ephemeris_moon_phase",
|
|
77
|
+
"ephemeris_next_eclipse",
|
|
78
|
+
"ephemeris_synastry",
|
|
79
|
+
"ephemeris_relocation",
|
|
80
|
+
"ephemeris_electional",
|
|
81
|
+
"human_design_chart",
|
|
82
82
|
];
|
|
83
83
|
for (const name of expectedTools) {
|
|
84
84
|
expect(toolRegistry[name], `Missing tool: ${name}`).toBeDefined();
|
|
@@ -92,9 +92,7 @@ describe("BackendClient", () => {
|
|
|
92
92
|
baseURL: srv.baseURL,
|
|
93
93
|
});
|
|
94
94
|
const message = await getErrorMessage(() => client.get("/secure"));
|
|
95
|
-
expect(message).
|
|
96
|
-
expect(message).toContain("auth.login");
|
|
97
|
-
expect(message).toContain("OPENEPHEMERIS_API_KEY");
|
|
95
|
+
expect(message).toMatch(/Account connection required|Authentication required:/);
|
|
98
96
|
}
|
|
99
97
|
finally {
|
|
100
98
|
await srv.close();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openephemeris/mcp-server",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.2",
|
|
4
4
|
"description": "Model Context Protocol server for the Open Ephemeris astronomical computation API",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -18,9 +18,13 @@
|
|
|
18
18
|
"scripts": {
|
|
19
19
|
"build": "tsc",
|
|
20
20
|
"dev": "tsx watch src/index.ts",
|
|
21
|
+
"dev:sse": "tsx watch src/server-sse.ts",
|
|
21
22
|
"start": "node dist/index.js",
|
|
23
|
+
"start:sse": "node dist/src/server-sse.js",
|
|
22
24
|
"test": "vitest run --exclude \"dist/**\"",
|
|
23
25
|
"test:watch": "vitest --exclude \"dist/**\"",
|
|
26
|
+
"test:integration": "tsx scripts/test-client.ts",
|
|
27
|
+
"test:sse": "tsx scripts/test-sse-client.ts",
|
|
24
28
|
"typecheck": "tsc --noEmit",
|
|
25
29
|
"regen:dev-allowlist": "tsx scripts/dev-allowlist.ts --write",
|
|
26
30
|
"check:dev-allowlist": "tsx scripts/dev-allowlist.ts --check",
|
|
@@ -48,9 +52,11 @@
|
|
|
48
52
|
"dependencies": {
|
|
49
53
|
"@modelcontextprotocol/sdk": "^1.24.3",
|
|
50
54
|
"axios": "^1.13.2",
|
|
55
|
+
"express": "^5.1.0",
|
|
51
56
|
"zod": "^4.1.13"
|
|
52
57
|
},
|
|
53
58
|
"devDependencies": {
|
|
59
|
+
"@types/express": "^5.0.3",
|
|
54
60
|
"@types/node": "^25.0.1",
|
|
55
61
|
"tsx": "^4.21.0",
|
|
56
62
|
"typescript": "^5.9.3",
|