@openephemeris/mcp-server 3.1.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.
Files changed (58) hide show
  1. package/README.md +51 -22
  2. package/config/dev-allowlist.json +1262 -1318
  3. package/dist/index.js +0 -0
  4. package/dist/scripts/dev-allowlist.d.ts +1 -0
  5. package/dist/scripts/dev-allowlist.js +287 -0
  6. package/dist/scripts/pack-audit.d.ts +1 -0
  7. package/dist/scripts/pack-audit.js +45 -0
  8. package/dist/scripts/schema-packs.d.ts +1 -0
  9. package/dist/scripts/schema-packs.js +150 -0
  10. package/dist/scripts/smoke-dev-profile.d.ts +1 -0
  11. package/dist/scripts/smoke-dev-profile.js +25 -0
  12. package/dist/scripts/sync-readme.d.ts +1 -0
  13. package/dist/scripts/sync-readme.js +141 -0
  14. package/dist/scripts/test-client.d.ts +1 -0
  15. package/dist/scripts/test-client.js +69 -0
  16. package/dist/scripts/test-sse-client.d.ts +1 -0
  17. package/dist/scripts/test-sse-client.js +221 -0
  18. package/dist/src/auth/credentials.d.ts +65 -0
  19. package/dist/src/auth/credentials.js +200 -0
  20. package/dist/src/auth/device-auth.d.ts +56 -0
  21. package/dist/src/auth/device-auth.js +147 -0
  22. package/dist/src/backend/client.d.ts +61 -0
  23. package/dist/src/backend/client.js +335 -0
  24. package/dist/src/index.d.ts +2 -0
  25. package/dist/src/index.js +98 -0
  26. package/dist/src/schema-packs/llm.d.ts +105 -0
  27. package/dist/src/schema-packs/llm.js +429 -0
  28. package/dist/src/server-sse.d.ts +1 -0
  29. package/dist/src/server-sse.js +264 -0
  30. package/dist/src/tools/auth.d.ts +1 -0
  31. package/dist/src/tools/auth.js +202 -0
  32. package/dist/src/tools/dev.d.ts +1 -0
  33. package/dist/src/tools/dev.js +195 -0
  34. package/dist/src/tools/index.d.ts +33 -0
  35. package/dist/src/tools/index.js +56 -0
  36. package/dist/src/tools/specialized/eclipse.d.ts +1 -0
  37. package/dist/src/tools/specialized/eclipse.js +53 -0
  38. package/dist/src/tools/specialized/electional.d.ts +1 -0
  39. package/dist/src/tools/specialized/electional.js +80 -0
  40. package/dist/src/tools/specialized/human_design.d.ts +1 -0
  41. package/dist/src/tools/specialized/human_design.js +54 -0
  42. package/dist/src/tools/specialized/moon.d.ts +1 -0
  43. package/dist/src/tools/specialized/moon.js +51 -0
  44. package/dist/src/tools/specialized/natal.d.ts +1 -0
  45. package/dist/src/tools/specialized/natal.js +80 -0
  46. package/dist/src/tools/specialized/relocation.d.ts +1 -0
  47. package/dist/src/tools/specialized/relocation.js +76 -0
  48. package/dist/src/tools/specialized/synastry.d.ts +1 -0
  49. package/dist/src/tools/specialized/synastry.js +73 -0
  50. package/dist/src/tools/specialized/transits.d.ts +1 -0
  51. package/dist/src/tools/specialized/transits.js +87 -0
  52. package/dist/test/allowlist-and-tools.test.d.ts +1 -0
  53. package/dist/test/allowlist-and-tools.test.js +96 -0
  54. package/dist/test/backend-client.test.d.ts +1 -0
  55. package/dist/test/backend-client.test.js +284 -0
  56. package/dist/test/credentials.test.d.ts +1 -0
  57. package/dist/test/credentials.test.js +143 -0
  58. package/package.json +27 -18
@@ -0,0 +1,33 @@
1
+ import { z } from "zod";
2
+ export interface ToolDefinition {
3
+ name: string;
4
+ description: string;
5
+ inputSchema: z.ZodType<any> | Record<string, unknown>;
6
+ annotations?: {
7
+ title?: string;
8
+ readOnlyHint?: boolean;
9
+ destructiveHint?: boolean;
10
+ idempotentHint?: boolean;
11
+ openWorldHint?: boolean;
12
+ };
13
+ handler: (args: any) => Promise<any>;
14
+ }
15
+ export declare const toolRegistry: Record<string, ToolDefinition>;
16
+ export declare function registerTool(tool: ToolDefinition): void;
17
+ export type ToolProfile = "dev" | "legacy";
18
+ /**
19
+ * Initializes tool modules.
20
+ *
21
+ * - `dev`: registers the allowlist-gated generic call tools AND all specialized
22
+ * domain tools (natal chart, transits, moon phase, eclipse, synastry, HD).
23
+ * - `legacy`: registers only the generic tools (back-compat).
24
+ */
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;
@@ -0,0 +1,56 @@
1
+ export const toolRegistry = {};
2
+ export function registerTool(tool) {
3
+ toolRegistry[tool.name] = tool;
4
+ }
5
+ let toolsInitialized = false;
6
+ /**
7
+ * Initializes tool modules.
8
+ *
9
+ * - `dev`: registers the allowlist-gated generic call tools AND all specialized
10
+ * domain tools (natal chart, transits, moon phase, eclipse, synastry, HD).
11
+ * - `legacy`: registers only the generic tools (back-compat).
12
+ */
13
+ export async function initTools(profile) {
14
+ if (toolsInitialized)
15
+ return;
16
+ toolsInitialized = true;
17
+ const resolvedProfile = (profile || process.env.OPENEPHEMERIS_PROFILE || process.env.ASTROMCP_PROFILE || "dev").toLowerCase();
18
+ // Always register auth tools (available in all profiles).
19
+ await import("./auth.js");
20
+ // Always register the generic proxy tools (dev.call + dev.list_allowed).
21
+ await import("./dev.js");
22
+ if (resolvedProfile === "dev") {
23
+ // Register all specialized domain tools.
24
+ await import("./specialized/natal.js");
25
+ await import("./specialized/transits.js");
26
+ await import("./specialized/moon.js");
27
+ await import("./specialized/eclipse.js");
28
+ await import("./specialized/human_design.js");
29
+ await import("./specialized/synastry.js");
30
+ await import("./specialized/relocation.js");
31
+ await import("./specialized/electional.js");
32
+ }
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
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,53 @@
1
+ import { registerTool, validateRequired, validateCoordinates } from "../index.js";
2
+ import { backendClient } from "../../backend/client.js";
3
+ registerTool({
4
+ name: "ephemeris_next_eclipse",
5
+ description: "Find the next solar or lunar eclipse visible from a given location (or globally). " +
6
+ "Returns the eclipse type, date/time of maximum, magnitude, duration of totality (if any), " +
7
+ "and local contact times if coordinates are provided.\n\n" +
8
+ "CREDIT COST: 1 credit per call.\n\n" +
9
+ "EXAMPLE: Find the next solar eclipse visible from New York:\n" +
10
+ " eclipse_type='solar', latitude=40.7128, longitude=-74.006\n\n" +
11
+ "EXAMPLE: Find the next lunar eclipse globally:\n" +
12
+ " eclipse_type='lunar'",
13
+ inputSchema: {
14
+ type: "object",
15
+ properties: {
16
+ eclipse_type: {
17
+ type: "string",
18
+ enum: ["solar", "lunar"],
19
+ description: "Eclipse type to search for.",
20
+ },
21
+ latitude: {
22
+ type: "number",
23
+ description: "Observer latitude in decimal degrees. If provided, returns local visibility and contact times.",
24
+ },
25
+ longitude: {
26
+ type: "number",
27
+ description: "Observer longitude in decimal degrees.",
28
+ },
29
+ after_date: {
30
+ type: "string",
31
+ description: "ISO 8601 date to search after (e.g. '2026-01-01'). Defaults to today if omitted.",
32
+ },
33
+ },
34
+ required: ["eclipse_type"],
35
+ additionalProperties: false,
36
+ },
37
+ handler: async (args) => {
38
+ validateRequired(args, ["eclipse_type"]);
39
+ validateCoordinates(args, "latitude", "longitude");
40
+ const params = {};
41
+ if (args.latitude != null)
42
+ params.lat = args.latitude;
43
+ if (args.longitude != null)
44
+ params.lon = args.longitude;
45
+ if (args.after_date)
46
+ params.date = args.after_date;
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 });
52
+ },
53
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,80 @@
1
+ import { registerTool, validateRequired } from "../index.js";
2
+ import { backendClient } from "../../backend/client.js";
3
+ registerTool({
4
+ name: "ephemeris_electional",
5
+ description: "Find optimal planetary timing windows (electional astrology). Scans a date range to find the " +
6
+ "best times for an event based on essential dignity, aspect quality, sect, and void-of-course " +
7
+ "moon penalties. Evaluates every hour and clusters the best continuous windows.\n\n" +
8
+ "CREDIT COST: 5 credits per call (heavy calculation).\n\n" +
9
+ "EXAMPLE: Find the best time to launch a business in early March 2026.\n" +
10
+ " start_date='2026-03-01', end_date='2026-03-10', latitude=40.7128, longitude=-74.0060,\n" +
11
+ " avoid_voc=true, lunar_phase='waxing'",
12
+ inputSchema: {
13
+ type: "object",
14
+ properties: {
15
+ start_date: {
16
+ type: "string",
17
+ description: "ISO 8601 start date or datetime for the search window (e.g., 2026-03-01).",
18
+ },
19
+ end_date: {
20
+ type: "string",
21
+ description: "ISO 8601 end date or datetime for the search window.",
22
+ },
23
+ latitude: {
24
+ type: "number",
25
+ description: "Latitude of location in decimal degrees (positive = North).",
26
+ },
27
+ longitude: {
28
+ type: "number",
29
+ description: "Longitude of location in decimal degrees (positive = East).",
30
+ },
31
+ max_results: {
32
+ type: "number",
33
+ description: "Maximum number of top windows to return (default 5).",
34
+ },
35
+ avoid_retrograde: {
36
+ type: "string",
37
+ description: "Comma-separated list of planets to avoid when retrograde (e.g., 'mercury,venus').",
38
+ },
39
+ lunar_phase: {
40
+ type: "string",
41
+ enum: ["waxing", "waning", "new", "full", "any"],
42
+ description: "Filter windows by lunar phase. Defaults to 'any'.",
43
+ },
44
+ avoid_voc: {
45
+ type: "boolean",
46
+ description: "If true, strictly ignores any moments where the Moon is Void of Course.",
47
+ },
48
+ format: {
49
+ type: "string",
50
+ enum: ["json", "llm"],
51
+ description: "Output format. 'llm' = compact token-efficient output (available on all tiers).",
52
+ },
53
+ },
54
+ required: ["start_date", "end_date", "latitude", "longitude"],
55
+ additionalProperties: false,
56
+ },
57
+ handler: async (args) => {
58
+ validateRequired(args, ["start_date", "end_date", "latitude", "longitude"]);
59
+ const query = {
60
+ start_date: args.start_date,
61
+ end_date: args.end_date,
62
+ latitude: args.latitude,
63
+ longitude: args.longitude,
64
+ };
65
+ if (args.max_results !== undefined)
66
+ query.max_results = args.max_results;
67
+ if (args.avoid_retrograde)
68
+ query.avoid_retrograde = args.avoid_retrograde;
69
+ if (args.lunar_phase)
70
+ query.lunar_phase = args.lunar_phase;
71
+ if (args.avoid_voc !== undefined)
72
+ query.avoid_voc = args.avoid_voc;
73
+ if (args.format)
74
+ query.format = args.format;
75
+ return await backendClient.request("GET", "/electional/find-window", {
76
+ data: {},
77
+ params: query,
78
+ });
79
+ },
80
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,54 @@
1
+ import { registerTool, validateRequired } from "../index.js";
2
+ import { backendClient } from "../../backend/client.js";
3
+ registerTool({
4
+ name: "human_design_chart",
5
+ description: "Calculate a full Human Design bodygraph chart from birth data. Returns the person's Type " +
6
+ "(Generator, Manifesting Generator, Projector, Manifestor, Reflector), Strategy, Authority, " +
7
+ "Profile (e.g. 1/3, 2/4), defined and undefined Centers, activated Gates and Channels, " +
8
+ "Incarnation Cross, and both Personality (conscious) and Design (unconscious) planetary positions.\n\n" +
9
+ "CREDIT COST: 1 credit per call.\n\n" +
10
+ "Human Design uses two calculation moments: the birth time (Personality) and ~88° of Sun motion " +
11
+ "before birth (~3 months prior, the Design calculation). The API handles this automatically.\n\n" +
12
+ "EXAMPLE: Get the Human Design chart for someone born April 15, 1990 at 2:30 PM in Chicago:\n" +
13
+ " datetime='1990-04-15T14:30:00', latitude=41.8781, longitude=-87.6298",
14
+ inputSchema: {
15
+ type: "object",
16
+ properties: {
17
+ datetime: {
18
+ type: "string",
19
+ description: "ISO 8601 birth datetime (local time at birth location), e.g. '1990-04-15T14:30:00'.",
20
+ },
21
+ latitude: {
22
+ type: "number",
23
+ description: "Latitude of birth location in decimal degrees (positive = North).",
24
+ },
25
+ longitude: {
26
+ type: "number",
27
+ description: "Longitude of birth location in decimal degrees (positive = East).",
28
+ },
29
+ format: {
30
+ type: "string",
31
+ enum: ["json", "llm"],
32
+ description: "Output format. 'llm' returns compact array projection for token efficiency (available on all tiers). " +
33
+ "'json' returns verbose full output.",
34
+ },
35
+ },
36
+ required: ["datetime", "latitude", "longitude"],
37
+ additionalProperties: false,
38
+ },
39
+ handler: async (args) => {
40
+ validateRequired(args, ["datetime", "latitude", "longitude"]);
41
+ const body = {
42
+ birth_datetime_utc: args.datetime,
43
+ latitude: args.latitude,
44
+ longitude: args.longitude,
45
+ };
46
+ const query = {};
47
+ if (args.format)
48
+ query.format = args.format;
49
+ return await backendClient.request("POST", "/human-design/chart", {
50
+ data: body,
51
+ params: query,
52
+ });
53
+ },
54
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,51 @@
1
+ import { registerTool, validateCoordinates } from "../index.js";
2
+ import { backendClient } from "../../backend/client.js";
3
+ registerTool({
4
+ name: "ephemeris_moon_phase",
5
+ description: "Get the current Moon phase and void-of-course status. Returns the Moon's sign, phase name " +
6
+ "(New, Waxing Crescent, First Quarter, Waxing Gibbous, Full, Waning Gibbous, Last Quarter, " +
7
+ "Waning Crescent), exact phase angle, illumination percentage, and the next void-of-course period.\n\n" +
8
+ "CREDIT COST: 1 credit per call.\n\n" +
9
+ "If no datetime is provided, returns the current (live) moon phase.\n\n" +
10
+ "EXAMPLE: Get moon phase for a specific date/time:\n" +
11
+ " datetime='2026-03-20T12:00:00Z'\n\n" +
12
+ "EXAMPLE: Get the current moon phase right now:\n" +
13
+ " (call with no arguments)",
14
+ inputSchema: {
15
+ type: "object",
16
+ properties: {
17
+ datetime: {
18
+ type: "string",
19
+ description: "ISO 8601 datetime to query. If omitted, returns the current live moon phase (UTC now).",
20
+ },
21
+ latitude: {
22
+ type: "number",
23
+ description: "Observer latitude (optional, used for local void-of-course calculations).",
24
+ },
25
+ longitude: {
26
+ type: "number",
27
+ description: "Observer longitude (optional, used for local void-of-course calculations).",
28
+ },
29
+ },
30
+ required: [],
31
+ additionalProperties: false,
32
+ },
33
+ handler: async (args) => {
34
+ validateCoordinates(args, "latitude", "longitude");
35
+ const params = {};
36
+ if (args.datetime)
37
+ params.datetime = args.datetime;
38
+ if (args.latitude != null)
39
+ params.latitude = args.latitude;
40
+ if (args.longitude != null)
41
+ params.longitude = args.longitude;
42
+ const [phase, voc] = await Promise.allSettled([
43
+ backendClient.request("GET", "/ephemeris/moon/phase", { params }),
44
+ backendClient.request("GET", "/ephemeris/moon/void-of-course", { params }),
45
+ ]);
46
+ return {
47
+ phase: phase.status === "fulfilled" ? phase.value : { error: phase.reason?.message },
48
+ void_of_course: voc.status === "fulfilled" ? voc.value : { error: voc.reason?.message },
49
+ };
50
+ },
51
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,80 @@
1
+ import { registerTool, validateRequired } from "../index.js";
2
+ import { backendClient } from "../../backend/client.js";
3
+ const DATETIME_DESC = "ISO 8601 datetime string, e.g. '1990-04-15T14:30:00' (local time at birth location). " +
4
+ "Include timezone offset if known, e.g. '1990-04-15T14:30:00-05:00'.";
5
+ registerTool({
6
+ name: "ephemeris_natal_chart",
7
+ description: "Calculate a full natal (birth) chart for a person. Returns planetary positions, house cusps, " +
8
+ "aspects, and chart patterns. Use format='llm' for a compact, token-efficient output ideal for " +
9
+ "interpretation (available on all tiers). The result includes all major planets, angles (ASC/MC/DSC/IC), " +
10
+ "essential dignities, retrograde status, house system data, and major aspect grid.\n\n" +
11
+ "CREDIT COST: 1 credit per call.\n\n" +
12
+ "EXAMPLE: Calculate the natal chart for someone born April 15, 1990 at 2:30 PM in Chicago:\n" +
13
+ " datetime='1990-04-15T14:30:00', latitude=41.8781, longitude=-87.6298",
14
+ inputSchema: {
15
+ type: "object",
16
+ properties: {
17
+ datetime: {
18
+ type: "string",
19
+ description: DATETIME_DESC,
20
+ },
21
+ latitude: {
22
+ type: "number",
23
+ description: "Geographic latitude of birth location in decimal degrees (positive = North).",
24
+ },
25
+ longitude: {
26
+ type: "number",
27
+ description: "Geographic longitude of birth location in decimal degrees (positive = East).",
28
+ },
29
+ house_system: {
30
+ type: "string",
31
+ enum: ["placidus", "whole_sign", "equal", "koch", "campanus", "regiomontanus", "porphyry", "alcabitius", "morinus"],
32
+ description: "House system to use. Defaults to 'placidus' if omitted.",
33
+ },
34
+ format: {
35
+ type: "string",
36
+ enum: ["json", "llm"],
37
+ description: "Output format. 'llm' returns a compact array-based projection optimized for LLM token efficiency (available on all tiers). 'json' returns full verbose JSON.",
38
+ },
39
+ include_arabic_parts: {
40
+ type: "boolean",
41
+ description: "Reserved for future use. Hermetic Lots / Arabic Parts are currently available via the dedicated /ephemeris/hermetic-lots endpoint.",
42
+ },
43
+ include_fixed_stars: {
44
+ type: "boolean",
45
+ description: "Reserved for future use. Fixed star positions are currently available via the dedicated /ephemeris/fixed-stars endpoint.",
46
+ },
47
+ },
48
+ required: ["datetime", "latitude", "longitude"],
49
+ additionalProperties: false,
50
+ },
51
+ handler: async (args) => {
52
+ validateRequired(args, ["datetime", "latitude", "longitude"]);
53
+ const body = {
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
+ }
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
+ }
72
+ const query = {};
73
+ if (args.format)
74
+ query.format = args.format;
75
+ return await backendClient.request("POST", "/ephemeris/natal-chart", {
76
+ data: body,
77
+ params: query,
78
+ });
79
+ },
80
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,76 @@
1
+ import { registerTool, validateRequired } from "../index.js";
2
+ import { backendClient } from "../../backend/client.js";
3
+ registerTool({
4
+ name: "ephemeris_relocation",
5
+ description: "Calculate a relocation chart — the same natal planetary positions re-cast for a different " +
6
+ "geographic location. Used to understand how living in a different city shifts house placements " +
7
+ "and angles, without changing the planetary longitudes in the chart.\n\n" +
8
+ "CREDIT COST: 1 credit per call.\n\n" +
9
+ "EXAMPLE: How does moving from Chicago to London change someone's chart?\n" +
10
+ " natal_datetime='1990-04-15T14:30:00', natal_latitude=41.8781, natal_longitude=-87.6298,\n" +
11
+ " relocation_latitude=51.5074, relocation_longitude=-0.1278",
12
+ inputSchema: {
13
+ type: "object",
14
+ properties: {
15
+ natal_datetime: {
16
+ type: "string",
17
+ description: "ISO 8601 birth datetime (local time at birth location).",
18
+ },
19
+ natal_latitude: {
20
+ type: "number",
21
+ description: "Latitude of birth location in decimal degrees.",
22
+ },
23
+ natal_longitude: {
24
+ type: "number",
25
+ description: "Longitude of birth location in decimal degrees.",
26
+ },
27
+ relocation_latitude: {
28
+ type: "number",
29
+ description: "Latitude of the relocation city in decimal degrees (positive = North).",
30
+ },
31
+ relocation_longitude: {
32
+ type: "number",
33
+ description: "Longitude of the relocation city in decimal degrees (positive = East).",
34
+ },
35
+ house_system: {
36
+ type: "string",
37
+ enum: ["placidus", "whole_sign", "equal", "koch", "campanus", "regiomontanus", "porphyry"],
38
+ description: "House system to use. Defaults to 'placidus'.",
39
+ },
40
+ format: {
41
+ type: "string",
42
+ enum: ["json", "llm"],
43
+ description: "Output format. 'llm' = compact token-efficient output (available on all tiers).",
44
+ },
45
+ },
46
+ required: ["natal_datetime", "natal_latitude", "natal_longitude", "relocation_latitude", "relocation_longitude"],
47
+ additionalProperties: false,
48
+ },
49
+ handler: async (args) => {
50
+ validateRequired(args, ["natal_datetime", "natal_latitude", "natal_longitude", "relocation_latitude", "relocation_longitude"]);
51
+ const body = {
52
+ natal: {
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
+ },
62
+ },
63
+ relocation_lat: args.relocation_latitude,
64
+ relocation_lon: args.relocation_longitude,
65
+ };
66
+ if (args.house_system)
67
+ body.house_system = args.house_system;
68
+ const query = {};
69
+ if (args.format)
70
+ query.format = args.format;
71
+ return await backendClient.request("POST", "/ephemeris/relocation", {
72
+ data: body,
73
+ params: query,
74
+ });
75
+ },
76
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,73 @@
1
+ import { registerTool, validateRequired } from "../index.js";
2
+ import { backendClient } from "../../backend/client.js";
3
+ registerTool({
4
+ name: "ephemeris_synastry",
5
+ description: "Calculate a synastry chart comparing two people's natal charts. Returns inter-aspects " +
6
+ "(planetary connections between the two charts), composite points, and relationship indicators. " +
7
+ "Use this for compatibility analysis, relationship timing, or partnership insights.\n\n" +
8
+ "CREDIT COST: 3 credits per call.\n\n" +
9
+ "EXAMPLE: Compare two people's charts:\n" +
10
+ " person_a_datetime='1990-04-15T14:30:00', person_a_latitude=41.8781, person_a_longitude=-87.6298,\n" +
11
+ " person_b_datetime='1988-09-22T08:15:00', person_b_latitude=34.0522, person_b_longitude=-118.2437",
12
+ inputSchema: {
13
+ type: "object",
14
+ properties: {
15
+ person_a_datetime: { type: "string", description: "Person A birth datetime (ISO 8601)." },
16
+ person_a_latitude: { type: "number", description: "Person A birth latitude." },
17
+ person_a_longitude: { type: "number", description: "Person A birth longitude." },
18
+ person_b_datetime: { type: "string", description: "Person B birth datetime (ISO 8601)." },
19
+ person_b_latitude: { type: "number", description: "Person B birth latitude." },
20
+ person_b_longitude: { type: "number", description: "Person B birth longitude." },
21
+ house_system: {
22
+ type: "string",
23
+ enum: ["placidus", "whole_sign", "equal", "koch"],
24
+ description: "House system for both charts. Defaults to 'placidus'.",
25
+ },
26
+ format: {
27
+ type: "string",
28
+ enum: ["json", "llm"],
29
+ description: "Output format. 'llm' is compact and token-efficient (available on all tiers).",
30
+ },
31
+ },
32
+ required: [
33
+ "person_a_datetime", "person_a_latitude", "person_a_longitude",
34
+ "person_b_datetime", "person_b_latitude", "person_b_longitude",
35
+ ],
36
+ additionalProperties: false,
37
+ },
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
+ ]);
43
+ const body = {
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
+ },
52
+ },
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
+ },
61
+ },
62
+ };
63
+ if (args.house_system)
64
+ body.house_system = args.house_system;
65
+ const query = {};
66
+ if (args.format)
67
+ query.format = args.format;
68
+ return await backendClient.request("POST", "/comparative/synastry", {
69
+ data: body,
70
+ params: query,
71
+ });
72
+ },
73
+ });
@@ -0,0 +1 @@
1
+ export {};