@openephemeris/mcp-server 3.2.0 → 3.2.3

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.
@@ -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
- // here: .../dist/tools OR .../src/tools
14
- const mcpServerRoot = path.resolve(here, "..", "..");
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: "dev.call",
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 (ephemeris.natal_chart, ephemeris.transits, etc.) first for common operations. " +
36
- "Call dev.list_allowed to see all currently available endpoint paths.\n\n" +
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: {datetime, latitude, longitude})\n" +
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: latitude, longitude)\n" +
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
- const method = String(args.method || "").toUpperCase();
122
- const pathname = String(args.path || "");
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: "dev.list_allowed",
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 dev.call, or to verify an endpoint path. " +
168
- "Typed shortcut tools (ephemeris.natal_chart, ephemeris.transits, etc.) cover the most common operations — " +
169
- "check those first before reaching for dev.call.",
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;
@@ -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: "ephemeris.next_eclipse",
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.latitude = args.latitude;
42
+ params.lat = args.latitude;
41
43
  if (args.longitude != null)
42
- params.longitude = args.longitude;
44
+ params.lon = args.longitude;
43
45
  if (args.after_date)
44
46
  params.date = args.after_date;
45
- if (args.eclipse_type === "solar") {
46
- // Use local endpoint if coordinates provided, otherwise global
47
- if (args.latitude != null && args.longitude != null) {
48
- return await backendClient.request("GET", "/eclipse/solar/local", { params, timeoutMs: 60_000 });
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: "ephemeris.electional",
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: "human_design.chart",
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
- datetime: args.datetime,
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: "ephemeris.moon_phase",
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: "ephemeris.natal_chart",
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: "If true, include Hermetic Lots (Arabic Parts) in the response.",
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: "If true, include fixed star conjunctions in the response.",
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
- datetime: args.datetime,
54
- latitude: args.latitude,
55
- longitude: args.longitude,
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.house_system = args.house_system;
59
- if (args.include_arabic_parts)
60
- body.include_arabic_parts = args.include_arabic_parts;
61
- if (args.include_fixed_stars)
62
- body.include_fixed_stars = args.include_fixed_stars;
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: "ephemeris.relocation",
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
- datetime: args.natal_datetime,
53
- latitude: args.natal_latitude,
54
- longitude: args.natal_longitude,
55
- },
56
- relocation: {
57
- latitude: args.relocation_latitude,
58
- longitude: args.relocation_longitude,
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: "ephemeris.synastry",
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: 5 credits per call.\n\n" +
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
- natal_a: {
41
- datetime: args.person_a_datetime,
42
- latitude: args.person_a_latitude,
43
- longitude: args.person_a_longitude,
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
- natal_b: {
46
- datetime: args.person_b_datetime,
47
- latitude: args.person_b_latitude,
48
- longitude: args.person_b_longitude,
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: "ephemeris.transits",
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
- natal: {
63
- datetime: args.natal_datetime,
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.transiting_planets = args.transiting_planets;
75
+ body.planet_names = args.transiting_planets;
76
+ const searchCriteria = {};
72
77
  if (args.natal_points)
73
- body.natal_points = args.natal_points;
78
+ searchCriteria.natal_points = args.natal_points;
74
79
  if (args.aspects)
75
- body.aspects = args.aspects;
80
+ searchCriteria.aspects = args.aspects;
76
81
  if (args.orb != null)
77
- body.orb = args.orb;
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
- "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",
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).toContain("Authentication required:");
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.0",
3
+ "version": "3.2.3",
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",