@openephemeris/mcp-server 4.7.0 → 4.8.1
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 +43 -0
- package/README.md +35 -3
- package/dist/index.js +5 -2
- package/dist/oauth/token.js +30 -3
- package/dist/server-sse.js +7 -2
- package/dist/tools/apps/bazi-app.js +4 -4
- package/dist/tools/apps/bi-wheel-app.js +57 -23
- package/dist/tools/apps/bodygraph-app.js +12 -9
- package/dist/tools/apps/chart-wheel-app.js +4 -4
- package/dist/tools/apps/location-tools.js +2 -2
- package/dist/tools/apps/moon-phase-app.js +1 -1
- package/dist/tools/apps/vedic-chart-app.js +1 -1
- package/dist/tools/auth.js +8 -0
- package/dist/tools/dev.js +15 -5
- package/dist/tools/index.d.ts +41 -2
- package/dist/tools/index.js +201 -4
- package/dist/tools/specialized/account.js +5 -1
- package/dist/tools/specialized/acg.js +2 -2
- package/dist/tools/specialized/bazi.js +7 -7
- package/dist/tools/specialized/bi_wheel.js +1 -1
- package/dist/tools/specialized/chart_wheel.js +1 -1
- package/dist/tools/specialized/comparative.js +4 -4
- package/dist/tools/specialized/eclipse.js +1 -1
- package/dist/tools/specialized/electional.js +5 -5
- package/dist/tools/specialized/ephemeris_core.js +2 -2
- package/dist/tools/specialized/ephemeris_extended.js +8 -8
- package/dist/tools/specialized/hd_bodygraph.js +1 -1
- package/dist/tools/specialized/hd_cycles.js +2 -2
- package/dist/tools/specialized/hd_group.js +2 -2
- package/dist/tools/specialized/human_design.js +1 -1
- package/dist/tools/specialized/moon.js +10 -13
- package/dist/tools/specialized/natal.js +1 -1
- package/dist/tools/specialized/progressed.js +1 -1
- package/dist/tools/specialized/relocation.js +1 -1
- package/dist/tools/specialized/returns.js +3 -3
- package/dist/tools/specialized/synastry.js +1 -1
- package/dist/tools/specialized/transits.js +1 -1
- package/dist/tools/specialized/vedic.js +1 -1
- package/dist/tools/specialized/venus_star_points.js +6 -6
- package/dist/ui/chart-wheel.html +56 -53
- package/package.json +4 -2
- package/smithery.yaml +14 -3
package/dist/tools/index.d.ts
CHANGED
|
@@ -57,7 +57,11 @@ export interface ToolDefinition {
|
|
|
57
57
|
* multi-tenant HTTP server cannot vary it per session, and an unregistered
|
|
58
58
|
* tool becomes uncallable rather than merely hidden.
|
|
59
59
|
*/
|
|
60
|
-
export type ToolSurface = "core" | "full";
|
|
60
|
+
export type ToolSurface = "core" | "full" | ToolsetSurface;
|
|
61
|
+
/** A caller-chosen set of traditions, e.g. `?profile=hd,bazi`. */
|
|
62
|
+
export interface ToolsetSurface {
|
|
63
|
+
readonly toolsets: readonly string[];
|
|
64
|
+
}
|
|
61
65
|
/**
|
|
62
66
|
* The default advertised surface: everyday astrology work, one tool per job.
|
|
63
67
|
*
|
|
@@ -80,6 +84,28 @@ export type ToolSurface = "core" | "full";
|
|
|
80
84
|
* explore_natal_chart are present. Do not remove those four.
|
|
81
85
|
*/
|
|
82
86
|
export declare const CORE_TOOL_NAMES: ReadonlySet<string>;
|
|
87
|
+
/**
|
|
88
|
+
* Tool surfaces grouped by tradition, so a caller pays context only for the
|
|
89
|
+
* work they actually do: `?profile=hd,bazi` on HTTP, `OPENEPHEMERIS_TOOLS=hd`
|
|
90
|
+
* on stdio. BASE_TOOLSET is always included.
|
|
91
|
+
*
|
|
92
|
+
* This is the same *view* filter as `core`/`full` — nothing is unregistered,
|
|
93
|
+
* every tool stays callable by name — so a toolset is a context-budget choice,
|
|
94
|
+
* never a capability one.
|
|
95
|
+
*
|
|
96
|
+
* Why this exists: the core surface is re-sent on every model pass and sat at
|
|
97
|
+
* its ceiling with ~0 headroom, and the only levers left were deleting the
|
|
98
|
+
* disambiguation prose that makes tool selection work. Grouping by tradition
|
|
99
|
+
* is the structural fix — a Human Design user should not pay for Venus Star
|
|
100
|
+
* Points on every message.
|
|
101
|
+
*
|
|
102
|
+
* Names here are asserted against the registry by a test; a typo fails the
|
|
103
|
+
* build rather than silently narrowing someone's surface.
|
|
104
|
+
*/
|
|
105
|
+
export declare const TOOLSETS: Readonly<Record<string, readonly string[]>>;
|
|
106
|
+
export declare const TOOLSET_NAMES: readonly string[];
|
|
107
|
+
/** Resolve a toolset selection to the tool names it advertises. */
|
|
108
|
+
export declare function toolsetSelectionNames(names: readonly string[]): Set<string>;
|
|
83
109
|
/**
|
|
84
110
|
* Returns tools that should be exposed to Claude in the ListTools response.
|
|
85
111
|
* Filters out tools that have visibility restricted to the UI (app-only),
|
|
@@ -87,8 +113,21 @@ export declare const CORE_TOOL_NAMES: ReadonlySet<string>;
|
|
|
87
113
|
* "core" — everything outside CORE_TOOL_NAMES.
|
|
88
114
|
*/
|
|
89
115
|
export declare function modelVisibleTools(transport?: "stdio" | "http", surface?: ToolSurface): ToolDefinition[];
|
|
90
|
-
/**
|
|
116
|
+
/**
|
|
117
|
+
* Parses a caller-supplied surface hint.
|
|
118
|
+
*
|
|
119
|
+
* "full" -> every model-visible tool
|
|
120
|
+
* "hd" | "hd,bazi" -> those traditions plus BASE_TOOLSET
|
|
121
|
+
* anything else -> "core" (the curated default)
|
|
122
|
+
*
|
|
123
|
+
* Unknown names in a comma list are ignored rather than rejected: a typo
|
|
124
|
+
* degrades to a smaller surface, never to an error at `initialize` that would
|
|
125
|
+
* leave the connector dead. If NO name in the list is recognised, fall back to
|
|
126
|
+
* "core" so a mistyped profile can't strand a caller on BASE alone.
|
|
127
|
+
*/
|
|
91
128
|
export declare function parseToolSurface(raw: unknown): ToolSurface;
|
|
129
|
+
/** Stable label for logs/analytics — an object surface has no useful toString. */
|
|
130
|
+
export declare function describeSurface(surface: ToolSurface): string;
|
|
92
131
|
export declare const toolRegistry: Record<string, ToolDefinition>;
|
|
93
132
|
export declare function registerTool(tool: ToolDefinition): void;
|
|
94
133
|
export type ToolProfile = "dev" | "legacy";
|
package/dist/tools/index.js
CHANGED
|
@@ -12,6 +12,9 @@ export const SERVER_VERSION = (() => {
|
|
|
12
12
|
return "0.0.0-unknown";
|
|
13
13
|
}
|
|
14
14
|
})();
|
|
15
|
+
function isToolsetSurface(s) {
|
|
16
|
+
return typeof s === "object" && Array.isArray(s.toolsets);
|
|
17
|
+
}
|
|
15
18
|
/**
|
|
16
19
|
* The default advertised surface: everyday astrology work, one tool per job.
|
|
17
20
|
*
|
|
@@ -53,6 +56,13 @@ export const CORE_TOOL_NAMES = new Set([
|
|
|
53
56
|
"ephemeris_transits",
|
|
54
57
|
"ephemeris_synastry",
|
|
55
58
|
"ephemeris_retrograde_status",
|
|
59
|
+
// Promoted 2026-08-02 on usage: both were reachable only via profile=full,
|
|
60
|
+
// yet ephemeris_aspect_check drew 19 calls from 3 users over 90 days (more
|
|
61
|
+
// than 20 of the tools already in this set) and ephemeris_angles_points drew
|
|
62
|
+
// 9. People found them while they were hidden; advertising them is cheap
|
|
63
|
+
// (202 and 227 tokens) and earns its place.
|
|
64
|
+
"ephemeris_aspect_check",
|
|
65
|
+
"ephemeris_angles_points",
|
|
56
66
|
"ephemeris_next_eclipse",
|
|
57
67
|
"ephemeris_relocation",
|
|
58
68
|
"ephemeris_progressed_chart",
|
|
@@ -60,13 +70,31 @@ export const CORE_TOOL_NAMES = new Set([
|
|
|
60
70
|
// Traditions
|
|
61
71
|
"human_design_chart",
|
|
62
72
|
"vedic_chart",
|
|
63
|
-
"chinese_bazi",
|
|
64
73
|
"bazi_annual_pillar",
|
|
74
|
+
// chinese_bazi demoted 2026-08-02: explore_bazi_chart POSTs the same
|
|
75
|
+
// /chinese/bazi at the same 1-credit base and keeps every substantive field
|
|
76
|
+
// (year/month/day/hour pillars + day_master), dropping only the `metadata`
|
|
77
|
+
// and `success` envelope keys — verified against a live response, not
|
|
78
|
+
// inferred from the names. It also degrades to a text summary on hosts that
|
|
79
|
+
// cannot render the iframe, so nothing is lost without an MCP Apps host.
|
|
80
|
+
// chinese_bazi drew 0 calls in 90 days while advertised; explore_bazi_chart
|
|
81
|
+
// drew 3. Still registered and callable by name, and still in profile=full.
|
|
82
|
+
//
|
|
83
|
+
// NOTE: this reasoning does NOT extend to the sibling pairs. vedic_chart (1
|
|
84
|
+
// credit) vs explore_vedic_chart (3), and human_design_chart (2 credits, full
|
|
85
|
+
// response) vs explore_human_design (4 credits, and buildHdModelPayload drops
|
|
86
|
+
// activations/design/personality/strategy/variables) are not folds — they
|
|
87
|
+
// would cost users money and data. Verify in the handler before adding to
|
|
88
|
+
// this list.
|
|
65
89
|
// Timing
|
|
66
90
|
"ephemeris_electional",
|
|
67
91
|
"electional_moment_analysis",
|
|
68
92
|
"electional_station_tracker",
|
|
69
|
-
|
|
93
|
+
// electional_angle_crossings demoted 2026-08-02: 715 tokens (the fattest
|
|
94
|
+
// non-proxy tool in this set) for 0 calls in 90 days, and at 5 credits it is
|
|
95
|
+
// advanced-practitioner work rather than an everyday ask. Three electional
|
|
96
|
+
// entry points remain advertised; this one is a profile=full / skill-named
|
|
97
|
+
// tool now.
|
|
70
98
|
// Astrocartography
|
|
71
99
|
"acg_power_lines",
|
|
72
100
|
"acg_hits",
|
|
@@ -83,6 +111,141 @@ export const CORE_TOOL_NAMES = new Set([
|
|
|
83
111
|
"auth_status",
|
|
84
112
|
"auth_logout",
|
|
85
113
|
]);
|
|
114
|
+
/**
|
|
115
|
+
* Tools every session needs regardless of tradition: turning a place name into
|
|
116
|
+
* coordinates, checking credits, and the allowlist-gated escape hatch. Always
|
|
117
|
+
* unioned into a toolset selection — a caller who asks for `hd` still has to be
|
|
118
|
+
* able to resolve a birthplace, or they are back to recalling coordinates from
|
|
119
|
+
* memory, which is the bug BASE exists to prevent.
|
|
120
|
+
*/
|
|
121
|
+
const BASE_TOOLSET = [
|
|
122
|
+
"location_search",
|
|
123
|
+
"timezone_resolve",
|
|
124
|
+
"account_usage",
|
|
125
|
+
"dev_read_api",
|
|
126
|
+
// The write half of the escape hatch. 26 of the 31 allowlisted operations
|
|
127
|
+
// are POST — BASE without dev_write_api would strand a toolset caller with
|
|
128
|
+
// only the 5 GET proxies. (Deliberately NOT advertised in `core`, where the
|
|
129
|
+
// curated typed tools cover the write surface; a toolset selection has no
|
|
130
|
+
// such coverage, so it needs the proxy whole.)
|
|
131
|
+
"dev_write_api",
|
|
132
|
+
"dev_list_allowed",
|
|
133
|
+
"auth_login",
|
|
134
|
+
"auth_status",
|
|
135
|
+
"auth_logout",
|
|
136
|
+
];
|
|
137
|
+
/**
|
|
138
|
+
* Tool surfaces grouped by tradition, so a caller pays context only for the
|
|
139
|
+
* work they actually do: `?profile=hd,bazi` on HTTP, `OPENEPHEMERIS_TOOLS=hd`
|
|
140
|
+
* on stdio. BASE_TOOLSET is always included.
|
|
141
|
+
*
|
|
142
|
+
* This is the same *view* filter as `core`/`full` — nothing is unregistered,
|
|
143
|
+
* every tool stays callable by name — so a toolset is a context-budget choice,
|
|
144
|
+
* never a capability one.
|
|
145
|
+
*
|
|
146
|
+
* Why this exists: the core surface is re-sent on every model pass and sat at
|
|
147
|
+
* its ceiling with ~0 headroom, and the only levers left were deleting the
|
|
148
|
+
* disambiguation prose that makes tool selection work. Grouping by tradition
|
|
149
|
+
* is the structural fix — a Human Design user should not pay for Venus Star
|
|
150
|
+
* Points on every message.
|
|
151
|
+
*
|
|
152
|
+
* Names here are asserted against the registry by a test; a typo fails the
|
|
153
|
+
* build rather than silently narrowing someone's surface.
|
|
154
|
+
*/
|
|
155
|
+
export const TOOLSETS = {
|
|
156
|
+
/** Western natal/predictive work — the default tradition. */
|
|
157
|
+
astrology: [
|
|
158
|
+
"ephemeris_natal_chart",
|
|
159
|
+
"explore_natal_chart",
|
|
160
|
+
"ephemeris_planet_position",
|
|
161
|
+
"ephemeris_house_cusps",
|
|
162
|
+
"ephemeris_angles_points",
|
|
163
|
+
"ephemeris_aspect_check",
|
|
164
|
+
"ephemeris_transits",
|
|
165
|
+
"ephemeris_natal_transits",
|
|
166
|
+
"explore_transit_timeline",
|
|
167
|
+
"ephemeris_synastry",
|
|
168
|
+
"explore_bi_wheel",
|
|
169
|
+
"bi_wheel_synopsis",
|
|
170
|
+
"ephemeris_retrograde_status",
|
|
171
|
+
"ephemeris_progressed_chart",
|
|
172
|
+
"ephemeris_solar_return",
|
|
173
|
+
"ephemeris_lunar_return",
|
|
174
|
+
"ephemeris_planetary_return",
|
|
175
|
+
"ephemeris_relocation",
|
|
176
|
+
"ephemeris_dignities",
|
|
177
|
+
"ephemeris_midpoints",
|
|
178
|
+
"ephemeris_hermetic_lots",
|
|
179
|
+
"ephemeris_fixed_stars",
|
|
180
|
+
"ephemeris_composite",
|
|
181
|
+
"ephemeris_composite_midpoint",
|
|
182
|
+
"ephemeris_overlay",
|
|
183
|
+
"ephemeris_chart_wheel",
|
|
184
|
+
"ephemeris_bi_wheel",
|
|
185
|
+
"ephemeris_natal_batch",
|
|
186
|
+
],
|
|
187
|
+
/** Lunar phase, void-of-course and eclipse work. */
|
|
188
|
+
moon: [
|
|
189
|
+
"ephemeris_moon_phase",
|
|
190
|
+
"explore_moon_phase",
|
|
191
|
+
"ephemeris_next_lunar_phase",
|
|
192
|
+
"ephemeris_next_eclipse",
|
|
193
|
+
],
|
|
194
|
+
/** Human Design. */
|
|
195
|
+
hd: [
|
|
196
|
+
"human_design_chart",
|
|
197
|
+
"explore_human_design",
|
|
198
|
+
"explore_human_design_transit",
|
|
199
|
+
"explore_human_design_connection",
|
|
200
|
+
"human_design_bodygraph",
|
|
201
|
+
"human_design_composite",
|
|
202
|
+
"human_design_penta",
|
|
203
|
+
"hd_opposition",
|
|
204
|
+
"hd_planetary_return",
|
|
205
|
+
],
|
|
206
|
+
/** Chinese BaZi / Four Pillars. */
|
|
207
|
+
bazi: [
|
|
208
|
+
"explore_bazi_chart",
|
|
209
|
+
"chinese_bazi",
|
|
210
|
+
"bazi_chart",
|
|
211
|
+
"bazi_ten_gods",
|
|
212
|
+
"bazi_element_balance",
|
|
213
|
+
"bazi_luck_pillars",
|
|
214
|
+
"bazi_annual_pillar",
|
|
215
|
+
"bazi_compatibility",
|
|
216
|
+
],
|
|
217
|
+
/** Vedic / Jyotish. */
|
|
218
|
+
vedic: ["vedic_chart", "explore_vedic_chart"],
|
|
219
|
+
/** Astrocartography. */
|
|
220
|
+
acg: ["acg_hits", "acg_power_lines"],
|
|
221
|
+
/** Electional timing. */
|
|
222
|
+
electional: [
|
|
223
|
+
"ephemeris_electional",
|
|
224
|
+
"electional_moment_analysis",
|
|
225
|
+
"electional_station_tracker",
|
|
226
|
+
"electional_angle_crossings",
|
|
227
|
+
"electional_aspect_search",
|
|
228
|
+
],
|
|
229
|
+
/** Venus Star Point / synodic-cycle work. */
|
|
230
|
+
venus: [
|
|
231
|
+
"venus_star_points",
|
|
232
|
+
"venus_star_points_conjunctions",
|
|
233
|
+
"venus_phase",
|
|
234
|
+
"venus_elongations",
|
|
235
|
+
"venus_stations",
|
|
236
|
+
"venus_eight_year_star",
|
|
237
|
+
],
|
|
238
|
+
};
|
|
239
|
+
export const TOOLSET_NAMES = Object.keys(TOOLSETS);
|
|
240
|
+
/** Resolve a toolset selection to the tool names it advertises. */
|
|
241
|
+
export function toolsetSelectionNames(names) {
|
|
242
|
+
const out = new Set(BASE_TOOLSET);
|
|
243
|
+
for (const n of names) {
|
|
244
|
+
for (const t of TOOLSETS[n] ?? [])
|
|
245
|
+
out.add(t);
|
|
246
|
+
}
|
|
247
|
+
return out;
|
|
248
|
+
}
|
|
86
249
|
/**
|
|
87
250
|
* Returns tools that should be exposed to Claude in the ListTools response.
|
|
88
251
|
* Filters out tools that have visibility restricted to the UI (app-only),
|
|
@@ -90,6 +253,9 @@ export const CORE_TOOL_NAMES = new Set([
|
|
|
90
253
|
* "core" — everything outside CORE_TOOL_NAMES.
|
|
91
254
|
*/
|
|
92
255
|
export function modelVisibleTools(transport = "stdio", surface = "full") {
|
|
256
|
+
const allowed = isToolsetSurface(surface)
|
|
257
|
+
? toolsetSelectionNames(surface.toolsets)
|
|
258
|
+
: null;
|
|
93
259
|
return Object.values(toolRegistry).filter((t) => {
|
|
94
260
|
if (transport === "http" && t.stdioOnly)
|
|
95
261
|
return false;
|
|
@@ -97,14 +263,45 @@ export function modelVisibleTools(transport = "stdio", surface = "full") {
|
|
|
97
263
|
// If visibility is explicitly ["app"] (no "model"), hide from model
|
|
98
264
|
if (vis && !vis.includes("model") && vis.includes("app"))
|
|
99
265
|
return false;
|
|
266
|
+
if (allowed)
|
|
267
|
+
return allowed.has(t.name);
|
|
100
268
|
if (surface === "core" && !CORE_TOOL_NAMES.has(t.name))
|
|
101
269
|
return false;
|
|
102
270
|
return true;
|
|
103
271
|
});
|
|
104
272
|
}
|
|
105
|
-
/**
|
|
273
|
+
/**
|
|
274
|
+
* Parses a caller-supplied surface hint.
|
|
275
|
+
*
|
|
276
|
+
* "full" -> every model-visible tool
|
|
277
|
+
* "hd" | "hd,bazi" -> those traditions plus BASE_TOOLSET
|
|
278
|
+
* anything else -> "core" (the curated default)
|
|
279
|
+
*
|
|
280
|
+
* Unknown names in a comma list are ignored rather than rejected: a typo
|
|
281
|
+
* degrades to a smaller surface, never to an error at `initialize` that would
|
|
282
|
+
* leave the connector dead. If NO name in the list is recognised, fall back to
|
|
283
|
+
* "core" so a mistyped profile can't strand a caller on BASE alone.
|
|
284
|
+
*/
|
|
106
285
|
export function parseToolSurface(raw) {
|
|
107
|
-
|
|
286
|
+
if (typeof raw !== "string")
|
|
287
|
+
return "core";
|
|
288
|
+
const value = raw.trim().toLowerCase();
|
|
289
|
+
if (value === "full")
|
|
290
|
+
return "full";
|
|
291
|
+
if (value === "" || value === "core")
|
|
292
|
+
return "core";
|
|
293
|
+
const requested = value
|
|
294
|
+
.split(",")
|
|
295
|
+
.map((s) => s.trim())
|
|
296
|
+
.filter(Boolean);
|
|
297
|
+
const known = requested.filter((n) => Object.hasOwn(TOOLSETS, n));
|
|
298
|
+
if (known.length === 0)
|
|
299
|
+
return "core";
|
|
300
|
+
return { toolsets: known };
|
|
301
|
+
}
|
|
302
|
+
/** Stable label for logs/analytics — an object surface has no useful toString. */
|
|
303
|
+
export function describeSurface(surface) {
|
|
304
|
+
return isToolsetSurface(surface) ? surface.toolsets.join("+") : surface;
|
|
108
305
|
}
|
|
109
306
|
export const toolRegistry = {};
|
|
110
307
|
export function registerTool(tool) {
|
|
@@ -27,7 +27,7 @@ registerTool({
|
|
|
27
27
|
additionalProperties: false,
|
|
28
28
|
},
|
|
29
29
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
30
|
-
annotations: { title: "Account Usage", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
30
|
+
annotations: { title: "Account Usage", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
31
31
|
handler: async (args) => {
|
|
32
32
|
const params = {};
|
|
33
33
|
if (args?.month)
|
|
@@ -39,7 +39,11 @@ registerTool({
|
|
|
39
39
|
}
|
|
40
40
|
catch (error) {
|
|
41
41
|
if (error instanceof BackendError && error.status === 401) {
|
|
42
|
+
// A real auth failure must carry isError — a success-shaped response
|
|
43
|
+
// teaches the model the call worked and it stops trying to fix auth.
|
|
44
|
+
// The guidance text is kept so hosts still show the user a way forward.
|
|
42
45
|
return {
|
|
46
|
+
isError: true,
|
|
43
47
|
content: [{
|
|
44
48
|
type: "text",
|
|
45
49
|
text: `**Not signed in.** To check usage, connect an OpenEphemeris account first.\n\n` +
|
|
@@ -60,7 +60,7 @@ registerTool({
|
|
|
60
60
|
additionalProperties: false,
|
|
61
61
|
},
|
|
62
62
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
63
|
-
annotations: { title: "Astrocartography Lines", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
63
|
+
annotations: { title: "Astrocartography Lines", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
64
64
|
handler: async (args) => {
|
|
65
65
|
validateRequired(args, ["birth_datetime", "birth_latitude", "birth_longitude"]);
|
|
66
66
|
const body = {
|
|
@@ -157,7 +157,7 @@ registerTool({
|
|
|
157
157
|
additionalProperties: false,
|
|
158
158
|
},
|
|
159
159
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
160
|
-
annotations: { title: "Astrocartography Location Hits", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
160
|
+
annotations: { title: "Astrocartography Location Hits", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
161
161
|
handler: async (args) => {
|
|
162
162
|
validateRequired(args, ["birth_datetime", "birth_latitude", "birth_longitude", "query_latitude", "query_longitude"]);
|
|
163
163
|
const body = {
|
|
@@ -158,7 +158,7 @@ registerTool({
|
|
|
158
158
|
additionalProperties: false,
|
|
159
159
|
},
|
|
160
160
|
outputSchema: OUTPUT_SCHEMA_IMAGE_AND_JSON,
|
|
161
|
-
annotations: { title: "BaZi Four Pillars Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
161
|
+
annotations: { title: "BaZi Four Pillars Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
162
162
|
handler: async (args) => {
|
|
163
163
|
const body = { ...parseBaziArgs(args) };
|
|
164
164
|
applyVisualConfig(body, args);
|
|
@@ -195,7 +195,7 @@ registerTool({
|
|
|
195
195
|
additionalProperties: false,
|
|
196
196
|
},
|
|
197
197
|
outputSchema: OUTPUT_SCHEMA_IMAGE_AND_JSON,
|
|
198
|
-
annotations: { title: "BaZi Ten Gods", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
198
|
+
annotations: { title: "BaZi Ten Gods", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
199
199
|
handler: async (args) => {
|
|
200
200
|
const body = { ...parseBaziArgs(args) };
|
|
201
201
|
applyVisualConfig(body, args);
|
|
@@ -230,7 +230,7 @@ registerTool({
|
|
|
230
230
|
additionalProperties: false,
|
|
231
231
|
},
|
|
232
232
|
outputSchema: OUTPUT_SCHEMA_IMAGE_AND_JSON,
|
|
233
|
-
annotations: { title: "BaZi Element Balance", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
233
|
+
annotations: { title: "BaZi Element Balance", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
234
234
|
handler: async (args) => {
|
|
235
235
|
const body = { ...parseBaziArgs(args) };
|
|
236
236
|
applyVisualConfig(body, args);
|
|
@@ -273,7 +273,7 @@ registerTool({
|
|
|
273
273
|
additionalProperties: false,
|
|
274
274
|
},
|
|
275
275
|
outputSchema: OUTPUT_SCHEMA_IMAGE_AND_JSON,
|
|
276
|
-
annotations: { title: "BaZi Luck Pillars", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
276
|
+
annotations: { title: "BaZi Luck Pillars", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
277
277
|
handler: async (args) => {
|
|
278
278
|
if (!args.gender) {
|
|
279
279
|
throw new Error("gender is required for luck pillar calculation. " +
|
|
@@ -316,7 +316,7 @@ registerTool({
|
|
|
316
316
|
additionalProperties: false,
|
|
317
317
|
},
|
|
318
318
|
outputSchema: OUTPUT_SCHEMA_IMAGE_AND_JSON,
|
|
319
|
-
annotations: { title: "BaZi Annual Pillar", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
319
|
+
annotations: { title: "BaZi Annual Pillar", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
320
320
|
handler: async (args) => {
|
|
321
321
|
if (!args.year) {
|
|
322
322
|
throw new Error("year is required. Provide a Gregorian year (1–9999), e.g. year=2025.");
|
|
@@ -385,7 +385,7 @@ registerTool({
|
|
|
385
385
|
additionalProperties: false,
|
|
386
386
|
},
|
|
387
387
|
outputSchema: OUTPUT_SCHEMA_IMAGE_AND_JSON,
|
|
388
|
-
annotations: { title: "BaZi Compatibility", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
388
|
+
annotations: { title: "BaZi Compatibility", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
389
389
|
handler: async (args) => {
|
|
390
390
|
const chartAComponents = parseBaziArgs({
|
|
391
391
|
year: args.chart_a_year,
|
|
@@ -447,7 +447,7 @@ registerTool({
|
|
|
447
447
|
additionalProperties: false,
|
|
448
448
|
},
|
|
449
449
|
outputSchema: OUTPUT_SCHEMA_IMAGE_AND_JSON,
|
|
450
|
-
annotations: { title: "BaZi Chart (Visual)", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
450
|
+
annotations: { title: "BaZi Chart (Visual)", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
451
451
|
handler: async (args) => {
|
|
452
452
|
const { year, month, day, hour } = parseBaziArgs(args);
|
|
453
453
|
const body = {
|
|
@@ -40,7 +40,7 @@ registerTool({
|
|
|
40
40
|
additionalProperties: false,
|
|
41
41
|
},
|
|
42
42
|
outputSchema: OUTPUT_SCHEMA_IMAGE,
|
|
43
|
-
annotations: { title: "Bi-Wheel Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
43
|
+
annotations: { title: "Bi-Wheel Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
44
44
|
handler: async (args) => {
|
|
45
45
|
validateRequired(args, ["datetime_a", "latitude_a", "longitude_a", "datetime_b", "latitude_b", "longitude_b"]);
|
|
46
46
|
assertZonedDatetime("datetime_a", args.datetime_a, args.timezone_a, "timezone_a");
|
|
@@ -45,7 +45,7 @@ registerTool({
|
|
|
45
45
|
additionalProperties: false,
|
|
46
46
|
},
|
|
47
47
|
outputSchema: OUTPUT_SCHEMA_IMAGE,
|
|
48
|
-
annotations: { title: "Chart Wheel SVG", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
48
|
+
annotations: { title: "Chart Wheel SVG", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
49
49
|
handler: async (args) => {
|
|
50
50
|
validateRequired(args, ["datetime", "latitude", "longitude"]);
|
|
51
51
|
assertZonedDatetime("datetime", args.datetime, args.timezone);
|
|
@@ -51,7 +51,7 @@ registerTool({
|
|
|
51
51
|
additionalProperties: false,
|
|
52
52
|
},
|
|
53
53
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
54
|
-
annotations: { title: "Davison Relationship Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
54
|
+
annotations: { title: "Davison Relationship Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
55
55
|
handler: async (args) => {
|
|
56
56
|
validateRequired(args, [
|
|
57
57
|
"person_a_datetime", "person_a_latitude", "person_a_longitude",
|
|
@@ -99,7 +99,7 @@ registerTool({
|
|
|
99
99
|
additionalProperties: false,
|
|
100
100
|
},
|
|
101
101
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
102
|
-
annotations: { title: "Midpoint Composite Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
102
|
+
annotations: { title: "Midpoint Composite Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
103
103
|
handler: async (args) => {
|
|
104
104
|
validateRequired(args, [
|
|
105
105
|
"person_a_datetime", "person_a_latitude", "person_a_longitude",
|
|
@@ -147,7 +147,7 @@ registerTool({
|
|
|
147
147
|
additionalProperties: false,
|
|
148
148
|
},
|
|
149
149
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
150
|
-
annotations: { title: "Synastry House Overlay", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
150
|
+
annotations: { title: "Synastry House Overlay", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
151
151
|
handler: async (args) => {
|
|
152
152
|
validateRequired(args, [
|
|
153
153
|
"person_a_datetime", "person_a_latitude", "person_a_longitude",
|
|
@@ -193,7 +193,7 @@ registerTool({
|
|
|
193
193
|
additionalProperties: false,
|
|
194
194
|
},
|
|
195
195
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
196
|
-
annotations: { title: "Transits to Natal Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
196
|
+
annotations: { title: "Transits to Natal Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
197
197
|
handler: async (args) => {
|
|
198
198
|
validateRequired(args, ["natal_datetime", "natal_latitude", "natal_longitude"]);
|
|
199
199
|
assertPersonZoned("natal", args);
|
|
@@ -42,7 +42,7 @@ registerTool({
|
|
|
42
42
|
additionalProperties: false,
|
|
43
43
|
},
|
|
44
44
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
45
|
-
annotations: { title: "Eclipse Finder", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
45
|
+
annotations: { title: "Eclipse Finder", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
46
46
|
handler: async (args) => {
|
|
47
47
|
validateRequired(args, ["eclipse_type"]);
|
|
48
48
|
validateCoordinates(args, "latitude", "longitude");
|
|
@@ -59,7 +59,7 @@ registerTool({
|
|
|
59
59
|
additionalProperties: false,
|
|
60
60
|
},
|
|
61
61
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
62
|
-
annotations: { title: "Electional Timing Window", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
62
|
+
annotations: { title: "Electional Timing Window", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
63
63
|
handler: async (args) => {
|
|
64
64
|
validateRequired(args, ["start_date", "end_date", "latitude", "longitude"]);
|
|
65
65
|
const query = {
|
|
@@ -110,7 +110,7 @@ registerTool({
|
|
|
110
110
|
additionalProperties: false,
|
|
111
111
|
},
|
|
112
112
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
113
|
-
annotations: { title: "Moment Quality Analysis", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
113
|
+
annotations: { title: "Moment Quality Analysis", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
114
114
|
handler: async (args) => {
|
|
115
115
|
const query = {};
|
|
116
116
|
if (args.date)
|
|
@@ -164,7 +164,7 @@ registerTool({
|
|
|
164
164
|
additionalProperties: false,
|
|
165
165
|
},
|
|
166
166
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
167
|
-
annotations: { title: "Retrograde Station Tracker", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
167
|
+
annotations: { title: "Retrograde Station Tracker", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
168
168
|
handler: async (args) => {
|
|
169
169
|
const query = {};
|
|
170
170
|
// Map human-readable planet names to IDs if needed
|
|
@@ -224,7 +224,7 @@ registerTool({
|
|
|
224
224
|
additionalProperties: false,
|
|
225
225
|
},
|
|
226
226
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
227
|
-
annotations: { title: "Active Aspects Search", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
227
|
+
annotations: { title: "Active Aspects Search", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
228
228
|
handler: async (args) => {
|
|
229
229
|
const query = {};
|
|
230
230
|
if (args.date)
|
|
@@ -302,7 +302,7 @@ registerTool({
|
|
|
302
302
|
additionalProperties: false,
|
|
303
303
|
},
|
|
304
304
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
305
|
-
annotations: { title: "Angle Crossing Search", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
305
|
+
annotations: { title: "Angle Crossing Search", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
306
306
|
handler: async (args) => {
|
|
307
307
|
validateRequired(args, ["latitude", "longitude"]);
|
|
308
308
|
const query = {
|
|
@@ -40,7 +40,7 @@ registerTool({
|
|
|
40
40
|
additionalProperties: false,
|
|
41
41
|
},
|
|
42
42
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
43
|
-
annotations: { title: "Planet Position", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
43
|
+
annotations: { title: "Planet Position", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
44
44
|
handler: async (args) => {
|
|
45
45
|
validateRequired(args, ["planet_id", "datetime"]);
|
|
46
46
|
const body = {
|
|
@@ -89,7 +89,7 @@ registerTool({
|
|
|
89
89
|
additionalProperties: false,
|
|
90
90
|
},
|
|
91
91
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
92
|
-
annotations: { title: "House Cusps", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
92
|
+
annotations: { title: "House Cusps", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
93
93
|
handler: async (args) => {
|
|
94
94
|
validateRequired(args, ["datetime", "latitude", "longitude"]);
|
|
95
95
|
const body = {
|
|
@@ -56,7 +56,7 @@ registerTool({
|
|
|
56
56
|
additionalProperties: false,
|
|
57
57
|
},
|
|
58
58
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
59
|
-
annotations: { title: "Natal Batch Charts", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
59
|
+
annotations: { title: "Natal Batch Charts", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
60
60
|
handler: async (args) => {
|
|
61
61
|
validateRequired(args, ["subjects"]);
|
|
62
62
|
args.subjects.forEach((s, i) => assertZonedDatetime(`subjects[${i}].datetime`, s?.datetime, s?.timezone, `subjects[${i}].timezone`));
|
|
@@ -82,7 +82,7 @@ registerTool({
|
|
|
82
82
|
additionalProperties: false,
|
|
83
83
|
},
|
|
84
84
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
85
|
-
annotations: { title: "Essential Dignities", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
85
|
+
annotations: { title: "Essential Dignities", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
86
86
|
handler: async (args) => {
|
|
87
87
|
validateRequired(args, ["datetime"]);
|
|
88
88
|
return await getActiveClient().post("/ephemeris/dignities", {
|
|
@@ -122,7 +122,7 @@ registerTool({
|
|
|
122
122
|
additionalProperties: false,
|
|
123
123
|
},
|
|
124
124
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
125
|
-
annotations: { title: "Retrograde Status", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
125
|
+
annotations: { title: "Retrograde Status", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
126
126
|
handler: async (args) => {
|
|
127
127
|
validateRequired(args, ["datetime"]);
|
|
128
128
|
const client = getActiveClient();
|
|
@@ -173,7 +173,7 @@ registerTool({
|
|
|
173
173
|
additionalProperties: false,
|
|
174
174
|
},
|
|
175
175
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
176
|
-
annotations: { title: "Midpoints", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
176
|
+
annotations: { title: "Midpoints", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
177
177
|
handler: async (args) => {
|
|
178
178
|
validateRequired(args, ["datetime", "latitude", "longitude"]);
|
|
179
179
|
return await getActiveClient().post("/ephemeris/midpoints", {
|
|
@@ -219,7 +219,7 @@ registerTool({
|
|
|
219
219
|
additionalProperties: false,
|
|
220
220
|
},
|
|
221
221
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
222
|
-
annotations: { title: "Fixed Stars", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
222
|
+
annotations: { title: "Fixed Stars", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
223
223
|
handler: async (args) => {
|
|
224
224
|
validateRequired(args, ["datetime"]);
|
|
225
225
|
// A lone latitude would silently drop the angles from the scan, which is
|
|
@@ -262,7 +262,7 @@ registerTool({
|
|
|
262
262
|
additionalProperties: false,
|
|
263
263
|
},
|
|
264
264
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
265
|
-
annotations: { title: "Hermetic Lots", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
265
|
+
annotations: { title: "Hermetic Lots", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
266
266
|
handler: async (args) => {
|
|
267
267
|
validateRequired(args, ["datetime", "latitude", "longitude"]);
|
|
268
268
|
return await getActiveClient().post("/ephemeris/hermetic-lots", {
|
|
@@ -290,7 +290,7 @@ registerTool({
|
|
|
290
290
|
additionalProperties: false,
|
|
291
291
|
},
|
|
292
292
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
293
|
-
annotations: { title: "Angles & Points", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
293
|
+
annotations: { title: "Angles & Points", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
294
294
|
handler: async (args) => {
|
|
295
295
|
validateRequired(args, ["datetime", "latitude", "longitude"]);
|
|
296
296
|
return await getActiveClient().post("/ephemeris/angles-points", {
|
|
@@ -319,7 +319,7 @@ registerTool({
|
|
|
319
319
|
additionalProperties: false,
|
|
320
320
|
},
|
|
321
321
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
322
|
-
annotations: { title: "Aspect Check", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
322
|
+
annotations: { title: "Aspect Check", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
323
323
|
handler: async (args) => {
|
|
324
324
|
validateRequired(args, ["longitude_1", "longitude_2"]);
|
|
325
325
|
// Backend field names are longitude1 / longitude2 (no underscore)
|
|
@@ -49,7 +49,7 @@ registerTool({
|
|
|
49
49
|
additionalProperties: false,
|
|
50
50
|
},
|
|
51
51
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
52
|
-
annotations: { title: "Human Design Bodygraph", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
52
|
+
annotations: { title: "Human Design Bodygraph", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
53
53
|
handler: async (args) => {
|
|
54
54
|
validateRequired(args, ["datetime"]);
|
|
55
55
|
const body = {
|
|
@@ -58,7 +58,7 @@ registerTool({
|
|
|
58
58
|
additionalProperties: false,
|
|
59
59
|
},
|
|
60
60
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
61
|
-
annotations: { title: "HD Planetary Return", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
61
|
+
annotations: { title: "HD Planetary Return", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
62
62
|
handler: async (args) => {
|
|
63
63
|
validateRequired(args, ["planet", "datetime", "return_year"]);
|
|
64
64
|
return await getActiveClient().request("POST", "/human-design/cycles/return", {
|
|
@@ -112,7 +112,7 @@ registerTool({
|
|
|
112
112
|
additionalProperties: false,
|
|
113
113
|
},
|
|
114
114
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
115
|
-
annotations: { title: "HD Opposition Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
115
|
+
annotations: { title: "HD Opposition Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
116
116
|
handler: async (args) => {
|
|
117
117
|
validateRequired(args, ["planet", "datetime", "target_year"]);
|
|
118
118
|
return await getActiveClient().request("POST", "/human-design/cycles/opposition", {
|