@openephemeris/mcp-server 4.16.0 → 4.18.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 +46 -0
- package/README.md +15 -15
- package/config/dev-allowlist.json +31 -19
- package/dist/backend/client.d.ts +57 -1
- package/dist/backend/client.js +125 -15
- package/dist/prompts.js +26 -24
- package/dist/server-sse.d.ts +18 -0
- package/dist/server-sse.js +41 -6
- package/dist/tools/apps/_render-token.d.ts +34 -0
- package/dist/tools/apps/_render-token.js +50 -0
- package/dist/tools/apps/bazi-app.js +36 -12
- package/dist/tools/apps/bi-wheel-app.d.ts +9 -2
- package/dist/tools/apps/bi-wheel-app.js +76 -117
- package/dist/tools/apps/bodygraph-app.js +88 -41
- package/dist/tools/apps/chart-wheel-app.js +56 -16
- package/dist/tools/apps/location-tools.js +10 -3
- package/dist/tools/apps/moon-phase-app.js +10 -2
- package/dist/tools/apps/transit-timeline-app.d.ts +3 -0
- package/dist/tools/apps/transit-timeline-app.js +77 -9
- package/dist/tools/apps/vedic-chart-app.js +10 -1
- package/dist/tools/datetime-historical.js +7 -2
- package/dist/tools/datetime.js +2 -1
- package/dist/tools/dev.js +7 -6
- package/dist/tools/specialized/electional.js +4 -3
- package/dist/tools/specialized/ephemeris_extended.js +19 -4
- package/dist/tools/specialized/hd_group.js +2 -2
- package/dist/tools/specialized/moon.d.ts +1 -1
- package/dist/tools/specialized/moon.js +51 -43
- package/dist/tools/specialized/progressed.js +2 -25
- package/dist/tools/specialized/transits.js +5 -5
- package/dist/ui/bazi.html +1523 -1522
- package/dist/ui/bi-wheel.html +406 -374
- package/dist/ui/bodygraph.html +83 -79
- package/dist/ui/chart-wheel.html +394 -361
- package/dist/ui/transit-timeline.html +3 -3
- package/dist/ui/vedic-chart.html +818 -818
- package/package.json +1 -1
package/dist/server-sse.d.ts
CHANGED
|
@@ -1,4 +1,22 @@
|
|
|
1
1
|
import express from "express";
|
|
2
|
+
/**
|
|
3
|
+
* The hosted server is multi-tenant: every tool call must be authenticated
|
|
4
|
+
* and metered as the connecting user. A credential in the process environment
|
|
5
|
+
* breaks that — the client interceptor sends X-Service-Key ahead of the user's
|
|
6
|
+
* key (every user unmetered), and an env API key / JWT would bill every
|
|
7
|
+
* session to one account.
|
|
8
|
+
*
|
|
9
|
+
* - A service key in the environment is a hard startup failure: there is no
|
|
10
|
+
* safe way to run with it present.
|
|
11
|
+
* - A user API key / JWT in the environment is logged loudly and ignored.
|
|
12
|
+
* - The module singleton (built from the environment at import, and what
|
|
13
|
+
* getActiveClient() falls back to outside a session context) is stripped of
|
|
14
|
+
* every credential either way; per-session clients are built with
|
|
15
|
+
* `inheritEnvCredentials: false`.
|
|
16
|
+
*
|
|
17
|
+
* Exported for tests. stdio mode never calls this.
|
|
18
|
+
*/
|
|
19
|
+
export declare function enforceHostedCredentialPolicy(env?: NodeJS.ProcessEnv): void;
|
|
2
20
|
/**
|
|
3
21
|
* Build the full Express app (auth gate, OAuth routes, /mcp Streamable HTTP,
|
|
4
22
|
* health, resources). Exported so tests can drive the REAL handlers with
|
package/dist/server-sse.js
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
* Architecture:
|
|
9
9
|
* - The SSE server validates the user's API key at connection time by
|
|
10
10
|
* making a lightweight call to the Go backend.
|
|
11
|
-
* -
|
|
11
|
+
* - Each session gets its own BackendClient built from the validated key
|
|
12
|
+
* (never from env credentials — see enforceHostedCredentialPolicy), so
|
|
12
13
|
* all subsequent tool calls authenticate as the connecting user.
|
|
13
14
|
* - Usage credits are metered against the user's account and tier.
|
|
14
15
|
*/
|
|
@@ -26,7 +27,7 @@ import { CallToolRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema
|
|
|
26
27
|
import { initTools, toolRegistry, formatToolResponse, formatToolError, modelVisibleTools, parseToolSurface, describeSurface } from "./tools/index.js";
|
|
27
28
|
import { buildUiMeta } from "./tools/ui-meta.js";
|
|
28
29
|
import { SERVER_INSTRUCTIONS } from "./instructions.js";
|
|
29
|
-
import { BackendClient, runWithClient } from "./backend/client.js";
|
|
30
|
+
import { BackendClient, backendClient, presentCredentialEnvVars, runWithClient } from "./backend/client.js";
|
|
30
31
|
import { CHART_WHEEL_RESOURCE_URI, CHART_WHEEL_MIME_TYPE, getChartWheelBundle, } from "./tools/apps/chart-wheel-app.js";
|
|
31
32
|
import { BODYGRAPH_RESOURCE_URI, BODYGRAPH_MIME_TYPE, getBodygraphBundle, } from "./tools/apps/bodygraph-app.js";
|
|
32
33
|
import { BI_WHEEL_RESOURCE_URI, BI_WHEEL_MIME_TYPE, getBiWheelBundle, } from "./tools/apps/bi-wheel-app.js";
|
|
@@ -557,12 +558,43 @@ function startSseKeepalive(res) {
|
|
|
557
558
|
}, 25_000);
|
|
558
559
|
res.on("close", () => clearInterval(ping));
|
|
559
560
|
}
|
|
561
|
+
/**
|
|
562
|
+
* The hosted server is multi-tenant: every tool call must be authenticated
|
|
563
|
+
* and metered as the connecting user. A credential in the process environment
|
|
564
|
+
* breaks that — the client interceptor sends X-Service-Key ahead of the user's
|
|
565
|
+
* key (every user unmetered), and an env API key / JWT would bill every
|
|
566
|
+
* session to one account.
|
|
567
|
+
*
|
|
568
|
+
* - A service key in the environment is a hard startup failure: there is no
|
|
569
|
+
* safe way to run with it present.
|
|
570
|
+
* - A user API key / JWT in the environment is logged loudly and ignored.
|
|
571
|
+
* - The module singleton (built from the environment at import, and what
|
|
572
|
+
* getActiveClient() falls back to outside a session context) is stripped of
|
|
573
|
+
* every credential either way; per-session clients are built with
|
|
574
|
+
* `inheritEnvCredentials: false`.
|
|
575
|
+
*
|
|
576
|
+
* Exported for tests. stdio mode never calls this.
|
|
577
|
+
*/
|
|
578
|
+
export function enforceHostedCredentialPolicy(env = process.env) {
|
|
579
|
+
const { serviceKeys, userCredentials } = presentCredentialEnvVars(env);
|
|
580
|
+
if (serviceKeys.length > 0) {
|
|
581
|
+
throw new Error(`Refusing to start the hosted MCP HTTP server: ${serviceKeys.join(", ")} is set. ` +
|
|
582
|
+
`A service key bypasses per-user metering for every session. Unset it — the ` +
|
|
583
|
+
`hosted server authenticates each session with the user's own API key or OAuth token.`);
|
|
584
|
+
}
|
|
585
|
+
if (userCredentials.length > 0) {
|
|
586
|
+
console.error(`[SECURITY] ${userCredentials.join(", ")} is set in the hosted MCP server environment. ` +
|
|
587
|
+
`IGNORING it — sessions authenticate only with their own credential. Remove it from the deployment.`);
|
|
588
|
+
}
|
|
589
|
+
backendClient.clearCredentials();
|
|
590
|
+
}
|
|
560
591
|
/**
|
|
561
592
|
* Build the full Express app (auth gate, OAuth routes, /mcp Streamable HTTP,
|
|
562
593
|
* health, resources). Exported so tests can drive the REAL handlers with
|
|
563
594
|
* supertest instead of a re-implemented stub. Does not listen.
|
|
564
595
|
*/
|
|
565
596
|
export async function createSseApp() {
|
|
597
|
+
enforceHostedCredentialPolicy();
|
|
566
598
|
await initTools();
|
|
567
599
|
const app = express();
|
|
568
600
|
// NOTE: express.json() is applied per-route (jsonParser on /mcp POST), not
|
|
@@ -700,18 +732,19 @@ export async function createSseApp() {
|
|
|
700
732
|
serverInfo: {
|
|
701
733
|
name: "Open Ephemeris",
|
|
702
734
|
version,
|
|
703
|
-
description:
|
|
735
|
+
description: `NASA JPL DE440-backed astronomical computation engine for AI agents. ${tools.length} typed tools covering ` +
|
|
704
736
|
"natal charts, transit forecasting, Human Design bodygraphs, eclipses, astrocartography " +
|
|
705
737
|
"power lines, Venus Star Points, electional timing, synastry, composite charts, Vedic/Jyotish, " +
|
|
706
738
|
"Chinese BaZi, and more — powered by JPL DE440 ephemerides for sub-arcsecond " +
|
|
707
|
-
"zero-hallucination accuracy. Free Explorer tier
|
|
739
|
+
"zero-hallucination accuracy. Free Explorer tier: 150 one-time credits.",
|
|
708
740
|
iconUrl: "https://mcp.openephemeris.com/icon.png",
|
|
709
741
|
homepage: "https://openephemeris.com",
|
|
710
742
|
},
|
|
711
743
|
authentication: {
|
|
712
744
|
required: true,
|
|
713
745
|
schemes: ["apiKey"],
|
|
714
|
-
instructions: "
|
|
746
|
+
instructions: "Sign in with OAuth 2.1 (PKCE, dynamic client registration), or pass your Open Ephemeris API key " +
|
|
747
|
+
"via the X-API-Key header (or Authorization: Bearer opene-…). Every call is metered to that user. " +
|
|
715
748
|
"Get a free Explorer key at https://openephemeris.com/dashboard — no credit card required.",
|
|
716
749
|
},
|
|
717
750
|
tools,
|
|
@@ -871,7 +904,9 @@ export async function createSseApp() {
|
|
|
871
904
|
console.error(`[HTTP] Session initialized: ${id}`);
|
|
872
905
|
},
|
|
873
906
|
});
|
|
874
|
-
|
|
907
|
+
// inheritEnvCredentials:false — this client must authenticate as THIS
|
|
908
|
+
// session's user only, never as an env service key / API key / JWT.
|
|
909
|
+
const client = new BackendClient({ baseURL: BACKEND_URL, apiKey, jwt, inheritEnvCredentials: false });
|
|
875
910
|
const analyticsId = distinctIdFor(apiKey ?? jwt);
|
|
876
911
|
// Tool surface is fixed for the life of the session: we do not declare
|
|
877
912
|
// `tools.listChanged`, so a host has no obligation to re-fetch the list.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* _render-token.ts — plumbing for the API's server-issued render token.
|
|
3
|
+
*
|
|
4
|
+
* The Go API stamps every charged visual on a render-family route
|
|
5
|
+
* (/human-design/chart + /visualization/bodygraph, /human-design/transit-chart,
|
|
6
|
+
* /human-design/composite, /chinese/bazi, /vedic/chart) with a short-lived
|
|
7
|
+
* token bound to the account and the chart-defining inputs
|
|
8
|
+
* (auth/render_token.go). Presenting it on a re-render of the SAME chart —
|
|
9
|
+
* the iframe's host light/dark reconciliation, a layout flip — makes that
|
|
10
|
+
* re-render free (base charge and visual surcharge). Anything else is charged
|
|
11
|
+
* normally, so passing a stale or foreign token is harmless.
|
|
12
|
+
*
|
|
13
|
+
* Flow: explore_* puts the token on the payload as `_render_token`; the iframe
|
|
14
|
+
* hands it back as the `render_token` argument of its recalc / refetch call;
|
|
15
|
+
* the tool sends it in the X-OE-Render-Token header.
|
|
16
|
+
*/
|
|
17
|
+
export declare const RENDER_TOKEN_HEADER = "X-OE-Render-Token";
|
|
18
|
+
/** Input-schema property for the app-only recalc args. Never set by the model. */
|
|
19
|
+
export declare const RENDER_TOKEN_PROPERTY: {
|
|
20
|
+
readonly type: "string";
|
|
21
|
+
readonly description: string;
|
|
22
|
+
};
|
|
23
|
+
/** Request headers presenting `token`, or undefined when there is none. */
|
|
24
|
+
export declare function renderTokenHeaders(token: unknown): Record<string, string> | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* The token the API injected on the root <svg> of a /visualization/bodygraph
|
|
27
|
+
* response (a binary route, whose response headers the backend client does
|
|
28
|
+
* not surface).
|
|
29
|
+
*/
|
|
30
|
+
export declare function renderTokenFromSvg(svg: string | null | undefined): string | undefined;
|
|
31
|
+
/** `visual.render_token` from an include_visual JSON response. */
|
|
32
|
+
export declare function renderTokenFromVisual(visual: unknown): string | undefined;
|
|
33
|
+
/** Copy of tool args with the per-call token removed (for `_refetch.args`). */
|
|
34
|
+
export declare function withoutRenderToken<T extends Record<string, unknown>>(args: T): T;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* _render-token.ts — plumbing for the API's server-issued render token.
|
|
3
|
+
*
|
|
4
|
+
* The Go API stamps every charged visual on a render-family route
|
|
5
|
+
* (/human-design/chart + /visualization/bodygraph, /human-design/transit-chart,
|
|
6
|
+
* /human-design/composite, /chinese/bazi, /vedic/chart) with a short-lived
|
|
7
|
+
* token bound to the account and the chart-defining inputs
|
|
8
|
+
* (auth/render_token.go). Presenting it on a re-render of the SAME chart —
|
|
9
|
+
* the iframe's host light/dark reconciliation, a layout flip — makes that
|
|
10
|
+
* re-render free (base charge and visual surcharge). Anything else is charged
|
|
11
|
+
* normally, so passing a stale or foreign token is harmless.
|
|
12
|
+
*
|
|
13
|
+
* Flow: explore_* puts the token on the payload as `_render_token`; the iframe
|
|
14
|
+
* hands it back as the `render_token` argument of its recalc / refetch call;
|
|
15
|
+
* the tool sends it in the X-OE-Render-Token header.
|
|
16
|
+
*/
|
|
17
|
+
export const RENDER_TOKEN_HEADER = "X-OE-Render-Token";
|
|
18
|
+
/** Input-schema property for the app-only recalc args. Never set by the model. */
|
|
19
|
+
export const RENDER_TOKEN_PROPERTY = {
|
|
20
|
+
type: "string",
|
|
21
|
+
description: "Set automatically by the embedded app when it re-renders a chart it already paid for " +
|
|
22
|
+
"(e.g. to match the host's light/dark theme). Never pass this yourself.",
|
|
23
|
+
};
|
|
24
|
+
/** Request headers presenting `token`, or undefined when there is none. */
|
|
25
|
+
export function renderTokenHeaders(token) {
|
|
26
|
+
return typeof token === "string" && token.trim() !== ""
|
|
27
|
+
? { [RENDER_TOKEN_HEADER]: token.trim() }
|
|
28
|
+
: undefined;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The token the API injected on the root <svg> of a /visualization/bodygraph
|
|
32
|
+
* response (a binary route, whose response headers the backend client does
|
|
33
|
+
* not surface).
|
|
34
|
+
*/
|
|
35
|
+
export function renderTokenFromSvg(svg) {
|
|
36
|
+
if (!svg)
|
|
37
|
+
return undefined;
|
|
38
|
+
const m = /<svg\b[^>]*\sdata-oe-render-token="([A-Za-z0-9._-]+)"/.exec(svg);
|
|
39
|
+
return m ? m[1] : undefined;
|
|
40
|
+
}
|
|
41
|
+
/** `visual.render_token` from an include_visual JSON response. */
|
|
42
|
+
export function renderTokenFromVisual(visual) {
|
|
43
|
+
const t = visual?.render_token;
|
|
44
|
+
return typeof t === "string" && t !== "" ? t : undefined;
|
|
45
|
+
}
|
|
46
|
+
/** Copy of tool args with the per-call token removed (for `_refetch.args`). */
|
|
47
|
+
export function withoutRenderToken(args) {
|
|
48
|
+
const { render_token: _drop, ...rest } = args;
|
|
49
|
+
return rest;
|
|
50
|
+
}
|
|
@@ -26,6 +26,7 @@ import { OUTPUT_SCHEMA_JSON } from "../output-schemas.js";
|
|
|
26
26
|
// this replaces used `new Date(naive)`, which resolves in the host process's
|
|
27
27
|
// timezone and silently shifted the hour pillar on any non-UTC server.
|
|
28
28
|
import { parseBaziArgs, buildBaziConventionFields, CONVENTION_PROPERTIES } from "../specialized/bazi.js";
|
|
29
|
+
import { RENDER_TOKEN_PROPERTY, renderTokenFromVisual, renderTokenHeaders } from "./_render-token.js";
|
|
29
30
|
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
30
31
|
export const BAZI_RESOURCE_URI = "ui://openephemeris/bazi";
|
|
31
32
|
export const BAZI_MIME_TYPE = "text/html;profile=mcp-app";
|
|
@@ -60,26 +61,39 @@ export function getBaziBundle() {
|
|
|
60
61
|
export function clearBaziBundleCache() {
|
|
61
62
|
cachedBundle = null;
|
|
62
63
|
}
|
|
64
|
+
const CONVENTION_ARG_KEYS = ["year_boundary", "day_boundary", "true_solar_time", "latitude", "longitude", "timezone"];
|
|
65
|
+
function pickConventionArgs(args) {
|
|
66
|
+
const out = {};
|
|
67
|
+
for (const k of CONVENTION_ARG_KEYS) {
|
|
68
|
+
if (args?.[k] != null && args[k] !== "")
|
|
69
|
+
out[k] = args[k];
|
|
70
|
+
}
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
63
73
|
/**
|
|
64
74
|
* Fetch the BaZi chart via the include_visual intercept — one call returns
|
|
65
75
|
* both the structured pillar data (year/month/day/hour/day_master) and the
|
|
66
76
|
* Go-rendered SVG (bazi.RenderBaziChartSVG), same shape the strict
|
|
67
77
|
* /chinese/bazi handler returns with a `visual` key attached.
|
|
68
78
|
*/
|
|
69
|
-
async function fetchBaziChart(components, theme = "dark", conventionFields = {},
|
|
79
|
+
async function fetchBaziChart(components, theme = "dark", conventionFields = {}, renderToken) {
|
|
70
80
|
const client = getActiveClient();
|
|
71
81
|
const body = {
|
|
72
82
|
...components,
|
|
73
83
|
...conventionFields,
|
|
74
84
|
include_visual: true,
|
|
75
85
|
visual_config: { format: "svg", theme, size: 800 },
|
|
76
|
-
// Set only by bazi_recalculate's theme-reconcile path below — waives the
|
|
77
|
-
// visual surcharge server-side for a same-birth-data re-render (#551).
|
|
78
|
-
...(reconcile ? { _visual_reconcile: true } : {}),
|
|
79
86
|
};
|
|
80
|
-
|
|
87
|
+
// bazi_recalculate's theme reconcile presents the token the first render
|
|
88
|
+
// was issued; the API waives the whole re-render only when it verifies for
|
|
89
|
+
// this account and these exact inputs (the old self-serve
|
|
90
|
+
// `_visual_reconcile` flag is gone).
|
|
91
|
+
return await client.request("POST", "/chinese/bazi", {
|
|
92
|
+
data: body,
|
|
93
|
+
headers: renderTokenHeaders(renderToken),
|
|
94
|
+
});
|
|
81
95
|
}
|
|
82
|
-
function buildModelPayload(data, components, theme) {
|
|
96
|
+
function buildModelPayload(data, components, theme, conventionArgs = {}) {
|
|
83
97
|
return {
|
|
84
98
|
year: data.year,
|
|
85
99
|
month: data.month,
|
|
@@ -88,7 +102,9 @@ function buildModelPayload(data, components, theme) {
|
|
|
88
102
|
day_master: data.day_master,
|
|
89
103
|
_svg: data.visual?.data,
|
|
90
104
|
_birth_params: components,
|
|
105
|
+
...(Object.keys(conventionArgs).length ? { _convention_args: conventionArgs } : {}),
|
|
91
106
|
_theme: theme,
|
|
107
|
+
...(renderTokenFromVisual(data.visual) ? { _render_token: renderTokenFromVisual(data.visual) } : {}),
|
|
92
108
|
};
|
|
93
109
|
}
|
|
94
110
|
function buildSummary(payload) {
|
|
@@ -180,7 +196,7 @@ registerTool({
|
|
|
180
196
|
return { content: [{ type: "text", text: buildSummary(payload) }] };
|
|
181
197
|
}
|
|
182
198
|
const data = await fetchBaziChart(components, theme, conventionFields);
|
|
183
|
-
const payload = buildModelPayload(data, components, theme);
|
|
199
|
+
const payload = buildModelPayload(data, components, theme, pickConventionArgs(args));
|
|
184
200
|
const summary = buildSummary(payload);
|
|
185
201
|
// MCP Apps wire format: the UI is declared via `_meta.ui.resourceUri` and
|
|
186
202
|
// delivered through resources/read — NOT as a content block.
|
|
@@ -199,9 +215,9 @@ registerTool({
|
|
|
199
215
|
name: "bazi_recalculate",
|
|
200
216
|
description: "Recalculates a BaZi Four Pillars chart with new birth data or theme. " +
|
|
201
217
|
"App-only: called by the embedded chart itself to reconcile the initial " +
|
|
202
|
-
"server-rendered SVG to the host's actual light/dark theme.
|
|
203
|
-
"
|
|
204
|
-
"
|
|
218
|
+
"server-rendered SVG to the host's actual light/dark theme. Free when it " +
|
|
219
|
+
"presents the render_token of the chart it re-renders (same birth data and " +
|
|
220
|
+
"conventions); otherwise billed like a new chart (3 credits).",
|
|
205
221
|
inputSchema: {
|
|
206
222
|
type: "object",
|
|
207
223
|
properties: {
|
|
@@ -209,11 +225,15 @@ registerTool({
|
|
|
209
225
|
month: { type: "integer" },
|
|
210
226
|
day: { type: "integer" },
|
|
211
227
|
hour: { type: "integer" },
|
|
228
|
+
minute: { type: "integer" },
|
|
229
|
+
...CONVENTION_PROPERTIES,
|
|
230
|
+
timezone: { type: "string", description: "IANA timezone name the chart was cast in." },
|
|
212
231
|
theme: {
|
|
213
232
|
type: "string",
|
|
214
233
|
enum: ["light", "dark"],
|
|
215
234
|
description: "Render palette for the BaZi SVG. Mirrors the MCP host's light/dark color scheme.",
|
|
216
235
|
},
|
|
236
|
+
render_token: RENDER_TOKEN_PROPERTY,
|
|
217
237
|
},
|
|
218
238
|
required: ["year", "month", "day"],
|
|
219
239
|
},
|
|
@@ -223,8 +243,12 @@ registerTool({
|
|
|
223
243
|
handler: async (args) => {
|
|
224
244
|
const components = parseBaziArgs(args);
|
|
225
245
|
const theme = args.theme === "light" ? "light" : "dark";
|
|
226
|
-
|
|
227
|
-
|
|
246
|
+
// Re-cast under the SAME conventions as the original chart — dropping them
|
|
247
|
+
// (as this tool used to) rendered a different chart on any non-default
|
|
248
|
+
// year/day boundary, and would not match the render token either.
|
|
249
|
+
const conventionArgs = pickConventionArgs(args);
|
|
250
|
+
const data = await fetchBaziChart(components, theme, buildBaziConventionFields(conventionArgs), args.render_token);
|
|
251
|
+
const payload = buildModelPayload(data, components, theme, conventionArgs);
|
|
228
252
|
return {
|
|
229
253
|
content: [{ type: "text", text: JSON.stringify({ ...payload, server_version: SERVER_VERSION }) }],
|
|
230
254
|
};
|
|
@@ -12,20 +12,27 @@
|
|
|
12
12
|
* Supported modes (BiWheelMode):
|
|
13
13
|
* synastry — two natal charts (person1 inner, person2 outer)
|
|
14
14
|
* transit — natal inner + transiting planets outer
|
|
15
|
-
* progressed — natal inner + secondary progressions outer (POST /ephemeris/progressed)
|
|
15
|
+
* progressed — natal inner + secondary progressions outer (POST /ephemeris/progressed, method=secondary)
|
|
16
16
|
* solar_return — natal inner + solar return chart outer (POST /predictive/returns/solar)
|
|
17
17
|
* lunar_return — natal inner + lunar return chart outer (POST /predictive/returns/lunar)
|
|
18
|
-
* solar_arc — natal inner + solar arc directions outer (
|
|
18
|
+
* solar_arc — natal inner + solar arc directions outer (POST /ephemeris/progressed, method=solar_arc)
|
|
19
19
|
*
|
|
20
20
|
* Cross-aspects are computed client-side (UI) AND server-side (for summary/fallback).
|
|
21
21
|
* The payload cross_aspects array is capped at 30 tightest-orb aspects.
|
|
22
22
|
*/
|
|
23
|
+
import { getActiveClient } from "../../backend/client.js";
|
|
23
24
|
export declare const BI_WHEEL_RESOURCE_URI = "ui://openephemeris/bi-wheel";
|
|
24
25
|
export declare const BI_WHEEL_MIME_TYPE = "text/html;profile=mcp-app";
|
|
25
26
|
/** Read the pre-built HTML bundle. Returns null if not yet built. */
|
|
26
27
|
export declare function getBiWheelBundle(): string | null;
|
|
27
28
|
export declare function clearBiWheelBundleCache(): void;
|
|
28
29
|
export type BiWheelMode = "synastry" | "transit" | "progressed" | "solar_return" | "lunar_return" | "solar_arc";
|
|
30
|
+
/**
|
|
31
|
+
* Route outer-wheel fetch to the correct API endpoint based on mode, and hand
|
|
32
|
+
* back a chart in the natal-chart shape (top-level planets/houses/angles):
|
|
33
|
+
* /ephemeris/progressed nests it under `data`, the return endpoints under `chart`.
|
|
34
|
+
*/
|
|
35
|
+
export declare function fetchOuterChart(client: Pick<ReturnType<typeof getActiveClient>, "post">, mode: BiWheelMode, dt1: string, lat1: number | undefined, lon1: number | undefined, tz1: string | undefined, dt2: string, lat2: number | undefined, lon2: number | undefined, tz2: string | undefined, houseSystem?: string): Promise<Record<string, unknown>>;
|
|
29
36
|
interface PlanetPoint {
|
|
30
37
|
name: string;
|
|
31
38
|
longitude: number;
|