@openephemeris/mcp-server 3.13.9 → 3.14.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/CHANGELOG.md +39 -0
- package/README.md +3 -3
- package/config/dev-allowlist.json +28 -4
- package/dist/index.js +20 -13
- package/dist/oauth/dcr.d.ts +11 -0
- package/dist/oauth/dcr.js +49 -0
- package/dist/oauth/discovery.d.ts +12 -0
- package/dist/oauth/discovery.js +45 -0
- package/dist/oauth/pkce.d.ts +18 -0
- package/dist/oauth/pkce.js +34 -0
- package/dist/oauth/rate-limit.d.ts +20 -0
- package/dist/oauth/rate-limit.js +65 -0
- package/dist/oauth/store.d.ts +88 -0
- package/dist/oauth/store.js +361 -0
- package/dist/oauth/supabase-jwt.d.ts +39 -0
- package/dist/oauth/supabase-jwt.js +194 -0
- package/dist/oauth/token.d.ts +21 -0
- package/dist/oauth/token.js +215 -0
- package/dist/prompts.js +180 -159
- package/dist/server-sse.js +154 -49
- package/dist/tools/apps/bazi-app.d.ts +23 -0
- package/dist/tools/apps/bazi-app.js +226 -0
- package/dist/tools/apps/bi-wheel-app.d.ts +14 -7
- package/dist/tools/apps/bi-wheel-app.js +347 -113
- package/dist/tools/apps/bodygraph-app.js +106 -13
- package/dist/tools/apps/chart-wheel-app.js +53 -4
- package/dist/tools/apps/location-tools.js +3 -0
- package/dist/tools/apps/moon-phase-app.d.ts +19 -0
- package/dist/tools/apps/moon-phase-app.js +244 -0
- package/dist/tools/apps/transit-timeline-app.d.ts +20 -0
- package/dist/tools/apps/transit-timeline-app.js +295 -0
- package/dist/tools/apps/vedic-chart-app.d.ts +17 -0
- package/dist/tools/apps/vedic-chart-app.js +228 -0
- package/dist/tools/auth.js +4 -0
- package/dist/tools/dev.js +3 -0
- package/dist/tools/index.d.ts +6 -0
- package/dist/tools/index.js +9 -4
- package/dist/tools/output-schemas.d.ts +37 -0
- package/dist/tools/output-schemas.js +130 -0
- package/dist/tools/specialized/acg.js +5 -2
- package/dist/tools/specialized/bazi.js +375 -44
- package/dist/tools/specialized/bi_wheel.js +3 -1
- package/dist/tools/specialized/chart_wheel.js +3 -1
- package/dist/tools/specialized/comparative.js +9 -4
- package/dist/tools/specialized/eclipse.js +3 -1
- package/dist/tools/specialized/electional.js +9 -4
- package/dist/tools/specialized/ephemeris_core.js +5 -2
- package/dist/tools/specialized/ephemeris_extended.js +17 -8
- package/dist/tools/specialized/hd_bodygraph.js +3 -1
- package/dist/tools/specialized/hd_cycles.js +5 -2
- package/dist/tools/specialized/hd_group.js +5 -2
- package/dist/tools/specialized/human_design.js +3 -1
- package/dist/tools/specialized/moon.js +5 -2
- package/dist/tools/specialized/natal.js +3 -1
- package/dist/tools/specialized/progressed.js +3 -1
- package/dist/tools/specialized/relocation.js +3 -1
- package/dist/tools/specialized/returns.js +7 -3
- package/dist/tools/specialized/synastry.js +3 -1
- package/dist/tools/specialized/transits.js +53 -44
- package/dist/tools/specialized/vedic.js +3 -1
- package/dist/tools/specialized/venus_star_points.js +13 -6
- package/dist/ui/bazi.html +213 -0
- package/dist/ui/bi-wheel.html +3714 -3048
- package/dist/ui/bodygraph.html +1952 -1766
- package/dist/ui/chart-wheel.html +3431 -2964
- package/dist/ui/moon-phase.html +6764 -0
- package/dist/ui/transit-timeline.html +6874 -0
- package/dist/ui/vedic-chart.html +210 -0
- package/package.json +15 -12
- package/smithery.yaml +16 -1
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* transit-timeline-app.ts — MCP App tool registration for the Transit Timeline Explorer.
|
|
3
|
+
*
|
|
4
|
+
* Registers 1 tool:
|
|
5
|
+
* • explore_transit_timeline — primary entry point, returns data + UI resource [model]
|
|
6
|
+
*
|
|
7
|
+
* The timeline is rendered client-side from structured JSON — the tool reuses
|
|
8
|
+
* the existing `ephemeris_transits` handler logic to compute natal chart positions
|
|
9
|
+
* and search for transit events, then returns the data with a UI resource link
|
|
10
|
+
* so the iframe can render a Gantt-style timeline.
|
|
11
|
+
*
|
|
12
|
+
* Also exports resource helpers (getTransitTimelineBundle, etc.) for use in
|
|
13
|
+
* index.ts and server-sse.ts.
|
|
14
|
+
*/
|
|
15
|
+
export declare const TRANSIT_TIMELINE_RESOURCE_URI = "ui://openephemeris/transit-timeline";
|
|
16
|
+
export declare const TRANSIT_TIMELINE_MIME_TYPE = "text/html;profile=mcp-app";
|
|
17
|
+
/** Read the pre-built HTML bundle. Returns null if not yet built. */
|
|
18
|
+
export declare function getTransitTimelineBundle(): string | null;
|
|
19
|
+
/** Reset cache (useful in dev watch mode or tests). */
|
|
20
|
+
export declare function clearTransitTimelineBundleCache(): void;
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* transit-timeline-app.ts — MCP App tool registration for the Transit Timeline Explorer.
|
|
3
|
+
*
|
|
4
|
+
* Registers 1 tool:
|
|
5
|
+
* • explore_transit_timeline — primary entry point, returns data + UI resource [model]
|
|
6
|
+
*
|
|
7
|
+
* The timeline is rendered client-side from structured JSON — the tool reuses
|
|
8
|
+
* the existing `ephemeris_transits` handler logic to compute natal chart positions
|
|
9
|
+
* and search for transit events, then returns the data with a UI resource link
|
|
10
|
+
* so the iframe can render a Gantt-style timeline.
|
|
11
|
+
*
|
|
12
|
+
* Also exports resource helpers (getTransitTimelineBundle, etc.) for use in
|
|
13
|
+
* index.ts and server-sse.ts.
|
|
14
|
+
*/
|
|
15
|
+
import fs from "node:fs";
|
|
16
|
+
import path from "node:path";
|
|
17
|
+
import { fileURLToPath } from "node:url";
|
|
18
|
+
import { registerTool, validateRequired, SERVER_VERSION } from "../index.js";
|
|
19
|
+
import { getActiveClient } from "../../backend/client.js";
|
|
20
|
+
import { OUTPUT_SCHEMA_JSON } from "../output-schemas.js";
|
|
21
|
+
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
22
|
+
export const TRANSIT_TIMELINE_RESOURCE_URI = "ui://openephemeris/transit-timeline";
|
|
23
|
+
export const TRANSIT_TIMELINE_MIME_TYPE = "text/html;profile=mcp-app";
|
|
24
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
25
|
+
const BUNDLE_PATHS = [
|
|
26
|
+
path.resolve(here, "..", "..", "..", "dist", "ui", "transit-timeline.html"),
|
|
27
|
+
];
|
|
28
|
+
function findBundlePath() {
|
|
29
|
+
for (const p of BUNDLE_PATHS) {
|
|
30
|
+
if (fs.existsSync(p))
|
|
31
|
+
return p;
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
let cachedBundle = null;
|
|
36
|
+
/** Read the pre-built HTML bundle. Returns null if not yet built. */
|
|
37
|
+
export function getTransitTimelineBundle() {
|
|
38
|
+
if (cachedBundle)
|
|
39
|
+
return cachedBundle;
|
|
40
|
+
const bundlePath = findBundlePath();
|
|
41
|
+
if (!bundlePath)
|
|
42
|
+
return null;
|
|
43
|
+
try {
|
|
44
|
+
cachedBundle = fs.readFileSync(bundlePath, "utf-8");
|
|
45
|
+
return cachedBundle;
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/** Reset cache (useful in dev watch mode or tests). */
|
|
52
|
+
export function clearTransitTimelineBundleCache() {
|
|
53
|
+
cachedBundle = null;
|
|
54
|
+
}
|
|
55
|
+
// ── Summary builder ──────────────────────────────────────────────────────────
|
|
56
|
+
function buildTransitSummary(natalPositions, transitResult, aspectAngle, startDate, endDate) {
|
|
57
|
+
const transits = Array.isArray(transitResult)
|
|
58
|
+
? transitResult
|
|
59
|
+
: (transitResult?.events ?? transitResult?.transits ?? transitResult?.results ?? []);
|
|
60
|
+
const count = transits.length;
|
|
61
|
+
const planets = [...new Set(transits.map((t) => String(t.planet ?? "")).filter(Boolean))];
|
|
62
|
+
const natalPoints = Object.keys(natalPositions);
|
|
63
|
+
const aspectLabel = aspectAngle === 0 ? "conjunction" : aspectAngle === 180 ? "opposition" : aspectAngle === 90 ? "square" : aspectAngle === 120 ? "trine" : aspectAngle + "°";
|
|
64
|
+
let summary = "**Transit Timeline — " + startDate + " to " + endDate + "**\n\n" +
|
|
65
|
+
count + " transit event" + (count !== 1 ? "s" : "") + " found";
|
|
66
|
+
if (aspectAngle !== 0) {
|
|
67
|
+
summary += " (" + aspectLabel + " aspect)";
|
|
68
|
+
}
|
|
69
|
+
summary += ".\n";
|
|
70
|
+
if (planets.length > 0) {
|
|
71
|
+
summary += "Transiting planets: " + planets.join(", ") + "\n";
|
|
72
|
+
}
|
|
73
|
+
if (natalPoints.length > 0) {
|
|
74
|
+
summary += "Natal points targeted: " + natalPoints.join(", ") + "\n";
|
|
75
|
+
}
|
|
76
|
+
summary += "\nClick any transit bar on the timeline for details and interpretation.";
|
|
77
|
+
return summary;
|
|
78
|
+
}
|
|
79
|
+
// ── Tool: explore_transit_timeline ────────────────────────────────────────────
|
|
80
|
+
registerTool({
|
|
81
|
+
name: "explore_transit_timeline",
|
|
82
|
+
description: "Generate an interactive transit timeline visualization showing astrological transit events " +
|
|
83
|
+
"as a visual Gantt chart. Each transit appears as a colored bar on a time axis, grouped by " +
|
|
84
|
+
"transiting planet, with markers at exact aspect moments.\n\n" +
|
|
85
|
+
"This tool first calculates the natal chart to extract planetary longitudes, then searches " +
|
|
86
|
+
"for transiting planets crossing those degrees during the specified date range.\n\n" +
|
|
87
|
+
"SUPPORTED BODIES: Sun, Moon, Mercury, Venus, Mars, Jupiter, Saturn, Uranus, Neptune, " +
|
|
88
|
+
"Pluto, Chiron, Pholus, Ceres, Pallas, Juno, Vesta, MeanNode.\n\n" +
|
|
89
|
+
"ASPECT ANGLES: 0 = conjunction/return (default), 180 = opposition, 90 = square, 120 = trine.\n\n" +
|
|
90
|
+
"Use this for a rich, interactive transit visualization in MCP Apps-capable hosts (Claude Desktop). " +
|
|
91
|
+
"Falls back to a text summary in other hosts.",
|
|
92
|
+
inputSchema: {
|
|
93
|
+
type: "object",
|
|
94
|
+
properties: {
|
|
95
|
+
natal_datetime: {
|
|
96
|
+
type: "string",
|
|
97
|
+
description: "ISO 8601 birth datetime for the natal chart.",
|
|
98
|
+
},
|
|
99
|
+
natal_latitude: {
|
|
100
|
+
type: "number",
|
|
101
|
+
description: "Latitude of birth location in decimal degrees.",
|
|
102
|
+
},
|
|
103
|
+
natal_longitude: {
|
|
104
|
+
type: "number",
|
|
105
|
+
description: "Longitude of birth location in decimal degrees.",
|
|
106
|
+
},
|
|
107
|
+
start_date: {
|
|
108
|
+
type: "string",
|
|
109
|
+
description: "Start of the transit search window, ISO 8601 date or datetime.",
|
|
110
|
+
},
|
|
111
|
+
end_date: {
|
|
112
|
+
type: "string",
|
|
113
|
+
description: "End of the transit search window, ISO 8601 date or datetime.",
|
|
114
|
+
},
|
|
115
|
+
transiting_planets: {
|
|
116
|
+
type: "array",
|
|
117
|
+
items: { type: "string" },
|
|
118
|
+
description: "List of transiting planet IDs to search. Omit to search all outer planets.",
|
|
119
|
+
},
|
|
120
|
+
natal_points: {
|
|
121
|
+
type: "array",
|
|
122
|
+
items: { type: "string" },
|
|
123
|
+
description: "Natal point IDs whose longitudes should be targeted. Omit for all core points.",
|
|
124
|
+
},
|
|
125
|
+
aspect_angle: {
|
|
126
|
+
type: "number",
|
|
127
|
+
description: "Aspect angle in degrees. 0 = conjunction (default), 180 = opposition, 90 = square, 120 = trine.",
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
required: ["natal_datetime", "natal_latitude", "natal_longitude", "start_date", "end_date"],
|
|
131
|
+
additionalProperties: false,
|
|
132
|
+
},
|
|
133
|
+
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
134
|
+
annotations: {
|
|
135
|
+
title: "Interactive Transit Timeline Explorer",
|
|
136
|
+
readOnlyHint: true,
|
|
137
|
+
destructiveHint: false,
|
|
138
|
+
idempotentHint: true,
|
|
139
|
+
openWorldHint: false,
|
|
140
|
+
},
|
|
141
|
+
_meta: {
|
|
142
|
+
ui: {
|
|
143
|
+
resourceUri: TRANSIT_TIMELINE_RESOURCE_URI,
|
|
144
|
+
visibility: ["model", "app"],
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
handler: async (args) => {
|
|
148
|
+
validateRequired(args, ["natal_datetime", "natal_latitude", "natal_longitude", "start_date", "end_date"]);
|
|
149
|
+
const client = getActiveClient();
|
|
150
|
+
// Guard: reject excessively wide date ranges before hitting the backend.
|
|
151
|
+
// The Go backend clamps Explorer tier to 1 year, but larger tiers can still
|
|
152
|
+
// trigger compute-bound 45-second timeouts on very wide windows.
|
|
153
|
+
const startMs = new Date(args.start_date).getTime();
|
|
154
|
+
const endMs = new Date(args.end_date).getTime();
|
|
155
|
+
if (!isNaN(startMs) && !isNaN(endMs)) {
|
|
156
|
+
const spanDays = (endMs - startMs) / (1000 * 60 * 60 * 24);
|
|
157
|
+
if (spanDays > 5 * 365) {
|
|
158
|
+
throw new Error(`Date range too wide (${Math.round(spanDays / 365)} years). ` +
|
|
159
|
+
`The transit timeline works best with windows under 2 years — try narrowing ` +
|
|
160
|
+
`start_date and end_date to a 1–2 year window and re-run.`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// Step 1: Compute natal chart to extract planetary longitudes
|
|
164
|
+
const natalBody = {
|
|
165
|
+
subject: {
|
|
166
|
+
name: "Transit Natal Subject",
|
|
167
|
+
birth_datetime: { iso: args.natal_datetime },
|
|
168
|
+
birth_location: {
|
|
169
|
+
latitude: { decimal: args.natal_latitude },
|
|
170
|
+
longitude: { decimal: args.natal_longitude },
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
const natalResult = await client.request("POST", "/ephemeris/natal-chart", { data: natalBody });
|
|
175
|
+
const targetDegrees = [];
|
|
176
|
+
const natalPositionMap = {};
|
|
177
|
+
const planets = natalResult?.planets || natalResult?.data?.planets || [];
|
|
178
|
+
const planetArray = Array.isArray(planets) ? planets : Object.values(planets);
|
|
179
|
+
// ── Core natal points to target (conjunction search) ─────────────────────
|
|
180
|
+
// When no natal_points filter is given, we limit to 7 core bodies to keep
|
|
181
|
+
// target_degrees count manageable. 4 outer planets × 10+ targets creates a
|
|
182
|
+
// combinatoric explosion (40+ serial SearchTransit calls on the backend).
|
|
183
|
+
const CORE_BODIES_DEFAULT = new Set([
|
|
184
|
+
"sun", "moon", "mercury", "venus", "mars", "jupiter", "saturn",
|
|
185
|
+
]);
|
|
186
|
+
const wantedPoints = args.natal_points
|
|
187
|
+
? new Set(args.natal_points.map((p) => p.toLowerCase()))
|
|
188
|
+
: CORE_BODIES_DEFAULT;
|
|
189
|
+
for (const planet of planetArray) {
|
|
190
|
+
const name = (planet.name || planet.id || "").toLowerCase();
|
|
191
|
+
const lon = planet.longitude ?? planet.ecliptic_longitude ?? planet.lon;
|
|
192
|
+
if (typeof lon !== "number" || !isFinite(lon))
|
|
193
|
+
continue;
|
|
194
|
+
if (!wantedPoints.has(name))
|
|
195
|
+
continue;
|
|
196
|
+
targetDegrees.push(Math.round(lon * 100) / 100);
|
|
197
|
+
natalPositionMap[name] = lon;
|
|
198
|
+
}
|
|
199
|
+
// Extract angles (ASC, MC) — only if explicitly requested via natal_points
|
|
200
|
+
if (args.natal_points) {
|
|
201
|
+
const angles = natalResult?.angles || natalResult?.data?.angles;
|
|
202
|
+
if (angles) {
|
|
203
|
+
const angleEntries = Array.isArray(angles)
|
|
204
|
+
? angles
|
|
205
|
+
: Object.entries(angles).map(([k, v]) => ({ name: k, ...(typeof v === "object" ? v : { longitude: v }) }));
|
|
206
|
+
for (const angle of angleEntries) {
|
|
207
|
+
const name = (angle.name || angle.id || "").toLowerCase();
|
|
208
|
+
const lon = angle.longitude ?? angle.ecliptic_longitude ?? angle.lon;
|
|
209
|
+
if (typeof lon !== "number" || !isFinite(lon))
|
|
210
|
+
continue;
|
|
211
|
+
if (!wantedPoints.has(name))
|
|
212
|
+
continue;
|
|
213
|
+
targetDegrees.push(Math.round(lon * 100) / 100);
|
|
214
|
+
natalPositionMap[name] = lon;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
// Safety cap: never send more than 12 target degrees to the backend
|
|
219
|
+
// (prevents combinatoric timeouts when many planets + many targets overlap).
|
|
220
|
+
const MAX_TARGETS = 12;
|
|
221
|
+
if (targetDegrees.length > MAX_TARGETS) {
|
|
222
|
+
targetDegrees.splice(MAX_TARGETS);
|
|
223
|
+
// Prune natalPositionMap to match
|
|
224
|
+
const keepNames = new Set(Object.entries(natalPositionMap)
|
|
225
|
+
.filter(([, v]) => targetDegrees.includes(Math.round(v * 100) / 100))
|
|
226
|
+
.map(([k]) => k));
|
|
227
|
+
for (const k of Object.keys(natalPositionMap)) {
|
|
228
|
+
if (!keepNames.has(k))
|
|
229
|
+
delete natalPositionMap[k];
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (targetDegrees.length === 0) {
|
|
233
|
+
throw new Error("Could not extract natal planet longitudes. Verify natal_datetime, natal_latitude, and natal_longitude.");
|
|
234
|
+
}
|
|
235
|
+
// Step 2: Search transits
|
|
236
|
+
let startStr = args.start_date;
|
|
237
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(startStr))
|
|
238
|
+
startStr += "T00:00:00Z";
|
|
239
|
+
let endStr = args.end_date;
|
|
240
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(endStr))
|
|
241
|
+
endStr += "T23:59:59Z";
|
|
242
|
+
const aspectAngle = typeof args.aspect_angle === "number" ? args.aspect_angle : 0;
|
|
243
|
+
let effectiveTargets = targetDegrees;
|
|
244
|
+
if (aspectAngle !== 0) {
|
|
245
|
+
effectiveTargets = targetDegrees.map((deg) => ((deg + aspectAngle) % 360 + 360) % 360);
|
|
246
|
+
}
|
|
247
|
+
const transitBody = {
|
|
248
|
+
start_date: startStr,
|
|
249
|
+
end_date: endStr,
|
|
250
|
+
target_degrees: effectiveTargets,
|
|
251
|
+
};
|
|
252
|
+
if (args.transiting_planets)
|
|
253
|
+
transitBody.planet_names = args.transiting_planets;
|
|
254
|
+
if (aspectAngle !== 0) {
|
|
255
|
+
transitBody.search_criteria = { aspect_angle: aspectAngle };
|
|
256
|
+
}
|
|
257
|
+
// Give the backend a 90s window — explorer tier allows 30s Go compute
|
|
258
|
+
// timeout, but the node-to-Fly round-trip including queuing can extend this.
|
|
259
|
+
const transitResult = await client.request("POST", "/predictive/transits/search", { data: transitBody, timeoutMs: 90_000 });
|
|
260
|
+
// Normalize the API response into a flat array so the iframe UI always
|
|
261
|
+
// receives an iterable — some backend response shapes wrap events in a
|
|
262
|
+
// container object rather than returning a bare array.
|
|
263
|
+
const transitArray = Array.isArray(transitResult)
|
|
264
|
+
? transitResult
|
|
265
|
+
: (transitResult?.events ??
|
|
266
|
+
transitResult?.transits ??
|
|
267
|
+
transitResult?.results ??
|
|
268
|
+
[]);
|
|
269
|
+
// Build payload
|
|
270
|
+
const payload = {
|
|
271
|
+
natal_positions: natalPositionMap,
|
|
272
|
+
target_degrees_used: effectiveTargets,
|
|
273
|
+
aspect_angle: aspectAngle,
|
|
274
|
+
transit_results: transitArray,
|
|
275
|
+
search_window: { start_date: args.start_date, end_date: args.end_date },
|
|
276
|
+
server_version: SERVER_VERSION,
|
|
277
|
+
};
|
|
278
|
+
const summary = buildTransitSummary(natalPositionMap, transitArray, aspectAngle, args.start_date, args.end_date);
|
|
279
|
+
const bundleAvailable = Boolean(getTransitTimelineBundle());
|
|
280
|
+
if (bundleAvailable) {
|
|
281
|
+
return {
|
|
282
|
+
content: [
|
|
283
|
+
{ type: "text", text: summary },
|
|
284
|
+
{ type: "text", text: JSON.stringify(payload) },
|
|
285
|
+
],
|
|
286
|
+
_meta: {
|
|
287
|
+
"ui/resourceUri": TRANSIT_TIMELINE_RESOURCE_URI,
|
|
288
|
+
ui: { resourceUri: TRANSIT_TIMELINE_RESOURCE_URI },
|
|
289
|
+
},
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
// Fallback: text summary only
|
|
293
|
+
return { content: [{ type: "text", text: summary }] };
|
|
294
|
+
},
|
|
295
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vedic-chart-app.ts — MCP App tool registration for the Vedic Jyotish Chart Explorer.
|
|
3
|
+
*
|
|
4
|
+
* Registers 1 tool:
|
|
5
|
+
* • explore_vedic_chart — primary entry point [model + app visible]
|
|
6
|
+
* Calls POST /vedic/chart and returns a data payload + UI resource URI
|
|
7
|
+
* so Claude Desktop can render the interactive South Indian Rashi grid.
|
|
8
|
+
*
|
|
9
|
+
* Also exports resource helpers (getVedicChartBundle, etc.) for use in
|
|
10
|
+
* server-sse.ts resource listing and reading.
|
|
11
|
+
*/
|
|
12
|
+
export declare const VEDIC_CHART_RESOURCE_URI = "ui://openephemeris/vedic-chart";
|
|
13
|
+
export declare const VEDIC_CHART_MIME_TYPE = "text/html;profile=mcp-app";
|
|
14
|
+
/** Read the pre-built HTML bundle. Returns null if not yet built. */
|
|
15
|
+
export declare function getVedicChartBundle(): string | null;
|
|
16
|
+
/** Reset cache (useful in dev watch mode or tests). */
|
|
17
|
+
export declare function clearVedicChartBundleCache(): void;
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vedic-chart-app.ts — MCP App tool registration for the Vedic Jyotish Chart Explorer.
|
|
3
|
+
*
|
|
4
|
+
* Registers 1 tool:
|
|
5
|
+
* • explore_vedic_chart — primary entry point [model + app visible]
|
|
6
|
+
* Calls POST /vedic/chart and returns a data payload + UI resource URI
|
|
7
|
+
* so Claude Desktop can render the interactive South Indian Rashi grid.
|
|
8
|
+
*
|
|
9
|
+
* Also exports resource helpers (getVedicChartBundle, etc.) for use in
|
|
10
|
+
* server-sse.ts resource listing and reading.
|
|
11
|
+
*/
|
|
12
|
+
import fs from "node:fs";
|
|
13
|
+
import path from "node:path";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
15
|
+
import { registerTool, SERVER_VERSION } from "../index.js";
|
|
16
|
+
import { getActiveClient } from "../../backend/client.js";
|
|
17
|
+
import { OUTPUT_SCHEMA_JSON } from "../output-schemas.js";
|
|
18
|
+
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
19
|
+
export const VEDIC_CHART_RESOURCE_URI = "ui://openephemeris/vedic-chart";
|
|
20
|
+
export const VEDIC_CHART_MIME_TYPE = "text/html;profile=mcp-app";
|
|
21
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
22
|
+
const BUNDLE_PATHS = [
|
|
23
|
+
path.resolve(here, "..", "..", "..", "dist", "ui", "vedic-chart.html"),
|
|
24
|
+
];
|
|
25
|
+
function findBundlePath() {
|
|
26
|
+
for (const p of BUNDLE_PATHS) {
|
|
27
|
+
if (fs.existsSync(p))
|
|
28
|
+
return p;
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
let cachedBundle = null;
|
|
33
|
+
/** Read the pre-built HTML bundle. Returns null if not yet built. */
|
|
34
|
+
export function getVedicChartBundle() {
|
|
35
|
+
if (cachedBundle)
|
|
36
|
+
return cachedBundle;
|
|
37
|
+
const bundlePath = findBundlePath();
|
|
38
|
+
if (!bundlePath)
|
|
39
|
+
return null;
|
|
40
|
+
try {
|
|
41
|
+
cachedBundle = fs.readFileSync(bundlePath, "utf-8");
|
|
42
|
+
return cachedBundle;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/** Reset cache (useful in dev watch mode or tests). */
|
|
49
|
+
export function clearVedicChartBundleCache() {
|
|
50
|
+
cachedBundle = null;
|
|
51
|
+
}
|
|
52
|
+
function parseVedicArgs(args) {
|
|
53
|
+
const { datetime, latitude, longitude, ayanamsa } = args;
|
|
54
|
+
if (!datetime) {
|
|
55
|
+
throw new Error("datetime is required. Provide an ISO 8601 datetime in UTC, " +
|
|
56
|
+
"e.g. datetime='1987-07-15T14:30:00Z'.");
|
|
57
|
+
}
|
|
58
|
+
if (latitude == null || longitude == null) {
|
|
59
|
+
throw new Error("latitude and longitude are required for Vedic chart calculation. " +
|
|
60
|
+
"Example: latitude=28.6, longitude=77.2 (New Delhi).");
|
|
61
|
+
}
|
|
62
|
+
return { datetime, latitude, longitude, ayanamsa: ayanamsa || undefined };
|
|
63
|
+
}
|
|
64
|
+
// ── Summary builder ──────────────────────────────────────────────────────────
|
|
65
|
+
// Convert a sidereal longitude (0–360°) to the Western sign name
|
|
66
|
+
function lonToSignName(lon) {
|
|
67
|
+
const SIGNS = [
|
|
68
|
+
"Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo",
|
|
69
|
+
"Libra", "Scorpio", "Sagittarius", "Capricorn", "Aquarius", "Pisces",
|
|
70
|
+
];
|
|
71
|
+
const idx = Math.floor(((lon % 360) + 360) % 360 / 30) % 12;
|
|
72
|
+
return SIGNS[idx];
|
|
73
|
+
}
|
|
74
|
+
function buildVedicSummary(data) {
|
|
75
|
+
const planets = data.planets || [];
|
|
76
|
+
const metadata = data.metadata || {};
|
|
77
|
+
// lagna is a degree float from the backend — convert to sign name
|
|
78
|
+
const lagnaSign = typeof metadata.lagna === "number"
|
|
79
|
+
? lonToSignName(metadata.lagna)
|
|
80
|
+
: metadata.lagna || "unknown";
|
|
81
|
+
// Backend returns "ayanamsha" (with extra h) — normalise
|
|
82
|
+
const ayanamsa = metadata.ayanamsa || metadata.ayanamsha || "Lahiri";
|
|
83
|
+
const ayanamsaVal = metadata.ayanamsa_value != null
|
|
84
|
+
? " (" + metadata.ayanamsa_value.toFixed(2) + "°)"
|
|
85
|
+
: metadata.correction != null
|
|
86
|
+
? " (" + metadata.correction.toFixed(2) + "°)"
|
|
87
|
+
: "";
|
|
88
|
+
let summary = "**Vedic Jyotish Chart** — Lagna: **" + lagnaSign + "** · Ayanamsa: " +
|
|
89
|
+
ayanamsa + ayanamsaVal + "\n\n";
|
|
90
|
+
// Planet placement table
|
|
91
|
+
if (planets.length > 0) {
|
|
92
|
+
summary += "| Planet | Rashi | Degree | Nakshatra | Pada |\n";
|
|
93
|
+
summary += "|--------|-------|--------|-----------|------|\n";
|
|
94
|
+
for (const p of planets) {
|
|
95
|
+
const retroMark = p.is_retrograde ? " ℞" : "";
|
|
96
|
+
summary +=
|
|
97
|
+
"| " + capFirst(String(p.planet)) + retroMark +
|
|
98
|
+
" | " + (p.rashi_western || p.rashi || "?") +
|
|
99
|
+
" | " + (typeof p.rashi_degree === "number" ? p.rashi_degree.toFixed(2) + "°" : "?") +
|
|
100
|
+
" | " + (p.nakshatra?.name || "?") +
|
|
101
|
+
" | " + (p.nakshatra?.pada || "?") +
|
|
102
|
+
" |\n";
|
|
103
|
+
}
|
|
104
|
+
summary += "\n";
|
|
105
|
+
}
|
|
106
|
+
summary += "Click on any Rashi cell to explore the planets placed there.";
|
|
107
|
+
return summary;
|
|
108
|
+
}
|
|
109
|
+
function capFirst(s) {
|
|
110
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
111
|
+
}
|
|
112
|
+
// ── Tool: explore_vedic_chart ─────────────────────────────────────────────────
|
|
113
|
+
registerTool({
|
|
114
|
+
name: "explore_vedic_chart",
|
|
115
|
+
description: "Generate an interactive Vedic Jyotish (South Indian) Rashi chart visualization.\n\n" +
|
|
116
|
+
"Renders a 4×4 fixed-sign grid with:\n" +
|
|
117
|
+
" • All nine Jyotish planets (Sun through Ketu) placed in their Rashi\n" +
|
|
118
|
+
" • Lagna (Ascendant) cell marked with a gold diagonal\n" +
|
|
119
|
+
" • Sanskrit/Devanagari sign labels toggle\n" +
|
|
120
|
+
" • Click any Rashi cell → detailed planet info drawer\n" +
|
|
121
|
+
" (degree, Nakshatra, Pada, lord, Navamsa sign, dignity)\n" +
|
|
122
|
+
" • Ask Claude buttons for Lagna, Moon/Nakshatra, and full chart interpretation\n" +
|
|
123
|
+
" • Ayanamsa system displayed (Lahiri by default)\n\n" +
|
|
124
|
+
"CREDIT COST: 1 credit.\n\n" +
|
|
125
|
+
"Use this for a rich, interactive Vedic chart experience in MCP Apps-capable hosts " +
|
|
126
|
+
"(Claude Desktop). Falls back to a markdown table summary in other hosts.",
|
|
127
|
+
inputSchema: {
|
|
128
|
+
type: "object",
|
|
129
|
+
properties: {
|
|
130
|
+
datetime: {
|
|
131
|
+
type: "string",
|
|
132
|
+
description: "Birth date and time as ISO 8601, e.g. '1987-07-15T14:30:00Z'. " +
|
|
133
|
+
"Always include time when known — it determines the Lagna (Ascendant).",
|
|
134
|
+
},
|
|
135
|
+
latitude: {
|
|
136
|
+
type: "number",
|
|
137
|
+
description: "Birth latitude in decimal degrees, e.g. 28.6 for New Delhi.",
|
|
138
|
+
},
|
|
139
|
+
longitude: {
|
|
140
|
+
type: "number",
|
|
141
|
+
description: "Birth longitude in decimal degrees, e.g. 77.2 for New Delhi.",
|
|
142
|
+
},
|
|
143
|
+
ayanamsa: {
|
|
144
|
+
type: "string",
|
|
145
|
+
enum: ["lahiri", "fagan_bradley", "krishnamurti", "raman", "yukteshwar"],
|
|
146
|
+
description: "Ayanamsa system for sidereal calculation. " +
|
|
147
|
+
"Options: 'lahiri' (default, most common), 'raman', 'krishnamurti', 'fagan_bradley', 'yukteshwar'.",
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
required: ["datetime", "latitude", "longitude"],
|
|
151
|
+
additionalProperties: false,
|
|
152
|
+
},
|
|
153
|
+
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
154
|
+
annotations: {
|
|
155
|
+
title: "Interactive Vedic Jyotish Chart",
|
|
156
|
+
readOnlyHint: true,
|
|
157
|
+
destructiveHint: false,
|
|
158
|
+
idempotentHint: true,
|
|
159
|
+
openWorldHint: false,
|
|
160
|
+
},
|
|
161
|
+
_meta: {
|
|
162
|
+
ui: {
|
|
163
|
+
resourceUri: VEDIC_CHART_RESOURCE_URI,
|
|
164
|
+
visibility: ["model", "app"],
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
handler: async (args) => {
|
|
168
|
+
const { datetime, latitude, longitude, ayanamsa } = parseVedicArgs(args);
|
|
169
|
+
const client = getActiveClient();
|
|
170
|
+
// Backend field is datetime_utc (matches vedic.ts specialized tool)
|
|
171
|
+
const body = {
|
|
172
|
+
datetime_utc: datetime,
|
|
173
|
+
latitude,
|
|
174
|
+
longitude,
|
|
175
|
+
};
|
|
176
|
+
if (ayanamsa)
|
|
177
|
+
body.ayanamsa = ayanamsa;
|
|
178
|
+
const result = await client.request("POST", "/vedic/chart", { data: body });
|
|
179
|
+
const rawMeta = result.metadata || {};
|
|
180
|
+
// Backend returns lagna as a degree float (e.g. 213.4) but the iframe
|
|
181
|
+
// expects a sign name string (e.g. "Scorpio") for cell matching.
|
|
182
|
+
const lagnaRaw = rawMeta.lagna;
|
|
183
|
+
const lagnaSign = typeof lagnaRaw === "number"
|
|
184
|
+
? lonToSignName(lagnaRaw)
|
|
185
|
+
: lagnaRaw || "";
|
|
186
|
+
// Backend returns "ayanamsha" (extra h) — normalise to "ayanamsa".
|
|
187
|
+
const ayanamsaName = rawMeta.ayanamsa || rawMeta.ayanamsha || "Lahiri";
|
|
188
|
+
const normalizedMeta = {
|
|
189
|
+
...rawMeta,
|
|
190
|
+
lagna: lagnaSign,
|
|
191
|
+
ayanamsa: ayanamsaName,
|
|
192
|
+
// Expose correction as ayanamsa_value so the iframe can show the degree
|
|
193
|
+
ayanamsa_value: rawMeta.ayanamsa_value ?? rawMeta.correction ?? null,
|
|
194
|
+
};
|
|
195
|
+
// Build a Set from the retrograde_planets list the Go handler populated.
|
|
196
|
+
const retrogradeList = Array.isArray(rawMeta.retrograde_planets)
|
|
197
|
+
? rawMeta.retrograde_planets
|
|
198
|
+
: [];
|
|
199
|
+
const retrogradeSet = new Set(retrogradeList);
|
|
200
|
+
const uiPayload = {
|
|
201
|
+
// Stamp is_retrograde on each planet using the retrograde_planets list
|
|
202
|
+
// from metadata — the Go struct has no such field, so it lives in metadata.
|
|
203
|
+
planets: (result.planets || []).map((p) => ({
|
|
204
|
+
...p,
|
|
205
|
+
is_retrograde: retrogradeSet.has(String(p.planet)),
|
|
206
|
+
})),
|
|
207
|
+
metadata: normalizedMeta,
|
|
208
|
+
server_version: SERVER_VERSION,
|
|
209
|
+
};
|
|
210
|
+
const summary = buildVedicSummary(uiPayload);
|
|
211
|
+
const bundleAvailable = Boolean(getVedicChartBundle());
|
|
212
|
+
if (bundleAvailable) {
|
|
213
|
+
return {
|
|
214
|
+
content: [
|
|
215
|
+
{ type: "text", text: summary },
|
|
216
|
+
// Second text block is injected as window.__VEDIC_CHART_DATA__ by ext-apps SDK
|
|
217
|
+
{ type: "text", text: JSON.stringify(uiPayload) },
|
|
218
|
+
],
|
|
219
|
+
_meta: {
|
|
220
|
+
"ui/resourceUri": VEDIC_CHART_RESOURCE_URI,
|
|
221
|
+
ui: { resourceUri: VEDIC_CHART_RESOURCE_URI },
|
|
222
|
+
},
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
// Fallback: text-only table
|
|
226
|
+
return { content: [{ type: "text", text: summary }] };
|
|
227
|
+
},
|
|
228
|
+
});
|
package/dist/tools/auth.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { registerTool } from "./index.js";
|
|
2
2
|
import { CredentialManager } from "../auth/credentials.js";
|
|
3
3
|
import { DeviceAuthFlow } from "../auth/device-auth.js";
|
|
4
|
+
import { OUTPUT_SCHEMA_AUTH } from "./output-schemas.js";
|
|
4
5
|
const credentialManager = new CredentialManager();
|
|
5
6
|
// ────────────────────────────────────────────────────────
|
|
6
7
|
// auth.login — Start the device authorization flow
|
|
@@ -17,6 +18,7 @@ registerTool({
|
|
|
17
18
|
properties: {},
|
|
18
19
|
additionalProperties: false,
|
|
19
20
|
},
|
|
21
|
+
outputSchema: OUTPUT_SCHEMA_AUTH,
|
|
20
22
|
annotations: {
|
|
21
23
|
title: "Connect Account",
|
|
22
24
|
readOnlyHint: false,
|
|
@@ -95,6 +97,7 @@ registerTool({
|
|
|
95
97
|
properties: {},
|
|
96
98
|
additionalProperties: false,
|
|
97
99
|
},
|
|
100
|
+
outputSchema: OUTPUT_SCHEMA_AUTH,
|
|
98
101
|
annotations: {
|
|
99
102
|
title: "Auth Status",
|
|
100
103
|
readOnlyHint: true,
|
|
@@ -179,6 +182,7 @@ registerTool({
|
|
|
179
182
|
properties: {},
|
|
180
183
|
additionalProperties: false,
|
|
181
184
|
},
|
|
185
|
+
outputSchema: OUTPUT_SCHEMA_AUTH,
|
|
182
186
|
annotations: {
|
|
183
187
|
title: "Disconnect Account",
|
|
184
188
|
readOnlyHint: false,
|
package/dist/tools/dev.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { registerTool, validateRequired } from "./index.js";
|
|
2
2
|
import { getActiveClient } from "../backend/client.js";
|
|
3
|
+
import { OUTPUT_SCHEMA_JSON } from "./output-schemas.js";
|
|
3
4
|
import fs from "node:fs";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
import { fileURLToPath } from "node:url";
|
|
@@ -123,6 +124,7 @@ registerTool({
|
|
|
123
124
|
required: ["method", "path"],
|
|
124
125
|
additionalProperties: false,
|
|
125
126
|
},
|
|
127
|
+
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
126
128
|
handler: async (args) => {
|
|
127
129
|
validateRequired(args, ["path"]);
|
|
128
130
|
const methodStr = String(args.method || "GET").toUpperCase();
|
|
@@ -189,6 +191,7 @@ registerTool({
|
|
|
189
191
|
properties: {},
|
|
190
192
|
additionalProperties: false,
|
|
191
193
|
},
|
|
194
|
+
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
192
195
|
handler: async () => {
|
|
193
196
|
const allowlist = loadAllowlist();
|
|
194
197
|
return {
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -5,6 +5,12 @@ export interface ToolDefinition {
|
|
|
5
5
|
name: string;
|
|
6
6
|
description: string;
|
|
7
7
|
inputSchema: z.ZodType<any> | Record<string, unknown>;
|
|
8
|
+
/**
|
|
9
|
+
* MCP spec 2025-11-25 — JSON Schema describing the structured output of this tool.
|
|
10
|
+
* Smithery uses this to score "Output schemas" in the quality rubric.
|
|
11
|
+
* All tools return MCP content block arrays; specialized tools declare richer shapes.
|
|
12
|
+
*/
|
|
13
|
+
outputSchema?: Record<string, unknown>;
|
|
8
14
|
annotations?: {
|
|
9
15
|
title?: string;
|
|
10
16
|
readOnlyHint?: boolean;
|
package/dist/tools/index.js
CHANGED
|
@@ -47,7 +47,11 @@ export async function initTools(profile) {
|
|
|
47
47
|
// Always register the generic proxy tools (dev.call + dev.list_allowed).
|
|
48
48
|
await import("./dev.js");
|
|
49
49
|
if (resolvedProfile === "dev") {
|
|
50
|
-
//
|
|
50
|
+
// ── FULL BUILD: all specialized tools ───────────────────────────
|
|
51
|
+
// Payload size theory was disproven. The "Failed to call tool"
|
|
52
|
+
// bug was caused by Claude Desktop's MCP App iframe handler
|
|
53
|
+
// corruption. Iframe tools are purged; all specialized tools are restored.
|
|
54
|
+
// ────────────────────────────────────────────────────────────────
|
|
51
55
|
await import("./specialized/natal.js");
|
|
52
56
|
await import("./specialized/transits.js");
|
|
53
57
|
await import("./specialized/moon.js");
|
|
@@ -70,11 +74,12 @@ export async function initTools(profile) {
|
|
|
70
74
|
await import("./specialized/ephemeris_extended.js");
|
|
71
75
|
await import("./specialized/venus_star_points.js");
|
|
72
76
|
await import("./specialized/acg.js");
|
|
73
|
-
|
|
77
|
+
await import("./apps/location-tools.js");
|
|
78
|
+
// Re-enable requested MCP apps (iframes)
|
|
74
79
|
await import("./apps/chart-wheel-app.js");
|
|
75
|
-
await import("./apps/bodygraph-app.js");
|
|
76
80
|
await import("./apps/bi-wheel-app.js");
|
|
77
|
-
await import("./apps/
|
|
81
|
+
await import("./apps/bodygraph-app.js");
|
|
82
|
+
await import("./apps/moon-phase-app.js");
|
|
78
83
|
}
|
|
79
84
|
}
|
|
80
85
|
/**
|