@openephemeris/mcp-server 3.1.0 → 3.2.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/README.md +30 -21
- package/dist/index.js +0 -0
- package/dist/scripts/dev-allowlist.d.ts +1 -0
- package/dist/scripts/dev-allowlist.js +287 -0
- package/dist/scripts/pack-audit.d.ts +1 -0
- package/dist/scripts/pack-audit.js +45 -0
- package/dist/scripts/schema-packs.d.ts +1 -0
- package/dist/scripts/schema-packs.js +150 -0
- package/dist/scripts/smoke-dev-profile.d.ts +1 -0
- package/dist/scripts/smoke-dev-profile.js +25 -0
- package/dist/scripts/sync-readme.d.ts +1 -0
- package/dist/scripts/sync-readme.js +141 -0
- package/dist/src/auth/credentials.d.ts +65 -0
- package/dist/src/auth/credentials.js +200 -0
- package/dist/src/auth/device-auth.d.ts +56 -0
- package/dist/src/auth/device-auth.js +144 -0
- package/dist/src/backend/client.d.ts +61 -0
- package/dist/src/backend/client.js +335 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +92 -0
- package/dist/src/schema-packs/llm.d.ts +105 -0
- package/dist/src/schema-packs/llm.js +429 -0
- package/dist/src/tools/auth.d.ts +1 -0
- package/dist/src/tools/auth.js +202 -0
- package/dist/src/tools/dev.d.ts +1 -0
- package/dist/src/tools/dev.js +187 -0
- package/dist/src/tools/index.d.ts +25 -0
- package/dist/src/tools/index.js +33 -0
- package/dist/src/tools/specialized/eclipse.d.ts +1 -0
- package/dist/src/tools/specialized/eclipse.js +56 -0
- package/dist/src/tools/specialized/electional.d.ts +1 -0
- package/dist/src/tools/specialized/electional.js +79 -0
- package/dist/src/tools/specialized/human_design.d.ts +1 -0
- package/dist/src/tools/specialized/human_design.js +53 -0
- package/dist/src/tools/specialized/moon.d.ts +1 -0
- package/dist/src/tools/specialized/moon.js +50 -0
- package/dist/src/tools/specialized/natal.d.ts +1 -0
- package/dist/src/tools/specialized/natal.js +71 -0
- package/dist/src/tools/specialized/relocation.d.ts +1 -0
- package/dist/src/tools/specialized/relocation.js +71 -0
- package/dist/src/tools/specialized/synastry.d.ts +1 -0
- package/dist/src/tools/specialized/synastry.js +61 -0
- package/dist/src/tools/specialized/transits.d.ts +1 -0
- package/dist/src/tools/specialized/transits.js +80 -0
- package/dist/test/allowlist-and-tools.test.d.ts +1 -0
- package/dist/test/allowlist-and-tools.test.js +96 -0
- package/dist/test/backend-client.test.d.ts +1 -0
- package/dist/test/backend-client.test.js +286 -0
- package/dist/test/credentials.test.d.ts +1 -0
- package/dist/test/credentials.test.js +143 -0
- package/package.json +21 -18
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { registerTool } from "./index.js";
|
|
2
|
+
import { backendClient } from "../backend/client.js";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
function getAllowlistPath() {
|
|
7
|
+
const envPath = process.env.OPENEPHEMERIS_DEV_ALLOWLIST_PATH || process.env.ASTROMCP_DEV_ALLOWLIST_PATH;
|
|
8
|
+
if (envPath && envPath.trim())
|
|
9
|
+
return envPath;
|
|
10
|
+
// dist lives at mcp-server/dist; src lives at mcp-server/src.
|
|
11
|
+
// We resolve relative to current file location for both dev+prod.
|
|
12
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
// here: .../dist/tools OR .../src/tools
|
|
14
|
+
const mcpServerRoot = path.resolve(here, "..", "..");
|
|
15
|
+
return path.join(mcpServerRoot, "config", "dev-allowlist.json");
|
|
16
|
+
}
|
|
17
|
+
function loadAllowlist() {
|
|
18
|
+
const allowlistPath = getAllowlistPath();
|
|
19
|
+
const raw = fs.readFileSync(allowlistPath, "utf-8");
|
|
20
|
+
const parsed = JSON.parse(raw);
|
|
21
|
+
if (!parsed || parsed.schema !== "astromcp-dev-allowlist-v1" || !Array.isArray(parsed.allow)) {
|
|
22
|
+
throw new Error(`Invalid allowlist file at ${allowlistPath}. Expected schema astromcp-dev-allowlist-v1.`);
|
|
23
|
+
}
|
|
24
|
+
return parsed;
|
|
25
|
+
}
|
|
26
|
+
function isDeniedByPrefix(pathname, prefixes) {
|
|
27
|
+
return prefixes.some((p) => pathname.startsWith(p));
|
|
28
|
+
}
|
|
29
|
+
function isAllowedOperation(method, pathname, allow) {
|
|
30
|
+
return allow.some((e) => e.method === method && e.path === pathname);
|
|
31
|
+
}
|
|
32
|
+
registerTool({
|
|
33
|
+
name: "dev.call",
|
|
34
|
+
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
|
+
"AUTH: Set OPENEPHEMERIS_API_KEY in your environment. See openephemeris.com/dashboard for active plan limits.\n\n" +
|
|
38
|
+
"CREDIT COSTS:\n" +
|
|
39
|
+
" • Standard chart math (natal, progressed, bazi, HD): 1 credit\n" +
|
|
40
|
+
" • Visualization rendering (chart-wheel, bi-wheel, charts/*): 2 credits\n" +
|
|
41
|
+
" • Comparative math (synastry, composite, overlay): 3 credits\n" +
|
|
42
|
+
" • Predictive ops (transits, returns, transit-chart): 5 credits\n" +
|
|
43
|
+
" • ACG / astrocartography: 5 credits\n" +
|
|
44
|
+
" • Catalog / metadata / health endpoints: 0 credits\n" +
|
|
45
|
+
" • format=llm (token-optimized output): available on all tiers\n\n" +
|
|
46
|
+
"COMMON CALLS:\n" +
|
|
47
|
+
" POST /ephemeris/natal-chart — Full natal chart (body: {datetime, latitude, longitude})\n" +
|
|
48
|
+
" POST /ephemeris/natal/batch — Up to 50 natal charts in one request\n" +
|
|
49
|
+
" POST /ephemeris/relocation — Relocated chart (same natal, new location)\n" +
|
|
50
|
+
" POST /predictive/transits/search — Transit event search over a date range\n" +
|
|
51
|
+
" POST /predictive/returns/solar — Solar return chart\n" +
|
|
52
|
+
" POST /predictive/returns/lunar — Lunar return chart\n" +
|
|
53
|
+
" POST /comparative/synastry — Two-person synastry chart\n" +
|
|
54
|
+
" POST /comparative/composite — Composite (midpoint) chart\n" +
|
|
55
|
+
" POST /human-design/chart — Full HD bodygraph\n" +
|
|
56
|
+
" GET /ephemeris/moon/phase — Current/queried moon phase\n" +
|
|
57
|
+
" GET /ephemeris/moon/void-of-course — Next void-of-course period\n" +
|
|
58
|
+
" GET /ephemeris/agro/daily — Biodynamic farming day quality\n" +
|
|
59
|
+
" GET /ephemeris/agro/calendar — Multi-day biodynamic calendar\n" +
|
|
60
|
+
" GET /ephemeris/agro/void-of-course — Biodynamic VoC periods\n" +
|
|
61
|
+
" 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" +
|
|
64
|
+
" GET /tidal/forcing — Gravitational tidal forcing index\n" +
|
|
65
|
+
" GET /tidal/forcing/deep-time — Extended tidal deep-time analysis\n" +
|
|
66
|
+
" POST /acg/power-lines — Astrocartography power lines (lat/lon GeoJSON)\n" +
|
|
67
|
+
" POST /acg/hits — ACG power at a specific location\n" +
|
|
68
|
+
" GET /calendar/astrology/moon-phases — Moon phase calendar for a date range\n" +
|
|
69
|
+
" GET /location/autocomplete — Geocode a place name (query: q=City Name)\n" +
|
|
70
|
+
" POST /timezone/lookup — Resolve timezone + UTC offset for a location\n" +
|
|
71
|
+
" POST /chinese/bazi — Chinese Ba Zi (Four Pillars) chart\n" +
|
|
72
|
+
" GET /chinese/zodiac — Chinese zodiac year element/animal\n" +
|
|
73
|
+
" POST /vedic/chart — Vedic (Jyotish) natal chart\n" +
|
|
74
|
+
" GET /catalogs/bodies — List all supported celestial bodies\n\n" +
|
|
75
|
+
"BINARY RESPONSES:\n" +
|
|
76
|
+
" • Binary/image endpoints return {content_type, content_length, encoding, data_base64}\n" +
|
|
77
|
+
" so callers can decode bytes deterministically.\n\n" +
|
|
78
|
+
"ECLIPSE NOTE: Eclipse endpoints accept format=llm via the query param like other endpoints.\n\n" +
|
|
79
|
+
"format=llm NOTE: Add query: {format: 'llm'} to natal/synastry/composite/HD endpoints for " +
|
|
80
|
+
"compact columnar output optimized for LLM token budgets (availability depends on your current plan).",
|
|
81
|
+
inputSchema: {
|
|
82
|
+
type: "object",
|
|
83
|
+
properties: {
|
|
84
|
+
method: {
|
|
85
|
+
type: "string",
|
|
86
|
+
enum: ["GET", "POST", "PUT", "PATCH", "DELETE"],
|
|
87
|
+
description: "HTTP method",
|
|
88
|
+
},
|
|
89
|
+
path: {
|
|
90
|
+
type: "string",
|
|
91
|
+
description: "Absolute API path (e.g., /ephemeris/natal-chart)",
|
|
92
|
+
},
|
|
93
|
+
query: {
|
|
94
|
+
type: "object",
|
|
95
|
+
additionalProperties: true,
|
|
96
|
+
description: "Query params for GET requests (optional)",
|
|
97
|
+
},
|
|
98
|
+
body: {
|
|
99
|
+
description: "JSON body for POST/PUT/PATCH (optional)",
|
|
100
|
+
},
|
|
101
|
+
preset: {
|
|
102
|
+
type: "string",
|
|
103
|
+
enum: ["full", "simple"],
|
|
104
|
+
description: "Convenience: if provided, set query.preset (optional).",
|
|
105
|
+
},
|
|
106
|
+
format: {
|
|
107
|
+
type: "string",
|
|
108
|
+
enum: ["json", "llm", "llm_v2"],
|
|
109
|
+
description: "Convenience: if provided, set query.format (optional). 'llm' is canonical; 'llm_v2' is accepted as a legacy alias.",
|
|
110
|
+
},
|
|
111
|
+
output_mode: {
|
|
112
|
+
type: "string",
|
|
113
|
+
enum: ["full", "simple", "llm", "llm_v2"],
|
|
114
|
+
description: "Legacy convenience (deprecated): if provided, set query.output_mode and also map to query.preset/query.format when possible.",
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
required: ["method", "path"],
|
|
118
|
+
additionalProperties: false,
|
|
119
|
+
},
|
|
120
|
+
handler: async (args) => {
|
|
121
|
+
const method = String(args.method || "").toUpperCase();
|
|
122
|
+
const pathname = String(args.path || "");
|
|
123
|
+
if (!pathname.startsWith("/")) {
|
|
124
|
+
throw new Error("path must start with '/'");
|
|
125
|
+
}
|
|
126
|
+
const allowlist = loadAllowlist();
|
|
127
|
+
const denyPrefixes = allowlist.deny?.path_prefixes ?? [];
|
|
128
|
+
if (denyPrefixes.length > 0 && isDeniedByPrefix(pathname, denyPrefixes)) {
|
|
129
|
+
throw new Error(`Endpoint denied by policy: ${pathname}`);
|
|
130
|
+
}
|
|
131
|
+
if (!isAllowedOperation(method, pathname, allowlist.allow)) {
|
|
132
|
+
throw new Error(`Endpoint not allowlisted: ${method} ${pathname}`);
|
|
133
|
+
}
|
|
134
|
+
const queryBase = args.query && typeof args.query === "object" ? args.query : {};
|
|
135
|
+
const query = { ...queryBase };
|
|
136
|
+
const body = args.body;
|
|
137
|
+
if (args.preset) {
|
|
138
|
+
query.preset = args.preset;
|
|
139
|
+
}
|
|
140
|
+
if (args.format) {
|
|
141
|
+
const rawFormat = String(args.format).trim().toLowerCase();
|
|
142
|
+
// Canonical: "llm". Normalize legacy alias "llm_v2" → "llm".
|
|
143
|
+
query.format = rawFormat === "llm_v2" ? "llm" : rawFormat;
|
|
144
|
+
}
|
|
145
|
+
// Legacy: output_mode used to overload both compute preset and output projection.
|
|
146
|
+
// Keep forwarding it for older endpoints, but also map it to preset/format for new endpoints.
|
|
147
|
+
if (args.output_mode) {
|
|
148
|
+
const rawMode = String(args.output_mode).trim().toLowerCase();
|
|
149
|
+
query.output_mode = rawMode;
|
|
150
|
+
if ((rawMode === "full" || rawMode === "simple") && query.preset == null) {
|
|
151
|
+
query.preset = rawMode;
|
|
152
|
+
}
|
|
153
|
+
if ((rawMode === "llm" || rawMode === "llm_v2") && query.format == null) {
|
|
154
|
+
query.format = "llm";
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return await backendClient.request(method, pathname, {
|
|
158
|
+
params: query,
|
|
159
|
+
data: body,
|
|
160
|
+
});
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
registerTool({
|
|
164
|
+
name: "dev.list_allowed",
|
|
165
|
+
description: "List all API operations (method + path) that this MCP instance is authorized to call. " +
|
|
166
|
+
"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.",
|
|
170
|
+
inputSchema: {
|
|
171
|
+
type: "object",
|
|
172
|
+
properties: {},
|
|
173
|
+
additionalProperties: false,
|
|
174
|
+
},
|
|
175
|
+
handler: async () => {
|
|
176
|
+
const allowlist = loadAllowlist();
|
|
177
|
+
return {
|
|
178
|
+
schema: allowlist.schema,
|
|
179
|
+
generated_from: allowlist.generated_from,
|
|
180
|
+
deny: allowlist.deny ?? null,
|
|
181
|
+
allow: allowlist.allow,
|
|
182
|
+
counts: {
|
|
183
|
+
allow: allowlist.allow.length,
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
},
|
|
187
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
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>;
|
|
@@ -0,0 +1,33 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { registerTool } 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
|
+
const params = {};
|
|
39
|
+
if (args.latitude != null)
|
|
40
|
+
params.latitude = args.latitude;
|
|
41
|
+
if (args.longitude != null)
|
|
42
|
+
params.longitude = args.longitude;
|
|
43
|
+
if (args.after_date)
|
|
44
|
+
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
|
+
}
|
|
55
|
+
},
|
|
56
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { registerTool } 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
|
+
const query = {
|
|
59
|
+
start_date: args.start_date,
|
|
60
|
+
end_date: args.end_date,
|
|
61
|
+
latitude: args.latitude,
|
|
62
|
+
longitude: args.longitude,
|
|
63
|
+
};
|
|
64
|
+
if (args.max_results !== undefined)
|
|
65
|
+
query.max_results = args.max_results;
|
|
66
|
+
if (args.avoid_retrograde)
|
|
67
|
+
query.avoid_retrograde = args.avoid_retrograde;
|
|
68
|
+
if (args.lunar_phase)
|
|
69
|
+
query.lunar_phase = args.lunar_phase;
|
|
70
|
+
if (args.avoid_voc !== undefined)
|
|
71
|
+
query.avoid_voc = args.avoid_voc;
|
|
72
|
+
if (args.format)
|
|
73
|
+
query.format = args.format;
|
|
74
|
+
return await backendClient.request("GET", "/electional/find-window", {
|
|
75
|
+
data: {},
|
|
76
|
+
params: query,
|
|
77
|
+
});
|
|
78
|
+
},
|
|
79
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { registerTool } 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
|
+
const body = {
|
|
41
|
+
datetime: args.datetime,
|
|
42
|
+
latitude: args.latitude,
|
|
43
|
+
longitude: args.longitude,
|
|
44
|
+
};
|
|
45
|
+
const query = {};
|
|
46
|
+
if (args.format)
|
|
47
|
+
query.format = args.format;
|
|
48
|
+
return await backendClient.request("POST", "/human-design/chart", {
|
|
49
|
+
data: body,
|
|
50
|
+
params: query,
|
|
51
|
+
});
|
|
52
|
+
},
|
|
53
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { registerTool } 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
|
+
const params = {};
|
|
35
|
+
if (args.datetime)
|
|
36
|
+
params.datetime = args.datetime;
|
|
37
|
+
if (args.latitude != null)
|
|
38
|
+
params.latitude = args.latitude;
|
|
39
|
+
if (args.longitude != null)
|
|
40
|
+
params.longitude = args.longitude;
|
|
41
|
+
const [phase, voc] = await Promise.allSettled([
|
|
42
|
+
backendClient.request("GET", "/ephemeris/moon/phase", { params }),
|
|
43
|
+
backendClient.request("GET", "/ephemeris/moon/void-of-course", { params }),
|
|
44
|
+
]);
|
|
45
|
+
return {
|
|
46
|
+
phase: phase.status === "fulfilled" ? phase.value : { error: phase.reason?.message },
|
|
47
|
+
void_of_course: voc.status === "fulfilled" ? voc.value : { error: voc.reason?.message },
|
|
48
|
+
};
|
|
49
|
+
},
|
|
50
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { registerTool } 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: "If true, include Hermetic Lots (Arabic Parts) in the response.",
|
|
42
|
+
},
|
|
43
|
+
include_fixed_stars: {
|
|
44
|
+
type: "boolean",
|
|
45
|
+
description: "If true, include fixed star conjunctions in the response.",
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
required: ["datetime", "latitude", "longitude"],
|
|
49
|
+
additionalProperties: false,
|
|
50
|
+
},
|
|
51
|
+
handler: async (args) => {
|
|
52
|
+
const body = {
|
|
53
|
+
datetime: args.datetime,
|
|
54
|
+
latitude: args.latitude,
|
|
55
|
+
longitude: args.longitude,
|
|
56
|
+
};
|
|
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
|
+
const query = {};
|
|
64
|
+
if (args.format)
|
|
65
|
+
query.format = args.format;
|
|
66
|
+
return await backendClient.request("POST", "/ephemeris/natal-chart", {
|
|
67
|
+
data: body,
|
|
68
|
+
params: query,
|
|
69
|
+
});
|
|
70
|
+
},
|
|
71
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { registerTool } 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
|
+
const body = {
|
|
51
|
+
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,
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
if (args.house_system)
|
|
62
|
+
body.house_system = args.house_system;
|
|
63
|
+
const query = {};
|
|
64
|
+
if (args.format)
|
|
65
|
+
query.format = args.format;
|
|
66
|
+
return await backendClient.request("POST", "/ephemeris/relocation", {
|
|
67
|
+
data: body,
|
|
68
|
+
params: query,
|
|
69
|
+
});
|
|
70
|
+
},
|
|
71
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|