@openephemeris/mcp-server 3.2.6 → 3.2.8

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 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`): **15**
183
- - Typed tools: `auth_login`, `auth_logout`, `auth_status`, `chinese_bazi`, `dev_call`, `dev_list_allowed`, `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`): **17**
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`
184
184
  - Generic tools:
185
185
 
186
186
  ### Allowlist Families
@@ -206,9 +206,11 @@ Generated by `npm run sync:readme` from `config/dev-allowlist.json` and the live
206
206
  | `visualization` | 2 | `POST /visualization/bi-wheel`, `POST /visualization/chart-wheel` |
207
207
  <!-- GENERATED:RUNTIME_SNAPSHOT:END -->
208
208
 
209
+ ## Why OpenEphemeris for AI Agents?
209
210
 
210
- NPM publish
211
+ Most LLMs (like Claude and ChatGPT) struggle heavily with astronomical calculations (trigonometry, Julian date conversions, and Swiss Ephemeris lookups). OpenEphemeris serves as a **secure, remote math engine**.
211
212
 
212
- publish from /c/src/openephemeris/apps/api/mcp-server
213
- npm login
214
- npm publish
213
+ By pairing LLMs with the OpenEphemeris MCP server, your agents can instantly access:
214
+ - **Zero-hallucination coordinates**: Direct, sub-arcsecond NASA JPL DE440/DE441 calculations.
215
+ - **LLM-optimized tokens (`format=llm`)**: We compress standard 25,000 token JSON chart responses into minimal text blocks, cutting your inference costs by 50%.
216
+ - **Ready-to-use astrology layers**: Built-in support for Astrocartography geoJSON lines, Hermetic Lots, Fixed Stars, and complex Human Design matrix generation.
@@ -256,6 +256,10 @@ export class BackendClient {
256
256
  if (status >= 500)
257
257
  return new BackendError(`Backend error (${status}): ${msg}`, status, "server_error", true);
258
258
  }
259
+ // Explicit timeout or abort interception
260
+ if (error.code === "ECONNABORTED" || error.code === "ETIMEDOUT") {
261
+ return new BackendError(`The computation took too long and timed out. This usually happens when requesting a massive date range (like 20+ years of transits) or an overly complex query. Please narrow your request and try again.`, 408, "timeout", false);
262
+ }
259
263
  return new BackendError(`Network error: ${error.message}`, 0, "network_error", true);
260
264
  }
261
265
  async get(path, params, timeoutMs) {
package/dist/src/index.js CHANGED
@@ -99,19 +99,63 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
99
99
  if (!tool) {
100
100
  throw new Error(`Unknown tool: ${toolName}`);
101
101
  }
102
+ const startTime = Date.now();
103
+ console.error(`[MCP] Executing tool: ${toolName}`);
102
104
  try {
103
105
  const result = await tool.handler(request.params.arguments ?? {});
106
+ const durationMs = Date.now() - startTime;
107
+ console.error(`[MCP] ✅ Success: ${toolName} (${durationMs}ms)`);
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
+ }
104
146
  return {
105
147
  content: [
106
148
  {
107
149
  type: "text",
108
- text: JSON.stringify(result, null, 2),
150
+ text: jsonStr,
109
151
  },
110
152
  ],
111
153
  };
112
154
  }
113
155
  catch (error) {
156
+ const durationMs = Date.now() - startTime;
114
157
  const errorMessage = error instanceof Error ? error.message : String(error);
158
+ console.error(`[MCP] ❌ Failed: ${toolName} (${durationMs}ms) - ${errorMessage}`);
115
159
  return {
116
160
  content: [
117
161
  {
@@ -31,6 +31,8 @@ export async function initTools(profile) {
31
31
  await import("./specialized/electional.js");
32
32
  await import("./specialized/vedic.js");
33
33
  await import("./specialized/bazi.js");
34
+ await import("./specialized/chart_wheel.js");
35
+ await import("./specialized/bi_wheel.js");
34
36
  }
35
37
  }
36
38
  /**
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,66 @@
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
+ const HOUSE_SYSTEM_MAP = {
6
+ placidus: "P", whole_sign: "W", equal: "E", koch: "K",
7
+ campanus: "C", regiomontanus: "R", porphyry: "O",
8
+ alcabitius: "B", morinus: "M",
9
+ };
10
+ registerTool({
11
+ name: "ephemeris_bi_wheel",
12
+ description: "Generate a Bi-Wheel (Synastry/Transit) image (PNG) comparing two charts. Draws Subject A's planets on the inside wheel and Subject B's on the outside wheel. The tool returns a raw image (base64) which most Vision-enabled AI agents can display.\n\n" +
13
+ "CREDIT COST: 2 credits per call.\n\n" +
14
+ "EXAMPLE: Compare someone born April 15, 1990 (A) to someone born June 10, 1992 (B):\n" +
15
+ " datetime_a='...', latitude_a=..., longitude_a=..., datetime_b='...', latitude_b=..., longitude_b=...",
16
+ inputSchema: {
17
+ type: "object",
18
+ properties: {
19
+ datetime_a: { type: "string", description: DATETIME_DESC },
20
+ latitude_a: { type: "number", description: "Latitude for subject A" },
21
+ longitude_a: { type: "number", description: "Longitude for subject A" },
22
+ datetime_b: { type: "string", description: DATETIME_DESC },
23
+ latitude_b: { type: "number", description: "Latitude for subject B" },
24
+ longitude_b: { type: "number", description: "Longitude for subject B" },
25
+ house_system: {
26
+ type: "string",
27
+ enum: ["placidus", "whole_sign", "equal", "koch", "campanus", "regiomontanus", "porphyry", "alcabitius", "morinus"],
28
+ description: "House system to use. Defaults to 'placidus' if omitted.",
29
+ },
30
+ style: {
31
+ type: "string",
32
+ enum: ["modern", "classic", "dark"],
33
+ description: "Aesthetic style of the chart. Defaults to 'modern'.",
34
+ }
35
+ },
36
+ required: ["datetime_a", "latitude_a", "longitude_a", "datetime_b", "latitude_b", "longitude_b"],
37
+ additionalProperties: false,
38
+ },
39
+ handler: async (args) => {
40
+ validateRequired(args, ["datetime_a", "latitude_a", "longitude_a", "datetime_b", "latitude_b", "longitude_b"]);
41
+ const body = {
42
+ subject_a: {
43
+ name: "Subject A",
44
+ birth_datetime: { iso: args.datetime_a },
45
+ birth_location: {
46
+ latitude: { decimal: args.latitude_a },
47
+ longitude: { decimal: args.longitude_a }
48
+ },
49
+ },
50
+ subject_b: {
51
+ name: "Subject B",
52
+ birth_datetime: { iso: args.datetime_b },
53
+ birth_location: {
54
+ latitude: { decimal: args.latitude_b },
55
+ longitude: { decimal: args.longitude_b }
56
+ },
57
+ }
58
+ };
59
+ if (args.house_system) {
60
+ const mappedCode = HOUSE_SYSTEM_MAP[args.house_system] || "P";
61
+ body.configuration = { house_system: mappedCode };
62
+ }
63
+ const styleParam = args.style ? `&style=${args.style}` : "";
64
+ return await backendClient.post(`/visualization/bi-wheel?format=png&size=800${styleParam}`, body);
65
+ },
66
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,64 @@
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
+ const HOUSE_SYSTEM_MAP = {
6
+ placidus: "P", whole_sign: "W", equal: "E", koch: "K",
7
+ campanus: "C", regiomontanus: "R", porphyry: "O",
8
+ alcabitius: "B", morinus: "M",
9
+ };
10
+ registerTool({
11
+ name: "ephemeris_chart_wheel",
12
+ description: "Generate a classic astrological Chart Wheel image (PNG) for a person/event. This draws a standard circular chart wheel with planets, aspects, and house cusps. The tool returns a raw image (base64) which most Vision-enabled AI agents (GPT-4o, Claude 3.5 Sonnet) can display and visually read.\n\n" +
13
+ "CREDIT COST: 2 credits per call.\n\n" +
14
+ "EXAMPLE: Generate a natal chart wheel for someone born April 15, 1990 in Chicago:\n" +
15
+ " datetime='1990-04-15T14:30:00', latitude=41.8781, longitude=-87.6298",
16
+ inputSchema: {
17
+ type: "object",
18
+ properties: {
19
+ datetime: {
20
+ type: "string",
21
+ description: DATETIME_DESC,
22
+ },
23
+ latitude: {
24
+ type: "number",
25
+ description: "Geographic latitude of birth location in decimal degrees (positive = North).",
26
+ },
27
+ longitude: {
28
+ type: "number",
29
+ description: "Geographic longitude of birth location in decimal degrees (positive = East).",
30
+ },
31
+ house_system: {
32
+ type: "string",
33
+ enum: ["placidus", "whole_sign", "equal", "koch", "campanus", "regiomontanus", "porphyry", "alcabitius", "morinus"],
34
+ description: "House system to use. Defaults to 'placidus' if omitted.",
35
+ },
36
+ style: {
37
+ type: "string",
38
+ enum: ["modern", "classic", "dark"],
39
+ description: "Aesthetic style of the chart. Defaults to 'modern'.",
40
+ }
41
+ },
42
+ required: ["datetime", "latitude", "longitude"],
43
+ additionalProperties: false,
44
+ },
45
+ handler: async (args) => {
46
+ validateRequired(args, ["datetime", "latitude", "longitude"]);
47
+ const body = {
48
+ subject: {
49
+ name: "Chart Wheel Request",
50
+ birth_datetime: { iso: args.datetime },
51
+ birth_location: {
52
+ latitude: { decimal: args.latitude },
53
+ longitude: { decimal: args.longitude }
54
+ },
55
+ }
56
+ };
57
+ if (args.house_system) {
58
+ const mappedCode = HOUSE_SYSTEM_MAP[args.house_system] || "P";
59
+ body.configuration = { house_system: mappedCode };
60
+ }
61
+ const styleParam = args.style ? `&style=${args.style}` : "";
62
+ return await backendClient.post(`/visualization/chart-wheel?format=png&size=800${styleParam}`, body);
63
+ },
64
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openephemeris/mcp-server",
3
- "version": "3.2.6",
3
+ "version": "3.2.8",
4
4
  "description": "Model Context Protocol server for the Open Ephemeris astronomical computation API",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -42,7 +42,11 @@
42
42
  "astrology",
43
43
  "astronomy",
44
44
  "ephemeris",
45
- "openephemeris"
45
+ "openephemeris",
46
+ "llm",
47
+ "claude",
48
+ "ai",
49
+ "swiss ephemeris"
46
50
  ],
47
51
  "author": "Open Ephemeris",
48
52
  "license": "MIT",