@openephemeris/mcp-server 4.6.0 → 4.8.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 +69 -0
- package/README.md +35 -3
- package/dist/index.js +5 -2
- package/dist/oauth/token.js +30 -3
- package/dist/prompts.js +3 -2
- package/dist/server-sse.js +7 -2
- package/dist/tools/apps/bazi-app.js +3 -3
- package/dist/tools/apps/bi-wheel-app.js +52 -18
- package/dist/tools/apps/bodygraph-app.js +12 -8
- package/dist/tools/apps/location-tools.js +67 -5
- package/dist/tools/apps/moon-phase-app.js +3 -2
- package/dist/tools/apps/vedic-chart-app.js +3 -3
- package/dist/tools/datetime-historical.d.ts +19 -0
- package/dist/tools/datetime-historical.js +77 -0
- package/dist/tools/index.d.ts +41 -2
- package/dist/tools/index.js +201 -4
- package/dist/tools/specialized/acg.js +4 -3
- package/dist/tools/specialized/hd_bodygraph.js +12 -3
- package/dist/tools/specialized/hd_cycles.js +19 -3
- package/dist/tools/specialized/hd_group.js +31 -6
- package/dist/tools/specialized/human_design.js +3 -2
- package/dist/tools/specialized/moon.js +11 -13
- package/dist/tools/specialized/vedic.js +3 -2
- package/dist/ui/bazi.html +30 -30
- package/dist/ui/bi-wheel.html +35 -35
- package/dist/ui/bodygraph.html +39 -39
- package/dist/ui/chart-wheel.html +93 -90
- package/dist/ui/moon-phase.html +32 -32
- package/dist/ui/transit-timeline.html +30 -30
- package/dist/ui/vedic-chart.html +35 -35
- package/package.json +4 -2
- package/smithery.yaml +14 -3
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,75 @@ Version numbering follows [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [4.8.0] — 2026-08-02
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **Toolsets by tradition.** Tool definitions are re-sent to the model on every
|
|
14
|
+
message, so callers who work in one tradition can now advertise just that
|
|
15
|
+
tradition instead of the general-purpose default: `OPENEPHEMERIS_TOOLS=hd`
|
|
16
|
+
(stdio) or `?profile=hd,bazi` (hosted). Eight toolsets — `astrology`, `moon`,
|
|
17
|
+
`hd`, `bazi`, `vedic`, `acg`, `electional`, `venus` — each including
|
|
18
|
+
geocoding, account usage, and the API escape hatch. A Human Design session
|
|
19
|
+
drops from ~19,100 to ~7,800 advertised tokens (−59%); Vedic to ~3,400
|
|
20
|
+
(−82%). `astrology,moon` matches the default's tool count at ~1,700 fewer
|
|
21
|
+
tokens while covering the long tail (dignities, midpoints, hermetic lots,
|
|
22
|
+
fixed stars, composites) the curated set omits. As with `core`/`full`, this
|
|
23
|
+
filters `tools/list` only — every tool stays callable by name. Also exposed
|
|
24
|
+
as the `toolSurface` option in the Smithery config.
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
- `explore_human_design` now states its real credit cost: 4 where the bodygraph
|
|
28
|
+
renders (2 chart + 2 visual), 2 in text-only hosts — it previously claimed a
|
|
29
|
+
flat 2. `ephemeris_moon_phase` (2, not 1) and `ephemeris_next_lunar_phase`
|
|
30
|
+
(1–2 per occurrence) corrected the same way.
|
|
31
|
+
- `explore_bi_wheel` in synastry mode now requires Person 2's coordinates (or a
|
|
32
|
+
resolvable `person2_location`) instead of silently casting Person 2's chart
|
|
33
|
+
for 0°N 0°E.
|
|
34
|
+
- Chart-wheel widget escapes birth parameters before rendering them into the
|
|
35
|
+
info chips.
|
|
36
|
+
- OAuth token endpoint binds authorization codes to their `redirect_uri` and
|
|
37
|
+
`client_id`; both checks were previously skippable by omitting the parameter.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## [4.7.0] — 2026-08-02
|
|
42
|
+
|
|
43
|
+
Historically correct birth-time conversion. IANA timezone databases — including
|
|
44
|
+
the one inside every JavaScript runtime — are only authoritative from 1970.
|
|
45
|
+
Before the US Uniform Time Act took effect in 1967, daylight saving was state
|
|
46
|
+
and municipal law, and a zone like `America/Chicago` models Chicago alone. A
|
|
47
|
+
1961 Minnesota birth converted with standard timezone math lands an hour off,
|
|
48
|
+
which flips the Ascendant sign and the Human Design design-Moon gate.
|
|
49
|
+
|
|
50
|
+
### Added
|
|
51
|
+
|
|
52
|
+
- **Pre-1970 births now resolve through the API's historical correction
|
|
53
|
+
overlay.** Any tool given a naive local birth time with a timezone and
|
|
54
|
+
coordinates — natal, Human Design (chart, composite, penta, cycles, groups),
|
|
55
|
+
Vedic, BaZi apps, ACG, moon tools, and the embedded chart apps — routes the
|
|
56
|
+
local→UTC conversion through the API's `/timezone/offset` `datetime_local`
|
|
57
|
+
mode, which applies primary-source-cited state and local law (with a
|
|
58
|
+
`tz_confidence` grade and rule citation) instead of assuming the reference
|
|
59
|
+
city's rules. Post-1970 conversions stay on the local, zero-latency path,
|
|
60
|
+
which is exactly as authoritative as the server. If the server is
|
|
61
|
+
unreachable, tools fall back to the previous local conversion rather than
|
|
62
|
+
failing the chart call.
|
|
63
|
+
- **`timezone_resolve`** consults the same historical path for pre-1970 dates
|
|
64
|
+
and reports `tzConfidence`, `tzRuleSource`, and `datetimeStatus` (ambiguous
|
|
65
|
+
DST-fold and nonexistent-gap times are called out explicitly).
|
|
66
|
+
- **`location_search`** tags each suggestion's historical offset with a
|
|
67
|
+
confidence grade and a note when a pre-1970 date may be affected by
|
|
68
|
+
state/local law divergence.
|
|
69
|
+
|
|
70
|
+
### Fixed
|
|
71
|
+
|
|
72
|
+
- Three skills and one prompt still instructed the model to convert local
|
|
73
|
+
birth times to UTC itself with generic timezone math — the exact manual
|
|
74
|
+
conversion the historical overlay exists to prevent. They now direct all
|
|
75
|
+
conversion through the API.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
10
79
|
## [4.6.0] — 2026-07-30
|
|
11
80
|
|
|
12
81
|
Fixes the tool that could not answer the question it is named for, closes the
|
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/prompts.js
CHANGED
|
@@ -377,8 +377,9 @@ export const PROMPTS = [
|
|
|
377
377
|
"even 10–15 minutes can shift the Type, Authority, or Profile. If truly unknown, " +
|
|
378
378
|
"proceed with solar noon but flag that results may be imprecise.\n" +
|
|
379
379
|
"4. **Birth city and country** — needed for timezone conversion\n\n" +
|
|
380
|
-
"## Step 2 —
|
|
381
|
-
"Human Design
|
|
380
|
+
"## Step 2 — Resolve Coordinates and Timezone\n" +
|
|
381
|
+
"Human Design needs the exact birth instant; the tool resolves it from local time + " +
|
|
382
|
+
"timezone + coordinates automatically (historically correct even pre-1970).\n\n" +
|
|
382
383
|
"**Resolve the birth place with `location_search`** — pass `query: 'Birth City, Country'` " +
|
|
383
384
|
"and `date` set to the birth date (`YYYY-MM-DD`). One call returns decimal lat/lon, the " +
|
|
384
385
|
"IANA timezone, and `utcOffsetAtDate` — the offset that actually applied at that place on " +
|
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: {
|
|
@@ -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
|
|
@@ -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([
|
|
@@ -25,7 +25,7 @@ import { fileURLToPath } from "node:url";
|
|
|
25
25
|
import { registerTool, SERVER_VERSION } from "../index.js";
|
|
26
26
|
import { getActiveClient } from "../../backend/client.js";
|
|
27
27
|
import { OUTPUT_SCHEMA_JSON } from "../output-schemas.js";
|
|
28
|
-
import {
|
|
28
|
+
import { localToUtcIsoHistorical } from "../datetime-historical.js";
|
|
29
29
|
import { coordsFromArgsOrLocation } from "./_location-resolver.js";
|
|
30
30
|
// ── Constants ─────────────────────────────────────────────────────────────
|
|
31
31
|
export const BODYGRAPH_RESOURCE_URI = "ui://openephemeris/bodygraph";
|
|
@@ -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, " +
|
|
@@ -372,9 +375,9 @@ registerTool({
|
|
|
372
375
|
location: args.location,
|
|
373
376
|
});
|
|
374
377
|
const timezone = args.timezone ?? resolved.timezone;
|
|
375
|
-
const datetime = localToUtcIso("datetime", String(args.datetime), timezone);
|
|
376
378
|
const lat = resolved.latitude;
|
|
377
379
|
const lon = resolved.longitude;
|
|
380
|
+
const datetime = await localToUtcIsoHistorical("datetime", String(args.datetime), timezone, { latitude: lat, longitude: lon });
|
|
378
381
|
if (lat == null || lon == null) {
|
|
379
382
|
throw new Error("explore_human_design requires either `latitude` + `longitude` or a resolvable `location` name. " +
|
|
380
383
|
"Human Design is sensitive to birth location; a chart at 0°N 0°E is silently wrong.");
|
|
@@ -473,10 +476,11 @@ registerTool({
|
|
|
473
476
|
handler: async (args) => {
|
|
474
477
|
const client = getActiveClient();
|
|
475
478
|
const timezone = args.timezone;
|
|
476
|
-
// Convert local-time input → UTC using IANA timezone when provided
|
|
477
|
-
const datetime = localToUtcIso("datetime", String(args.datetime), timezone);
|
|
478
479
|
const lat = args.latitude;
|
|
479
480
|
const lon = args.longitude;
|
|
481
|
+
// Convert local-time input → UTC using IANA timezone when provided;
|
|
482
|
+
// pre-1970 births route through the API's historical correction overlay.
|
|
483
|
+
const datetime = await localToUtcIsoHistorical("datetime", String(args.datetime), timezone, { latitude: lat, longitude: lon });
|
|
480
484
|
const theme = args.theme === "light" ? "light" : "dark";
|
|
481
485
|
const layout = args.layout === "mandala" ? "mandala" : undefined;
|
|
482
486
|
const rings = typeof args.rings === "string" ? args.rings : undefined;
|
|
@@ -1365,9 +1369,9 @@ registerTool({
|
|
|
1365
1369
|
location: args.location,
|
|
1366
1370
|
});
|
|
1367
1371
|
const timezone = args.timezone ?? resolved.timezone;
|
|
1368
|
-
const natalIso = localToUtcIso("datetime", String(args.datetime), timezone);
|
|
1369
1372
|
const lat = resolved.latitude;
|
|
1370
1373
|
const lon = resolved.longitude;
|
|
1374
|
+
const natalIso = await localToUtcIsoHistorical("datetime", String(args.datetime), timezone, { latitude: lat, longitude: lon });
|
|
1371
1375
|
if (lat == null || lon == null) {
|
|
1372
1376
|
throw new Error("explore_human_design_transit requires either `latitude` + `longitude` or a resolvable `location` name. " +
|
|
1373
1377
|
"Human Design is sensitive to birth location; a chart at 0°N 0°E is silently wrong.");
|
|
@@ -1389,7 +1393,7 @@ registerTool({
|
|
|
1389
1393
|
};
|
|
1390
1394
|
if (args.transit_datetime) {
|
|
1391
1395
|
body.transit_datetime = {
|
|
1392
|
-
iso:
|
|
1396
|
+
iso: await localToUtcIsoHistorical("transit_datetime", String(args.transit_datetime), args.timezone, { latitude: lat, longitude: lon }),
|
|
1393
1397
|
};
|
|
1394
1398
|
}
|
|
1395
1399
|
const bundleAvailable = Boolean(getBodygraphBundle());
|
|
@@ -1520,7 +1524,7 @@ registerTool({
|
|
|
1520
1524
|
"Human Design is sensitive to birth location; a chart at 0°N 0°E is silently wrong.");
|
|
1521
1525
|
}
|
|
1522
1526
|
return {
|
|
1523
|
-
birth_datetime_utc:
|
|
1527
|
+
birth_datetime_utc: await localToUtcIsoHistorical(`${label}.datetime`, String(p?.datetime), tz, { latitude: lat, longitude: lon }, `${label}.timezone`),
|
|
1524
1528
|
latitude: lat,
|
|
1525
1529
|
longitude: lon,
|
|
1526
1530
|
};
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import { registerTool, validateRequired } from "../index.js";
|
|
2
2
|
import { getActiveClient } from "../../backend/client.js";
|
|
3
3
|
import { OUTPUT_SCHEMA_JSON } from "../output-schemas.js";
|
|
4
|
+
import { TZDATA_AUTHORITATIVE_FROM_YEAR } from "../datetime-historical.js";
|
|
5
|
+
/** Render minutes east of UTC as `±HH:MM`. */
|
|
6
|
+
function formatOffsetMinutes(offsetMinutes) {
|
|
7
|
+
const sign = offsetMinutes < 0 ? "-" : "+";
|
|
8
|
+
const abs = Math.abs(offsetMinutes);
|
|
9
|
+
const hh = String(Math.floor(abs / 60)).padStart(2, "0");
|
|
10
|
+
const mm = String(Math.round(abs % 60)).padStart(2, "0");
|
|
11
|
+
return `${sign}${hh}:${mm}`;
|
|
12
|
+
}
|
|
4
13
|
/**
|
|
5
14
|
* The UTC offset of an IANA zone is a function of place AND date: 1987 US DST
|
|
6
15
|
* rules are not today's rules, and America/Chicago ran on CDT through the whole
|
|
@@ -143,6 +152,8 @@ registerTool({
|
|
|
143
152
|
// Map the API's snake_case response to the camelCase shape the app UI consumes.
|
|
144
153
|
const list = Array.isArray(raw?.suggestions) ? raw.suggestions : [];
|
|
145
154
|
const date = args.date != null && String(args.date).trim() !== "" ? String(args.date).trim() : null;
|
|
155
|
+
const dateYear = date ? Number(date.slice(0, 4)) : null;
|
|
156
|
+
const preTzdataEra = dateYear != null && Number.isFinite(dateYear) && dateYear < TZDATA_AUTHORITATIVE_FROM_YEAR;
|
|
146
157
|
const suggestions = list.map((s) => {
|
|
147
158
|
const mapped = {
|
|
148
159
|
displayName: s.display_name,
|
|
@@ -154,8 +165,17 @@ registerTool({
|
|
|
154
165
|
longitude: s.longitude,
|
|
155
166
|
timezone: s.timezone,
|
|
156
167
|
};
|
|
157
|
-
if (date && s.timezone)
|
|
158
|
-
|
|
168
|
+
if (date && s.timezone) {
|
|
169
|
+
const offset = offsetAtLocalNoon(s.timezone, date);
|
|
170
|
+
if (offset) {
|
|
171
|
+
// These offsets come from local tzdata, which is only
|
|
172
|
+
// authoritative from 1970 — before that it models the
|
|
173
|
+
// zone's reference city, not state/local law.
|
|
174
|
+
Object.assign(mapped, offset, {
|
|
175
|
+
tzConfidence: preTzdataEra ? "historical_estimate" : "authoritative",
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
159
179
|
return mapped;
|
|
160
180
|
});
|
|
161
181
|
// Ambiguity signal. "portland" really does match Oregon, Maine, Texas,
|
|
@@ -180,6 +200,11 @@ registerTool({
|
|
|
180
200
|
return {
|
|
181
201
|
suggestions,
|
|
182
202
|
matchCount: suggestions.length,
|
|
203
|
+
...(preTzdataEra
|
|
204
|
+
? {
|
|
205
|
+
historicalNote: "Pre-1970 date: tzdata offsets are reference-city estimates. For the chosen place, call timezone_resolve with the same date (or submit the naive local time + timezone + coordinates to the chart endpoint) to apply the API's historical correction overlay.",
|
|
206
|
+
}
|
|
207
|
+
: {}),
|
|
183
208
|
ambiguous,
|
|
184
209
|
...(ambiguous
|
|
185
210
|
? {
|
|
@@ -203,7 +228,7 @@ registerTool({
|
|
|
203
228
|
longitude: { type: "number" },
|
|
204
229
|
date: {
|
|
205
230
|
type: "string",
|
|
206
|
-
description: "Optional birth/event date
|
|
231
|
+
description: "Optional birth/event date 'YYYY-MM-DD'. Adds utcOffsetAtDate / utcOffsetMinutes / isDst / tzConfidence. Post-1970 resolves locally (free); pre-1970 consults the API's historical correction overlay (1 extra credit).",
|
|
207
232
|
},
|
|
208
233
|
},
|
|
209
234
|
required: ["latitude", "longitude"],
|
|
@@ -228,9 +253,46 @@ registerTool({
|
|
|
228
253
|
const date = args.date != null && String(args.date).trim() !== "" ? String(args.date).trim() : null;
|
|
229
254
|
const tz = typeof result?.timezone === "string" ? result.timezone : null;
|
|
230
255
|
if (date && tz) {
|
|
256
|
+
// Pre-1970 dates: tzdata (Node's and Go's alike) models the zone's
|
|
257
|
+
// reference city only, so ask the API — its historical correction
|
|
258
|
+
// overlay knows where state law diverged (e.g. Minnesota 1959-66).
|
|
259
|
+
// Costs one extra credit, only for pre-1970 dates.
|
|
260
|
+
const year = Number(date.slice(0, 4));
|
|
261
|
+
if (Number.isFinite(year) && year < TZDATA_AUTHORITATIVE_FROM_YEAR) {
|
|
262
|
+
try {
|
|
263
|
+
const off = (await getActiveClient().request("POST", "/timezone/offset", {
|
|
264
|
+
data: { lat: args.latitude, lon: args.longitude, datetime_local: `${date.slice(0, 10)}T12:00:00` },
|
|
265
|
+
}));
|
|
266
|
+
if (typeof off?.offset_seconds === "number") {
|
|
267
|
+
const minutes = off.offset_seconds / 60;
|
|
268
|
+
return {
|
|
269
|
+
...result,
|
|
270
|
+
utcOffsetAtDate: formatOffsetMinutes(minutes),
|
|
271
|
+
utcOffsetMinutes: minutes,
|
|
272
|
+
isDst: Boolean(off.is_dst),
|
|
273
|
+
date,
|
|
274
|
+
tzConfidence: typeof off.tz_confidence === "string" ? off.tz_confidence : "historical_estimate",
|
|
275
|
+
...(typeof off.tz_rule_source === "string" ? { tzRuleSource: off.tz_rule_source } : {}),
|
|
276
|
+
...(typeof off.datetime_status === "string" ? { datetimeStatus: off.datetime_status } : {}),
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
catch {
|
|
281
|
+
// Server path unavailable — fall through to the local
|
|
282
|
+
// tzdata estimate below, flagged as such.
|
|
283
|
+
}
|
|
284
|
+
}
|
|
231
285
|
const offset = offsetAtLocalNoon(tz, date);
|
|
232
|
-
if (offset)
|
|
233
|
-
return {
|
|
286
|
+
if (offset) {
|
|
287
|
+
return {
|
|
288
|
+
...result,
|
|
289
|
+
...offset,
|
|
290
|
+
date,
|
|
291
|
+
tzConfidence: Number.isFinite(year) && year < TZDATA_AUTHORITATIVE_FROM_YEAR
|
|
292
|
+
? "historical_estimate"
|
|
293
|
+
: "authoritative",
|
|
294
|
+
};
|
|
295
|
+
}
|
|
234
296
|
}
|
|
235
297
|
return result;
|
|
236
298
|
},
|
|
@@ -17,7 +17,8 @@ import { fileURLToPath } from "node:url";
|
|
|
17
17
|
import { registerTool, validateCoordinates, SERVER_VERSION } from "../index.js";
|
|
18
18
|
import { getActiveClient } from "../../backend/client.js";
|
|
19
19
|
import { OUTPUT_SCHEMA_JSON } from "../output-schemas.js";
|
|
20
|
-
import { TIMEZONE_PROPERTY
|
|
20
|
+
import { TIMEZONE_PROPERTY } from "../datetime.js";
|
|
21
|
+
import { localToUtcIsoHistorical } from "../datetime-historical.js";
|
|
21
22
|
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
22
23
|
export const MOON_PHASE_RESOURCE_URI = "ui://openephemeris/moon-phase";
|
|
23
24
|
export const MOON_PHASE_MIME_TYPE = "text/html;profile=mcp-app";
|
|
@@ -98,7 +99,7 @@ async function computeMoonData(args) {
|
|
|
98
99
|
const client = getActiveClient();
|
|
99
100
|
const params = {};
|
|
100
101
|
params.datetime = args.datetime
|
|
101
|
-
?
|
|
102
|
+
? await localToUtcIsoHistorical("datetime", String(args.datetime), args.timezone, { latitude: args.latitude, longitude: args.longitude })
|
|
102
103
|
: new Date().toISOString();
|
|
103
104
|
if (args.latitude != null)
|
|
104
105
|
params.latitude = args.latitude;
|
|
@@ -23,7 +23,7 @@ import { fileURLToPath } from "node:url";
|
|
|
23
23
|
import { registerTool, validateRequired, validateCoordinates, SERVER_VERSION } from "../index.js";
|
|
24
24
|
import { getActiveClient } from "../../backend/client.js";
|
|
25
25
|
import { OUTPUT_SCHEMA_JSON } from "../output-schemas.js";
|
|
26
|
-
import {
|
|
26
|
+
import { localToUtcIsoHistorical } from "../datetime-historical.js";
|
|
27
27
|
import { coordsFromArgsOrLocation } from "./_location-resolver.js";
|
|
28
28
|
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
29
29
|
export const VEDIC_CHART_RESOURCE_URI = "ui://openephemeris/vedic-chart";
|
|
@@ -165,9 +165,9 @@ registerTool({
|
|
|
165
165
|
validateCoordinates(argsWithResolved, "latitude", "longitude");
|
|
166
166
|
const client = getActiveClient();
|
|
167
167
|
const timezone = args.timezone ?? resolved.timezone;
|
|
168
|
-
const datetime = localToUtcIso("datetime", String(args.datetime), timezone);
|
|
169
168
|
const lat = Number(resolved.latitude);
|
|
170
169
|
const lon = Number(resolved.longitude);
|
|
170
|
+
const datetime = await localToUtcIsoHistorical("datetime", String(args.datetime), timezone, { latitude: lat, longitude: lon });
|
|
171
171
|
const ayanamsa = args.ayanamsa;
|
|
172
172
|
const location = String(resolved.location ?? args.location ?? `${lat}, ${lon}`).slice(0, 120);
|
|
173
173
|
const bundleAvailable = Boolean(getVedicChartBundle());
|
|
@@ -244,9 +244,9 @@ registerTool({
|
|
|
244
244
|
_meta: { ui: { resourceUri: VEDIC_CHART_RESOURCE_URI, visibility: ["app"] } },
|
|
245
245
|
handler: async (args) => {
|
|
246
246
|
const client = getActiveClient();
|
|
247
|
-
const datetime = localToUtcIso("datetime", String(args.datetime), args.timezone);
|
|
248
247
|
const lat = args.latitude != null ? Number(args.latitude) : undefined;
|
|
249
248
|
const lon = args.longitude != null ? Number(args.longitude) : undefined;
|
|
249
|
+
const datetime = await localToUtcIsoHistorical("datetime", String(args.datetime), args.timezone, { latitude: lat, longitude: lon });
|
|
250
250
|
const ayanamsa = args.ayanamsa;
|
|
251
251
|
const theme = args.theme === "light" ? "light" : "dark";
|
|
252
252
|
const body = {
|