@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/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,49 @@ Version numbering follows [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [4.8.1] — 2026-08-02
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- Every tool now declares all four MCP annotation hints explicitly
|
|
14
|
+
(`readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`).
|
|
15
|
+
Notably `dev_write_api` is `destructiveHint: false` — every allowlisted
|
|
16
|
+
write is stateless chart computation — so strict clients no longer gate it
|
|
17
|
+
behind a confirmation, and `openWorldHint` is uniformly `false` (the server
|
|
18
|
+
reaches only the fixed OpenEphemeris API).
|
|
19
|
+
- `account_usage` reports a real authentication failure as a tool error
|
|
20
|
+
(`isError: true`) instead of a success-shaped message.
|
|
21
|
+
|
|
22
|
+
## [4.8.0] — 2026-08-02
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
- **Toolsets by tradition.** Tool definitions are re-sent to the model on every
|
|
26
|
+
message, so callers who work in one tradition can now advertise just that
|
|
27
|
+
tradition instead of the general-purpose default: `OPENEPHEMERIS_TOOLS=hd`
|
|
28
|
+
(stdio) or `?profile=hd,bazi` (hosted). Eight toolsets — `astrology`, `moon`,
|
|
29
|
+
`hd`, `bazi`, `vedic`, `acg`, `electional`, `venus` — each including
|
|
30
|
+
geocoding, account usage, and the API escape hatch. A Human Design session
|
|
31
|
+
drops from ~19,100 to ~7,800 advertised tokens (−59%); Vedic to ~3,400
|
|
32
|
+
(−82%). `astrology,moon` matches the default's tool count at ~1,700 fewer
|
|
33
|
+
tokens while covering the long tail (dignities, midpoints, hermetic lots,
|
|
34
|
+
fixed stars, composites) the curated set omits. As with `core`/`full`, this
|
|
35
|
+
filters `tools/list` only — every tool stays callable by name. Also exposed
|
|
36
|
+
as the `toolSurface` option in the Smithery config.
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
- `explore_human_design` now states its real credit cost: 4 where the bodygraph
|
|
40
|
+
renders (2 chart + 2 visual), 2 in text-only hosts — it previously claimed a
|
|
41
|
+
flat 2. `ephemeris_moon_phase` (2, not 1) and `ephemeris_next_lunar_phase`
|
|
42
|
+
(1–2 per occurrence) corrected the same way.
|
|
43
|
+
- `explore_bi_wheel` in synastry mode now requires Person 2's coordinates (or a
|
|
44
|
+
resolvable `person2_location`) instead of silently casting Person 2's chart
|
|
45
|
+
for 0°N 0°E.
|
|
46
|
+
- Chart-wheel widget escapes birth parameters before rendering them into the
|
|
47
|
+
info chips.
|
|
48
|
+
- OAuth token endpoint binds authorization codes to their `redirect_uri` and
|
|
49
|
+
`client_id`; both checks were previously skippable by omitting the parameter.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
10
53
|
## [4.7.0] — 2026-08-02
|
|
11
54
|
|
|
12
55
|
Historically correct birth-time conversion. IANA timezone databases — including
|
package/README.md
CHANGED
|
@@ -358,6 +358,38 @@ On the remote HTTP server, append `?profile=full` to the connector URL (or send
|
|
|
358
358
|
https://mcp.openephemeris.com/mcp?profile=full
|
|
359
359
|
```
|
|
360
360
|
|
|
361
|
+
### Toolsets by tradition
|
|
362
|
+
|
|
363
|
+
If you work in one tradition, ask for it by name instead of taking the general-purpose default. You get that tradition in full — including the long-tail tools the core set leaves out — for a fraction of the context.
|
|
364
|
+
|
|
365
|
+
```bash
|
|
366
|
+
OPENEPHEMERIS_TOOLS=hd npx -y @openephemeris/mcp-server # Human Design
|
|
367
|
+
OPENEPHEMERIS_TOOLS=astrology,moon npx -y @openephemeris/mcp-server
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
```
|
|
371
|
+
https://mcp.openephemeris.com/mcp?profile=hd,bazi
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
| Toolset | What it covers | Tools | Approx. tokens |
|
|
375
|
+
|---|---|---:|---:|
|
|
376
|
+
| `astrology` | Natal, transits, synastry, progressions, returns, relocation, dignities, midpoints, lots, fixed stars, composites | 32 | 15,800 |
|
|
377
|
+
| `hd` | Human Design charts, transits, connection charts, penta, bodygraph | 14 | 7,800 |
|
|
378
|
+
| `bazi` | Four Pillars, Ten Gods, element balance, luck pillars, compatibility | 13 | 7,200 |
|
|
379
|
+
| `electional` | Timing windows, angle crossings, stations, moment analysis | 10 | 4,600 |
|
|
380
|
+
| `moon` | Phases, void-of-course, eclipses | 9 | 4,100 |
|
|
381
|
+
| `venus` | Star points, phases, elongations, stations | 11 | 3,700 |
|
|
382
|
+
| `acg` | Astrocartography lines and hits | 7 | 3,700 |
|
|
383
|
+
| `vedic` | Jyotish Rashi chart | 7 | 3,400 |
|
|
384
|
+
| — | *core (default)* | 36 | 19,100 |
|
|
385
|
+
| — | *full* | 70 | 34,600 |
|
|
386
|
+
|
|
387
|
+
Every selection also includes geocoding (`location_search`, `timezone_resolve`), `account_usage`, and the allowlist-gated proxy — so a birthplace is always resolvable and nothing is stranded.
|
|
388
|
+
|
|
389
|
+
Combine with commas; unknown names are ignored rather than rejected, so a typo degrades to a smaller surface instead of a dead connector. As with `core`/`full`, this only filters `tools/list` — every tool remains callable by name.
|
|
390
|
+
|
|
391
|
+
Why it matters: tool definitions are re-sent to the model on **every** pass. `astrology,moon` advertises the same number of tools as the default but costs ~1,700 fewer tokens per message and covers more of the tradition.
|
|
392
|
+
|
|
361
393
|
The surface is fixed when the session initializes — this server does not advertise `tools.listChanged`, so switching requires reconnecting. `dev_list_allowed` enumerates every operation reachable through the generic proxy regardless of surface.
|
|
362
394
|
|
|
363
395
|
## Contributing & Support
|
|
@@ -443,8 +475,8 @@ Generated by `npm run sync:readme` from `config/dev-allowlist.json` and the live
|
|
|
443
475
|
- Allowlisted operations: **31**
|
|
444
476
|
- Methods: `GET=5`, `POST=26`, `PUT=0`, `PATCH=0`, `DELETE=0`
|
|
445
477
|
- Registered tools (`OPENEPHEMERIS_PROFILE=dev`): **92**
|
|
446
|
-
- Typed tools: `account_usage`, `acg_hits`, `acg_power_lines`, `auth_login`, `auth_logout`, `auth_status`, `bazi_annual_pillar`, `bazi_chart`, `bazi_compatibility`, `bazi_element_balance`, `bazi_luck_pillars`, `bazi_recalculate`, `bazi_ten_gods`, `bi_wheel_on_cross_aspect_click`, `bi_wheel_on_house_click`, `bi_wheel_on_planet_click`, `bi_wheel_recalculate`, `bi_wheel_synopsis`, `bodygraph_recalculate`, `chart_wheel_on_aspect_click`, `chart_wheel_on_house_click`, `chart_wheel_on_planet_click`, `chart_wheel_recalculate`, `chinese_bazi`, `
|
|
447
|
-
- Generic tools:
|
|
478
|
+
- Typed tools: `account_usage`, `acg_hits`, `acg_power_lines`, `auth_login`, `auth_logout`, `auth_status`, `bazi_annual_pillar`, `bazi_chart`, `bazi_compatibility`, `bazi_element_balance`, `bazi_luck_pillars`, `bazi_recalculate`, `bazi_ten_gods`, `bi_wheel_on_cross_aspect_click`, `bi_wheel_on_house_click`, `bi_wheel_on_planet_click`, `bi_wheel_recalculate`, `bi_wheel_synopsis`, `bodygraph_recalculate`, `chart_wheel_on_aspect_click`, `chart_wheel_on_house_click`, `chart_wheel_on_planet_click`, `chart_wheel_recalculate`, `chinese_bazi`, `electional_angle_crossings`, `electional_aspect_search`, `electional_moment_analysis`, `electional_station_tracker`, `ephemeris_angles_points`, `ephemeris_aspect_check`, `ephemeris_bi_wheel`, `ephemeris_chart_wheel`, `ephemeris_composite`, `ephemeris_composite_midpoint`, `ephemeris_dignities`, `ephemeris_electional`, `ephemeris_fixed_stars`, `ephemeris_hermetic_lots`, `ephemeris_house_cusps`, `ephemeris_lunar_return`, `ephemeris_midpoints`, `ephemeris_moon_phase`, `ephemeris_natal_batch`, `ephemeris_natal_chart`, `ephemeris_natal_transits`, `ephemeris_next_eclipse`, `ephemeris_next_lunar_phase`, `ephemeris_overlay`, `ephemeris_planet_position`, `ephemeris_planetary_return`, `ephemeris_progressed_chart`, `ephemeris_relocation`, `ephemeris_retrograde_status`, `ephemeris_solar_return`, `ephemeris_synastry`, `ephemeris_transits`, `explore_bazi_chart`, `explore_bi_wheel`, `explore_human_design`, `explore_human_design_connection`, `explore_human_design_transit`, `explore_moon_phase`, `explore_natal_chart`, `explore_transit_timeline`, `explore_vedic_chart`, `hd_on_center_click`, `hd_on_channel_click`, `hd_on_connection_channel_click`, `hd_on_gate_click`, `hd_on_planet_click`, `hd_on_transit_channel_click`, `hd_on_variable_click`, `hd_opposition`, `hd_planetary_return`, `human_design_bodygraph`, `human_design_chart`, `human_design_composite`, `human_design_penta`, `location_search`, `moon_phase_recalculate`, `timezone_resolve`, `vedic_chart`, `vedic_chart_recalculate`, `venus_eight_year_star`, `venus_elongations`, `venus_phase`, `venus_star_points`, `venus_star_points_conjunctions`, `venus_stations`
|
|
479
|
+
- Generic tools: `dev_list_allowed`, `dev_read_api`, `dev_write_api`
|
|
448
480
|
|
|
449
481
|
### Allowlist Families
|
|
450
482
|
|
|
@@ -466,5 +498,5 @@ Most LLMs (like Claude and ChatGPT) struggle heavily with astronomical calculati
|
|
|
466
498
|
|
|
467
499
|
By pairing LLMs with the OpenEphemeris MCP server, your agents can instantly access:
|
|
468
500
|
- **Zero-hallucination coordinates**: Direct, sub-arcsecond NASA JPL DE440 calculations spanning 1,100 years of astronomical data.
|
|
469
|
-
- **LLM-optimized tokens (`format=llm`)**: We compress standard 25,000 token JSON chart responses into minimal text blocks, cutting your inference costs by 50
|
|
501
|
+
- **LLM-optimized tokens (`format=llm`)**: We compress standard 25,000 token JSON chart responses into minimal text blocks, cutting your inference costs by 50–73% depending on endpoint.
|
|
470
502
|
- **Ready-to-use astrology layers**: Built-in support for Astrocartography geoJSON lines, Hermetic Lots, Fixed Stars, and complex Human Design matrix generation.
|
package/dist/index.js
CHANGED
|
@@ -73,9 +73,12 @@ const server = new Server({
|
|
|
73
73
|
"See the 'welcome_to_open_ephemeris' prompt for orientation.\n\n" +
|
|
74
74
|
DATETIME_CONTRACT_INSTRUCTIONS,
|
|
75
75
|
});
|
|
76
|
-
// Which slice of the registry this process advertises. Tools outside the
|
|
76
|
+
// Which slice of the registry this process advertises. Tools outside the
|
|
77
77
|
// surface stay callable by name — this only controls what tools/list returns.
|
|
78
|
-
//
|
|
78
|
+
// OPENEPHEMERIS_TOOLS=full advertise everything
|
|
79
|
+
// OPENEPHEMERIS_TOOLS=hd,bazi advertise those traditions (+ geocoding,
|
|
80
|
+
// account and the escape hatch)
|
|
81
|
+
// unset the curated core set
|
|
79
82
|
const STDIO_SURFACE = parseToolSurface(process.env.OPENEPHEMERIS_TOOLS);
|
|
80
83
|
/**
|
|
81
84
|
* Stable pseudonymous id for this install. Derived from the configured
|
package/dist/oauth/token.js
CHANGED
|
@@ -106,11 +106,38 @@ async function handleAuthorizationCode(req, res, store) {
|
|
|
106
106
|
});
|
|
107
107
|
return;
|
|
108
108
|
}
|
|
109
|
-
// Verify redirect_uri exact match
|
|
110
|
-
|
|
109
|
+
// Verify redirect_uri exact match.
|
|
110
|
+
//
|
|
111
|
+
// This used to read `if (redirect_uri && ...)`, so a client that simply omitted
|
|
112
|
+
// the parameter skipped the check entirely. RFC 6749 §4.1.3 requires the value
|
|
113
|
+
// to be present AND identical whenever the authorization request carried one,
|
|
114
|
+
// precisely so a dropped parameter cannot be the bypass. PKCE still gates the
|
|
115
|
+
// exchange, but that is the last line, not the only one.
|
|
116
|
+
if (stored.redirect_uri) {
|
|
117
|
+
if (!redirect_uri) {
|
|
118
|
+
res.status(400).json({
|
|
119
|
+
error: "invalid_request",
|
|
120
|
+
error_description: "Missing redirect_uri. It is required because the authorization request included one.",
|
|
121
|
+
});
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (!timingSafeStringEqual(redirect_uri, stored.redirect_uri)) {
|
|
125
|
+
res.status(400).json({
|
|
126
|
+
error: "invalid_grant",
|
|
127
|
+
error_description: "redirect_uri does not match the value used during authorization.",
|
|
128
|
+
});
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// Bind the code to the client it was issued to. `client_id` was read off the
|
|
133
|
+
// request but never compared, so any client holding the verifier could redeem
|
|
134
|
+
// another client's code. DCR client_ids are deterministic and non-secret
|
|
135
|
+
// (see oauth/dcr.ts), which makes this binding the only thing distinguishing
|
|
136
|
+
// one registered client from another.
|
|
137
|
+
if (stored.client_id && client_id && !timingSafeStringEqual(client_id, stored.client_id)) {
|
|
111
138
|
res.status(400).json({
|
|
112
139
|
error: "invalid_grant",
|
|
113
|
-
error_description: "
|
|
140
|
+
error_description: "client_id does not match the client the authorization code was issued to.",
|
|
114
141
|
});
|
|
115
142
|
return;
|
|
116
143
|
}
|
package/dist/server-sse.js
CHANGED
|
@@ -23,7 +23,7 @@ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/
|
|
|
23
23
|
import { InMemoryEventStore } from "./event-store.js";
|
|
24
24
|
import { captureEvent, distinctIdFor } from "./analytics.js";
|
|
25
25
|
import { CallToolRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, isInitializeRequest, } from "@modelcontextprotocol/sdk/types.js";
|
|
26
|
-
import { initTools, toolRegistry, formatToolResponse, formatToolError, modelVisibleTools, parseToolSurface } from "./tools/index.js";
|
|
26
|
+
import { initTools, toolRegistry, formatToolResponse, formatToolError, modelVisibleTools, parseToolSurface, describeSurface } from "./tools/index.js";
|
|
27
27
|
import { DATETIME_CONTRACT_INSTRUCTIONS } from "./tools/datetime.js";
|
|
28
28
|
import { BackendClient, runWithClient } from "./backend/client.js";
|
|
29
29
|
import { CHART_WHEEL_RESOURCE_URI, CHART_WHEEL_MIME_TYPE, getChartWheelBundle, } from "./tools/apps/chart-wheel-app.js";
|
|
@@ -215,7 +215,10 @@ function transportProps(server, surface) {
|
|
|
215
215
|
const info = server.getClientVersion();
|
|
216
216
|
return {
|
|
217
217
|
transport: "http",
|
|
218
|
-
|
|
218
|
+
// describeSurface, not the raw value: a toolset selection is an object and
|
|
219
|
+
// would land in PostHog as "[object Object]", making the property useless
|
|
220
|
+
// for exactly the question it exists to answer — which surfaces get used.
|
|
221
|
+
surface: describeSurface(surface),
|
|
219
222
|
client_name: info?.name ?? "unknown",
|
|
220
223
|
client_version: info?.version ?? "unknown",
|
|
221
224
|
server_version: version,
|
|
@@ -848,6 +851,8 @@ export async function createSseApp() {
|
|
|
848
851
|
// `tools.listChanged`, so a host has no obligation to re-fetch the list.
|
|
849
852
|
// Opt into the full surface with `?profile=full` on the connector URL, or
|
|
850
853
|
// an `X-OE-Tool-Surface: full` header where the host allows custom headers.
|
|
854
|
+
// A comma list of traditions works the same way — `?profile=hd,bazi` —
|
|
855
|
+
// and advertises just those plus geocoding, account and the escape hatch.
|
|
851
856
|
const surface = parseToolSurface(req.query.profile ?? req.headers["x-oe-tool-surface"]);
|
|
852
857
|
const server = createMcpServer(analyticsId, surface);
|
|
853
858
|
// Fire session_init after the handshake so getClientVersion() is populated
|
|
@@ -115,9 +115,9 @@ registerTool({
|
|
|
115
115
|
"(Year = ancestry/early life, Month = parents/career, Day = self/spouse, Hour = children/later life).\n\n" +
|
|
116
116
|
"CREDIT COST: 3 credits per call (1 base + 2 visual render).\n\n" +
|
|
117
117
|
"Use this for a rich, interactive BaZi experience in MCP Apps-capable hosts (Claude Desktop). " +
|
|
118
|
-
"Falls back to a text summary in other hosts
|
|
119
|
-
"
|
|
120
|
-
"on the full
|
|
118
|
+
"Falls back to a text summary in other hosts, so it is also the right call without a " +
|
|
119
|
+
"renderer. Deeper derivations (Ten Gods, element balance, luck pillars) and a plain " +
|
|
120
|
+
"non-visual pillars lookup have dedicated tools on the full surface (`?profile=full`).",
|
|
121
121
|
inputSchema: {
|
|
122
122
|
type: "object",
|
|
123
123
|
properties: {
|
|
@@ -204,7 +204,7 @@ registerTool({
|
|
|
204
204
|
required: ["year", "month", "day"],
|
|
205
205
|
},
|
|
206
206
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
207
|
-
annotations: { title: "Recalculate BaZi Chart", readOnlyHint: true, openWorldHint: false },
|
|
207
|
+
annotations: { title: "Recalculate BaZi Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
208
208
|
_meta: { ui: { resourceUri: BAZI_RESOURCE_URI, visibility: ["app"] } },
|
|
209
209
|
handler: async (args) => {
|
|
210
210
|
const components = parseBaziArgs(args);
|
|
@@ -104,13 +104,21 @@ const HOUSE_SYSTEM_MAP = {
|
|
|
104
104
|
campanus: "C", regiomontanus: "R",
|
|
105
105
|
};
|
|
106
106
|
function buildNatalBody(datetime, lat, lon, timezone, houseSystem = "placidus") {
|
|
107
|
+
// A natal chart with no birthplace is never meaningful — houses and angles are
|
|
108
|
+
// derived entirely from it. This used to read `lat ?? 0`, which cast the chart
|
|
109
|
+
// for 0°N 0°E and returned it as though nothing were wrong. Callers that can
|
|
110
|
+
// legitimately omit coords resolve them first (see coordsFromArgsOrLocation) or
|
|
111
|
+
// fall back to Person 1's; anything reaching here without them is a bug.
|
|
112
|
+
if (lat == null || lon == null) {
|
|
113
|
+
throw new Error("Internal: buildNatalBody requires latitude and longitude — refusing to cast a chart for 0°N 0°E.");
|
|
114
|
+
}
|
|
107
115
|
const body = {
|
|
108
116
|
subject: {
|
|
109
117
|
name: "MCP Request",
|
|
110
118
|
birth_datetime: { iso: datetime },
|
|
111
119
|
birth_location: {
|
|
112
|
-
latitude: { decimal: lat
|
|
113
|
-
longitude: { decimal: lon
|
|
120
|
+
latitude: { decimal: lat },
|
|
121
|
+
longitude: { decimal: lon },
|
|
114
122
|
},
|
|
115
123
|
},
|
|
116
124
|
configuration: {
|
|
@@ -413,16 +421,15 @@ registerTool({
|
|
|
413
421
|
"CREDIT COST: 2 credits per call for most modes (1 per wheel computed); " +
|
|
414
422
|
"solar_return/lunar_return modes cost 6 (5 for the return + 1 for the natal wheel).\n\n" +
|
|
415
423
|
"The inner wheel is always Person 1's natal chart. The outer ring depends on mode:\n" +
|
|
416
|
-
"• synastry — Person 2's natal chart (
|
|
417
|
-
"• transit — Transiting planets for a given date
|
|
418
|
-
"• progressed — Secondary progressed positions (person2_datetime = target date)
|
|
419
|
-
"• solar_return — Nearest solar return chart (person2_datetime = year to return for)
|
|
420
|
-
"• lunar_return — Nearest lunar return chart (person2_datetime = target month)
|
|
421
|
-
"• solar_arc — Solar arc directed positions (person2_datetime = target date)
|
|
422
|
-
"Cross-aspects
|
|
423
|
-
"
|
|
424
|
-
"
|
|
425
|
-
"MCP Apps-capable hosts (Claude Desktop) and falls back to static SVG in other hosts. " +
|
|
424
|
+
"• synastry — Person 2's natal chart (needs Person 2's own birthplace).\n" +
|
|
425
|
+
"• transit — Transiting planets for a given date.\n" +
|
|
426
|
+
"• progressed — Secondary progressed positions (person2_datetime = target date).\n" +
|
|
427
|
+
"• solar_return — Nearest solar return chart (person2_datetime = year to return for).\n" +
|
|
428
|
+
"• lunar_return — Nearest lunar return chart (person2_datetime = target month).\n" +
|
|
429
|
+
"• solar_arc — Solar arc directed positions (person2_datetime = target date).\n" +
|
|
430
|
+
"Cross-aspects are drawn as coloured dashed lines; click any planet, aspect line, or " +
|
|
431
|
+
"house cusp for interpretation. Prefer this whenever the user should SEE the comparison " +
|
|
432
|
+
"— it renders interactively in MCP Apps hosts and falls back to static SVG elsewhere. " +
|
|
426
433
|
"For raw synastry data without a visual, use ephemeris_synastry.",
|
|
427
434
|
inputSchema: {
|
|
428
435
|
type: "object",
|
|
@@ -444,8 +451,8 @@ registerTool({
|
|
|
444
451
|
"synastry = Person 2 birth datetime; transit/progressed/solar_arc = target date; " +
|
|
445
452
|
"solar_return/lunar_return = any date within the target year/month. " + DATETIME_DESC,
|
|
446
453
|
},
|
|
447
|
-
person2_latitude: { type: "number", description: "Birth latitude for Person 2
|
|
448
|
-
person2_longitude: { type: "number", description: "Birth longitude for Person 2
|
|
454
|
+
person2_latitude: { type: "number", description: "Birth latitude for Person 2. REQUIRED in synastry unless `person2_location` is given — never inferred from Person 1. Other modes default to Person 1's coords." },
|
|
455
|
+
person2_longitude: { type: "number", description: "Birth longitude for Person 2. REQUIRED in synastry unless `person2_location` is given. Other modes default to Person 1's coords." },
|
|
449
456
|
person2_timezone: timezoneProperty("Person 2 / the outer chart", "America/Chicago"),
|
|
450
457
|
person2_name: {
|
|
451
458
|
type: "string",
|
|
@@ -453,7 +460,11 @@ registerTool({
|
|
|
453
460
|
},
|
|
454
461
|
location: {
|
|
455
462
|
type: "string",
|
|
456
|
-
description: "Person 1 / Natal location. Prefer a plain place name like 'New York, NY' —
|
|
463
|
+
description: "Person 1 / Natal location. Prefer a plain place name like 'New York, NY' — resolved via the same lookup `location_search` uses; ambiguous names throw with a disambiguation hint. Supplied person1_latitude/person1_longitude win.",
|
|
464
|
+
},
|
|
465
|
+
person2_location: {
|
|
466
|
+
type: "string",
|
|
467
|
+
description: "Person 2 / outer-chart place name, resolved like `location`. person2_latitude/person2_longitude win.",
|
|
457
468
|
},
|
|
458
469
|
mode: {
|
|
459
470
|
type: "string",
|
|
@@ -493,10 +504,26 @@ registerTool({
|
|
|
493
504
|
const lat1 = resolved1.latitude;
|
|
494
505
|
const lon1 = resolved1.longitude;
|
|
495
506
|
const tz1 = args.person1_timezone ?? resolved1.timezone;
|
|
507
|
+
const resolved2 = await coordsFromArgsOrLocation({
|
|
508
|
+
latitude: args.person2_latitude,
|
|
509
|
+
longitude: args.person2_longitude,
|
|
510
|
+
timezone: args.person2_timezone,
|
|
511
|
+
location: args.person2_location,
|
|
512
|
+
});
|
|
513
|
+
// Only synastry casts a standalone chart from Person 2's own birthplace. The
|
|
514
|
+
// other modes either fall back to Person 1's coords (transit, *_return) or
|
|
515
|
+
// ignore lat2/lon2 entirely (progressed, solar_arc), so an unresolved Person 2
|
|
516
|
+
// is a hard error there alone. Without this the coords fell through to
|
|
517
|
+
// `lat ?? 0` in buildNatalBody and silently produced a chart cast for
|
|
518
|
+
// 0°N 0°E — no error, no warning, wrong houses and angles.
|
|
519
|
+
if (mode === "synastry" && (resolved2.latitude == null || resolved2.longitude == null)) {
|
|
520
|
+
throw new Error("explore_bi_wheel in synastry mode requires either `person2_latitude` + `person2_longitude` " +
|
|
521
|
+
"or a resolvable `person2_location` name. Person 2's birthplace is never inferred from Person 1's.");
|
|
522
|
+
}
|
|
496
523
|
const dt2 = String(args.person2_datetime);
|
|
497
|
-
const lat2 =
|
|
498
|
-
const lon2 =
|
|
499
|
-
const tz2 = args.person2_timezone;
|
|
524
|
+
const lat2 = resolved2.latitude;
|
|
525
|
+
const lon2 = resolved2.longitude;
|
|
526
|
+
const tz2 = args.person2_timezone ?? resolved2.timezone;
|
|
500
527
|
const loc1 = String(args.person1_name ?? resolved1.location ?? args.location ?? `${lat1},${lon1}`);
|
|
501
528
|
const loc2 = MODE_META[mode].outerLabel(dt2, args.person2_name);
|
|
502
529
|
// Fetch inner (natal) and outer (mode-dependent) charts in parallel where possible
|
|
@@ -561,7 +588,7 @@ registerTool({
|
|
|
561
588
|
required: ["mode", "person1_datetime", "person1_latitude", "person1_longitude", "person2_datetime"],
|
|
562
589
|
},
|
|
563
590
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
564
|
-
annotations: { title: "Recalculate Bi-Wheel", readOnlyHint: true, openWorldHint: false },
|
|
591
|
+
annotations: { title: "Recalculate Bi-Wheel", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
565
592
|
_meta: { ui: { resourceUri: BI_WHEEL_RESOURCE_URI, visibility: ["app"] } },
|
|
566
593
|
handler: async (args) => {
|
|
567
594
|
const client = getActiveClient();
|
|
@@ -575,6 +602,13 @@ registerTool({
|
|
|
575
602
|
const lat2 = args.person2_latitude;
|
|
576
603
|
const lon2 = args.person2_longitude;
|
|
577
604
|
const tz2 = args.person2_timezone;
|
|
605
|
+
// Same 0°N 0°E trap as explore_bi_wheel: person1 coords are required by the
|
|
606
|
+
// schema but person2's cannot be (they are only needed in synastry mode), so
|
|
607
|
+
// the mode-specific requirement is enforced here. The widget always supplies
|
|
608
|
+
// them; a hand-rolled call need not.
|
|
609
|
+
if (mode === "synastry" && (lat2 == null || lon2 == null)) {
|
|
610
|
+
throw new Error("bi_wheel_recalculate in synastry mode requires `person2_latitude` and `person2_longitude`.");
|
|
611
|
+
}
|
|
578
612
|
const loc1 = String(args.person1_name ?? args.location ?? `${lat1 ?? "?"},${lon1 ?? "?"}`);
|
|
579
613
|
const loc2 = MODE_META[mode].outerLabel(dt2, args.person2_name);
|
|
580
614
|
const [innerData, outerData] = await Promise.all([
|
|
@@ -610,7 +644,7 @@ registerTool({
|
|
|
610
644
|
required: ["planet1", "planet2", "aspect_type"],
|
|
611
645
|
},
|
|
612
646
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
613
|
-
annotations: { title: "Cross-Aspect Interpretation", readOnlyHint: true, openWorldHint: false },
|
|
647
|
+
annotations: { title: "Cross-Aspect Interpretation", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
614
648
|
_meta: { ui: { resourceUri: BI_WHEEL_RESOURCE_URI, visibility: ["app"] } },
|
|
615
649
|
handler: async (args) => {
|
|
616
650
|
const p1 = capitalize(String(args.planet1));
|
|
@@ -649,7 +683,7 @@ registerTool({
|
|
|
649
683
|
required: ["planet", "wheel", "longitude"],
|
|
650
684
|
},
|
|
651
685
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
652
|
-
annotations: { title: "Bi-Wheel Planet Interpretation", readOnlyHint: true, openWorldHint: false },
|
|
686
|
+
annotations: { title: "Bi-Wheel Planet Interpretation", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
653
687
|
_meta: { ui: { resourceUri: BI_WHEEL_RESOURCE_URI, visibility: ["app"] } },
|
|
654
688
|
handler: async (args) => {
|
|
655
689
|
const planet = String(args.planet);
|
|
@@ -850,7 +884,7 @@ registerTool({
|
|
|
850
884
|
required: ["house_number"],
|
|
851
885
|
},
|
|
852
886
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
853
|
-
annotations: { title: "Bi-Wheel House Interpretation", readOnlyHint: true, openWorldHint: false },
|
|
887
|
+
annotations: { title: "Bi-Wheel House Interpretation", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
854
888
|
_meta: { ui: { resourceUri: BI_WHEEL_RESOURCE_URI, visibility: ["app"] } },
|
|
855
889
|
handler: async (args) => {
|
|
856
890
|
const num = Number(args.house_number);
|
|
@@ -910,7 +944,7 @@ registerTool({
|
|
|
910
944
|
required: ["mode"],
|
|
911
945
|
},
|
|
912
946
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
913
|
-
annotations: { title: "Bi-Wheel Synopsis", readOnlyHint: true, openWorldHint: false },
|
|
947
|
+
annotations: { title: "Bi-Wheel Synopsis", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
914
948
|
_meta: { ui: { resourceUri: BI_WHEEL_RESOURCE_URI, visibility: ["app", "model"] } },
|
|
915
949
|
handler: async (args) => {
|
|
916
950
|
const mode = args.mode ?? "synastry";
|
|
@@ -306,7 +306,10 @@ function buildHdSummary(payload, location) {
|
|
|
306
306
|
registerTool({
|
|
307
307
|
name: "explore_human_design",
|
|
308
308
|
description: "Generate an interactive Human Design Bodygraph with clickable centers, gates, and channels.\n\n" +
|
|
309
|
-
"CREDIT COST:
|
|
309
|
+
"CREDIT COST: 4 credits where the bodygraph renders (2 for the chart + 2 for the " +
|
|
310
|
+
"visual), 2 in text-only hosts that skip the render. For chart data alone at 2 " +
|
|
311
|
+
"credits — including the activations, design, personality, strategy and variables " +
|
|
312
|
+
"this tool does not return — use human_design_chart.\n\n" +
|
|
310
313
|
"Returns an embedded visual bodygraph explorer that lets you click any center or gate " +
|
|
311
314
|
"for instant Human Design interpretation. " +
|
|
312
315
|
"Shows defined/undefined centers, active gates, channels, Type, Profile, Authority, " +
|
|
@@ -468,7 +471,7 @@ registerTool({
|
|
|
468
471
|
required: ["datetime"],
|
|
469
472
|
},
|
|
470
473
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
471
|
-
annotations: { title: "Recalculate Bodygraph", readOnlyHint: true, openWorldHint: false },
|
|
474
|
+
annotations: { title: "Recalculate Bodygraph", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
472
475
|
_meta: { ui: { resourceUri: BODYGRAPH_RESOURCE_URI, visibility: ["app"] } },
|
|
473
476
|
handler: async (args) => {
|
|
474
477
|
const client = getActiveClient();
|
|
@@ -533,7 +536,7 @@ registerTool({
|
|
|
533
536
|
required: ["center", "defined"],
|
|
534
537
|
},
|
|
535
538
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
536
|
-
annotations: { title: "Center Interpretation", readOnlyHint: true, openWorldHint: false },
|
|
539
|
+
annotations: { title: "Center Interpretation", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
537
540
|
_meta: { ui: { resourceUri: BODYGRAPH_RESOURCE_URI, visibility: ["app"] } },
|
|
538
541
|
handler: async (args) => {
|
|
539
542
|
const center = String(args.center);
|
|
@@ -591,7 +594,7 @@ registerTool({
|
|
|
591
594
|
required: ["gate"],
|
|
592
595
|
},
|
|
593
596
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
594
|
-
annotations: { title: "Gate Interpretation", readOnlyHint: true, openWorldHint: false },
|
|
597
|
+
annotations: { title: "Gate Interpretation", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
595
598
|
_meta: { ui: { resourceUri: BODYGRAPH_RESOURCE_URI, visibility: ["app"] } },
|
|
596
599
|
handler: async (args) => {
|
|
597
600
|
const gate = Number(args.gate);
|
|
@@ -646,7 +649,7 @@ registerTool({
|
|
|
646
649
|
required: ["channel"],
|
|
647
650
|
},
|
|
648
651
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
649
|
-
annotations: { title: "Channel Interpretation", readOnlyHint: true, openWorldHint: false },
|
|
652
|
+
annotations: { title: "Channel Interpretation", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
650
653
|
_meta: { ui: { resourceUri: BODYGRAPH_RESOURCE_URI, visibility: ["app"] } },
|
|
651
654
|
handler: async (args) => {
|
|
652
655
|
const channelStr = String(args.channel);
|
|
@@ -748,7 +751,7 @@ registerTool({
|
|
|
748
751
|
required: ["channel"],
|
|
749
752
|
},
|
|
750
753
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
751
|
-
annotations: { title: "Connection Channel Interpretation", readOnlyHint: true, openWorldHint: false },
|
|
754
|
+
annotations: { title: "Connection Channel Interpretation", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
752
755
|
_meta: { ui: { resourceUri: BODYGRAPH_RESOURCE_URI, visibility: ["app"] } },
|
|
753
756
|
handler: async (args) => {
|
|
754
757
|
const channelStr = String(args.channel ?? "");
|
|
@@ -804,7 +807,7 @@ registerTool({
|
|
|
804
807
|
required: ["channel"],
|
|
805
808
|
},
|
|
806
809
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
807
|
-
annotations: { title: "Transit Channel Interpretation", readOnlyHint: true, openWorldHint: false },
|
|
810
|
+
annotations: { title: "Transit Channel Interpretation", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
808
811
|
_meta: { ui: { resourceUri: BODYGRAPH_RESOURCE_URI, visibility: ["app"] } },
|
|
809
812
|
handler: async (args) => {
|
|
810
813
|
const channelStr = String(args.channel ?? "");
|
|
@@ -1132,7 +1135,7 @@ registerTool({
|
|
|
1132
1135
|
required: ["planet", "column"],
|
|
1133
1136
|
},
|
|
1134
1137
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
1135
|
-
annotations: { title: "Planet Sidebar Interpretation", readOnlyHint: true, openWorldHint: false },
|
|
1138
|
+
annotations: { title: "Planet Sidebar Interpretation", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
1136
1139
|
_meta: { ui: { resourceUri: BODYGRAPH_RESOURCE_URI, visibility: ["app"] } },
|
|
1137
1140
|
handler: async (args) => {
|
|
1138
1141
|
const planet = String(args.planet);
|
|
@@ -1201,7 +1204,7 @@ registerTool({
|
|
|
1201
1204
|
required: ["variable", "direction"],
|
|
1202
1205
|
},
|
|
1203
1206
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
1204
|
-
annotations: { title: "Variable Arrow Interpretation", readOnlyHint: true, openWorldHint: false },
|
|
1207
|
+
annotations: { title: "Variable Arrow Interpretation", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
1205
1208
|
_meta: { ui: { resourceUri: BODYGRAPH_RESOURCE_URI, visibility: ["app"] } },
|
|
1206
1209
|
handler: async (args) => {
|
|
1207
1210
|
const variable = String(args.variable).toLowerCase();
|
|
@@ -259,7 +259,7 @@ registerTool({
|
|
|
259
259
|
required: ["planet", "longitude"],
|
|
260
260
|
},
|
|
261
261
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
262
|
-
annotations: { title: "Planet Interpretation", readOnlyHint: true, openWorldHint: false },
|
|
262
|
+
annotations: { title: "Planet Interpretation", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
263
263
|
_meta: { ui: { resourceUri: CHART_WHEEL_RESOURCE_URI, visibility: ["app"] } },
|
|
264
264
|
handler: async (args) => {
|
|
265
265
|
const planet = String(args.planet);
|
|
@@ -294,7 +294,7 @@ registerTool({
|
|
|
294
294
|
required: ["house_number"],
|
|
295
295
|
},
|
|
296
296
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
297
|
-
annotations: { title: "House Interpretation", readOnlyHint: true, openWorldHint: false },
|
|
297
|
+
annotations: { title: "House Interpretation", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
298
298
|
_meta: { ui: { resourceUri: CHART_WHEEL_RESOURCE_URI, visibility: ["app"] } },
|
|
299
299
|
handler: async (args) => {
|
|
300
300
|
const num = Number(args.house_number);
|
|
@@ -330,7 +330,7 @@ registerTool({
|
|
|
330
330
|
required: ["planet1", "planet2"],
|
|
331
331
|
},
|
|
332
332
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
333
|
-
annotations: { title: "Aspect Interpretation", readOnlyHint: true, openWorldHint: false },
|
|
333
|
+
annotations: { title: "Aspect Interpretation", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
334
334
|
_meta: { ui: { resourceUri: CHART_WHEEL_RESOURCE_URI, visibility: ["app"] } },
|
|
335
335
|
handler: async (args) => {
|
|
336
336
|
const p1 = capitalize(String(args.planet1));
|
|
@@ -379,7 +379,7 @@ registerTool({
|
|
|
379
379
|
required: ["datetime", "latitude", "longitude", "house_system"],
|
|
380
380
|
},
|
|
381
381
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
382
|
-
annotations: { title: "Recalculate Chart", readOnlyHint: true, openWorldHint: false },
|
|
382
|
+
annotations: { title: "Recalculate Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
383
383
|
_meta: { ui: { resourceUri: CHART_WHEEL_RESOURCE_URI, visibility: ["app"] } },
|
|
384
384
|
handler: async (args) => {
|
|
385
385
|
const client = getActiveClient();
|
|
@@ -131,7 +131,7 @@ registerTool({
|
|
|
131
131
|
additionalProperties: false,
|
|
132
132
|
},
|
|
133
133
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
134
|
-
annotations: { title: "Location Search", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
134
|
+
annotations: { title: "Location Search", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
135
135
|
// Model-visible as well as app-visible: without this the model had no
|
|
136
136
|
// geocoding tool and every prompt had to route through
|
|
137
137
|
// `dev_read_api /location/autocomplete` instead.
|
|
@@ -235,7 +235,7 @@ registerTool({
|
|
|
235
235
|
additionalProperties: false,
|
|
236
236
|
},
|
|
237
237
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
238
|
-
annotations: { title: "Timezone Resolve", readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
238
|
+
annotations: { title: "Timezone Resolve", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
239
239
|
_meta: {
|
|
240
240
|
ui: {
|
|
241
241
|
visibility: ["model", "app"],
|
|
@@ -295,7 +295,7 @@ registerTool({
|
|
|
295
295
|
additionalProperties: false,
|
|
296
296
|
},
|
|
297
297
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
298
|
-
annotations: { title: "Recalculate Moon Phase", readOnlyHint: true, openWorldHint: false },
|
|
298
|
+
annotations: { title: "Recalculate Moon Phase", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
299
299
|
_meta: { ui: { resourceUri: MOON_PHASE_RESOURCE_URI, visibility: ["app"] } },
|
|
300
300
|
handler: async (args) => {
|
|
301
301
|
const { summary, uiPayload } = await computeMoonData(args);
|
|
@@ -240,7 +240,7 @@ registerTool({
|
|
|
240
240
|
required: ["datetime"],
|
|
241
241
|
},
|
|
242
242
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
243
|
-
annotations: { title: "Recalculate Vedic Chart", readOnlyHint: true, openWorldHint: false },
|
|
243
|
+
annotations: { title: "Recalculate Vedic Chart", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
244
244
|
_meta: { ui: { resourceUri: VEDIC_CHART_RESOURCE_URI, visibility: ["app"] } },
|
|
245
245
|
handler: async (args) => {
|
|
246
246
|
const client = getActiveClient();
|
package/dist/tools/auth.js
CHANGED
|
@@ -24,6 +24,10 @@ registerTool({
|
|
|
24
24
|
title: "Connect Account",
|
|
25
25
|
readOnlyHint: false,
|
|
26
26
|
destructiveHint: false,
|
|
27
|
+
// Each call starts a fresh device-auth flow with a new code — repeating
|
|
28
|
+
// it is harmless but not a no-op.
|
|
29
|
+
idempotentHint: false,
|
|
30
|
+
openWorldHint: false,
|
|
27
31
|
},
|
|
28
32
|
stdioOnly: true,
|
|
29
33
|
handler: async () => {
|
|
@@ -105,6 +109,8 @@ registerTool({
|
|
|
105
109
|
title: "Auth Status",
|
|
106
110
|
readOnlyHint: true,
|
|
107
111
|
destructiveHint: false,
|
|
112
|
+
idempotentHint: true,
|
|
113
|
+
openWorldHint: false,
|
|
108
114
|
},
|
|
109
115
|
stdioOnly: true,
|
|
110
116
|
handler: async () => {
|
|
@@ -191,6 +197,8 @@ registerTool({
|
|
|
191
197
|
title: "Disconnect Account",
|
|
192
198
|
readOnlyHint: false,
|
|
193
199
|
destructiveHint: true,
|
|
200
|
+
idempotentHint: true,
|
|
201
|
+
openWorldHint: false,
|
|
194
202
|
},
|
|
195
203
|
stdioOnly: true,
|
|
196
204
|
handler: async () => {
|
package/dist/tools/dev.js
CHANGED
|
@@ -196,8 +196,10 @@ registerTool({
|
|
|
196
196
|
DEV_API_REFERENCE,
|
|
197
197
|
inputSchema: makeProxyInputSchema(READ_METHODS),
|
|
198
198
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
199
|
-
// GET-only: never mutates server state.
|
|
200
|
-
|
|
199
|
+
// GET-only: never mutates server state. openWorldHint false — the proxy
|
|
200
|
+
// reaches only the fixed, allowlisted OpenEphemeris API (a closed world),
|
|
201
|
+
// same as every typed tool.
|
|
202
|
+
annotations: { title: "API Read", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
201
203
|
handler: makeProxyHandler(READ_METHODS, "GET"),
|
|
202
204
|
});
|
|
203
205
|
registerTool({
|
|
@@ -209,8 +211,16 @@ registerTool({
|
|
|
209
211
|
DEV_API_REFERENCE,
|
|
210
212
|
inputSchema: makeProxyInputSchema(WRITE_METHODS),
|
|
211
213
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
212
|
-
// Issues POST/PUT/PATCH/DELETE — must not be advertised as read-only.
|
|
213
|
-
|
|
214
|
+
// Issues POST/PUT/PATCH/DELETE — must not be advertised as read-only. But
|
|
215
|
+
// destructiveHint is explicitly FALSE: every allowlisted write operation is
|
|
216
|
+
// stateless chart computation (natal, synastry, ACG, returns…) that debits
|
|
217
|
+
// credits and returns a result — nothing on the allowlist mutates or deletes
|
|
218
|
+
// user data, and the deny rules block /auth, /billing, /account, /api-keys.
|
|
219
|
+
// Left unset, strict clients default destructiveHint to true and gate every
|
|
220
|
+
// call behind a confirmation. idempotentHint true for the same reason: the
|
|
221
|
+
// same body yields the same chart. openWorldHint false — fixed allowlisted
|
|
222
|
+
// API only, a closed world.
|
|
223
|
+
annotations: { title: "API Write", readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
214
224
|
handler: makeProxyHandler(WRITE_METHODS, "POST"),
|
|
215
225
|
});
|
|
216
226
|
registerTool({
|
|
@@ -226,7 +236,7 @@ registerTool({
|
|
|
226
236
|
additionalProperties: false,
|
|
227
237
|
},
|
|
228
238
|
outputSchema: OUTPUT_SCHEMA_JSON,
|
|
229
|
-
annotations: { title: "List Allowed API Operations", readOnlyHint: true, openWorldHint: false },
|
|
239
|
+
annotations: { title: "List Allowed API Operations", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
230
240
|
handler: async () => {
|
|
231
241
|
const allowlist = loadAllowlist();
|
|
232
242
|
return {
|