@openephemeris/mcp-server 3.0.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 +163 -0
- package/config/dev-allowlist.json +1165 -0
- package/dist/backend/client.d.ts +33 -0
- package/dist/backend/client.js +167 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +87 -0
- package/dist/schema-packs/llm.d.ts +105 -0
- package/dist/schema-packs/llm.js +429 -0
- package/dist/tools/dev.d.ts +1 -0
- package/dist/tools/dev.js +183 -0
- package/dist/tools/index.d.ts +18 -0
- package/dist/tools/index.js +31 -0
- package/dist/tools/specialized/eclipse.d.ts +1 -0
- package/dist/tools/specialized/eclipse.js +56 -0
- package/dist/tools/specialized/electional.d.ts +1 -0
- package/dist/tools/specialized/electional.js +79 -0
- package/dist/tools/specialized/human_design.d.ts +1 -0
- package/dist/tools/specialized/human_design.js +53 -0
- package/dist/tools/specialized/moon.d.ts +1 -0
- package/dist/tools/specialized/moon.js +50 -0
- package/dist/tools/specialized/natal.d.ts +1 -0
- package/dist/tools/specialized/natal.js +71 -0
- package/dist/tools/specialized/relocation.d.ts +1 -0
- package/dist/tools/specialized/relocation.js +71 -0
- package/dist/tools/specialized/synastry.d.ts +1 -0
- package/dist/tools/specialized/synastry.js +61 -0
- package/dist/tools/specialized/transits.d.ts +1 -0
- package/dist/tools/specialized/transits.js +80 -0
- package/package.json +60 -0
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const LLM_V2_POINTS_SCHEMA = [
|
|
3
|
+
"id",
|
|
4
|
+
"kind",
|
|
5
|
+
"src",
|
|
6
|
+
"lon",
|
|
7
|
+
"lat",
|
|
8
|
+
"spd",
|
|
9
|
+
"rx",
|
|
10
|
+
"sg",
|
|
11
|
+
"deg",
|
|
12
|
+
"hs",
|
|
13
|
+
"dec",
|
|
14
|
+
"oob",
|
|
15
|
+
"ed",
|
|
16
|
+
"ed_sc",
|
|
17
|
+
"ad_sc",
|
|
18
|
+
"on_cusp",
|
|
19
|
+
];
|
|
20
|
+
export const LLM_V2_ASPECTS_SCHEMA = ["a_i", "b_i", "t", "orb", "orb_pct", "app", "str"];
|
|
21
|
+
export const LLM_V2_DICT = {
|
|
22
|
+
sign_id: ["ari", "tau", "gem", "can", "leo", "vir", "lib", "sco", "sag", "cap", "aqu", "pis"],
|
|
23
|
+
aspect_id: ["con", "opp", "tri", "sqr", "sex"],
|
|
24
|
+
aspect_angle: [0, 180, 120, 90, 60],
|
|
25
|
+
kind_id: ["planet", "angle", "node", "lilith", "asteroid", "uranian", "other"],
|
|
26
|
+
};
|
|
27
|
+
const PointsSchemaZ = z.tuple(LLM_V2_POINTS_SCHEMA.map((v) => z.literal(v)));
|
|
28
|
+
const AspectsSchemaZ = z.tuple(LLM_V2_ASPECTS_SCHEMA.map((v) => z.literal(v)));
|
|
29
|
+
const PointKindZ = z.enum(LLM_V2_DICT.kind_id);
|
|
30
|
+
const EdZ = z.enum(["none", "dom", "ex", "det", "fall"]);
|
|
31
|
+
const PointRowZ = z.tuple([
|
|
32
|
+
z.string(), // id
|
|
33
|
+
PointKindZ, // kind
|
|
34
|
+
z.number().int(), // src (single-frame currently 0)
|
|
35
|
+
z.number(), // lon
|
|
36
|
+
z.number().nullable(), // lat
|
|
37
|
+
z.number().nullable(), // spd
|
|
38
|
+
z.number().int(), // rx
|
|
39
|
+
z.number().int(), // sg
|
|
40
|
+
z.number(), // deg
|
|
41
|
+
z.number().nullable(), // hs
|
|
42
|
+
z.number().nullable(), // dec
|
|
43
|
+
z.number().int(), // oob
|
|
44
|
+
EdZ, // ed
|
|
45
|
+
z.number().nullable(), // ed_sc
|
|
46
|
+
z.number().nullable(), // ad_sc
|
|
47
|
+
z.number().int(), // on_cusp
|
|
48
|
+
]);
|
|
49
|
+
const AspectRowZ = z.tuple([
|
|
50
|
+
z.number().int(), // a_i
|
|
51
|
+
z.number().int(), // b_i
|
|
52
|
+
z.number().int(), // t
|
|
53
|
+
z.number(), // orb
|
|
54
|
+
z.number(), // orb_pct
|
|
55
|
+
z.number().int(), // app
|
|
56
|
+
z.number(), // str
|
|
57
|
+
]);
|
|
58
|
+
const ChartSubjectZ = z
|
|
59
|
+
.object({
|
|
60
|
+
datetime: z.string().nullable().optional(),
|
|
61
|
+
jd: z.number().nullable().optional(),
|
|
62
|
+
lat: z.number().nullable().optional(),
|
|
63
|
+
lon: z.number().nullable().optional(),
|
|
64
|
+
})
|
|
65
|
+
.strict();
|
|
66
|
+
const ChartZ = z
|
|
67
|
+
.object({
|
|
68
|
+
chart_type: z.enum(["natal", "synastry"]),
|
|
69
|
+
// Natal-style payloads include subject; synastry-style table payloads omit it.
|
|
70
|
+
subject: ChartSubjectZ.optional(),
|
|
71
|
+
})
|
|
72
|
+
.strict();
|
|
73
|
+
const RulesZ = z
|
|
74
|
+
.object({
|
|
75
|
+
zodiac_type: z.string().nullable().optional(),
|
|
76
|
+
house_system: z.string().nullable().optional(),
|
|
77
|
+
coordinate_system: z.string().nullable().optional(),
|
|
78
|
+
node_source: z.string().nullable().optional(),
|
|
79
|
+
lon_range: z.literal("0_360"),
|
|
80
|
+
aspect_set: z.literal("major_default"),
|
|
81
|
+
orb_profile: z.string().nullable().optional(),
|
|
82
|
+
strength_basis: z.literal("engine"),
|
|
83
|
+
point_order_id: z.literal("astro_point_order_v1"),
|
|
84
|
+
core_set_id: z.literal("core_interp_set_v1"),
|
|
85
|
+
})
|
|
86
|
+
.strict();
|
|
87
|
+
const ProvenanceZ = z
|
|
88
|
+
.object({
|
|
89
|
+
engine: z.string(),
|
|
90
|
+
ephemeris_source: z.string(),
|
|
91
|
+
ephemeris_version: z.string(),
|
|
92
|
+
validation_profile: z.string().nullable().optional(),
|
|
93
|
+
precision_level: z.string().nullable().optional(),
|
|
94
|
+
checksum: z.string().nullable().optional(),
|
|
95
|
+
})
|
|
96
|
+
.strict();
|
|
97
|
+
const DictZ = z
|
|
98
|
+
.object({
|
|
99
|
+
sign_id: z.tuple(LLM_V2_DICT.sign_id.map((v) => z.literal(v))),
|
|
100
|
+
aspect_id: z.tuple(LLM_V2_DICT.aspect_id.map((v) => z.literal(v))),
|
|
101
|
+
aspect_angle: z.tuple(LLM_V2_DICT.aspect_angle.map((v) => z.literal(v))),
|
|
102
|
+
kind_id: z.tuple(LLM_V2_DICT.kind_id.map((v) => z.literal(v))),
|
|
103
|
+
})
|
|
104
|
+
.strict();
|
|
105
|
+
const PresentZ = z
|
|
106
|
+
.object({
|
|
107
|
+
point_count: z.number().int(),
|
|
108
|
+
kinds: z.record(z.string(), z.number().int()),
|
|
109
|
+
})
|
|
110
|
+
.strict();
|
|
111
|
+
const AnglesZ = z
|
|
112
|
+
.object({
|
|
113
|
+
asc: z.number().nullable().optional(),
|
|
114
|
+
mc: z.number().nullable().optional(),
|
|
115
|
+
dsc: z.number().nullable().optional(),
|
|
116
|
+
ic: z.number().nullable().optional(),
|
|
117
|
+
})
|
|
118
|
+
.strict();
|
|
119
|
+
const HousesZ = z
|
|
120
|
+
.object({
|
|
121
|
+
system: z.string().nullable().optional(),
|
|
122
|
+
cusps: z.array(z.number()),
|
|
123
|
+
})
|
|
124
|
+
.strict();
|
|
125
|
+
const PatternsZ = z
|
|
126
|
+
.object({
|
|
127
|
+
stellium: z.array(z.unknown()),
|
|
128
|
+
t_square: z.array(z.unknown()),
|
|
129
|
+
grand_trine: z.array(z.unknown()),
|
|
130
|
+
kite: z.array(z.unknown()),
|
|
131
|
+
yod: z.array(z.unknown()),
|
|
132
|
+
grand_cross: z.array(z.unknown()),
|
|
133
|
+
})
|
|
134
|
+
.strict();
|
|
135
|
+
const ExtrasZ = z
|
|
136
|
+
.object({
|
|
137
|
+
fixed_star_conjunctions: z.array(z.unknown()),
|
|
138
|
+
lots: z.array(z.unknown()),
|
|
139
|
+
})
|
|
140
|
+
.strict();
|
|
141
|
+
const CountsZ = z
|
|
142
|
+
.object({
|
|
143
|
+
point_count: z.number().int(),
|
|
144
|
+
aspect_count: z.number().int(),
|
|
145
|
+
// Natal-style payloads include these; synastry-style may omit.
|
|
146
|
+
aspect_count_unfiltered: z.number().int().optional(),
|
|
147
|
+
midpoint_count: z.number().int().optional(),
|
|
148
|
+
declination_aspect_count: z.number().int().optional(),
|
|
149
|
+
stellium_count: z.number().int().optional(),
|
|
150
|
+
})
|
|
151
|
+
.strict();
|
|
152
|
+
const HumanDesignZ = z
|
|
153
|
+
.object({
|
|
154
|
+
gates_schema: z.array(z.string()),
|
|
155
|
+
gates: z.array(z.array(z.any())),
|
|
156
|
+
})
|
|
157
|
+
.strict();
|
|
158
|
+
export const LlmV2PayloadSchema = z
|
|
159
|
+
.object({
|
|
160
|
+
// Some fixtures include this (envelope-like), but backend projector may omit it.
|
|
161
|
+
output_mode: z.literal("llm").optional(),
|
|
162
|
+
// Backend keeps schema versioning internal; allow older fixtures to include it.
|
|
163
|
+
schema_version: z.literal("llm").optional(),
|
|
164
|
+
chart: ChartZ,
|
|
165
|
+
// Natal-style payloads include these blocks; synastry-style may omit.
|
|
166
|
+
rules: RulesZ.optional(),
|
|
167
|
+
reliability: z.record(z.string(), z.unknown()).optional(),
|
|
168
|
+
provenance: ProvenanceZ.optional(),
|
|
169
|
+
dict: DictZ,
|
|
170
|
+
present: PresentZ,
|
|
171
|
+
points_schema: PointsSchemaZ,
|
|
172
|
+
points: z.array(PointRowZ),
|
|
173
|
+
angles: AnglesZ.optional(),
|
|
174
|
+
houses: HousesZ.optional(),
|
|
175
|
+
aspects_schema: AspectsSchemaZ,
|
|
176
|
+
aspects: z.array(AspectRowZ),
|
|
177
|
+
patterns: PatternsZ.optional(),
|
|
178
|
+
extras: ExtrasZ.optional(),
|
|
179
|
+
counts: CountsZ,
|
|
180
|
+
// Optional enrichment blocks added by some endpoints.
|
|
181
|
+
analysis: z.record(z.string(), z.unknown()).optional(),
|
|
182
|
+
human_design: HumanDesignZ.optional(),
|
|
183
|
+
})
|
|
184
|
+
.strict()
|
|
185
|
+
.superRefine((value, ctx) => {
|
|
186
|
+
const expectedPointsLen = LLM_V2_POINTS_SCHEMA.length;
|
|
187
|
+
for (let i = 0; i < Math.min(value.points.length, 200); i++) {
|
|
188
|
+
const row = value.points[i];
|
|
189
|
+
if (row.length !== expectedPointsLen) {
|
|
190
|
+
ctx.addIssue({
|
|
191
|
+
code: z.ZodIssueCode.custom,
|
|
192
|
+
path: ["points", i],
|
|
193
|
+
message: `points[${i}] row length ${row.length} != ${expectedPointsLen}`,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
const expectedAspectsLen = LLM_V2_ASPECTS_SCHEMA.length;
|
|
198
|
+
for (let i = 0; i < Math.min(value.aspects.length, 2000); i++) {
|
|
199
|
+
const row = value.aspects[i];
|
|
200
|
+
if (row.length !== expectedAspectsLen) {
|
|
201
|
+
ctx.addIssue({
|
|
202
|
+
code: z.ZodIssueCode.custom,
|
|
203
|
+
path: ["aspects", i],
|
|
204
|
+
message: `aspects[${i}] row length ${row.length} != ${expectedAspectsLen}`,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
// A JSON Schema companion for downstream consumers.
|
|
210
|
+
// (We intentionally keep this small and explicit rather than depending on a zod->jsonschema lib.)
|
|
211
|
+
export const llmV2JsonSchema = {
|
|
212
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
213
|
+
title: "Open Ephemeris LLM Projection (format=llm)",
|
|
214
|
+
type: "object",
|
|
215
|
+
additionalProperties: false,
|
|
216
|
+
required: ["chart", "dict", "present", "points_schema", "points", "aspects_schema", "aspects", "counts"],
|
|
217
|
+
properties: {
|
|
218
|
+
output_mode: { type: "string", enum: ["llm"] },
|
|
219
|
+
schema_version: { type: "string", enum: ["llm"] },
|
|
220
|
+
chart: {
|
|
221
|
+
type: "object",
|
|
222
|
+
additionalProperties: false,
|
|
223
|
+
required: ["chart_type"],
|
|
224
|
+
properties: {
|
|
225
|
+
chart_type: { type: "string", enum: ["natal", "synastry"] },
|
|
226
|
+
subject: {
|
|
227
|
+
type: "object",
|
|
228
|
+
additionalProperties: false,
|
|
229
|
+
properties: {
|
|
230
|
+
datetime: { anyOf: [{ type: "string" }, { type: "null" }] },
|
|
231
|
+
jd: { anyOf: [{ type: "number" }, { type: "null" }] },
|
|
232
|
+
lat: { anyOf: [{ type: "number" }, { type: "null" }] },
|
|
233
|
+
lon: { anyOf: [{ type: "number" }, { type: "null" }] },
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
rules: {
|
|
239
|
+
type: "object",
|
|
240
|
+
additionalProperties: false,
|
|
241
|
+
required: [
|
|
242
|
+
"lon_range",
|
|
243
|
+
"aspect_set",
|
|
244
|
+
"strength_basis",
|
|
245
|
+
"point_order_id",
|
|
246
|
+
"core_set_id",
|
|
247
|
+
],
|
|
248
|
+
properties: {
|
|
249
|
+
zodiac_type: { anyOf: [{ type: "string" }, { type: "null" }] },
|
|
250
|
+
house_system: { anyOf: [{ type: "string" }, { type: "null" }] },
|
|
251
|
+
coordinate_system: { anyOf: [{ type: "string" }, { type: "null" }] },
|
|
252
|
+
node_source: { anyOf: [{ type: "string" }, { type: "null" }] },
|
|
253
|
+
lon_range: { type: "string", enum: ["0_360"] },
|
|
254
|
+
aspect_set: { type: "string", enum: ["major_default"] },
|
|
255
|
+
orb_profile: { anyOf: [{ type: "string" }, { type: "null" }] },
|
|
256
|
+
strength_basis: { type: "string", enum: ["engine"] },
|
|
257
|
+
point_order_id: { type: "string", enum: ["astro_point_order_v1"] },
|
|
258
|
+
core_set_id: { type: "string", enum: ["core_interp_set_v1"] },
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
reliability: { type: "object" },
|
|
262
|
+
provenance: {
|
|
263
|
+
type: "object",
|
|
264
|
+
additionalProperties: false,
|
|
265
|
+
required: ["engine", "ephemeris_source", "ephemeris_version"],
|
|
266
|
+
properties: {
|
|
267
|
+
engine: { type: "string" },
|
|
268
|
+
ephemeris_source: { type: "string" },
|
|
269
|
+
ephemeris_version: { type: "string" },
|
|
270
|
+
validation_profile: { anyOf: [{ type: "string" }, { type: "null" }] },
|
|
271
|
+
precision_level: { anyOf: [{ type: "string" }, { type: "null" }] },
|
|
272
|
+
checksum: { anyOf: [{ type: "string" }, { type: "null" }] },
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
dict: {
|
|
276
|
+
type: "object",
|
|
277
|
+
additionalProperties: false,
|
|
278
|
+
required: ["sign_id", "aspect_id", "aspect_angle", "kind_id"],
|
|
279
|
+
properties: {
|
|
280
|
+
sign_id: {
|
|
281
|
+
type: "array",
|
|
282
|
+
items: { type: "string" },
|
|
283
|
+
const: LLM_V2_DICT.sign_id,
|
|
284
|
+
},
|
|
285
|
+
aspect_id: {
|
|
286
|
+
type: "array",
|
|
287
|
+
items: { type: "string" },
|
|
288
|
+
const: LLM_V2_DICT.aspect_id,
|
|
289
|
+
},
|
|
290
|
+
aspect_angle: {
|
|
291
|
+
type: "array",
|
|
292
|
+
items: { type: "number" },
|
|
293
|
+
const: LLM_V2_DICT.aspect_angle,
|
|
294
|
+
},
|
|
295
|
+
kind_id: {
|
|
296
|
+
type: "array",
|
|
297
|
+
items: { type: "string" },
|
|
298
|
+
const: LLM_V2_DICT.kind_id,
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
},
|
|
302
|
+
present: {
|
|
303
|
+
type: "object",
|
|
304
|
+
additionalProperties: false,
|
|
305
|
+
required: ["point_count", "kinds"],
|
|
306
|
+
properties: {
|
|
307
|
+
point_count: { type: "integer" },
|
|
308
|
+
kinds: { type: "object", additionalProperties: { type: "integer" } },
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
points_schema: {
|
|
312
|
+
type: "array",
|
|
313
|
+
items: { type: "string" },
|
|
314
|
+
const: LLM_V2_POINTS_SCHEMA,
|
|
315
|
+
},
|
|
316
|
+
points: {
|
|
317
|
+
type: "array",
|
|
318
|
+
items: {
|
|
319
|
+
type: "array",
|
|
320
|
+
minItems: LLM_V2_POINTS_SCHEMA.length,
|
|
321
|
+
maxItems: LLM_V2_POINTS_SCHEMA.length,
|
|
322
|
+
prefixItems: [
|
|
323
|
+
{ type: "string" },
|
|
324
|
+
{ type: "string" },
|
|
325
|
+
{ type: "integer" },
|
|
326
|
+
{ type: "number" },
|
|
327
|
+
{ anyOf: [{ type: "number" }, { type: "null" }] },
|
|
328
|
+
{ anyOf: [{ type: "number" }, { type: "null" }] },
|
|
329
|
+
{ type: "integer" },
|
|
330
|
+
{ type: "integer" },
|
|
331
|
+
{ type: "number" },
|
|
332
|
+
{ anyOf: [{ type: "number" }, { type: "null" }] },
|
|
333
|
+
{ anyOf: [{ type: "number" }, { type: "null" }] },
|
|
334
|
+
{ type: "integer" },
|
|
335
|
+
{ type: "string" },
|
|
336
|
+
{ anyOf: [{ type: "number" }, { type: "null" }] },
|
|
337
|
+
{ anyOf: [{ type: "number" }, { type: "null" }] },
|
|
338
|
+
{ type: "integer" },
|
|
339
|
+
],
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
angles: {
|
|
343
|
+
type: "object",
|
|
344
|
+
additionalProperties: false,
|
|
345
|
+
properties: {
|
|
346
|
+
asc: { anyOf: [{ type: "number" }, { type: "null" }] },
|
|
347
|
+
mc: { anyOf: [{ type: "number" }, { type: "null" }] },
|
|
348
|
+
dsc: { anyOf: [{ type: "number" }, { type: "null" }] },
|
|
349
|
+
ic: { anyOf: [{ type: "number" }, { type: "null" }] },
|
|
350
|
+
},
|
|
351
|
+
},
|
|
352
|
+
houses: {
|
|
353
|
+
type: "object",
|
|
354
|
+
additionalProperties: false,
|
|
355
|
+
required: ["cusps"],
|
|
356
|
+
properties: {
|
|
357
|
+
system: { anyOf: [{ type: "string" }, { type: "null" }] },
|
|
358
|
+
cusps: { type: "array", items: { type: "number" } },
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
aspects_schema: {
|
|
362
|
+
type: "array",
|
|
363
|
+
items: { type: "string" },
|
|
364
|
+
const: LLM_V2_ASPECTS_SCHEMA,
|
|
365
|
+
},
|
|
366
|
+
aspects: {
|
|
367
|
+
type: "array",
|
|
368
|
+
items: {
|
|
369
|
+
type: "array",
|
|
370
|
+
minItems: LLM_V2_ASPECTS_SCHEMA.length,
|
|
371
|
+
maxItems: LLM_V2_ASPECTS_SCHEMA.length,
|
|
372
|
+
prefixItems: [
|
|
373
|
+
{ type: "integer" },
|
|
374
|
+
{ type: "integer" },
|
|
375
|
+
{ type: "integer" },
|
|
376
|
+
{ type: "number" },
|
|
377
|
+
{ type: "number" },
|
|
378
|
+
{ type: "integer" },
|
|
379
|
+
{ type: "number" },
|
|
380
|
+
],
|
|
381
|
+
},
|
|
382
|
+
},
|
|
383
|
+
patterns: {
|
|
384
|
+
type: "object",
|
|
385
|
+
additionalProperties: false,
|
|
386
|
+
required: ["stellium", "t_square", "grand_trine", "kite", "yod", "grand_cross"],
|
|
387
|
+
properties: {
|
|
388
|
+
stellium: { type: "array" },
|
|
389
|
+
t_square: { type: "array" },
|
|
390
|
+
grand_trine: { type: "array" },
|
|
391
|
+
kite: { type: "array" },
|
|
392
|
+
yod: { type: "array" },
|
|
393
|
+
grand_cross: { type: "array" },
|
|
394
|
+
},
|
|
395
|
+
},
|
|
396
|
+
extras: {
|
|
397
|
+
type: "object",
|
|
398
|
+
additionalProperties: false,
|
|
399
|
+
required: ["fixed_star_conjunctions", "lots"],
|
|
400
|
+
properties: {
|
|
401
|
+
fixed_star_conjunctions: { type: "array" },
|
|
402
|
+
lots: { type: "array" },
|
|
403
|
+
},
|
|
404
|
+
},
|
|
405
|
+
counts: {
|
|
406
|
+
type: "object",
|
|
407
|
+
additionalProperties: false,
|
|
408
|
+
required: ["point_count", "aspect_count"],
|
|
409
|
+
properties: {
|
|
410
|
+
point_count: { type: "integer" },
|
|
411
|
+
aspect_count: { type: "integer" },
|
|
412
|
+
aspect_count_unfiltered: { type: "integer" },
|
|
413
|
+
midpoint_count: { type: "integer" },
|
|
414
|
+
declination_aspect_count: { type: "integer" },
|
|
415
|
+
stellium_count: { type: "integer" },
|
|
416
|
+
},
|
|
417
|
+
},
|
|
418
|
+
analysis: { type: "object" },
|
|
419
|
+
human_design: {
|
|
420
|
+
type: "object",
|
|
421
|
+
additionalProperties: false,
|
|
422
|
+
required: ["gates_schema", "gates"],
|
|
423
|
+
properties: {
|
|
424
|
+
gates_schema: { type: "array", items: { type: "string" } },
|
|
425
|
+
gates: { type: "array", items: { type: "array" } },
|
|
426
|
+
},
|
|
427
|
+
},
|
|
428
|
+
},
|
|
429
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,183 @@
|
|
|
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.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 ASTROMCP_API_KEY in your environment. See openephemeris.com/dashboard for active plan limits.\n\n" +
|
|
38
|
+
"CREDIT COSTS:\n" +
|
|
39
|
+
" • Standard chart (natal, synastry, composite, progressed): 1 credit\n" +
|
|
40
|
+
" • Predictive ops (transits, returns, transit-chart): 5 credits\n" +
|
|
41
|
+
" • ACG / astrocartography: 5 credits\n" +
|
|
42
|
+
" • Human Design chart: 1 credit\n" +
|
|
43
|
+
" • Catalog / metadata / health endpoints: 0 credits\n" +
|
|
44
|
+
" • format=llm (token-optimized output): requires Pro tier ($49+/mo)\n\n" +
|
|
45
|
+
"COMMON CALLS:\n" +
|
|
46
|
+
" POST /ephemeris/natal-chart — Full natal chart (body: {datetime, latitude, longitude})\n" +
|
|
47
|
+
" POST /ephemeris/natal/batch — Up to 50 natal charts in one request\n" +
|
|
48
|
+
" POST /ephemeris/relocation — Relocated chart (same natal, new location)\n" +
|
|
49
|
+
" POST /predictive/transits/search — Transit event search over a date range\n" +
|
|
50
|
+
" POST /predictive/returns/solar — Solar return chart\n" +
|
|
51
|
+
" POST /predictive/returns/lunar — Lunar return chart\n" +
|
|
52
|
+
" POST /comparative/synastry — Two-person synastry chart\n" +
|
|
53
|
+
" POST /comparative/composite — Composite (midpoint) chart\n" +
|
|
54
|
+
" POST /human-design/chart — Full HD bodygraph\n" +
|
|
55
|
+
" GET /ephemeris/moon/phase — Current/queried moon phase\n" +
|
|
56
|
+
" GET /ephemeris/moon/void-of-course — Next void-of-course period\n" +
|
|
57
|
+
" GET /ephemeris/agro/daily — Biodynamic farming day quality\n" +
|
|
58
|
+
" GET /ephemeris/agro/calendar — Multi-day biodynamic calendar\n" +
|
|
59
|
+
" GET /ephemeris/agro/void-of-course — Biodynamic VoC periods\n" +
|
|
60
|
+
" GET /eclipse/solar/global — Next global solar eclipse (query: date=YYYY-MM-DD)\n" +
|
|
61
|
+
" GET /eclipse/solar/local — Local solar eclipse (query: latitude, longitude)\n" +
|
|
62
|
+
" GET /eclipse/next-visible — Next eclipse visible from a location\n" +
|
|
63
|
+
" GET /tidal/forcing — Gravitational tidal forcing index\n" +
|
|
64
|
+
" GET /tidal/forcing/deep-time — Extended tidal deep-time analysis\n" +
|
|
65
|
+
" POST /acg/natal-lines — Astrocartography natal lines (lat/lon GeoJSON)\n" +
|
|
66
|
+
" POST /acg/hits — ACG power at a specific location\n" +
|
|
67
|
+
" GET /calendar/astrology/moon-phases — Moon phase calendar for a date range\n" +
|
|
68
|
+
" GET /location/autocomplete — Geocode a place name (query: q=City Name)\n" +
|
|
69
|
+
" POST /timezone/lookup — Resolve timezone + UTC offset for a location\n" +
|
|
70
|
+
" POST /chinese/bazi — Chinese Ba Zi (Four Pillars) chart\n" +
|
|
71
|
+
" GET /chinese/zodiac — Chinese zodiac year element/animal\n" +
|
|
72
|
+
" POST /vedic/chart — Vedic (Jyotish) natal chart\n" +
|
|
73
|
+
" GET /catalogs/bodies — List all supported celestial bodies\n\n" +
|
|
74
|
+
"ECLIPSE NOTE: Eclipse endpoints accept format=llm via the query param like other endpoints.\n\n" +
|
|
75
|
+
"format=llm NOTE: Add query: {format: 'llm'} to natal/synastry/composite/HD endpoints for " +
|
|
76
|
+
"compact columnar output optimized for LLM token budgets (availability depends on your current plan).",
|
|
77
|
+
inputSchema: {
|
|
78
|
+
type: "object",
|
|
79
|
+
properties: {
|
|
80
|
+
method: {
|
|
81
|
+
type: "string",
|
|
82
|
+
enum: ["GET", "POST", "PUT", "PATCH", "DELETE"],
|
|
83
|
+
description: "HTTP method",
|
|
84
|
+
},
|
|
85
|
+
path: {
|
|
86
|
+
type: "string",
|
|
87
|
+
description: "Absolute API path (e.g., /ephemeris/natal-chart)",
|
|
88
|
+
},
|
|
89
|
+
query: {
|
|
90
|
+
type: "object",
|
|
91
|
+
additionalProperties: true,
|
|
92
|
+
description: "Query params for GET requests (optional)",
|
|
93
|
+
},
|
|
94
|
+
body: {
|
|
95
|
+
description: "JSON body for POST/PUT/PATCH (optional)",
|
|
96
|
+
},
|
|
97
|
+
preset: {
|
|
98
|
+
type: "string",
|
|
99
|
+
enum: ["full", "simple"],
|
|
100
|
+
description: "Convenience: if provided, set query.preset (optional).",
|
|
101
|
+
},
|
|
102
|
+
format: {
|
|
103
|
+
type: "string",
|
|
104
|
+
enum: ["json", "llm", "llm_v2"],
|
|
105
|
+
description: "Convenience: if provided, set query.format (optional). 'llm' is canonical; 'llm_v2' is accepted as a legacy alias.",
|
|
106
|
+
},
|
|
107
|
+
output_mode: {
|
|
108
|
+
type: "string",
|
|
109
|
+
enum: ["full", "simple", "llm", "llm_v2"],
|
|
110
|
+
description: "Legacy convenience (deprecated): if provided, set query.output_mode and also map to query.preset/query.format when possible.",
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
required: ["method", "path"],
|
|
114
|
+
additionalProperties: false,
|
|
115
|
+
},
|
|
116
|
+
handler: async (args) => {
|
|
117
|
+
const method = String(args.method || "").toUpperCase();
|
|
118
|
+
const pathname = String(args.path || "");
|
|
119
|
+
if (!pathname.startsWith("/")) {
|
|
120
|
+
throw new Error("path must start with '/'");
|
|
121
|
+
}
|
|
122
|
+
const allowlist = loadAllowlist();
|
|
123
|
+
const denyPrefixes = allowlist.deny?.path_prefixes ?? [];
|
|
124
|
+
if (denyPrefixes.length > 0 && isDeniedByPrefix(pathname, denyPrefixes)) {
|
|
125
|
+
throw new Error(`Endpoint denied by policy: ${pathname}`);
|
|
126
|
+
}
|
|
127
|
+
if (!isAllowedOperation(method, pathname, allowlist.allow)) {
|
|
128
|
+
throw new Error(`Endpoint not allowlisted: ${method} ${pathname}`);
|
|
129
|
+
}
|
|
130
|
+
const queryBase = args.query && typeof args.query === "object" ? args.query : {};
|
|
131
|
+
const query = { ...queryBase };
|
|
132
|
+
const body = args.body;
|
|
133
|
+
if (args.preset) {
|
|
134
|
+
query.preset = args.preset;
|
|
135
|
+
}
|
|
136
|
+
if (args.format) {
|
|
137
|
+
const rawFormat = String(args.format).trim().toLowerCase();
|
|
138
|
+
// Canonical: "llm". Normalize legacy alias "llm_v2" → "llm".
|
|
139
|
+
query.format = rawFormat === "llm_v2" ? "llm" : rawFormat;
|
|
140
|
+
}
|
|
141
|
+
// Legacy: output_mode used to overload both compute preset and output projection.
|
|
142
|
+
// Keep forwarding it for older endpoints, but also map it to preset/format for new endpoints.
|
|
143
|
+
if (args.output_mode) {
|
|
144
|
+
const rawMode = String(args.output_mode).trim().toLowerCase();
|
|
145
|
+
query.output_mode = rawMode;
|
|
146
|
+
if ((rawMode === "full" || rawMode === "simple") && query.preset == null) {
|
|
147
|
+
query.preset = rawMode;
|
|
148
|
+
}
|
|
149
|
+
if ((rawMode === "llm" || rawMode === "llm_v2") && query.format == null) {
|
|
150
|
+
query.format = "llm";
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return await backendClient.request(method, pathname, {
|
|
154
|
+
params: query,
|
|
155
|
+
data: body,
|
|
156
|
+
});
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
registerTool({
|
|
160
|
+
name: "dev.list_allowed",
|
|
161
|
+
description: "List all API operations (method + path) that this MCP instance is authorized to call. " +
|
|
162
|
+
"Returns endpoint entries grouped by method, plus the active deny rules. " +
|
|
163
|
+
"Use this to discover what's available before calling dev.call, or to verify an endpoint path. " +
|
|
164
|
+
"Typed shortcut tools (ephemeris.natal_chart, ephemeris.transits, etc.) cover the most common operations — " +
|
|
165
|
+
"check those first before reaching for dev.call.",
|
|
166
|
+
inputSchema: {
|
|
167
|
+
type: "object",
|
|
168
|
+
properties: {},
|
|
169
|
+
additionalProperties: false,
|
|
170
|
+
},
|
|
171
|
+
handler: async () => {
|
|
172
|
+
const allowlist = loadAllowlist();
|
|
173
|
+
return {
|
|
174
|
+
schema: allowlist.schema,
|
|
175
|
+
generated_from: allowlist.generated_from,
|
|
176
|
+
deny: allowlist.deny ?? null,
|
|
177
|
+
allow: allowlist.allow,
|
|
178
|
+
counts: {
|
|
179
|
+
allow: allowlist.allow.length,
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
},
|
|
183
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export interface ToolDefinition {
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
inputSchema: z.ZodType<any> | Record<string, unknown>;
|
|
6
|
+
handler: (args: any) => Promise<any>;
|
|
7
|
+
}
|
|
8
|
+
export declare const toolRegistry: Record<string, ToolDefinition>;
|
|
9
|
+
export declare function registerTool(tool: ToolDefinition): void;
|
|
10
|
+
export type ToolProfile = "dev" | "legacy";
|
|
11
|
+
/**
|
|
12
|
+
* Initializes tool modules.
|
|
13
|
+
*
|
|
14
|
+
* - `dev`: registers the allowlist-gated generic call tools AND all specialized
|
|
15
|
+
* domain tools (natal chart, transits, moon phase, eclipse, synastry, HD).
|
|
16
|
+
* - `legacy`: registers only the generic tools (back-compat).
|
|
17
|
+
*/
|
|
18
|
+
export declare function initTools(profile?: ToolProfile): Promise<void>;
|
|
@@ -0,0 +1,31 @@
|
|
|
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.ASTROMCP_PROFILE || "dev").toLowerCase();
|
|
18
|
+
// Always register the generic proxy tools (dev.call + dev.list_allowed).
|
|
19
|
+
await import("./dev.js");
|
|
20
|
+
if (resolvedProfile === "dev") {
|
|
21
|
+
// Register all specialized domain tools.
|
|
22
|
+
await import("./specialized/natal.js");
|
|
23
|
+
await import("./specialized/transits.js");
|
|
24
|
+
await import("./specialized/moon.js");
|
|
25
|
+
await import("./specialized/eclipse.js");
|
|
26
|
+
await import("./specialized/human_design.js");
|
|
27
|
+
await import("./specialized/synastry.js");
|
|
28
|
+
await import("./specialized/relocation.js");
|
|
29
|
+
await import("./specialized/electional.js");
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|