@openephemeris/mcp-server 4.18.0 → 4.20.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 +37 -0
- package/dist/index.js +8 -110
- package/dist/server-sse.js +7 -114
- package/dist/tools/apps/bazi-app.d.ts +1 -1
- package/dist/tools/apps/bazi-app.js +3 -2
- package/dist/tools/apps/bi-wheel-app.d.ts +1 -1
- package/dist/tools/apps/bi-wheel-app.js +3 -2
- package/dist/tools/apps/bodygraph-app.d.ts +7 -4
- package/dist/tools/apps/bodygraph-app.js +13 -5
- package/dist/tools/apps/chart-wheel-app.d.ts +1 -1
- package/dist/tools/apps/chart-wheel-app.js +3 -2
- package/dist/tools/apps/moon-phase-app.d.ts +1 -1
- package/dist/tools/apps/moon-phase-app.js +3 -2
- package/dist/tools/apps/transit-timeline-app.d.ts +1 -1
- package/dist/tools/apps/transit-timeline-app.js +3 -2
- package/dist/tools/apps/ui-resource.d.ts +43 -0
- package/dist/tools/apps/ui-resource.js +66 -0
- package/dist/tools/apps/ui-resources.d.ts +22 -0
- package/dist/tools/apps/ui-resources.js +105 -0
- package/dist/tools/apps/vedic-chart-app.d.ts +1 -1
- package/dist/tools/apps/vedic-chart-app.js +3 -2
- package/dist/tools/specialized/comparative.js +25 -23
- package/dist/tools/specialized/hd_cycles.js +22 -23
- package/dist/tools/specialized/progressed.js +5 -2
- package/dist/tools/specialized/relocation.js +5 -2
- package/dist/tools/visual.d.ts +15 -0
- package/dist/tools/visual.js +22 -0
- package/dist/ui/bazi.html +893 -856
- package/dist/ui/bi-wheel.html +555 -518
- package/dist/ui/bodygraph.html +433 -337
- package/dist/ui/chart-wheel.html +632 -595
- package/dist/ui/moon-phase.html +190 -155
- package/dist/ui/transit-timeline.html +865 -830
- package/dist/ui/vedic-chart.html +252 -215
- package/package.json +2 -1
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ui-resource.ts — identity and `_meta` of the seven MCP App `ui://` resources.
|
|
3
|
+
*
|
|
4
|
+
* Imported by every `*-app.ts` (for its resource URI) and by ui-resources.ts
|
|
5
|
+
* (the list/read registry both transports serve). Kept free of app imports so
|
|
6
|
+
* the app modules can depend on it without a cycle.
|
|
7
|
+
*/
|
|
8
|
+
import { SERVER_VERSION } from "../index.js";
|
|
9
|
+
export const UI_RESOURCE_MIME_TYPE = "text/html;profile=mcp-app";
|
|
10
|
+
/**
|
|
11
|
+
* Versioned resource URI: `ui://openephemeris/<slug>?v=<server version>`.
|
|
12
|
+
*
|
|
13
|
+
* ChatGPT caches a `ui://` resource per connector and never re-reads it for a
|
|
14
|
+
* URI it has seen — a deploy alone does not refresh the widget. Stamping the
|
|
15
|
+
* package version into the URI makes every release a new URI, so hosts fetch
|
|
16
|
+
* the new bundle on their own. Caveat: a widget change must ship with a
|
|
17
|
+
* version bump to bust the cache (same version ⇒ same URI).
|
|
18
|
+
*
|
|
19
|
+
* The SAME string is used in the tool `_meta` (ui.resourceUri and its aliases),
|
|
20
|
+
* in resources/list and in resources/read, on both transports.
|
|
21
|
+
*/
|
|
22
|
+
export function uiResourceUri(slug) {
|
|
23
|
+
return `ui://openephemeris/${slug}?v=${SERVER_VERSION}`;
|
|
24
|
+
}
|
|
25
|
+
/** The URI without its `?v=` query — what resources/read matches on. */
|
|
26
|
+
export function uiResourceBase(uri) {
|
|
27
|
+
const q = uri.indexOf("?");
|
|
28
|
+
return q === -1 ? uri : uri.slice(0, q);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* CSP the bundles actually need: none. Every bundle is a single self-contained
|
|
32
|
+
* HTML file (JS/CSS inlined, images and icons as data: URIs), and the apps talk
|
|
33
|
+
* to the host over postMessage only — no fetch, no remote script, style, image
|
|
34
|
+
* or font. Empty lists are the spec's secure default; declaring them explicitly
|
|
35
|
+
* documents that this is deliberate, not forgotten.
|
|
36
|
+
*/
|
|
37
|
+
export const UI_RESOURCE_CSP = {
|
|
38
|
+
connectDomains: [],
|
|
39
|
+
resourceDomains: [],
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* `_meta` for a `ui://` resource (resources/list entry and resources/read
|
|
43
|
+
* content item). Two wire forms, same values:
|
|
44
|
+
*
|
|
45
|
+
* ui.* — MCP Apps (SEP-1865) McpUiResourceMeta.
|
|
46
|
+
* openai/* — ChatGPT's aliases for the same fields.
|
|
47
|
+
*
|
|
48
|
+
* prefersBorder: true — the host draws the card border and background; the apps
|
|
49
|
+
* therefore paint no card, border or glow of their own (shared/tokens.css).
|
|
50
|
+
*
|
|
51
|
+
* ui.domain / openai/widgetDomain are deliberately NOT set: their format is
|
|
52
|
+
* host-specific (a hash subdomain on Claude, a derived *.oaiusercontent.com on
|
|
53
|
+
* ChatGPT) and a wrong value can stop the widget rendering. Omitted = the
|
|
54
|
+
* host's default sandbox origin, which is what every live render uses today.
|
|
55
|
+
*/
|
|
56
|
+
export function uiResourceMeta(widgetDescription) {
|
|
57
|
+
return {
|
|
58
|
+
ui: {
|
|
59
|
+
prefersBorder: true,
|
|
60
|
+
csp: UI_RESOURCE_CSP,
|
|
61
|
+
},
|
|
62
|
+
"openai/widgetPrefersBorder": true,
|
|
63
|
+
"openai/widgetCSP": { connect_domains: [], resource_domains: [] },
|
|
64
|
+
"openai/widgetDescription": widgetDescription,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface UiResourceEntry {
|
|
2
|
+
uri: string;
|
|
3
|
+
/** Short name used in the "bundle not found" error. */
|
|
4
|
+
label: string;
|
|
5
|
+
name: string;
|
|
6
|
+
description: string;
|
|
7
|
+
/** Tells the model what the widget already shows (ChatGPT openai/widgetDescription). */
|
|
8
|
+
widgetDescription: string;
|
|
9
|
+
getBundle: () => string | null;
|
|
10
|
+
}
|
|
11
|
+
export declare const UI_RESOURCES: UiResourceEntry[];
|
|
12
|
+
/** resources/list — only bundles that are actually built. */
|
|
13
|
+
export declare function listUiResources(): Array<Record<string, unknown>>;
|
|
14
|
+
/**
|
|
15
|
+
* resources/read. Matches on the URI WITHOUT its `?v=` so a host holding an
|
|
16
|
+
* older (or unversioned) URI from a cached tools/list still gets the current
|
|
17
|
+
* bundle; the content item echoes the URI that was asked for. Returns null for
|
|
18
|
+
* a URI that is not one of ours.
|
|
19
|
+
*/
|
|
20
|
+
export declare function readUiResource(uri: string): {
|
|
21
|
+
contents: Array<Record<string, unknown>>;
|
|
22
|
+
} | null;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ui-resources.ts — the resources/list + resources/read registry for the seven
|
|
3
|
+
* MCP App bundles, shared by BOTH transports (src/index.ts stdio and
|
|
4
|
+
* src/server-sse.ts HTTP). They used to carry copy-paste twins of the same
|
|
5
|
+
* seven if-blocks; one table keeps URI, name, description and `_meta` in step.
|
|
6
|
+
*/
|
|
7
|
+
import { UI_RESOURCE_MIME_TYPE, uiResourceBase, uiResourceMeta } from "./ui-resource.js";
|
|
8
|
+
import { CHART_WHEEL_RESOURCE_URI, getChartWheelBundle } from "./chart-wheel-app.js";
|
|
9
|
+
import { BODYGRAPH_RESOURCE_URI, getBodygraphBundle } from "./bodygraph-app.js";
|
|
10
|
+
import { BI_WHEEL_RESOURCE_URI, getBiWheelBundle } from "./bi-wheel-app.js";
|
|
11
|
+
import { MOON_PHASE_RESOURCE_URI, getMoonPhaseBundle } from "./moon-phase-app.js";
|
|
12
|
+
import { TRANSIT_TIMELINE_RESOURCE_URI, getTransitTimelineBundle } from "./transit-timeline-app.js";
|
|
13
|
+
import { VEDIC_CHART_RESOURCE_URI, getVedicChartBundle } from "./vedic-chart-app.js";
|
|
14
|
+
import { BAZI_RESOURCE_URI, getBaziBundle } from "./bazi-app.js";
|
|
15
|
+
export const UI_RESOURCES = [
|
|
16
|
+
{
|
|
17
|
+
uri: CHART_WHEEL_RESOURCE_URI,
|
|
18
|
+
label: "Chart Wheel",
|
|
19
|
+
name: "Chart Wheel Explorer",
|
|
20
|
+
description: "Interactive natal chart wheel with clickable planets, houses, and aspects.",
|
|
21
|
+
widgetDescription: "Shows the full natal chart wheel — every planet, house cusp and aspect line — and lets the user click any of them for a reading. Don't repeat the placements as a list; highlight what stands out and invite them to explore the wheel.",
|
|
22
|
+
getBundle: getChartWheelBundle,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
uri: BODYGRAPH_RESOURCE_URI,
|
|
26
|
+
label: "Bodygraph",
|
|
27
|
+
name: "Human Design Bodygraph Explorer",
|
|
28
|
+
description: "Interactive Human Design Bodygraph with clickable centers, gates, and channels.",
|
|
29
|
+
widgetDescription: "Shows the Human Design bodygraph with type, profile and authority, and lets the user click centers, channels and gates for their meaning. Don't redraw or list the whole chart; interpret the key points and point them to the graph.",
|
|
30
|
+
getBundle: getBodygraphBundle,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
uri: BI_WHEEL_RESOURCE_URI,
|
|
34
|
+
label: "Bi-Wheel",
|
|
35
|
+
name: "Bi-Wheel Explorer",
|
|
36
|
+
description: "Interactive synastry or transit bi-wheel with cross-aspect lines and clickable planets.",
|
|
37
|
+
widgetDescription: "Shows two charts as a bi-wheel (natal inside, transits or a second person outside) with the cross-aspects drawn, each clickable. Don't list every cross-aspect; summarize the strongest contacts.",
|
|
38
|
+
getBundle: getBiWheelBundle,
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
uri: MOON_PHASE_RESOURCE_URI,
|
|
42
|
+
label: "Moon Phase",
|
|
43
|
+
name: "Moon Phase Explorer",
|
|
44
|
+
description: "Interactive lunar phase dial showing illumination, sign, and void-of-course status.",
|
|
45
|
+
widgetDescription: "Shows the current moon: phase, illumination, sign, void-of-course status, speed and the next new and full moon. Don't restate those numbers; add meaning or answer the user's question.",
|
|
46
|
+
getBundle: getMoonPhaseBundle,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
uri: TRANSIT_TIMELINE_RESOURCE_URI,
|
|
50
|
+
label: "Transit Timeline",
|
|
51
|
+
name: "Transit Timeline Explorer",
|
|
52
|
+
description: "Interactive vertical timeline of upcoming transit hits grouped by month, with clickable events.",
|
|
53
|
+
widgetDescription: "Shows a dated timeline of upcoming transits to the natal chart, grouped by month, each clickable. Don't re-list the dates; call out the few that matter most.",
|
|
54
|
+
getBundle: getTransitTimelineBundle,
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
uri: VEDIC_CHART_RESOURCE_URI,
|
|
58
|
+
label: "Vedic Chart",
|
|
59
|
+
name: "Vedic Chart Explorer",
|
|
60
|
+
description: "Interactive Vedic (Jyotish) South Indian Rashi grid with clickable sign placements.",
|
|
61
|
+
widgetDescription: "Shows the sidereal South Indian Rashi chart with every graha in its sign, each clickable. Don't re-list placements; interpret the notable ones.",
|
|
62
|
+
getBundle: getVedicChartBundle,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
uri: BAZI_RESOURCE_URI,
|
|
66
|
+
label: "BaZi",
|
|
67
|
+
name: "BaZi Four Pillars Explorer",
|
|
68
|
+
description: "Interactive BaZi (Four Pillars of Destiny) chart with clickable Year/Month/Day/Hour pillars.",
|
|
69
|
+
widgetDescription: "Shows the Four Pillars (year, month, day, hour stems and branches) with the Day Master, each pillar clickable. Don't re-list the pillars; interpret the balance.",
|
|
70
|
+
getBundle: getBaziBundle,
|
|
71
|
+
},
|
|
72
|
+
];
|
|
73
|
+
/** resources/list — only bundles that are actually built. */
|
|
74
|
+
export function listUiResources() {
|
|
75
|
+
return UI_RESOURCES.filter((r) => r.getBundle()).map((r) => ({
|
|
76
|
+
uri: r.uri,
|
|
77
|
+
name: r.name,
|
|
78
|
+
description: r.description,
|
|
79
|
+
mimeType: UI_RESOURCE_MIME_TYPE,
|
|
80
|
+
_meta: uiResourceMeta(r.widgetDescription),
|
|
81
|
+
}));
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* resources/read. Matches on the URI WITHOUT its `?v=` so a host holding an
|
|
85
|
+
* older (or unversioned) URI from a cached tools/list still gets the current
|
|
86
|
+
* bundle; the content item echoes the URI that was asked for. Returns null for
|
|
87
|
+
* a URI that is not one of ours.
|
|
88
|
+
*/
|
|
89
|
+
export function readUiResource(uri) {
|
|
90
|
+
const base = uiResourceBase(uri);
|
|
91
|
+
const entry = UI_RESOURCES.find((r) => uiResourceBase(r.uri) === base);
|
|
92
|
+
if (!entry)
|
|
93
|
+
return null;
|
|
94
|
+
const bundle = entry.getBundle();
|
|
95
|
+
if (!bundle)
|
|
96
|
+
throw new Error(`${entry.label} UI bundle not found. Run \`npm run build:ui\` to build it.`);
|
|
97
|
+
return {
|
|
98
|
+
contents: [{
|
|
99
|
+
uri,
|
|
100
|
+
mimeType: UI_RESOURCE_MIME_TYPE,
|
|
101
|
+
text: bundle,
|
|
102
|
+
_meta: uiResourceMeta(entry.widgetDescription),
|
|
103
|
+
}],
|
|
104
|
+
};
|
|
105
|
+
}
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* Also exports resource helpers (getVedicChartBundle, etc.) for use in
|
|
18
18
|
* index.ts and server-sse.ts.
|
|
19
19
|
*/
|
|
20
|
-
export declare const VEDIC_CHART_RESOURCE_URI
|
|
20
|
+
export declare const VEDIC_CHART_RESOURCE_URI: string;
|
|
21
21
|
export declare const VEDIC_CHART_MIME_TYPE = "text/html;profile=mcp-app";
|
|
22
22
|
/** Read the pre-built HTML bundle. Returns null if not yet built. */
|
|
23
23
|
export declare function getVedicChartBundle(): string | null;
|
|
@@ -21,14 +21,15 @@ import fs from "node:fs";
|
|
|
21
21
|
import path from "node:path";
|
|
22
22
|
import { fileURLToPath } from "node:url";
|
|
23
23
|
import { registerTool, validateRequired, validateCoordinates, SERVER_VERSION } from "../index.js";
|
|
24
|
+
import { UI_RESOURCE_MIME_TYPE, uiResourceUri } from "./ui-resource.js";
|
|
24
25
|
import { getActiveClient } from "../../backend/client.js";
|
|
25
26
|
import { OUTPUT_SCHEMA_JSON } from "../output-schemas.js";
|
|
26
27
|
import { localToUtcIsoHistorical } from "../datetime-historical.js";
|
|
27
28
|
import { coordsFromArgsOrLocation } from "./_location-resolver.js";
|
|
28
29
|
import { RENDER_TOKEN_PROPERTY, renderTokenFromVisual, renderTokenHeaders } from "./_render-token.js";
|
|
29
30
|
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
30
|
-
export const VEDIC_CHART_RESOURCE_URI = "
|
|
31
|
-
export const VEDIC_CHART_MIME_TYPE =
|
|
31
|
+
export const VEDIC_CHART_RESOURCE_URI = uiResourceUri("vedic-chart");
|
|
32
|
+
export const VEDIC_CHART_MIME_TYPE = UI_RESOURCE_MIME_TYPE;
|
|
32
33
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
33
34
|
const BUNDLE_PATHS = [
|
|
34
35
|
path.resolve(here, "..", "..", "..", "dist", "ui", "vedic-chart.html"),
|
|
@@ -1,6 +1,7 @@
|
|
|
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
|
+
import { OUTPUT_SCHEMA_IMAGE_AND_JSON, OUTPUT_SCHEMA_JSON } from "../output-schemas.js";
|
|
4
|
+
import { applyVisual, visualProperties } from "../visual.js";
|
|
4
5
|
import { DATETIME_DESC, assertZonedDatetime, toDateTimeInputBody, timezoneProperty } from "../datetime.js";
|
|
5
6
|
/** Reject a zone-less person datetime here, before a credit is spent on it. */
|
|
6
7
|
function assertPersonZoned(prefix, args) {
|
|
@@ -23,8 +24,9 @@ function buildSubject(name, datetime, lat, lon, timezone) {
|
|
|
23
24
|
// POST /comparative/composite
|
|
24
25
|
registerTool({
|
|
25
26
|
name: "ephemeris_composite",
|
|
26
|
-
description: "Calculate a
|
|
27
|
-
"
|
|
27
|
+
description: "Calculate a Davison relationship chart for two people — a real chart cast for the " +
|
|
28
|
+
"midpoint in time and place between the two births, with its own houses and angles. " +
|
|
29
|
+
"For the planet-by-planet midpoint composite use ephemeris_composite_midpoint.\n\n" +
|
|
28
30
|
"CREDIT COST: 3 credits per call.\n\n" +
|
|
29
31
|
"EXAMPLE: Composite for two people:\n" +
|
|
30
32
|
" person_a_datetime='1990-04-15T14:30:00', person_a_timezone='America/Chicago',\n" +
|
|
@@ -43,6 +45,7 @@ registerTool({
|
|
|
43
45
|
person_b_latitude: { type: "number", description: "Person B birth latitude." },
|
|
44
46
|
person_b_longitude: { type: "number", description: "Person B birth longitude." },
|
|
45
47
|
format: { type: "string", enum: ["json", "llm"], description: "Use 'llm' for token-efficient LLM projection." },
|
|
48
|
+
...visualProperties("the relationship chart wheel"),
|
|
46
49
|
},
|
|
47
50
|
required: [
|
|
48
51
|
"person_a_datetime", "person_a_latitude", "person_a_longitude",
|
|
@@ -50,7 +53,7 @@ registerTool({
|
|
|
50
53
|
],
|
|
51
54
|
additionalProperties: false,
|
|
52
55
|
},
|
|
53
|
-
outputSchema:
|
|
56
|
+
outputSchema: OUTPUT_SCHEMA_IMAGE_AND_JSON,
|
|
54
57
|
annotations: { title: "Davison Relationship Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
55
58
|
handler: async (args) => {
|
|
56
59
|
validateRequired(args, [
|
|
@@ -62,15 +65,14 @@ registerTool({
|
|
|
62
65
|
const query = {};
|
|
63
66
|
if (args.format)
|
|
64
67
|
query.format = args.format;
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
});
|
|
68
|
+
const body = {
|
|
69
|
+
subjects: [
|
|
70
|
+
buildSubject("Person A", args.person_a_datetime, args.person_a_latitude, args.person_a_longitude, args.person_a_timezone),
|
|
71
|
+
buildSubject("Person B", args.person_b_datetime, args.person_b_latitude, args.person_b_longitude, args.person_b_timezone),
|
|
72
|
+
],
|
|
73
|
+
};
|
|
74
|
+
applyVisual(body, args);
|
|
75
|
+
return await getActiveClient().request("POST", "/comparative/composite", { params: query, data: body });
|
|
74
76
|
},
|
|
75
77
|
});
|
|
76
78
|
// POST /comparative/composite/midpoint
|
|
@@ -139,6 +141,7 @@ registerTool({
|
|
|
139
141
|
person_b_latitude: { type: "number", description: "Person B birth latitude." },
|
|
140
142
|
person_b_longitude: { type: "number", description: "Person B birth longitude." },
|
|
141
143
|
format: { type: "string", enum: ["json", "llm"], description: "Use 'llm' for token-efficient LLM projection." },
|
|
144
|
+
...visualProperties("a bi-wheel of the two charts"),
|
|
142
145
|
},
|
|
143
146
|
required: [
|
|
144
147
|
"person_a_datetime", "person_a_latitude", "person_a_longitude",
|
|
@@ -146,7 +149,7 @@ registerTool({
|
|
|
146
149
|
],
|
|
147
150
|
additionalProperties: false,
|
|
148
151
|
},
|
|
149
|
-
outputSchema:
|
|
152
|
+
outputSchema: OUTPUT_SCHEMA_IMAGE_AND_JSON,
|
|
150
153
|
annotations: { title: "Synastry House Overlay", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
151
154
|
handler: async (args) => {
|
|
152
155
|
validateRequired(args, [
|
|
@@ -158,15 +161,14 @@ registerTool({
|
|
|
158
161
|
const query = {};
|
|
159
162
|
if (args.format)
|
|
160
163
|
query.format = args.format;
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
});
|
|
164
|
+
const body = {
|
|
165
|
+
subjects: [
|
|
166
|
+
buildSubject("Person A", args.person_a_datetime, args.person_a_latitude, args.person_a_longitude, args.person_a_timezone),
|
|
167
|
+
buildSubject("Person B", args.person_b_datetime, args.person_b_latitude, args.person_b_longitude, args.person_b_timezone),
|
|
168
|
+
],
|
|
169
|
+
};
|
|
170
|
+
applyVisual(body, args);
|
|
171
|
+
return await getActiveClient().request("POST", "/comparative/overlay", { params: query, data: body });
|
|
170
172
|
},
|
|
171
173
|
});
|
|
172
174
|
// POST /comparative/natal-transits
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { registerTool, validateRequired } from "../index.js";
|
|
2
2
|
import { getActiveClient } from "../../backend/client.js";
|
|
3
|
-
import {
|
|
3
|
+
import { OUTPUT_SCHEMA_IMAGE_AND_JSON } from "../output-schemas.js";
|
|
4
|
+
import { applyVisual, visualProperties } from "../visual.js";
|
|
4
5
|
import { DATETIME_DESC, TIMEZONE_PROPERTY } from "../datetime.js";
|
|
5
6
|
import { localToUtcIsoHistorical } from "../datetime-historical.js";
|
|
6
7
|
/** Optional birth coordinates, present only to feed the historical timezone
|
|
@@ -53,24 +54,23 @@ registerTool({
|
|
|
53
54
|
enum: ["json", "llm"],
|
|
54
55
|
description: "Output format. 'llm' returns compact projection for token efficiency.",
|
|
55
56
|
},
|
|
57
|
+
...visualProperties("the bodygraph for that moment"),
|
|
56
58
|
},
|
|
57
59
|
required: ["planet", "datetime", "return_year"],
|
|
58
60
|
additionalProperties: false,
|
|
59
61
|
},
|
|
60
|
-
outputSchema:
|
|
62
|
+
outputSchema: OUTPUT_SCHEMA_IMAGE_AND_JSON,
|
|
61
63
|
annotations: { title: "HD Planetary Return", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
62
64
|
handler: async (args) => {
|
|
63
65
|
validateRequired(args, ["planet", "datetime", "return_year"]);
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
},
|
|
73
|
-
});
|
|
66
|
+
const body = {
|
|
67
|
+
planet: args.planet,
|
|
68
|
+
birth_datetime_utc: await localToUtcIsoHistorical("datetime", args.datetime, args.timezone, { latitude: args.latitude, longitude: args.longitude }),
|
|
69
|
+
return_year: args.return_year,
|
|
70
|
+
format: args.format,
|
|
71
|
+
};
|
|
72
|
+
applyVisual(body, args);
|
|
73
|
+
return await getActiveClient().request("POST", "/human-design/cycles/return", { data: body });
|
|
74
74
|
},
|
|
75
75
|
});
|
|
76
76
|
registerTool({
|
|
@@ -107,23 +107,22 @@ registerTool({
|
|
|
107
107
|
enum: ["json", "llm"],
|
|
108
108
|
description: "Output format. 'llm' returns compact projection for token efficiency.",
|
|
109
109
|
},
|
|
110
|
+
...visualProperties("the bodygraph for that moment"),
|
|
110
111
|
},
|
|
111
112
|
required: ["planet", "datetime", "target_year"],
|
|
112
113
|
additionalProperties: false,
|
|
113
114
|
},
|
|
114
|
-
outputSchema:
|
|
115
|
+
outputSchema: OUTPUT_SCHEMA_IMAGE_AND_JSON,
|
|
115
116
|
annotations: { title: "HD Opposition Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
116
117
|
handler: async (args) => {
|
|
117
118
|
validateRequired(args, ["planet", "datetime", "target_year"]);
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
},
|
|
127
|
-
});
|
|
119
|
+
const body = {
|
|
120
|
+
planet: args.planet,
|
|
121
|
+
birth_datetime_utc: await localToUtcIsoHistorical("datetime", args.datetime, args.timezone, { latitude: args.latitude, longitude: args.longitude }),
|
|
122
|
+
target_year: args.target_year,
|
|
123
|
+
format: args.format,
|
|
124
|
+
};
|
|
125
|
+
applyVisual(body, args);
|
|
126
|
+
return await getActiveClient().request("POST", "/human-design/cycles/opposition", { data: body });
|
|
128
127
|
},
|
|
129
128
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { registerTool, validateRequired } from "../index.js";
|
|
2
2
|
import { getActiveClient } from "../../backend/client.js";
|
|
3
|
-
import {
|
|
3
|
+
import { OUTPUT_SCHEMA_IMAGE_AND_JSON } from "../output-schemas.js";
|
|
4
|
+
import { applyVisual, visualProperties } from "../visual.js";
|
|
4
5
|
import { DATETIME_DESC, TIMEZONE_PROPERTY, WINDOW_DATE_DESC, assertZonedDatetime } from "../datetime.js";
|
|
5
6
|
registerTool({
|
|
6
7
|
name: "ephemeris_progressed_chart",
|
|
@@ -44,11 +45,12 @@ registerTool({
|
|
|
44
45
|
type: "boolean",
|
|
45
46
|
description: "Whether to include aspect grid in the response. Default false.",
|
|
46
47
|
},
|
|
48
|
+
...visualProperties("a natal-inside, progressed-outside bi-wheel"),
|
|
47
49
|
},
|
|
48
50
|
required: ["birth_datetime", "birth_latitude", "birth_longitude", "target_datetime"],
|
|
49
51
|
additionalProperties: false,
|
|
50
52
|
},
|
|
51
|
-
outputSchema:
|
|
53
|
+
outputSchema: OUTPUT_SCHEMA_IMAGE_AND_JSON,
|
|
52
54
|
annotations: { title: "Progressed Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
53
55
|
handler: async (args) => {
|
|
54
56
|
validateRequired(args, ["birth_datetime", "birth_latitude", "birth_longitude", "target_datetime"]);
|
|
@@ -76,6 +78,7 @@ registerTool({
|
|
|
76
78
|
if (args.include_aspects != null) {
|
|
77
79
|
body.options = { include_aspects: args.include_aspects };
|
|
78
80
|
}
|
|
81
|
+
applyVisual(body, args);
|
|
79
82
|
return await getActiveClient().post("/ephemeris/progressed", body);
|
|
80
83
|
},
|
|
81
84
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { registerTool, validateRequired } from "../index.js";
|
|
2
2
|
import { getActiveClient } from "../../backend/client.js";
|
|
3
|
-
import {
|
|
3
|
+
import { OUTPUT_SCHEMA_IMAGE_AND_JSON } from "../output-schemas.js";
|
|
4
|
+
import { applyVisual, visualProperties } from "../visual.js";
|
|
4
5
|
import { DATETIME_DESC, TIMEZONE_PROPERTY, assertZonedDatetime } from "../datetime.js";
|
|
5
6
|
registerTool({
|
|
6
7
|
name: "ephemeris_relocation",
|
|
@@ -45,11 +46,12 @@ registerTool({
|
|
|
45
46
|
enum: ["json", "llm"],
|
|
46
47
|
description: "Output format. 'llm' = compact token-efficient output (available on all tiers).",
|
|
47
48
|
},
|
|
49
|
+
...visualProperties("the relocated chart wheel"),
|
|
48
50
|
},
|
|
49
51
|
required: ["natal_datetime", "natal_latitude", "natal_longitude", "relocation_latitude", "relocation_longitude"],
|
|
50
52
|
additionalProperties: false,
|
|
51
53
|
},
|
|
52
|
-
outputSchema:
|
|
54
|
+
outputSchema: OUTPUT_SCHEMA_IMAGE_AND_JSON,
|
|
53
55
|
annotations: { title: "Relocation Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
54
56
|
handler: async (args) => {
|
|
55
57
|
validateRequired(args, ["natal_datetime", "natal_latitude", "natal_longitude", "relocation_latitude", "relocation_longitude"]);
|
|
@@ -72,6 +74,7 @@ registerTool({
|
|
|
72
74
|
};
|
|
73
75
|
if (args.house_system)
|
|
74
76
|
body.house_system = args.house_system;
|
|
77
|
+
applyVisual(body, args);
|
|
75
78
|
const query = {};
|
|
76
79
|
if (args.format)
|
|
77
80
|
query.format = args.format;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* include_visual for data tools whose endpoint can embed a rendered picture
|
|
3
|
+
* (+2 credits). formatToolResponse turns the returned `visual` into an image
|
|
4
|
+
* block next to the JSON. Kept to one terse flag: tool text is re-sent to the
|
|
5
|
+
* model on every pass (see tool-surface-budget.test.ts).
|
|
6
|
+
*/
|
|
7
|
+
/** Input-schema property; `what` names the picture, e.g. "the relocated wheel". */
|
|
8
|
+
export declare function visualProperties(what: string): {
|
|
9
|
+
readonly include_visual: {
|
|
10
|
+
readonly type: "boolean";
|
|
11
|
+
readonly description: `Also return ${string} as an image (+2 credits).`;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
/** Copies the visual request onto the API body. Always SVG: the MCP host rasterizes nothing. */
|
|
15
|
+
export declare function applyVisual(body: Record<string, unknown>, args: any): void;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* include_visual for data tools whose endpoint can embed a rendered picture
|
|
3
|
+
* (+2 credits). formatToolResponse turns the returned `visual` into an image
|
|
4
|
+
* block next to the JSON. Kept to one terse flag: tool text is re-sent to the
|
|
5
|
+
* model on every pass (see tool-surface-budget.test.ts).
|
|
6
|
+
*/
|
|
7
|
+
/** Input-schema property; `what` names the picture, e.g. "the relocated wheel". */
|
|
8
|
+
export function visualProperties(what) {
|
|
9
|
+
return {
|
|
10
|
+
include_visual: {
|
|
11
|
+
type: "boolean",
|
|
12
|
+
description: `Also return ${what} as an image (+2 credits).`,
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/** Copies the visual request onto the API body. Always SVG: the MCP host rasterizes nothing. */
|
|
17
|
+
export function applyVisual(body, args) {
|
|
18
|
+
if (!args.include_visual)
|
|
19
|
+
return;
|
|
20
|
+
body.include_visual = true;
|
|
21
|
+
body.visual_config = { format: "svg", theme: "light", size: 800 };
|
|
22
|
+
}
|