@openephemeris/mcp-server 3.2.13 → 3.3.1
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 +2 -2
- package/dist/index.js +2 -48
- package/dist/server-sse.js +7 -4
- package/dist/tools/index.d.ts +25 -0
- package/dist/tools/index.js +62 -0
- package/dist/tools/specialized/acg.d.ts +1 -0
- package/dist/tools/specialized/acg.js +143 -0
- package/dist/tools/specialized/comparative.d.ts +1 -0
- package/dist/tools/specialized/comparative.js +153 -0
- package/dist/tools/specialized/electional.js +178 -0
- package/dist/tools/specialized/ephemeris_core.d.ts +1 -0
- package/dist/tools/specialized/ephemeris_core.js +93 -0
- package/dist/tools/specialized/ephemeris_extended.d.ts +1 -0
- package/dist/tools/specialized/ephemeris_extended.js +234 -0
- package/dist/tools/specialized/hd_group.d.ts +1 -0
- package/dist/tools/specialized/hd_group.js +107 -0
- package/dist/tools/specialized/progressed.d.ts +1 -0
- package/dist/tools/specialized/progressed.js +65 -0
- package/dist/tools/specialized/returns.js +67 -49
- package/dist/tools/specialized/venus_star_points.d.ts +1 -0
- package/dist/tools/specialized/venus_star_points.js +187 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -179,8 +179,8 @@ Generated by `npm run sync:readme` from `config/dev-allowlist.json` and the live
|
|
|
179
179
|
|
|
180
180
|
- Allowlisted operations: **97**
|
|
181
181
|
- Methods: `GET=41`, `POST=56`, `PUT=0`, `PATCH=0`, `DELETE=0`
|
|
182
|
-
- Registered tools (`OPENEPHEMERIS_PROFILE=dev`): **
|
|
183
|
-
- Typed tools: `auth_login`, `auth_logout`, `auth_status`, `chinese_bazi`, `dev_call`, `dev_list_allowed`, `ephemeris_bi_wheel`, `ephemeris_chart_wheel`, `ephemeris_electional`, `ephemeris_moon_phase`, `ephemeris_natal_chart`, `ephemeris_next_eclipse`, `ephemeris_relocation`, `ephemeris_synastry`, `ephemeris_transits`, `human_design_chart`, `vedic_chart`
|
|
182
|
+
- Registered tools (`OPENEPHEMERIS_PROFILE=dev`): **49**
|
|
183
|
+
- Typed tools: `acg_hits`, `acg_power_lines`, `auth_login`, `auth_logout`, `auth_status`, `chinese_bazi`, `dev_call`, `dev_list_allowed`, `electional_aspect_search`, `electional_ingress_calendar`, `electional_moment_analysis`, `electional_station_tracker`, `ephemeris_angles_points`, `ephemeris_aspect_check`, `ephemeris_bi_wheel`, `ephemeris_chart_wheel`, `ephemeris_composite`, `ephemeris_composite_midpoint`, `ephemeris_dignities`, `ephemeris_electional`, `ephemeris_fixed_stars`, `ephemeris_hermetic_lots`, `ephemeris_house_cusps`, `ephemeris_lunar_return`, `ephemeris_midpoints`, `ephemeris_moon_phase`, `ephemeris_natal_batch`, `ephemeris_natal_chart`, `ephemeris_natal_transits`, `ephemeris_next_eclipse`, `ephemeris_overlay`, `ephemeris_planet_position`, `ephemeris_planetary_return`, `ephemeris_progressed_chart`, `ephemeris_relocation`, `ephemeris_retrograde_status`, `ephemeris_solar_return`, `ephemeris_synastry`, `ephemeris_transits`, `human_design_chart`, `human_design_composite`, `human_design_penta`, `vedic_chart`, `venus_eight_year_star`, `venus_elongations`, `venus_phase`, `venus_star_points`, `venus_star_points_conjunctions`, `venus_stations`
|
|
184
184
|
- Generic tools:
|
|
185
185
|
|
|
186
186
|
### Allowlist Families
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { fileURLToPath } from "node:url";
|
|
|
5
5
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
6
6
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7
7
|
import { CallToolRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
8
|
-
import { initTools, toolRegistry } from "./tools/index.js";
|
|
8
|
+
import { initTools, toolRegistry, formatToolResponse } from "./tools/index.js";
|
|
9
9
|
function resolveServerVersion() {
|
|
10
10
|
try {
|
|
11
11
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -104,53 +104,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
104
104
|
try {
|
|
105
105
|
const result = await tool.handler(request.params.arguments ?? {});
|
|
106
106
|
const durationMs = Date.now() - startTime;
|
|
107
|
-
|
|
108
|
-
// Intercept binary backend responses (like Chart Wheels) to return native MCP Images
|
|
109
|
-
if (result &&
|
|
110
|
-
typeof result === "object" &&
|
|
111
|
-
"encoding" in result &&
|
|
112
|
-
result.encoding === "base64" &&
|
|
113
|
-
"data_base64" in result &&
|
|
114
|
-
"content_type" in result) {
|
|
115
|
-
const binResp = result;
|
|
116
|
-
console.error(`[MCP] 🎨 Returning image (${binResp.content_length} bytes) for ${toolName}`);
|
|
117
|
-
return {
|
|
118
|
-
content: [
|
|
119
|
-
{
|
|
120
|
-
type: "image",
|
|
121
|
-
data: binResp.data_base64,
|
|
122
|
-
mimeType: binResp.content_type,
|
|
123
|
-
},
|
|
124
|
-
],
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
const jsonStr = JSON.stringify(result, null, 2);
|
|
128
|
-
// Safety limit: ~100kb JSON is roughly 20-30k tokens.
|
|
129
|
-
// Usually happens if LLMs ask for 50+ years of transits or raw ephemeris arrays.
|
|
130
|
-
if (jsonStr.length > 100_000) {
|
|
131
|
-
console.error(`[MCP] ⚠️ Warning: Payload too large (${(jsonStr.length / 1024).toFixed(1)}kb) for ${toolName}`);
|
|
132
|
-
return {
|
|
133
|
-
content: [
|
|
134
|
-
{
|
|
135
|
-
type: "text",
|
|
136
|
-
text: JSON.stringify({
|
|
137
|
-
success: false,
|
|
138
|
-
error: "PAYLOAD_TOO_LARGE",
|
|
139
|
-
message: `The requested query produced too much data (${(jsonStr.length / 1024).toFixed(1)}kb). Please significantly narrow your request parameters (e.g., fewer years, smaller coordinate bounding box, etc.) to fit within context limits.`
|
|
140
|
-
}, null, 2),
|
|
141
|
-
},
|
|
142
|
-
],
|
|
143
|
-
isError: true,
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
return {
|
|
147
|
-
content: [
|
|
148
|
-
{
|
|
149
|
-
type: "text",
|
|
150
|
-
text: jsonStr,
|
|
151
|
-
},
|
|
152
|
-
],
|
|
153
|
-
};
|
|
107
|
+
return formatToolResponse(toolName, result, durationMs);
|
|
154
108
|
}
|
|
155
109
|
catch (error) {
|
|
156
110
|
const durationMs = Date.now() - startTime;
|
package/dist/server-sse.js
CHANGED
|
@@ -20,7 +20,7 @@ import axios from "axios";
|
|
|
20
20
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
21
21
|
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|
22
22
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
23
|
-
import { initTools, toolRegistry } from "./tools/index.js";
|
|
23
|
+
import { initTools, toolRegistry, formatToolResponse } from "./tools/index.js";
|
|
24
24
|
import { backendClient } from "./backend/client.js";
|
|
25
25
|
// ---------------------------------------------------------------------------
|
|
26
26
|
// Helpers
|
|
@@ -108,13 +108,16 @@ function createMcpServer() {
|
|
|
108
108
|
throw new Error(`Unknown tool: ${toolName}`);
|
|
109
109
|
}
|
|
110
110
|
try {
|
|
111
|
+
const startTime = Date.now();
|
|
112
|
+
console.error(`[MCP] Executing tool: ${toolName}`);
|
|
111
113
|
const result = await tool.handler(request.params.arguments ?? {});
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
};
|
|
114
|
+
const durationMs = Date.now() - startTime;
|
|
115
|
+
return formatToolResponse(toolName, result, durationMs);
|
|
115
116
|
}
|
|
116
117
|
catch (error) {
|
|
118
|
+
const startTime = Date.now(); // Not strictly accurate for errors but acceptable for logging structure if needed
|
|
117
119
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
120
|
+
console.error(`[MCP] \u274c Failed: ${toolName} - ${errorMessage}`);
|
|
118
121
|
return {
|
|
119
122
|
content: [
|
|
120
123
|
{
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -31,3 +31,28 @@ export declare function validateRequired(args: any, requiredKeys: string[]): voi
|
|
|
31
31
|
* Throws an error if only one of the coordinate pair is provided.
|
|
32
32
|
*/
|
|
33
33
|
export declare function validateCoordinates(args: any, latKey: string, lonKey: string): void;
|
|
34
|
+
/**
|
|
35
|
+
* Formats a raw tool response into native MCP content blocks.
|
|
36
|
+
* Intercepts binary image responses (like Chart Wheels) and returns them as native MCP Images.
|
|
37
|
+
* Applies a safety limit to JSON payloads to prevent crashing LLM contexts.
|
|
38
|
+
*/
|
|
39
|
+
export declare function formatToolResponse(toolName: string, result: any, durationMs: number): {
|
|
40
|
+
content: {
|
|
41
|
+
type: string;
|
|
42
|
+
data: string;
|
|
43
|
+
mimeType: string;
|
|
44
|
+
}[];
|
|
45
|
+
isError?: undefined;
|
|
46
|
+
} | {
|
|
47
|
+
content: {
|
|
48
|
+
type: string;
|
|
49
|
+
text: string;
|
|
50
|
+
}[];
|
|
51
|
+
isError: boolean;
|
|
52
|
+
} | {
|
|
53
|
+
content: {
|
|
54
|
+
type: string;
|
|
55
|
+
text: string;
|
|
56
|
+
}[];
|
|
57
|
+
isError?: undefined;
|
|
58
|
+
};
|
package/dist/tools/index.js
CHANGED
|
@@ -33,6 +33,14 @@ export async function initTools(profile) {
|
|
|
33
33
|
await import("./specialized/bazi.js");
|
|
34
34
|
await import("./specialized/chart_wheel.js");
|
|
35
35
|
await import("./specialized/bi_wheel.js");
|
|
36
|
+
await import("./specialized/returns.js");
|
|
37
|
+
await import("./specialized/progressed.js");
|
|
38
|
+
await import("./specialized/ephemeris_core.js");
|
|
39
|
+
await import("./specialized/acg.js");
|
|
40
|
+
await import("./specialized/comparative.js");
|
|
41
|
+
await import("./specialized/hd_group.js");
|
|
42
|
+
await import("./specialized/ephemeris_extended.js");
|
|
43
|
+
await import("./specialized/venus_star_points.js");
|
|
36
44
|
}
|
|
37
45
|
}
|
|
38
46
|
/**
|
|
@@ -58,3 +66,57 @@ export function validateCoordinates(args, latKey, lonKey) {
|
|
|
58
66
|
throw new Error(`Both ${latKey} and ${lonKey} must be provided together, or both omitted.`);
|
|
59
67
|
}
|
|
60
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Formats a raw tool response into native MCP content blocks.
|
|
71
|
+
* Intercepts binary image responses (like Chart Wheels) and returns them as native MCP Images.
|
|
72
|
+
* Applies a safety limit to JSON payloads to prevent crashing LLM contexts.
|
|
73
|
+
*/
|
|
74
|
+
export function formatToolResponse(toolName, result, durationMs) {
|
|
75
|
+
// Intercept binary backend responses (like Chart Wheels) to return native MCP Images
|
|
76
|
+
if (result &&
|
|
77
|
+
typeof result === "object" &&
|
|
78
|
+
"encoding" in result &&
|
|
79
|
+
result.encoding === "base64" &&
|
|
80
|
+
"data_base64" in result &&
|
|
81
|
+
"content_type" in result) {
|
|
82
|
+
const binResp = result;
|
|
83
|
+
console.error(`[MCP] 🎨 Returning image (${binResp.content_length} bytes) for ${toolName} (${durationMs}ms)`);
|
|
84
|
+
return {
|
|
85
|
+
content: [
|
|
86
|
+
{
|
|
87
|
+
type: "image",
|
|
88
|
+
data: binResp.data_base64,
|
|
89
|
+
mimeType: binResp.content_type,
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
const jsonStr = JSON.stringify(result, null, 2);
|
|
95
|
+
// Safety limit: ~500kb JSON is roughly 100k+ tokens.
|
|
96
|
+
// Usually happens if LLMs ask for 50+ years of transits or raw ephemeris arrays.
|
|
97
|
+
if (jsonStr.length > 500_000) {
|
|
98
|
+
console.error(`[MCP] ⚠️ Warning: Payload too large (${(jsonStr.length / 1024).toFixed(1)}kb) for ${toolName}`);
|
|
99
|
+
return {
|
|
100
|
+
content: [
|
|
101
|
+
{
|
|
102
|
+
type: "text",
|
|
103
|
+
text: JSON.stringify({
|
|
104
|
+
success: false,
|
|
105
|
+
error: "PAYLOAD_TOO_LARGE",
|
|
106
|
+
message: `The requested query produced too much data (${(jsonStr.length / 1024).toFixed(1)}kb). Please significantly narrow your request parameters (e.g., fewer years, smaller coordinate bounding box, etc.) to fit within context limits.`
|
|
107
|
+
}, null, 2),
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
isError: true,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
console.error(`[MCP] ✅ Success: ${toolName} (${durationMs}ms)`);
|
|
114
|
+
return {
|
|
115
|
+
content: [
|
|
116
|
+
{
|
|
117
|
+
type: "text",
|
|
118
|
+
text: jsonStr,
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
};
|
|
122
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { registerTool, validateRequired } from "../index.js";
|
|
2
|
+
import { backendClient } from "../../backend/client.js";
|
|
3
|
+
const ACG_BODY_DESCRIPTION = "List of celestial bodies for line calculation. " +
|
|
4
|
+
"E.g. ['Sun', 'Moon', 'Venus', 'Mars', 'Jupiter', 'Saturn']. " +
|
|
5
|
+
"Aliases: 'NorthNode'/'Node'/'Rahu' → MeanNode, 'SouthNode'/'Ketu' → SouthNode. " +
|
|
6
|
+
"Omit to use all classical planets.";
|
|
7
|
+
// POST /acg/power-lines — OE-018
|
|
8
|
+
registerTool({
|
|
9
|
+
name: "acg_power_lines",
|
|
10
|
+
description: "Calculate Astrocartography power lines (MC, IC, AC/ASC, DC/DSC) for a natal chart. " +
|
|
11
|
+
"Returns GeoJSON LineStrings tracing each planetary angle line around the globe. " +
|
|
12
|
+
"These are the latitudinal lines where a planet was rising (AC), setting (DC), " +
|
|
13
|
+
"culminating (MC), or anti-culminating (IC) at birth.\n\n" +
|
|
14
|
+
"CREDIT COST: 5 credits per call.\n\n" +
|
|
15
|
+
"EXAMPLE: Saturn and Jupiter power lines for a chart born 1990-04-15 in Chicago:\n" +
|
|
16
|
+
" birth_datetime='1990-04-15T14:30:00', birth_latitude=41.8781, birth_longitude=-87.6298,\n" +
|
|
17
|
+
" bodies=['Saturn', 'Jupiter']",
|
|
18
|
+
inputSchema: {
|
|
19
|
+
type: "object",
|
|
20
|
+
properties: {
|
|
21
|
+
birth_datetime: {
|
|
22
|
+
type: "string",
|
|
23
|
+
description: "ISO 8601 natal birth date/time.",
|
|
24
|
+
},
|
|
25
|
+
birth_latitude: {
|
|
26
|
+
type: "number",
|
|
27
|
+
description: "Birth latitude in decimal degrees.",
|
|
28
|
+
},
|
|
29
|
+
birth_longitude: {
|
|
30
|
+
type: "number",
|
|
31
|
+
description: "Birth longitude in decimal degrees.",
|
|
32
|
+
},
|
|
33
|
+
bodies: {
|
|
34
|
+
type: "array",
|
|
35
|
+
items: { type: "string" },
|
|
36
|
+
description: ACG_BODY_DESCRIPTION,
|
|
37
|
+
},
|
|
38
|
+
query_latitude: {
|
|
39
|
+
type: "number",
|
|
40
|
+
description: "Filter lines near this latitude (optional, for hits mode).",
|
|
41
|
+
},
|
|
42
|
+
query_longitude: {
|
|
43
|
+
type: "number",
|
|
44
|
+
description: "Filter lines near this longitude (optional, for hits mode).",
|
|
45
|
+
},
|
|
46
|
+
radius_deg: {
|
|
47
|
+
type: "number",
|
|
48
|
+
description: "Search radius in degrees for hits filtering. Default 2°.",
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
required: ["birth_datetime", "birth_latitude", "birth_longitude"],
|
|
52
|
+
additionalProperties: false,
|
|
53
|
+
},
|
|
54
|
+
handler: async (args) => {
|
|
55
|
+
validateRequired(args, ["birth_datetime", "birth_latitude", "birth_longitude"]);
|
|
56
|
+
const body = {
|
|
57
|
+
natal: {
|
|
58
|
+
birthplace_lat: args.birth_latitude,
|
|
59
|
+
birthplace_lon: args.birth_longitude,
|
|
60
|
+
},
|
|
61
|
+
epoch: args.birth_datetime,
|
|
62
|
+
};
|
|
63
|
+
if (args.bodies)
|
|
64
|
+
body.bodies = args.bodies;
|
|
65
|
+
if (args.query_latitude != null)
|
|
66
|
+
body.query_lat = args.query_latitude;
|
|
67
|
+
if (args.query_longitude != null)
|
|
68
|
+
body.query_lon = args.query_longitude;
|
|
69
|
+
if (args.radius_deg != null)
|
|
70
|
+
body.radius_deg = args.radius_deg;
|
|
71
|
+
return await backendClient.post("/acg/power-lines", body);
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
// POST /acg/hits — OE-018
|
|
75
|
+
registerTool({
|
|
76
|
+
name: "acg_hits",
|
|
77
|
+
description: "Find all Astrocartography lines (power lines + aspect lines) passing near a specific location. " +
|
|
78
|
+
"Returns features sorted by distance, making it easy to interpret planetary influences at a place.\n\n" +
|
|
79
|
+
"CREDIT COST: 5 credits per call.\n\n" +
|
|
80
|
+
"EXAMPLE: All ACG lines within 3° of Paris for a chart born 1990-04-15 in Chicago:\n" +
|
|
81
|
+
" birth_datetime='1990-04-15T14:30:00', birth_latitude=41.8781, birth_longitude=-87.6298,\n" +
|
|
82
|
+
" query_latitude=48.8566, query_longitude=2.3522, radius_deg=3",
|
|
83
|
+
inputSchema: {
|
|
84
|
+
type: "object",
|
|
85
|
+
properties: {
|
|
86
|
+
birth_datetime: {
|
|
87
|
+
type: "string",
|
|
88
|
+
description: "ISO 8601 natal birth date/time.",
|
|
89
|
+
},
|
|
90
|
+
birth_latitude: {
|
|
91
|
+
type: "number",
|
|
92
|
+
description: "Birth latitude in decimal degrees.",
|
|
93
|
+
},
|
|
94
|
+
birth_longitude: {
|
|
95
|
+
type: "number",
|
|
96
|
+
description: "Birth longitude in decimal degrees.",
|
|
97
|
+
},
|
|
98
|
+
query_latitude: {
|
|
99
|
+
type: "number",
|
|
100
|
+
description: "Latitude of the location to check for nearby lines.",
|
|
101
|
+
},
|
|
102
|
+
query_longitude: {
|
|
103
|
+
type: "number",
|
|
104
|
+
description: "Longitude of the location to check for nearby lines.",
|
|
105
|
+
},
|
|
106
|
+
radius_deg: {
|
|
107
|
+
type: "number",
|
|
108
|
+
description: "Search radius in degrees (default 2°, max 90°).",
|
|
109
|
+
},
|
|
110
|
+
bodies: {
|
|
111
|
+
type: "array",
|
|
112
|
+
items: { type: "string" },
|
|
113
|
+
description: ACG_BODY_DESCRIPTION,
|
|
114
|
+
},
|
|
115
|
+
include_aspects: {
|
|
116
|
+
type: "boolean",
|
|
117
|
+
description: "Include aspect lines in results (default true).",
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
required: ["birth_datetime", "birth_latitude", "birth_longitude", "query_latitude", "query_longitude"],
|
|
121
|
+
additionalProperties: false,
|
|
122
|
+
},
|
|
123
|
+
handler: async (args) => {
|
|
124
|
+
validateRequired(args, ["birth_datetime", "birth_latitude", "birth_longitude", "query_latitude", "query_longitude"]);
|
|
125
|
+
const body = {
|
|
126
|
+
natal: {
|
|
127
|
+
birthplace_lat: args.birth_latitude,
|
|
128
|
+
birthplace_lon: args.birth_longitude,
|
|
129
|
+
},
|
|
130
|
+
epoch: args.birth_datetime,
|
|
131
|
+
query_lat: args.query_latitude,
|
|
132
|
+
query_lon: args.query_longitude,
|
|
133
|
+
};
|
|
134
|
+
if (args.bodies)
|
|
135
|
+
body.bodies = args.bodies;
|
|
136
|
+
if (args.radius_deg != null)
|
|
137
|
+
body.radius_deg = args.radius_deg;
|
|
138
|
+
if (args.include_aspects != null) {
|
|
139
|
+
body.options = { include_aspects: args.include_aspects };
|
|
140
|
+
}
|
|
141
|
+
return await backendClient.post("/acg/hits", body);
|
|
142
|
+
},
|
|
143
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { registerTool, validateRequired } from "../index.js";
|
|
2
|
+
import { backendClient } from "../../backend/client.js";
|
|
3
|
+
function buildSubject(name, datetime, lat, lon) {
|
|
4
|
+
return {
|
|
5
|
+
name,
|
|
6
|
+
birth_datetime: { iso: datetime },
|
|
7
|
+
birth_location: {
|
|
8
|
+
latitude: { decimal: lat },
|
|
9
|
+
longitude: { decimal: lon },
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
// POST /comparative/composite
|
|
14
|
+
registerTool({
|
|
15
|
+
name: "ephemeris_composite",
|
|
16
|
+
description: "Calculate a composite chart from two or more natal charts. The composite " +
|
|
17
|
+
"uses midpoints of each planet pair to derive a single relationship chart.\n\n" +
|
|
18
|
+
"CREDIT COST: 3 credits per call.\n\n" +
|
|
19
|
+
"EXAMPLE: Composite for two people:\n" +
|
|
20
|
+
" person_a_datetime='1990-04-15T14:30:00', person_a_latitude=41.88, person_a_longitude=-87.63,\n" +
|
|
21
|
+
" person_b_datetime='1988-09-22T08:15:00', person_b_latitude=34.05, person_b_longitude=-118.24",
|
|
22
|
+
inputSchema: {
|
|
23
|
+
type: "object",
|
|
24
|
+
properties: {
|
|
25
|
+
person_a_datetime: { type: "string", description: "Person A birth datetime (ISO 8601)." },
|
|
26
|
+
person_a_latitude: { type: "number", description: "Person A birth latitude." },
|
|
27
|
+
person_a_longitude: { type: "number", description: "Person A birth longitude." },
|
|
28
|
+
person_b_datetime: { type: "string", description: "Person B birth datetime (ISO 8601)." },
|
|
29
|
+
person_b_latitude: { type: "number", description: "Person B birth latitude." },
|
|
30
|
+
person_b_longitude: { type: "number", description: "Person B birth longitude." },
|
|
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
|
+
return await backendClient.post("/comparative/composite", {
|
|
44
|
+
subjects: [
|
|
45
|
+
buildSubject("Person A", args.person_a_datetime, args.person_a_latitude, args.person_a_longitude),
|
|
46
|
+
buildSubject("Person B", args.person_b_datetime, args.person_b_latitude, args.person_b_longitude),
|
|
47
|
+
],
|
|
48
|
+
});
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
// POST /comparative/composite/midpoint
|
|
52
|
+
registerTool({
|
|
53
|
+
name: "ephemeris_composite_midpoint",
|
|
54
|
+
description: "Calculate a midpoint composite chart — an alternative composite method " +
|
|
55
|
+
"using the midpoints of matching planet positions.\n\n" +
|
|
56
|
+
"CREDIT COST: 3 credits per call.",
|
|
57
|
+
inputSchema: {
|
|
58
|
+
type: "object",
|
|
59
|
+
properties: {
|
|
60
|
+
person_a_datetime: { type: "string", description: "Person A birth datetime (ISO 8601)." },
|
|
61
|
+
person_a_latitude: { type: "number", description: "Person A birth latitude." },
|
|
62
|
+
person_a_longitude: { type: "number", description: "Person A birth longitude." },
|
|
63
|
+
person_b_datetime: { type: "string", description: "Person B birth datetime (ISO 8601)." },
|
|
64
|
+
person_b_latitude: { type: "number", description: "Person B birth latitude." },
|
|
65
|
+
person_b_longitude: { type: "number", description: "Person B birth longitude." },
|
|
66
|
+
},
|
|
67
|
+
required: [
|
|
68
|
+
"person_a_datetime", "person_a_latitude", "person_a_longitude",
|
|
69
|
+
"person_b_datetime", "person_b_latitude", "person_b_longitude",
|
|
70
|
+
],
|
|
71
|
+
additionalProperties: false,
|
|
72
|
+
},
|
|
73
|
+
handler: async (args) => {
|
|
74
|
+
validateRequired(args, [
|
|
75
|
+
"person_a_datetime", "person_a_latitude", "person_a_longitude",
|
|
76
|
+
"person_b_datetime", "person_b_latitude", "person_b_longitude",
|
|
77
|
+
]);
|
|
78
|
+
return await backendClient.post("/comparative/composite/midpoint", {
|
|
79
|
+
subjects: [
|
|
80
|
+
buildSubject("Person A", args.person_a_datetime, args.person_a_latitude, args.person_a_longitude),
|
|
81
|
+
buildSubject("Person B", args.person_b_datetime, args.person_b_latitude, args.person_b_longitude),
|
|
82
|
+
],
|
|
83
|
+
});
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
// POST /comparative/overlay
|
|
87
|
+
registerTool({
|
|
88
|
+
name: "ephemeris_overlay",
|
|
89
|
+
description: "Calculate an overlay chart — place one person's planets in another person's houses. " +
|
|
90
|
+
"Shows how person A's energies impact person B's life areas and vice versa.\n\n" +
|
|
91
|
+
"CREDIT COST: 3 credits per call.",
|
|
92
|
+
inputSchema: {
|
|
93
|
+
type: "object",
|
|
94
|
+
properties: {
|
|
95
|
+
person_a_datetime: { type: "string", description: "Person A birth datetime (ISO 8601)." },
|
|
96
|
+
person_a_latitude: { type: "number", description: "Person A birth latitude." },
|
|
97
|
+
person_a_longitude: { type: "number", description: "Person A birth longitude." },
|
|
98
|
+
person_b_datetime: { type: "string", description: "Person B birth datetime (ISO 8601)." },
|
|
99
|
+
person_b_latitude: { type: "number", description: "Person B birth latitude." },
|
|
100
|
+
person_b_longitude: { type: "number", description: "Person B birth longitude." },
|
|
101
|
+
},
|
|
102
|
+
required: [
|
|
103
|
+
"person_a_datetime", "person_a_latitude", "person_a_longitude",
|
|
104
|
+
"person_b_datetime", "person_b_latitude", "person_b_longitude",
|
|
105
|
+
],
|
|
106
|
+
additionalProperties: false,
|
|
107
|
+
},
|
|
108
|
+
handler: async (args) => {
|
|
109
|
+
validateRequired(args, [
|
|
110
|
+
"person_a_datetime", "person_a_latitude", "person_a_longitude",
|
|
111
|
+
"person_b_datetime", "person_b_latitude", "person_b_longitude",
|
|
112
|
+
]);
|
|
113
|
+
return await backendClient.post("/comparative/overlay", {
|
|
114
|
+
subjects: [
|
|
115
|
+
buildSubject("Person A", args.person_a_datetime, args.person_a_latitude, args.person_a_longitude),
|
|
116
|
+
buildSubject("Person B", args.person_b_datetime, args.person_b_latitude, args.person_b_longitude),
|
|
117
|
+
],
|
|
118
|
+
});
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
// POST /comparative/natal-transits
|
|
122
|
+
registerTool({
|
|
123
|
+
name: "ephemeris_natal_transits",
|
|
124
|
+
description: "Calculate active transits to a natal chart — shows which current planets " +
|
|
125
|
+
"are aspecting natal positions. Essential for predictive astrology.\n\n" +
|
|
126
|
+
"CREDIT COST: 3 credits per call.\n\n" +
|
|
127
|
+
"EXAMPLE: Current transits to someone born 1990-04-15:\n" +
|
|
128
|
+
" natal_datetime='1990-04-15T14:30:00', natal_latitude=41.88, natal_longitude=-87.63",
|
|
129
|
+
inputSchema: {
|
|
130
|
+
type: "object",
|
|
131
|
+
properties: {
|
|
132
|
+
natal_datetime: { type: "string", description: "Natal birth datetime (ISO 8601)." },
|
|
133
|
+
natal_latitude: { type: "number", description: "Natal birth latitude." },
|
|
134
|
+
natal_longitude: { type: "number", description: "Natal birth longitude." },
|
|
135
|
+
transit_datetime: { type: "string", description: "Transit moment (ISO 8601). Defaults to now." },
|
|
136
|
+
},
|
|
137
|
+
required: ["natal_datetime", "natal_latitude", "natal_longitude"],
|
|
138
|
+
additionalProperties: false,
|
|
139
|
+
},
|
|
140
|
+
handler: async (args) => {
|
|
141
|
+
validateRequired(args, ["natal_datetime", "natal_latitude", "natal_longitude"]);
|
|
142
|
+
const subjects = [
|
|
143
|
+
buildSubject("Natal", args.natal_datetime, args.natal_latitude, args.natal_longitude),
|
|
144
|
+
];
|
|
145
|
+
// The transit subject uses the transit datetime or current time
|
|
146
|
+
if (args.transit_datetime) {
|
|
147
|
+
subjects.push(buildSubject("Transit", args.transit_datetime, args.natal_latitude, args.natal_longitude));
|
|
148
|
+
}
|
|
149
|
+
return await backendClient.post("/comparative/natal-transits", {
|
|
150
|
+
subjects,
|
|
151
|
+
});
|
|
152
|
+
},
|
|
153
|
+
});
|