@openephemeris/mcp-server 3.0.1 → 3.1.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 +6 -5
- package/config/dev-allowlist.json +1319 -1165
- package/dist/backend/client.d.ts +12 -0
- package/dist/backend/client.js +99 -35
- package/dist/index.js +5 -0
- package/dist/schema-packs/llm.d.ts +1 -1
- package/dist/schema-packs/llm.js +1 -1
- package/dist/tools/dev.js +7 -3
- package/dist/tools/index.d.ts +7 -0
- package/package.json +18 -21
package/dist/backend/client.d.ts
CHANGED
|
@@ -15,6 +15,12 @@ export interface BackendRequestOptions {
|
|
|
15
15
|
/** Override per-request timeout in milliseconds. */
|
|
16
16
|
timeoutMs?: number;
|
|
17
17
|
}
|
|
18
|
+
export interface BinaryBackendResponse {
|
|
19
|
+
content_type: string;
|
|
20
|
+
content_length: number;
|
|
21
|
+
encoding: "base64";
|
|
22
|
+
data_base64: string;
|
|
23
|
+
}
|
|
18
24
|
export declare class BackendClient {
|
|
19
25
|
private client;
|
|
20
26
|
private userId;
|
|
@@ -22,6 +28,12 @@ export declare class BackendClient {
|
|
|
22
28
|
private serviceKey?;
|
|
23
29
|
private apiKey?;
|
|
24
30
|
constructor(config: BackendConfig);
|
|
31
|
+
private expectsBinaryResponse;
|
|
32
|
+
private toBuffer;
|
|
33
|
+
private decodePayload;
|
|
34
|
+
private extractMessage;
|
|
35
|
+
private normalizeContentType;
|
|
36
|
+
private isBinaryContentType;
|
|
25
37
|
/** Maps an AxiosError to a concise MCP-friendly Error. */
|
|
26
38
|
private mapError;
|
|
27
39
|
get<T>(path: string, params?: Record<string, any>, timeoutMs?: number): Promise<T>;
|
package/dist/backend/client.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import axios, { AxiosError } from "axios";
|
|
2
2
|
const DEFAULT_TIMEOUT_MS = 45_000;
|
|
3
3
|
const RATE_LIMIT_RETRY_DELAYS_MS = [1_000, 2_000]; // Two retries on 429
|
|
4
|
+
const BINARY_ENDPOINT_PREFIXES = [
|
|
5
|
+
"/visualization/bi-wheel",
|
|
6
|
+
"/visualization/chart-wheel",
|
|
7
|
+
"/comparative/visualization/bi-wheel",
|
|
8
|
+
"/comparative/visualization/chart-wheel",
|
|
9
|
+
];
|
|
4
10
|
const DASHBOARD_ACCOUNT_URL = "https://openephemeris.com/dashboard?tab=account";
|
|
5
11
|
const LOGIN_SIGNUP_URL = "https://openephemeris.com/login?signup=true&redirect=%2Fdashboard%3Ftab%3Daccount";
|
|
6
12
|
const UPGRADE_URL = "https://openephemeris.com/pay";
|
|
@@ -62,12 +68,83 @@ export class BackendClient {
|
|
|
62
68
|
return req;
|
|
63
69
|
});
|
|
64
70
|
}
|
|
71
|
+
expectsBinaryResponse(path, options) {
|
|
72
|
+
const normalizedPath = path.split("?")[0].trim().toLowerCase();
|
|
73
|
+
if (BINARY_ENDPOINT_PREFIXES.some((prefix) => normalizedPath.startsWith(prefix))) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
const format = options?.params?.format;
|
|
77
|
+
if (typeof format === "string") {
|
|
78
|
+
const normalizedFormat = format.trim().toLowerCase();
|
|
79
|
+
if (normalizedFormat === "png" || normalizedFormat === "svg") {
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const acceptHeader = Object.entries(options?.headers ?? {}).find(([key]) => key.toLowerCase() === "accept")?.[1];
|
|
84
|
+
if (typeof acceptHeader === "string" && acceptHeader.toLowerCase().includes("image/")) {
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
toBuffer(data) {
|
|
90
|
+
if (data == null)
|
|
91
|
+
return null;
|
|
92
|
+
if (Buffer.isBuffer(data))
|
|
93
|
+
return data;
|
|
94
|
+
if (data instanceof ArrayBuffer)
|
|
95
|
+
return Buffer.from(data);
|
|
96
|
+
if (ArrayBuffer.isView(data)) {
|
|
97
|
+
return Buffer.from(data.buffer, data.byteOffset, data.byteLength);
|
|
98
|
+
}
|
|
99
|
+
if (typeof data === "string")
|
|
100
|
+
return Buffer.from(data);
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
decodePayload(data) {
|
|
104
|
+
const asBuffer = this.toBuffer(data);
|
|
105
|
+
if (!asBuffer) {
|
|
106
|
+
return data;
|
|
107
|
+
}
|
|
108
|
+
const text = asBuffer.toString("utf8").trim();
|
|
109
|
+
if (!text)
|
|
110
|
+
return "";
|
|
111
|
+
try {
|
|
112
|
+
return JSON.parse(text);
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return text;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
extractMessage(data, fallback) {
|
|
119
|
+
if (typeof data === "string") {
|
|
120
|
+
const trimmed = data.trim();
|
|
121
|
+
return trimmed || fallback;
|
|
122
|
+
}
|
|
123
|
+
if (data && typeof data === "object") {
|
|
124
|
+
const obj = data;
|
|
125
|
+
for (const key of ["message", "detail", "title", "error"]) {
|
|
126
|
+
const value = obj[key];
|
|
127
|
+
if (typeof value === "string" && value.trim()) {
|
|
128
|
+
return value.trim();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return fallback;
|
|
133
|
+
}
|
|
134
|
+
normalizeContentType(value) {
|
|
135
|
+
if (typeof value !== "string")
|
|
136
|
+
return "";
|
|
137
|
+
return value.split(";")[0]?.trim().toLowerCase() || "";
|
|
138
|
+
}
|
|
139
|
+
isBinaryContentType(contentType) {
|
|
140
|
+
return contentType.startsWith("image/") || contentType === "application/octet-stream";
|
|
141
|
+
}
|
|
65
142
|
/** Maps an AxiosError to a concise MCP-friendly Error. */
|
|
66
143
|
mapError(error) {
|
|
67
144
|
if (error.response) {
|
|
68
145
|
const status = error.response.status;
|
|
69
|
-
const data = error.response.data;
|
|
70
|
-
const msg = data
|
|
146
|
+
const data = this.decodePayload(error.response.data);
|
|
147
|
+
const msg = this.extractMessage(data, error.message);
|
|
71
148
|
if (status === 401) {
|
|
72
149
|
return new Error(`Authentication required: ${msg}. Sign in or create an account at ${LOGIN_SIGNUP_URL}, ` +
|
|
73
150
|
`create credentials in ${DASHBOARD_ACCOUNT_URL}, and set ASTROMCP_API_KEY (or ASTROMCP_JWT / ASTROMCP_SERVICE_KEY) before retrying.`);
|
|
@@ -94,45 +171,17 @@ export class BackendClient {
|
|
|
94
171
|
return new Error(`Network error: ${error.message}`);
|
|
95
172
|
}
|
|
96
173
|
async get(path, params, timeoutMs) {
|
|
97
|
-
|
|
98
|
-
const response = await this.client.get(path, {
|
|
99
|
-
params,
|
|
100
|
-
timeout: timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
101
|
-
});
|
|
102
|
-
return response.data;
|
|
103
|
-
}
|
|
104
|
-
catch (err) {
|
|
105
|
-
if (err instanceof AxiosError)
|
|
106
|
-
throw this.mapError(err);
|
|
107
|
-
throw err;
|
|
108
|
-
}
|
|
174
|
+
return this.request("GET", path, { params, timeoutMs });
|
|
109
175
|
}
|
|
110
176
|
async post(path, data, timeoutMs) {
|
|
111
|
-
|
|
112
|
-
const response = await this.client.post(path, data, {
|
|
113
|
-
timeout: timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
114
|
-
});
|
|
115
|
-
return response.data;
|
|
116
|
-
}
|
|
117
|
-
catch (err) {
|
|
118
|
-
if (err instanceof AxiosError)
|
|
119
|
-
throw this.mapError(err);
|
|
120
|
-
throw err;
|
|
121
|
-
}
|
|
177
|
+
return this.request("POST", path, { data, timeoutMs });
|
|
122
178
|
}
|
|
123
179
|
async delete(path) {
|
|
124
|
-
|
|
125
|
-
const response = await this.client.delete(path);
|
|
126
|
-
return response.data;
|
|
127
|
-
}
|
|
128
|
-
catch (err) {
|
|
129
|
-
if (err instanceof AxiosError)
|
|
130
|
-
throw this.mapError(err);
|
|
131
|
-
throw err;
|
|
132
|
-
}
|
|
180
|
+
return this.request("DELETE", path);
|
|
133
181
|
}
|
|
134
182
|
async request(method, path, options) {
|
|
135
183
|
const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
184
|
+
const expectsBinary = this.expectsBinaryResponse(path, options);
|
|
136
185
|
let lastError = new Error("Unknown error");
|
|
137
186
|
// Attempt + retries on 429
|
|
138
187
|
for (let attempt = 0; attempt <= RATE_LIMIT_RETRY_DELAYS_MS.length; attempt++) {
|
|
@@ -144,8 +193,23 @@ export class BackendClient {
|
|
|
144
193
|
data: options?.data,
|
|
145
194
|
headers: options?.headers,
|
|
146
195
|
timeout: timeoutMs,
|
|
196
|
+
responseType: expectsBinary ? "arraybuffer" : "json",
|
|
147
197
|
});
|
|
148
|
-
|
|
198
|
+
if (!expectsBinary) {
|
|
199
|
+
return response.data;
|
|
200
|
+
}
|
|
201
|
+
const contentType = this.normalizeContentType(response.headers?.["content-type"]);
|
|
202
|
+
if (!this.isBinaryContentType(contentType)) {
|
|
203
|
+
return this.decodePayload(response.data);
|
|
204
|
+
}
|
|
205
|
+
const bytes = this.toBuffer(response.data) ?? Buffer.alloc(0);
|
|
206
|
+
const payload = {
|
|
207
|
+
content_type: contentType || "application/octet-stream",
|
|
208
|
+
content_length: bytes.length,
|
|
209
|
+
encoding: "base64",
|
|
210
|
+
data_base64: bytes.toString("base64"),
|
|
211
|
+
};
|
|
212
|
+
return payload;
|
|
149
213
|
}
|
|
150
214
|
catch (err) {
|
|
151
215
|
if (err instanceof AxiosError) {
|
package/dist/index.js
CHANGED
|
@@ -36,6 +36,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
36
36
|
name: tool.name,
|
|
37
37
|
description: tool.description,
|
|
38
38
|
inputSchema: tool.inputSchema,
|
|
39
|
+
annotations: tool.annotations ?? {
|
|
40
|
+
title: tool.name,
|
|
41
|
+
readOnlyHint: true,
|
|
42
|
+
destructiveHint: false,
|
|
43
|
+
},
|
|
39
44
|
})),
|
|
40
45
|
};
|
|
41
46
|
});
|
|
@@ -5,7 +5,7 @@ export declare const LLM_V2_DICT: {
|
|
|
5
5
|
readonly sign_id: readonly ["ari", "tau", "gem", "can", "leo", "vir", "lib", "sco", "sag", "cap", "aqu", "pis"];
|
|
6
6
|
readonly aspect_id: readonly ["con", "opp", "tri", "sqr", "sex"];
|
|
7
7
|
readonly aspect_angle: readonly [0, 180, 120, 90, 60];
|
|
8
|
-
readonly kind_id: readonly ["planet", "angle", "node", "lilith", "asteroid", "
|
|
8
|
+
readonly kind_id: readonly ["planet", "angle", "node", "lilith", "asteroid", "other"];
|
|
9
9
|
};
|
|
10
10
|
export declare const LlmV2PayloadSchema: z.ZodObject<{
|
|
11
11
|
output_mode: z.ZodOptional<z.ZodLiteral<"llm">>;
|
package/dist/schema-packs/llm.js
CHANGED
|
@@ -22,7 +22,7 @@ export const LLM_V2_DICT = {
|
|
|
22
22
|
sign_id: ["ari", "tau", "gem", "can", "leo", "vir", "lib", "sco", "sag", "cap", "aqu", "pis"],
|
|
23
23
|
aspect_id: ["con", "opp", "tri", "sqr", "sex"],
|
|
24
24
|
aspect_angle: [0, 180, 120, 90, 60],
|
|
25
|
-
kind_id: ["planet", "angle", "node", "lilith", "asteroid", "
|
|
25
|
+
kind_id: ["planet", "angle", "node", "lilith", "asteroid", "other"],
|
|
26
26
|
};
|
|
27
27
|
const PointsSchemaZ = z.tuple(LLM_V2_POINTS_SCHEMA.map((v) => z.literal(v)));
|
|
28
28
|
const AspectsSchemaZ = z.tuple(LLM_V2_ASPECTS_SCHEMA.map((v) => z.literal(v)));
|
package/dist/tools/dev.js
CHANGED
|
@@ -36,10 +36,11 @@ registerTool({
|
|
|
36
36
|
"Call dev.list_allowed to see all currently available endpoint paths.\n\n" +
|
|
37
37
|
"AUTH: Set ASTROMCP_API_KEY in your environment. See openephemeris.com/dashboard for active plan limits.\n\n" +
|
|
38
38
|
"CREDIT COSTS:\n" +
|
|
39
|
-
" • Standard chart (natal,
|
|
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" +
|
|
40
42
|
" • Predictive ops (transits, returns, transit-chart): 5 credits\n" +
|
|
41
43
|
" • ACG / astrocartography: 5 credits\n" +
|
|
42
|
-
" • Human Design chart: 1 credit\n" +
|
|
43
44
|
" • Catalog / metadata / health endpoints: 0 credits\n" +
|
|
44
45
|
" • format=llm (token-optimized output): requires Pro tier ($49+/mo)\n\n" +
|
|
45
46
|
"COMMON CALLS:\n" +
|
|
@@ -62,7 +63,7 @@ registerTool({
|
|
|
62
63
|
" GET /eclipse/next-visible — Next eclipse visible from a location\n" +
|
|
63
64
|
" GET /tidal/forcing — Gravitational tidal forcing index\n" +
|
|
64
65
|
" GET /tidal/forcing/deep-time — Extended tidal deep-time analysis\n" +
|
|
65
|
-
" POST /acg/
|
|
66
|
+
" POST /acg/power-lines — Astrocartography power lines (lat/lon GeoJSON)\n" +
|
|
66
67
|
" POST /acg/hits — ACG power at a specific location\n" +
|
|
67
68
|
" GET /calendar/astrology/moon-phases — Moon phase calendar for a date range\n" +
|
|
68
69
|
" GET /location/autocomplete — Geocode a place name (query: q=City Name)\n" +
|
|
@@ -71,6 +72,9 @@ registerTool({
|
|
|
71
72
|
" GET /chinese/zodiac — Chinese zodiac year element/animal\n" +
|
|
72
73
|
" POST /vedic/chart — Vedic (Jyotish) natal chart\n" +
|
|
73
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" +
|
|
74
78
|
"ECLIPSE NOTE: Eclipse endpoints accept format=llm via the query param like other endpoints.\n\n" +
|
|
75
79
|
"format=llm NOTE: Add query: {format: 'llm'} to natal/synastry/composite/HD endpoints for " +
|
|
76
80
|
"compact columnar output optimized for LLM token budgets (availability depends on your current plan).",
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -3,6 +3,13 @@ export interface ToolDefinition {
|
|
|
3
3
|
name: string;
|
|
4
4
|
description: string;
|
|
5
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
|
+
};
|
|
6
13
|
handler: (args: any) => Promise<any>;
|
|
7
14
|
}
|
|
8
15
|
export declare const toolRegistry: Record<string, ToolDefinition>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openephemeris/mcp-server",
|
|
3
|
-
"version": "3.0
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "Model Context Protocol server for the Open Ephemeris astronomical computation API",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -15,24 +15,6 @@
|
|
|
15
15
|
"directories": {
|
|
16
16
|
"test": "test"
|
|
17
17
|
},
|
|
18
|
-
"scripts": {
|
|
19
|
-
"build": "tsc",
|
|
20
|
-
"dev": "tsx watch src/index.ts",
|
|
21
|
-
"start": "node dist/index.js",
|
|
22
|
-
"test": "vitest run",
|
|
23
|
-
"test:watch": "vitest",
|
|
24
|
-
"typecheck": "tsc --noEmit",
|
|
25
|
-
"regen:dev-allowlist": "tsx scripts/dev-allowlist.ts --write",
|
|
26
|
-
"check:dev-allowlist": "tsx scripts/dev-allowlist.ts --check",
|
|
27
|
-
"regen:schema-packs": "tsx scripts/schema-packs.ts --write",
|
|
28
|
-
"check:schema-packs": "tsx scripts/schema-packs.ts --check",
|
|
29
|
-
"sync:readme": "tsx scripts/sync-readme.ts --write",
|
|
30
|
-
"check:readme": "tsx scripts/sync-readme.ts --check",
|
|
31
|
-
"check:pack": "tsx scripts/pack-audit.ts",
|
|
32
|
-
"verify:release": "npm run check:dev-allowlist && npm run check:schema-packs && npm run check:readme && npm run typecheck && npm test && npm run build && npm run check:pack",
|
|
33
|
-
"prepack": "npm run build",
|
|
34
|
-
"prepublishOnly": "npm run verify:release"
|
|
35
|
-
},
|
|
36
18
|
"keywords": [
|
|
37
19
|
"mcp",
|
|
38
20
|
"astrology",
|
|
@@ -56,5 +38,20 @@
|
|
|
56
38
|
"typescript": "^5.9.3",
|
|
57
39
|
"vitest": "^4.0.15"
|
|
58
40
|
},
|
|
59
|
-
"
|
|
60
|
-
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "tsc",
|
|
43
|
+
"dev": "tsx watch src/index.ts",
|
|
44
|
+
"start": "node dist/index.js",
|
|
45
|
+
"test": "vitest run",
|
|
46
|
+
"test:watch": "vitest",
|
|
47
|
+
"typecheck": "tsc --noEmit",
|
|
48
|
+
"regen:dev-allowlist": "tsx scripts/dev-allowlist.ts --write",
|
|
49
|
+
"check:dev-allowlist": "tsx scripts/dev-allowlist.ts --check",
|
|
50
|
+
"regen:schema-packs": "tsx scripts/schema-packs.ts --write",
|
|
51
|
+
"check:schema-packs": "tsx scripts/schema-packs.ts --check",
|
|
52
|
+
"sync:readme": "tsx scripts/sync-readme.ts --write",
|
|
53
|
+
"check:readme": "tsx scripts/sync-readme.ts --check",
|
|
54
|
+
"check:pack": "tsx scripts/pack-audit.ts",
|
|
55
|
+
"verify:release": "npm run check:dev-allowlist && npm run check:schema-packs && npm run check:readme && npm run typecheck && npm test && npm run build && npm run check:pack"
|
|
56
|
+
}
|
|
57
|
+
}
|