caelus-mcp 0.3.0 → 0.4.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 CHANGED
@@ -1,9 +1,10 @@
1
1
  # caelus-mcp
2
2
 
3
3
  MCP server for the [caelus](https://github.com/heavyblotto/caelus) ephemeris
4
- engine: six chart tools over stdio. Computation only — positions, houses,
5
- aspects with orbs — the model does the interpreting. No API keys, no
6
- ephemeris files, no network calls; the engine data ships inside the package.
4
+ engine: seven chart tools over stdio. Computation only — positions, houses,
5
+ aspects with orbs, event search — the model does the interpreting. No API
6
+ keys, no ephemeris files, no network calls; the engine data ships inside the
7
+ package.
7
8
 
8
9
  ## Setup
9
10
 
@@ -31,10 +32,15 @@ Any MCP client that speaks stdio:
31
32
  | `synastry` | Two charts compared: inter-chart aspects, house overlays both ways |
32
33
  | `find_aspect_dates` | Exact dates a transiting body aspects a longitude or another body, retrograde re-hits included |
33
34
  | `rectification_grid` | ASC/MC sweep across a window of hours for birth-time rectification |
35
+ | `sky_events` | Rise/set/meridian transits, lunar phases, stations, zodiac crossings in a date range (≤370 days) |
34
36
 
35
- Bodies: sun through pluto, chiron, mean and true node. House systems:
36
- placidus (default), whole_sign, equal, porphyry. Placidus falls back to
37
- whole_sign above the polar circles and says so in the payload.
37
+ Bodies (core chart): sun through pluto, chiron, mean and true node. Optional
38
+ bodies (mean/true Lilith, asteroids, Uranians) follow engine data on the Node
39
+ loader path. House systems: twelve total placidus (default), whole_sign,
40
+ equal, porphyry, koch, regiomontanus, campanus, alcabitius, morinus,
41
+ meridian, polich_page, vehlow. Placidus and Koch fall back to whole_sign
42
+ above the polar circles and say so in the payload. `zodiac` supports tropical
43
+ (default) and five sidereal ayanamsas on chart tools.
38
44
 
39
45
  ## Output
40
46
 
@@ -66,7 +72,8 @@ east-positive everywhere; the Americas are negative.
66
72
 
67
73
  Checked against Swiss Ephemeris across 1900–2099: Sun–Saturn ≤1″,
68
74
  Uranus ≤1.9″, Neptune ≤4.6″, Moon ≤2.5″, Pluto ≤2.5″ (series valid
69
- 1885–2099), Chiron ≤1″, nodes ≤1″. Tables:
75
+ 1885–2099), Chiron ≤1″, mean node ≤1″, true node ≤1′ vs SE's built-in
76
+ ephemeris, asteroids ≤1″ (Horizons fits), Uranians ≤2.3″. Tables:
70
77
  [ephemengine.com/validation](https://ephemengine.com/validation).
71
78
 
72
79
  ## The caelus packages
@@ -3,7 +3,7 @@
3
3
  * caelus-mcp -- MCP server for the caelus ephemeris engine.
4
4
  *
5
5
  * Design (per 2026 MCP practice): one bounded context (chart computation),
6
- * a small curated tool surface (6 outcome-level tools, not API wrappers),
6
+ * a small curated tool surface (7 outcome-level tools, not API wrappers),
7
7
  * and token-frugal outputs (positions to 0.01 deg, terse keys, no prose --
8
8
  * the model does the interpreting, the server does the math).
9
9
  *
@@ -603,6 +603,39 @@ export declare const rectificationGridOut: z.ZodObject<{
603
603
  mc: string;
604
604
  }[];
605
605
  }>;
606
+ export declare const skyEventsOut: z.ZodObject<{
607
+ start: z.ZodString;
608
+ end: z.ZodString;
609
+ events: z.ZodArray<z.ZodObject<{
610
+ t: z.ZodString;
611
+ kind: z.ZodEnum<["rise", "set", "mtransit", "itransit", "phase", "station", "crossing"]>;
612
+ detail: z.ZodOptional<z.ZodString>;
613
+ }, "strip", z.ZodTypeAny, {
614
+ t: string;
615
+ kind: "set" | "rise" | "mtransit" | "itransit" | "phase" | "station" | "crossing";
616
+ detail?: string | undefined;
617
+ }, {
618
+ t: string;
619
+ kind: "set" | "rise" | "mtransit" | "itransit" | "phase" | "station" | "crossing";
620
+ detail?: string | undefined;
621
+ }>, "many">;
622
+ }, "strip", z.ZodTypeAny, {
623
+ start: string;
624
+ end: string;
625
+ events: {
626
+ t: string;
627
+ kind: "set" | "rise" | "mtransit" | "itransit" | "phase" | "station" | "crossing";
628
+ detail?: string | undefined;
629
+ }[];
630
+ }, {
631
+ start: string;
632
+ end: string;
633
+ events: {
634
+ t: string;
635
+ kind: "set" | "rise" | "mtransit" | "itransit" | "phase" | "station" | "crossing";
636
+ detail?: string | undefined;
637
+ }[];
638
+ }>;
606
639
  export declare const OUTPUT_SCHEMAS: {
607
640
  readonly natal_chart: z.ZodObject<{
608
641
  utc: z.ZodString;
@@ -1306,5 +1339,38 @@ export declare const OUTPUT_SCHEMAS: {
1306
1339
  mc: string;
1307
1340
  }[];
1308
1341
  }>;
1342
+ readonly sky_events: z.ZodObject<{
1343
+ start: z.ZodString;
1344
+ end: z.ZodString;
1345
+ events: z.ZodArray<z.ZodObject<{
1346
+ t: z.ZodString;
1347
+ kind: z.ZodEnum<["rise", "set", "mtransit", "itransit", "phase", "station", "crossing"]>;
1348
+ detail: z.ZodOptional<z.ZodString>;
1349
+ }, "strip", z.ZodTypeAny, {
1350
+ t: string;
1351
+ kind: "set" | "rise" | "mtransit" | "itransit" | "phase" | "station" | "crossing";
1352
+ detail?: string | undefined;
1353
+ }, {
1354
+ t: string;
1355
+ kind: "set" | "rise" | "mtransit" | "itransit" | "phase" | "station" | "crossing";
1356
+ detail?: string | undefined;
1357
+ }>, "many">;
1358
+ }, "strip", z.ZodTypeAny, {
1359
+ start: string;
1360
+ end: string;
1361
+ events: {
1362
+ t: string;
1363
+ kind: "set" | "rise" | "mtransit" | "itransit" | "phase" | "station" | "crossing";
1364
+ detail?: string | undefined;
1365
+ }[];
1366
+ }, {
1367
+ start: string;
1368
+ end: string;
1369
+ events: {
1370
+ t: string;
1371
+ kind: "set" | "rise" | "mtransit" | "itransit" | "phase" | "station" | "crossing";
1372
+ detail?: string | undefined;
1373
+ }[];
1374
+ }>;
1309
1375
  };
1310
1376
  export declare function buildServer(): McpServer;
@@ -3,7 +3,7 @@
3
3
  * caelus-mcp -- MCP server for the caelus ephemeris engine.
4
4
  *
5
5
  * Design (per 2026 MCP practice): one bounded context (chart computation),
6
- * a small curated tool surface (6 outcome-level tools, not API wrappers),
6
+ * a small curated tool surface (7 outcome-level tools, not API wrappers),
7
7
  * and token-frugal outputs (positions to 0.01 deg, terse keys, no prose --
8
8
  * the model does the interpreting, the server does the math).
9
9
  *
@@ -17,7 +17,7 @@ import { dirname, join } from "node:path";
17
17
  import { fileURLToPath } from "node:url";
18
18
  import { createRequire } from "node:module";
19
19
  import { realpathSync } from "node:fs";
20
- import { Engine, BODIES, julianDay, mod } from "caelus";
20
+ import { Engine, BODIES, julianDay, mod, riseSet, crossings, lunarPhases, stations, } from "caelus";
21
21
  import { loadNodeData } from "caelus/node";
22
22
  const require = createRequire(import.meta.url);
23
23
  const VERSION = require("caelus-mcp/package.json").version;
@@ -136,6 +136,15 @@ export const rectificationGridOut = z.object({
136
136
  asc_sign_changes: z.array(z.string()),
137
137
  grid: z.array(z.object({ utc: z.string(), asc: z.string(), mc: z.string() })),
138
138
  });
139
+ export const skyEventsOut = z.object({
140
+ start: z.string(),
141
+ end: z.string(),
142
+ events: z.array(z.object({
143
+ t: z.string(),
144
+ kind: z.enum(["rise", "set", "mtransit", "itransit", "phase", "station", "crossing"]),
145
+ detail: z.string().optional(),
146
+ })),
147
+ });
139
148
  export const OUTPUT_SCHEMAS = {
140
149
  natal_chart: chartOut,
141
150
  current_sky: chartOut,
@@ -143,12 +152,13 @@ export const OUTPUT_SCHEMAS = {
143
152
  synastry: synastryOut,
144
153
  find_aspect_dates: findAspectDatesOut,
145
154
  rectification_grid: rectificationGridOut,
155
+ sky_events: skyEventsOut,
146
156
  };
147
157
  // ---------------------------------------------------------------- server
148
158
  export function buildServer() {
149
159
  const server = new McpServer({ name: "caelus", version: VERSION });
150
160
  server.registerTool("natal_chart", {
151
- description: "A person's birth chart. Requires their exact birth date+time and birthplace (all three: date, lat, lon). Use this — not current_sky — whenever the question is about someone's natal/birth chart. Returns 13 bodies (sun–pluto, chiron, nodes) with sign, house, retrograde, speed; ASC/MC; cusps; major aspects with orbs. Vs Swiss Ephemeris (1900–2099): Sun–Saturn ≤1″, Uranus ≤1.9″, Neptune ≤4.6″, Moon ≤2.5″, Pluto ≤2.5″ (series valid 1885–2099), Chiron ≤1″, nodes ≤1″.",
161
+ description: "A person's birth chart. Requires their exact birth date+time and birthplace (all three: date, lat, lon). Use this — not current_sky — whenever the question is about someone's natal/birth chart. Returns 13 bodies (sun–pluto, chiron, nodes) with sign, house, retrograde, speed; ASC/MC; cusps; major aspects with orbs. Vs Swiss Ephemeris (1900–2099): Sun–Saturn ≤1″, Uranus ≤1.9″, Neptune ≤4.6″, Moon ≤2.5″, Pluto ≤2.5″ (series valid 1885–2099), Chiron ≤1″, mean node ≤1″, true node ≤ 1′ vs SE's built-in ephemeris.",
152
162
  inputSchema: { ...birth, house_system: houseSys, zodiac: zodiacSchema },
153
163
  }, async ({ date, lat, lon, house_system, zodiac }) => text(chartPayload(date, lat, lon, house_system, zodiac)));
154
164
  server.registerTool("current_sky", {
@@ -338,6 +348,67 @@ export function buildServer() {
338
348
  }
339
349
  return text({ date: date.slice(0, 10), lat, lon, asc_sign_changes: boundaries, grid });
340
350
  });
351
+ server.registerTool("sky_events", {
352
+ description: "Sky events in a UTC date range: rise/set/meridian transits (need lat+lon+body), lunar phases (new/quarters/full), stations (body turns retrograde/direct; needs body), zodiac degree crossings (needs body + target_lon). Times to the second vs Swiss Ephemeris (stations to ~1 min: ill-conditioned by nature). Range <= 370 days.",
353
+ inputSchema: {
354
+ start: z.string().describe("UTC ISO start date (convert from local first)"),
355
+ end: z.string().describe("UTC ISO end date; range <= 370 days"),
356
+ kinds: z.array(z.enum(["rise", "set", "mtransit", "itransit", "phase", "station", "crossing"]))
357
+ .min(1).describe("Event kinds to include"),
358
+ body: z.enum(BODIES).optional()
359
+ .describe("Required for rise/set/transit/station/crossing"),
360
+ lat: latSchema.optional().describe("Required for rise/set/transit"),
361
+ lon: lonSchema.optional().describe("Required for rise/set/transit"),
362
+ target_lon: z.number().min(0).max(360).optional()
363
+ .describe("Zodiac longitude for 'crossing', degrees"),
364
+ zodiac: zodiacSchema.describe("Zodiac for 'crossing' longitudes"),
365
+ },
366
+ }, async ({ start, end, kinds, body, lat, lon, target_lon, zodiac }) => {
367
+ const jd0 = jdFromIso(start);
368
+ const jd1 = jdFromIso(end);
369
+ if (jd1 - jd0 > 370)
370
+ throw new Error("Range too large (max 370 days)");
371
+ const iso = (jd) => new Date((jd - 2440587.5) * 86400000).toISOString().slice(0, 19) + "Z";
372
+ const events = [];
373
+ const riseKinds = kinds.filter((k) => k === "rise" || k === "set" || k === "mtransit" || k === "itransit");
374
+ if (riseKinds.length) {
375
+ if (body === undefined || lat === undefined || lon === undefined) {
376
+ throw new Error("rise/set/transit need body, lat, lon");
377
+ }
378
+ for (const k of riseKinds) {
379
+ let t = jd0;
380
+ while (t < jd1 && events.length < 200) {
381
+ const hit = riseSet(engine, body, t, lat, lon, k);
382
+ if (hit === null || hit > jd1)
383
+ break;
384
+ events.push({ t: iso(hit), kind: k });
385
+ t = hit + 1e-4;
386
+ }
387
+ }
388
+ }
389
+ if (kinds.includes("phase")) {
390
+ for (const [t, name] of lunarPhases(engine, jd0, jd1)) {
391
+ events.push({ t: iso(t), kind: "phase", detail: name });
392
+ }
393
+ }
394
+ if (kinds.includes("station")) {
395
+ if (body === undefined)
396
+ throw new Error("station needs body");
397
+ for (const [t, dir] of stations(engine, body, jd0, jd1)) {
398
+ events.push({ t: iso(t), kind: "station", detail: dir });
399
+ }
400
+ }
401
+ if (kinds.includes("crossing")) {
402
+ if (body === undefined || target_lon === undefined) {
403
+ throw new Error("crossing needs body and target_lon");
404
+ }
405
+ for (const t of crossings(engine, body, target_lon, jd0, jd1, zodiac)) {
406
+ events.push({ t: iso(t), kind: "crossing", detail: `${target_lon}°` });
407
+ }
408
+ }
409
+ events.sort((a, b) => a.t.localeCompare(b.t));
410
+ return text({ start, end, events });
411
+ });
341
412
  return server;
342
413
  }
343
414
  // ---------------------------------------------------------------- main
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "caelus-mcp",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "MCP server for caelus chart computation.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -14,7 +14,7 @@
14
14
  "dependencies": {
15
15
  "@modelcontextprotocol/sdk": "^1.12.0",
16
16
  "zod": "^3.24.0",
17
- "caelus": "^0.3.0"
17
+ "caelus": "^0.4.0"
18
18
  },
19
19
  "devDependencies": {
20
20
  "ajv": "^8.17.1"