@openephemeris/mcp-server 3.13.10 → 3.15.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 +53 -0
- package/README.md +6 -6
- package/dist/index.js +5 -43
- package/dist/oauth/discovery.d.ts +1 -0
- package/dist/oauth/discovery.js +8 -3
- package/dist/oauth/store.d.ts +20 -0
- package/dist/oauth/store.js +117 -3
- package/dist/oauth/token.js +4 -2
- package/dist/prompts.js +25 -16
- package/dist/server-sse.js +136 -78
- package/dist/tools/apps/bazi-app.js +2 -0
- package/dist/tools/apps/bi-wheel-app.js +65 -7
- package/dist/tools/apps/bodygraph-app.js +85 -11
- package/dist/tools/apps/chart-wheel-app.js +80 -7
- package/dist/tools/apps/location-tools.js +41 -5
- package/dist/tools/apps/moon-phase-app.js +82 -22
- package/dist/tools/apps/transit-timeline-app.js +75 -23
- package/dist/tools/apps/vedic-chart-app.js +2 -0
- package/dist/tools/auth.js +4 -0
- package/dist/tools/dev.js +104 -67
- package/dist/tools/index.d.ts +16 -0
- package/dist/tools/index.js +23 -8
- package/dist/tools/output-schemas.d.ts +37 -0
- package/dist/tools/output-schemas.js +130 -0
- package/dist/tools/specialized/acg.js +3 -0
- package/dist/tools/specialized/bazi.js +14 -6
- package/dist/tools/specialized/bi_wheel.js +5 -2
- package/dist/tools/specialized/chart_wheel.js +5 -2
- package/dist/tools/specialized/comparative.js +7 -2
- package/dist/tools/specialized/eclipse.js +2 -0
- package/dist/tools/specialized/electional.js +5 -0
- package/dist/tools/specialized/ephemeris_core.js +3 -0
- package/dist/tools/specialized/ephemeris_extended.js +9 -0
- package/dist/tools/specialized/hd_bodygraph.js +6 -3
- package/dist/tools/specialized/hd_cycles.js +3 -0
- package/dist/tools/specialized/hd_group.js +3 -0
- package/dist/tools/specialized/human_design.js +2 -0
- package/dist/tools/specialized/moon.js +3 -0
- package/dist/tools/specialized/natal.js +2 -0
- package/dist/tools/specialized/progressed.js +2 -0
- package/dist/tools/specialized/relocation.js +2 -0
- package/dist/tools/specialized/returns.js +4 -0
- package/dist/tools/specialized/synastry.js +2 -0
- package/dist/tools/specialized/transits.js +52 -43
- package/dist/tools/specialized/vedic.js +2 -0
- package/dist/tools/specialized/venus_star_points.js +7 -0
- package/dist/ui/bazi.html +22 -22
- package/dist/ui/bodygraph.html +1981 -1904
- package/dist/ui/chart-wheel.html +3148 -2806
- package/dist/ui/vedic-chart.html +22 -22
- package/package.json +18 -11
- package/smithery.yaml +16 -1
- package/dist/ui/bi-wheel.html +0 -7485
- package/dist/ui/moon-phase.html +0 -6758
- package/dist/ui/transit-timeline.html +0 -6835
|
@@ -17,6 +17,7 @@ import path from "node:path";
|
|
|
17
17
|
import { fileURLToPath } from "node:url";
|
|
18
18
|
import { registerTool, validateRequired, SERVER_VERSION } from "../index.js";
|
|
19
19
|
import { getActiveClient } from "../../backend/client.js";
|
|
20
|
+
import { OUTPUT_SCHEMA_JSON } from "../output-schemas.js";
|
|
20
21
|
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
21
22
|
export const TRANSIT_TIMELINE_RESOURCE_URI = "ui://openephemeris/transit-timeline";
|
|
22
23
|
export const TRANSIT_TIMELINE_MIME_TYPE = "text/html;profile=mcp-app";
|
|
@@ -129,6 +130,7 @@ registerTool({
|
|
|
129
130
|
required: ["natal_datetime", "natal_latitude", "natal_longitude", "start_date", "end_date"],
|
|
130
131
|
additionalProperties: false,
|
|
131
132
|
},
|
|
133
|
+
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
132
134
|
annotations: {
|
|
133
135
|
title: "Interactive Transit Timeline Explorer",
|
|
134
136
|
readOnlyHint: true,
|
|
@@ -145,6 +147,19 @@ registerTool({
|
|
|
145
147
|
handler: async (args) => {
|
|
146
148
|
validateRequired(args, ["natal_datetime", "natal_latitude", "natal_longitude", "start_date", "end_date"]);
|
|
147
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
|
+
}
|
|
148
163
|
// Step 1: Compute natal chart to extract planetary longitudes
|
|
149
164
|
const natalBody = {
|
|
150
165
|
subject: {
|
|
@@ -161,32 +176,57 @@ registerTool({
|
|
|
161
176
|
const natalPositionMap = {};
|
|
162
177
|
const planets = natalResult?.planets || natalResult?.data?.planets || [];
|
|
163
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
|
+
]);
|
|
164
186
|
const wantedPoints = args.natal_points
|
|
165
187
|
? new Set(args.natal_points.map((p) => p.toLowerCase()))
|
|
166
|
-
:
|
|
188
|
+
: CORE_BODIES_DEFAULT;
|
|
167
189
|
for (const planet of planetArray) {
|
|
168
190
|
const name = (planet.name || planet.id || "").toLowerCase();
|
|
169
191
|
const lon = planet.longitude ?? planet.ecliptic_longitude ?? planet.lon;
|
|
170
192
|
if (typeof lon !== "number" || !isFinite(lon))
|
|
171
193
|
continue;
|
|
172
|
-
if (
|
|
194
|
+
if (!wantedPoints.has(name))
|
|
173
195
|
continue;
|
|
174
196
|
targetDegrees.push(Math.round(lon * 100) / 100);
|
|
175
197
|
natalPositionMap[name] = lon;
|
|
176
198
|
}
|
|
177
|
-
// Extract angles (ASC, MC)
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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];
|
|
190
230
|
}
|
|
191
231
|
}
|
|
192
232
|
if (targetDegrees.length === 0) {
|
|
@@ -199,31 +239,43 @@ registerTool({
|
|
|
199
239
|
let endStr = args.end_date;
|
|
200
240
|
if (/^\d{4}-\d{2}-\d{2}$/.test(endStr))
|
|
201
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
|
+
}
|
|
202
247
|
const transitBody = {
|
|
203
248
|
start_date: startStr,
|
|
204
249
|
end_date: endStr,
|
|
205
|
-
target_degrees:
|
|
250
|
+
target_degrees: effectiveTargets,
|
|
206
251
|
};
|
|
207
252
|
if (args.transiting_planets)
|
|
208
253
|
transitBody.planet_names = args.transiting_planets;
|
|
209
|
-
let effectiveTargets = targetDegrees;
|
|
210
|
-
const aspectAngle = typeof args.aspect_angle === "number" ? args.aspect_angle : 0;
|
|
211
254
|
if (aspectAngle !== 0) {
|
|
212
|
-
effectiveTargets = targetDegrees.map((deg) => ((deg + aspectAngle) % 360 + 360) % 360);
|
|
213
|
-
transitBody.target_degrees = effectiveTargets;
|
|
214
255
|
transitBody.search_criteria = { aspect_angle: aspectAngle };
|
|
215
256
|
}
|
|
216
|
-
|
|
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
|
+
[]);
|
|
217
269
|
// Build payload
|
|
218
270
|
const payload = {
|
|
219
271
|
natal_positions: natalPositionMap,
|
|
220
272
|
target_degrees_used: effectiveTargets,
|
|
221
273
|
aspect_angle: aspectAngle,
|
|
222
|
-
transit_results:
|
|
274
|
+
transit_results: transitArray,
|
|
223
275
|
search_window: { start_date: args.start_date, end_date: args.end_date },
|
|
224
276
|
server_version: SERVER_VERSION,
|
|
225
277
|
};
|
|
226
|
-
const summary = buildTransitSummary(natalPositionMap,
|
|
278
|
+
const summary = buildTransitSummary(natalPositionMap, transitArray, aspectAngle, args.start_date, args.end_date);
|
|
227
279
|
const bundleAvailable = Boolean(getTransitTimelineBundle());
|
|
228
280
|
if (bundleAvailable) {
|
|
229
281
|
return {
|
|
@@ -14,6 +14,7 @@ import path from "node:path";
|
|
|
14
14
|
import { fileURLToPath } from "node:url";
|
|
15
15
|
import { registerTool, SERVER_VERSION } from "../index.js";
|
|
16
16
|
import { getActiveClient } from "../../backend/client.js";
|
|
17
|
+
import { OUTPUT_SCHEMA_JSON } from "../output-schemas.js";
|
|
17
18
|
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
18
19
|
export const VEDIC_CHART_RESOURCE_URI = "ui://openephemeris/vedic-chart";
|
|
19
20
|
export const VEDIC_CHART_MIME_TYPE = "text/html;profile=mcp-app";
|
|
@@ -149,6 +150,7 @@ registerTool({
|
|
|
149
150
|
required: ["datetime", "latitude", "longitude"],
|
|
150
151
|
additionalProperties: false,
|
|
151
152
|
},
|
|
153
|
+
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
152
154
|
annotations: {
|
|
153
155
|
title: "Interactive Vedic Jyotish Chart",
|
|
154
156
|
readOnlyHint: true,
|
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";
|
|
@@ -31,66 +32,72 @@ function isDeniedByPrefix(pathname, prefixes) {
|
|
|
31
32
|
function isAllowedOperation(method, pathname, allow) {
|
|
32
33
|
return allow.some((e) => e.method === method && e.path === pathname);
|
|
33
34
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
35
|
+
// Shared reference block appended to both read/write proxy tools. Names the
|
|
36
|
+
// target API (Open Ephemeris) explicitly — required by the directory for
|
|
37
|
+
// freeform-path tools — plus credit costs and common calls.
|
|
38
|
+
const DEV_API_REFERENCE = "Target API: Open Ephemeris REST API (https://api.openephemeris.com). " +
|
|
39
|
+
"Call dev_list_allowed to see all currently available endpoint paths.\n\n" +
|
|
40
|
+
"AUTH: Set OPENEPHEMERIS_API_KEY in your environment. See openephemeris.com/dashboard for active plan limits.\n\n" +
|
|
41
|
+
"CREDIT COSTS:\n" +
|
|
42
|
+
" • Standard chart math (natal, progressed, bazi, vedic, iching): 1 credit\n" +
|
|
43
|
+
" • Human Design: 2 credits\n" +
|
|
44
|
+
" • Visualization rendering (chart-wheel, bi-wheel, charts/*): 2 credits\n" +
|
|
45
|
+
" • Comparative math (synastry, composite, overlay): 3 credits\n" +
|
|
46
|
+
" • Predictive ops (transits/search, returns): 5 credits\n" +
|
|
47
|
+
" • Predictive transit-chart: 1 credit\n" +
|
|
48
|
+
" • ACG / astrocartography: 10 credits (acg/hits: 15 credits)\n" +
|
|
49
|
+
" • Calendar endpoints: 10 credits\n" +
|
|
50
|
+
" • Catalog / metadata / health endpoints: 0 credits\n" +
|
|
51
|
+
" • Compute surcharge: requests > 30s add 1 credit per 30s (predictive, acg, calendar, electional)\n" +
|
|
52
|
+
" • format=llm (token-optimized output): available on all tiers\n\n" +
|
|
53
|
+
"COMMON CALLS:\n" +
|
|
54
|
+
" POST /ephemeris/natal-chart — Full natal chart (body: {subject: {name: 'Name', birth_datetime: {iso: '1990-04-15T14:30:00-05:00'}, birth_location: {latitude: {decimal: 40.0}, longitude: {decimal: -70.0}, timezone: {}}}})\n" +
|
|
55
|
+
" POST /ephemeris/natal/batch — Up to 50 natal charts in one request\n" +
|
|
56
|
+
" POST /ephemeris/relocation — Relocated chart (same natal, new location)\n" +
|
|
57
|
+
" POST /predictive/transits/search — Transit event search over a date range\n" +
|
|
58
|
+
" POST /predictive/returns/solar — Solar return chart\n" +
|
|
59
|
+
" POST /predictive/returns/lunar — Lunar return chart\n" +
|
|
60
|
+
" POST /comparative/synastry — Two-person synastry chart\n" +
|
|
61
|
+
" POST /comparative/composite — Composite (midpoint) chart\n" +
|
|
62
|
+
" POST /human-design/chart — Full HD chart (body: {birth_datetime_utc: '1990-04-15T19:30:00Z'}) — lat/lon optional\n" +
|
|
63
|
+
" POST /time/julian-day — Convert date to JD (body: {year: 1987, month: 7, day: 15, hour: 14, minute: 1})\n" +
|
|
64
|
+
" GET /ephemeris/moon/phase — Current/queried moon phase\n" +
|
|
65
|
+
" GET /ephemeris/moon/void-of-course — Next void-of-course period\n" +
|
|
66
|
+
" GET /ephemeris/agro/daily — Biodynamic farming day quality\n" +
|
|
67
|
+
" GET /ephemeris/agro/calendar — Multi-day biodynamic calendar\n" +
|
|
68
|
+
" GET /ephemeris/agro/void-of-course — Biodynamic VoC periods\n" +
|
|
69
|
+
" GET /eclipse/next-visible — Next eclipse visible from a location (query: lat, lon, type=solar|lunar)\n" +
|
|
70
|
+
" GET /eclipse/solar/global — Next global solar eclipse (query: date=YYYY-MM-DD)\n" +
|
|
71
|
+
" GET /eclipse/solar/local — Local solar eclipse (query: lat, lon)\n" +
|
|
72
|
+
" GET /tidal/forcing — Gravitational tidal forcing index\n" +
|
|
73
|
+
" POST /acg/power-lines — Astrocartography power lines (lat/lon GeoJSON)\n" +
|
|
74
|
+
" POST /acg/hits — ACG power at a specific location\n" +
|
|
75
|
+
" GET /calendar/astrology/moon-phases — Moon phase calendar for a date range\n" +
|
|
76
|
+
" GET /location/autocomplete — Geocode a place name (query: q=City Name)\n" +
|
|
77
|
+
" POST /timezone/lookup — Resolve timezone + UTC offset for a location\n" +
|
|
78
|
+
" POST /chinese/bazi — Chinese Ba Zi (Four Pillars) chart (body: {year, month, day, hour})\n" +
|
|
79
|
+
" GET /chinese/zodiac — Chinese zodiac year element/animal\n" +
|
|
80
|
+
" POST /vedic/chart — Vedic (Jyotish) natal chart (body: {datetime_utc, latitude, longitude})\n" +
|
|
81
|
+
" GET /catalogs/bodies — List all supported celestial bodies\n\n" +
|
|
82
|
+
"BINARY RESPONSES:\n" +
|
|
83
|
+
" • Binary/image endpoints return {content_type, content_length, encoding, data_base64}\n" +
|
|
84
|
+
" so callers can decode bytes deterministically.\n\n" +
|
|
85
|
+
"ECLIPSE NOTE: Eclipse endpoints accept format=llm via the query param like other endpoints.\n\n" +
|
|
86
|
+
"format=llm NOTE: Add query: {format: 'llm'} to natal/synastry/composite/HD endpoints for " +
|
|
87
|
+
"compact columnar output optimized for LLM token budgets (availability depends on your current plan).";
|
|
88
|
+
// The read and write proxies are kept as separate tools (not one method-switching
|
|
89
|
+
// tool) so that safe GET reads never share a surface with state-changing writes —
|
|
90
|
+
// a hard requirement of the Anthropic connector directory.
|
|
91
|
+
const READ_METHODS = ["GET"];
|
|
92
|
+
const WRITE_METHODS = ["POST", "PUT", "PATCH", "DELETE"];
|
|
93
|
+
function makeProxyInputSchema(methods) {
|
|
94
|
+
return {
|
|
88
95
|
type: "object",
|
|
89
96
|
properties: {
|
|
90
97
|
method: {
|
|
91
98
|
type: "string",
|
|
92
|
-
enum:
|
|
93
|
-
description: "HTTP method",
|
|
99
|
+
enum: methods,
|
|
100
|
+
description: methods.length === 1 ? `HTTP method (always ${methods[0]})` : "HTTP method",
|
|
94
101
|
},
|
|
95
102
|
path: {
|
|
96
103
|
type: "string",
|
|
@@ -99,10 +106,10 @@ registerTool({
|
|
|
99
106
|
query: {
|
|
100
107
|
type: "object",
|
|
101
108
|
additionalProperties: true,
|
|
102
|
-
description: "Query params
|
|
109
|
+
description: "Query params (optional)",
|
|
103
110
|
},
|
|
104
111
|
body: {
|
|
105
|
-
description: "JSON body
|
|
112
|
+
description: "JSON request body (optional)",
|
|
106
113
|
},
|
|
107
114
|
preset: {
|
|
108
115
|
type: "string",
|
|
@@ -120,14 +127,16 @@ registerTool({
|
|
|
120
127
|
description: "Legacy convenience (deprecated): if provided, set query.output_mode and also map to query.preset/query.format when possible.",
|
|
121
128
|
},
|
|
122
129
|
},
|
|
123
|
-
required: ["
|
|
130
|
+
required: ["path"],
|
|
124
131
|
additionalProperties: false,
|
|
125
|
-
}
|
|
126
|
-
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function makeProxyHandler(allowedMethods, defaultMethod) {
|
|
135
|
+
return async (args) => {
|
|
127
136
|
validateRequired(args, ["path"]);
|
|
128
|
-
const methodStr = String(args.method ||
|
|
129
|
-
if (!
|
|
130
|
-
throw new Error(`
|
|
137
|
+
const methodStr = String(args.method || defaultMethod).toUpperCase();
|
|
138
|
+
if (!allowedMethods.includes(methodStr)) {
|
|
139
|
+
throw new Error(`Method ${methodStr} not permitted by this tool. Allowed: ${allowedMethods.join(", ")}.`);
|
|
131
140
|
}
|
|
132
141
|
const method = methodStr;
|
|
133
142
|
const pathname = String(args.path);
|
|
@@ -175,20 +184,48 @@ registerTool({
|
|
|
175
184
|
params: query,
|
|
176
185
|
data: body,
|
|
177
186
|
});
|
|
178
|
-
}
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
registerTool({
|
|
190
|
+
name: "dev_read_api",
|
|
191
|
+
description: "Read from any allowlisted Open Ephemeris API endpoint via HTTP GET. This is the read-only " +
|
|
192
|
+
"power-user escape hatch — use the typed tools (ephemeris_natal_chart, ephemeris_transits, etc.) " +
|
|
193
|
+
"first for common operations. For endpoints that compute via POST (natal-chart, synastry, etc.), " +
|
|
194
|
+
"use dev_write_api.\n\n" +
|
|
195
|
+
DEV_API_REFERENCE,
|
|
196
|
+
inputSchema: makeProxyInputSchema(READ_METHODS),
|
|
197
|
+
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
198
|
+
// GET-only: never mutates server state.
|
|
199
|
+
annotations: { title: "API Read", readOnlyHint: true, openWorldHint: true },
|
|
200
|
+
handler: makeProxyHandler(READ_METHODS, "GET"),
|
|
201
|
+
});
|
|
202
|
+
registerTool({
|
|
203
|
+
name: "dev_write_api",
|
|
204
|
+
description: "Send a request to any allowlisted Open Ephemeris API endpoint via POST/PUT/PATCH/DELETE. " +
|
|
205
|
+
"Most chart computations (natal-chart, synastry, composite, transits/search, returns, ACG) are " +
|
|
206
|
+
"POST endpoints and use this tool. Use the typed tools first for common operations; use dev_read_api " +
|
|
207
|
+
"for GET endpoints.\n\n" +
|
|
208
|
+
DEV_API_REFERENCE,
|
|
209
|
+
inputSchema: makeProxyInputSchema(WRITE_METHODS),
|
|
210
|
+
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
211
|
+
// Issues POST/PUT/PATCH/DELETE — must not be advertised as read-only.
|
|
212
|
+
annotations: { title: "API Write", readOnlyHint: false, openWorldHint: true },
|
|
213
|
+
handler: makeProxyHandler(WRITE_METHODS, "POST"),
|
|
179
214
|
});
|
|
180
215
|
registerTool({
|
|
181
216
|
name: "dev_list_allowed",
|
|
182
217
|
description: "List all API operations (method + path) that this MCP instance is authorized to call. " +
|
|
183
218
|
"Returns endpoint entries grouped by method, plus the active deny rules. " +
|
|
184
|
-
"Use this to discover what's available before calling
|
|
219
|
+
"Use this to discover what's available before calling dev_read_api / dev_write_api, or to verify an endpoint path. " +
|
|
185
220
|
"Typed shortcut tools (ephemeris_natal_chart, ephemeris_transits, etc.) cover the most common operations — " +
|
|
186
|
-
"check those first before reaching for
|
|
221
|
+
"check those first before reaching for the generic proxies.",
|
|
187
222
|
inputSchema: {
|
|
188
223
|
type: "object",
|
|
189
224
|
properties: {},
|
|
190
225
|
additionalProperties: false,
|
|
191
226
|
},
|
|
227
|
+
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
228
|
+
annotations: { title: "List Allowed API Operations", readOnlyHint: true, openWorldHint: false },
|
|
192
229
|
handler: async () => {
|
|
193
230
|
const allowlist = loadAllowlist();
|
|
194
231
|
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;
|
|
@@ -46,6 +52,16 @@ export declare function initTools(profile?: ToolProfile): Promise<void>;
|
|
|
46
52
|
* Throws an error if any of the required keys are missing or empty.
|
|
47
53
|
*/
|
|
48
54
|
export declare function validateRequired(args: any, requiredKeys: string[]): void;
|
|
55
|
+
/**
|
|
56
|
+
* Returns `value` only if it is one of `allowed`, otherwise `undefined`.
|
|
57
|
+
*
|
|
58
|
+
* Use for any tool argument that gets interpolated into a backend URL/query
|
|
59
|
+
* string. Even though the inputSchema declares an `enum`, the MCP host does
|
|
60
|
+
* not guarantee enum enforcement, so a hostile client could smuggle extra
|
|
61
|
+
* query parameters (e.g. style="dark&admin=1"). Whitelisting here closes that
|
|
62
|
+
* injection vector at the boundary (DATA-6).
|
|
63
|
+
*/
|
|
64
|
+
export declare function pickEnum<T extends string>(value: unknown, allowed: readonly T[]): T | undefined;
|
|
49
65
|
/**
|
|
50
66
|
* Throws an error if only one of the coordinate pair is provided.
|
|
51
67
|
*/
|
package/dist/tools/index.js
CHANGED
|
@@ -44,10 +44,14 @@ export async function initTools(profile) {
|
|
|
44
44
|
const resolvedProfile = (profile || process.env.OPENEPHEMERIS_PROFILE || process.env.ASTROMCP_PROFILE || "dev").toLowerCase();
|
|
45
45
|
// Always register auth tools (available in all profiles).
|
|
46
46
|
await import("./auth.js");
|
|
47
|
-
// Always register the generic proxy tools (
|
|
47
|
+
// Always register the generic proxy tools (dev_read_api + dev_write_api + 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,15 +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");
|
|
78
82
|
await import("./apps/moon-phase-app.js");
|
|
79
|
-
await import("./apps/bazi-app.js");
|
|
80
|
-
await import("./apps/vedic-chart-app.js");
|
|
81
|
-
await import("./apps/location-tools.js");
|
|
82
83
|
}
|
|
83
84
|
}
|
|
84
85
|
/**
|
|
@@ -92,6 +93,20 @@ export function validateRequired(args, requiredKeys) {
|
|
|
92
93
|
throw new Error(`Missing required arguments: ${missing.join(", ")}`);
|
|
93
94
|
}
|
|
94
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Returns `value` only if it is one of `allowed`, otherwise `undefined`.
|
|
98
|
+
*
|
|
99
|
+
* Use for any tool argument that gets interpolated into a backend URL/query
|
|
100
|
+
* string. Even though the inputSchema declares an `enum`, the MCP host does
|
|
101
|
+
* not guarantee enum enforcement, so a hostile client could smuggle extra
|
|
102
|
+
* query parameters (e.g. style="dark&admin=1"). Whitelisting here closes that
|
|
103
|
+
* injection vector at the boundary (DATA-6).
|
|
104
|
+
*/
|
|
105
|
+
export function pickEnum(value, allowed) {
|
|
106
|
+
return typeof value === "string" && allowed.includes(value)
|
|
107
|
+
? value
|
|
108
|
+
: undefined;
|
|
109
|
+
}
|
|
95
110
|
/**
|
|
96
111
|
* Throws an error if only one of the coordinate pair is provided.
|
|
97
112
|
*/
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* output-schemas.ts — Shared JSON Schema output schema definitions for MCP tools.
|
|
3
|
+
*
|
|
4
|
+
* MCP spec 2025-11-25 supports `outputSchema` on tool definitions. Smithery
|
|
5
|
+
* uses this field to score the "Output schemas" quality criterion (10.37pt / 57 tools).
|
|
6
|
+
*
|
|
7
|
+
* All tools return an MCP content block response. Most return a single JSON text
|
|
8
|
+
* block; visual tools return an image block or an image + text block.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* import { OUTPUT_SCHEMA_JSON, OUTPUT_SCHEMA_IMAGE } from "../output-schemas.js";
|
|
12
|
+
* registerTool({ ..., outputSchema: OUTPUT_SCHEMA_JSON });
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Standard tool output schema: a single JSON text block.
|
|
16
|
+
* Used by all data-returning tools (natal chart, transits, synastry, etc.).
|
|
17
|
+
*/
|
|
18
|
+
export declare const OUTPUT_SCHEMA_JSON: Record<string, unknown>;
|
|
19
|
+
/**
|
|
20
|
+
* Visual tool output schema: a single SVG or PNG image block.
|
|
21
|
+
* Used by chart wheel (direct image), BaZi chart, Vedic chart, etc.
|
|
22
|
+
*/
|
|
23
|
+
export declare const OUTPUT_SCHEMA_IMAGE: Record<string, unknown>;
|
|
24
|
+
/**
|
|
25
|
+
* Dual output schema: an image block followed by a JSON text block.
|
|
26
|
+
* Used by tools with include_visual=true (natal chart SVG + data, bi-wheel, etc.).
|
|
27
|
+
*/
|
|
28
|
+
export declare const OUTPUT_SCHEMA_IMAGE_AND_JSON: Record<string, unknown>;
|
|
29
|
+
/**
|
|
30
|
+
* Interactive app tool output schema: a text block containing MCP App metadata.
|
|
31
|
+
* Used by explore_* tools that return an embedded UI resource reference.
|
|
32
|
+
*/
|
|
33
|
+
export declare const OUTPUT_SCHEMA_APP: Record<string, unknown>;
|
|
34
|
+
/**
|
|
35
|
+
* Auth tool output schema: a JSON text block confirming auth status or credentials.
|
|
36
|
+
*/
|
|
37
|
+
export declare const OUTPUT_SCHEMA_AUTH: Record<string, unknown>;
|