@openephemeris/mcp-server 4.10.0 → 4.11.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 CHANGED
@@ -7,6 +7,56 @@ Version numbering follows [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  ---
9
9
 
10
+ ## [4.11.1] — 2026-08-12
11
+
12
+ ### Fixed
13
+ - **Location resolver didn't recognize US state abbreviations.**
14
+ `location: "San Francisco, CA"` was rejected as ambiguous while the
15
+ equivalent `"San Francisco, California"` resolved fine — the API returns
16
+ the full state name in `region`, and the ambiguity qualifier only matched
17
+ full-name substrings or ISO country-code tokens. Added a USPS abbreviation
18
+ lookup (50 states + DC + territories) so `"City, ST"` qualifies the same
19
+ as `"City, State Name"`.
20
+ - **`bazi_recalculate` double-charged for theme-only re-renders.** Its only
21
+ caller is the BaZi app's automatic host-theme reconciliation — never a
22
+ birth-data change — but it billed the full 3 credits as if it were a fresh
23
+ chart, on top of the identical charge already paid via `explore_bazi_chart`
24
+ moments earlier. Theme-only reconcile now skips the 2-credit visual-render
25
+ reservation.
26
+ - **Datetime tool descriptions could invite a silent 1-hour-off chart.**
27
+ Extended the shared datetime contract instructions to explicitly tell
28
+ callers to pass local wall-clock time + IANA `timezone` rather than
29
+ converting to UTC themselves — a calling model's own conversion mistake
30
+ (e.g. treating July San Francisco as PST instead of PDT) previously sailed
31
+ through with zero server-side errors and a confidently wrong chart.
32
+
33
+ ## [4.11.0] — 2026-08-07
34
+
35
+ ### Fixed
36
+ - **`explore_natal_chart` never drew aspect lines.** `buildNatalBody()` didn't
37
+ set `options.include_aspects`, so the API returned an empty aspects array
38
+ for the single-wheel chart (the bi-wheel was unaffected — it computes
39
+ cross-aspects client-side).
40
+ - **HD synastry connection panel showed only one person's data, unlabeled.**
41
+ `explore_human_design_connection` computed both people's type/profile/
42
+ authority/definition/incarnation-cross but only used Person B's to feed the
43
+ connection-channel classifier, then discarded it. The bodygraph panel now
44
+ shows both, labeled "Person A"/"Person B" to match the overlay legend.
45
+ - **Inconsistent planet/sign glyph weight** (Sun/Moon bold, Venus/Mars thin)
46
+ in the natal and bi-wheel apps. A bare `font-family: "serif"` resolves
47
+ per-glyph on Windows — Times New Roman covers ☉/☽ but not ♀/♂, so those
48
+ silently fell back to a different, thinner font. Pinned an explicit
49
+ astrological symbol-font stack everywhere these glyphs render.
50
+
51
+ ### Changed
52
+ - **Welcome popup → on-demand info button.** All 7 MCP apps (chart-wheel,
53
+ bi-wheel, bodygraph, moon-phase, transit-timeline, vedic-chart, bazi) no
54
+ longer auto-pop a modal on every load/reload — a small "i" button opens the
55
+ same guide on demand.
56
+ - **House ring now reads with visible depth.** The band between the zodiac
57
+ ring and the center previously matched the background exactly; it now uses
58
+ a step-up `--surface` shade on both the natal and bi-wheel apps.
59
+
10
60
  ## [4.10.0] — 2026-08-05
11
61
 
12
62
  ### Fixed
@@ -10,7 +10,7 @@ export interface ResolvedLocation {
10
10
  *
11
11
  * Ambiguity is detected the same way `location_search` reports it — a
12
12
  * count of *rivals* for the top short_name, unless the query itself
13
- * qualifies the place (e.g. "portland uk", "dallas texas").
13
+ * qualifies the place (e.g. "portland uk", "dallas texas", "dallas, tx").
14
14
  */
15
15
  export declare function resolveLocationOrThrow(location: string): Promise<ResolvedLocation>;
16
16
  /**
@@ -15,12 +15,31 @@
15
15
  // produces a chart nothing downstream can detect as wrong. Same failure
16
16
  // class as the naive-UTC ASC and the unlabelled cusp longitudes.
17
17
  import { getActiveClient } from "../../backend/client.js";
18
+ // The API returns the full state name in `region` ("Oregon", not "OR"), but
19
+ // most callers naturally type the USPS abbreviation ("Portland, OR"). Without
20
+ // this table, "San Francisco, CA" was flagged ambiguous while the equivalent
21
+ // "San Francisco, California" resolved fine — same query, arbitrarily
22
+ // different outcome depending on which form the caller happened to type.
23
+ const US_STATE_ABBREVIATIONS = {
24
+ al: "alabama", ak: "alaska", az: "arizona", ar: "arkansas", ca: "california",
25
+ co: "colorado", ct: "connecticut", de: "delaware", fl: "florida", ga: "georgia",
26
+ hi: "hawaii", id: "idaho", il: "illinois", in: "indiana", ia: "iowa",
27
+ ks: "kansas", ky: "kentucky", la: "louisiana", me: "maine", md: "maryland",
28
+ ma: "massachusetts", mi: "michigan", mn: "minnesota", ms: "mississippi", mo: "missouri",
29
+ mt: "montana", ne: "nebraska", nv: "nevada", nh: "new hampshire", nj: "new jersey",
30
+ nm: "new mexico", ny: "new york", nc: "north carolina", nd: "north dakota", oh: "ohio",
31
+ ok: "oklahoma", or: "oregon", pa: "pennsylvania", ri: "rhode island", sc: "south carolina",
32
+ sd: "south dakota", tn: "tennessee", tx: "texas", ut: "utah", vt: "vermont",
33
+ va: "virginia", wa: "washington", wv: "west virginia", wi: "wisconsin", wy: "wyoming",
34
+ dc: "district of columbia", pr: "puerto rico", vi: "virgin islands", gu: "guam",
35
+ as: "american samoa", mp: "northern mariana islands",
36
+ };
18
37
  /**
19
38
  * Resolve a place name to lat/lon/tz. Throws if ambiguous or unresolvable.
20
39
  *
21
40
  * Ambiguity is detected the same way `location_search` reports it — a
22
41
  * count of *rivals* for the top short_name, unless the query itself
23
- * qualifies the place (e.g. "portland uk", "dallas texas").
42
+ * qualifies the place (e.g. "portland uk", "dallas texas", "dallas, tx").
24
43
  */
25
44
  export async function resolveLocationOrThrow(location) {
26
45
  const query = location.trim();
@@ -40,7 +59,9 @@ export async function resolveLocationOrThrow(location) {
40
59
  const rivals = list.filter((s) => norm(s.short_name) === norm(top.short_name));
41
60
  const q = norm(query);
42
61
  const qTokens = new Set(q.split(/[^a-z0-9]+/).filter(Boolean));
43
- const qualified = (norm(top.region) !== "" && q.includes(norm(top.region))) ||
62
+ const topRegion = norm(top.region);
63
+ const qualified = (topRegion !== "" && q.includes(topRegion)) ||
64
+ (topRegion !== "" && [...qTokens].some((t) => US_STATE_ABBREVIATIONS[t] === topRegion)) ||
44
65
  (norm(top.country_code) !== "" && qTokens.has(norm(top.country_code)));
45
66
  const ambiguous = rivals.length > 1 && !qualified;
46
67
  if (ambiguous) {
@@ -66,13 +66,16 @@ export function clearBaziBundleCache() {
66
66
  * Go-rendered SVG (bazi.RenderBaziChartSVG), same shape the strict
67
67
  * /chinese/bazi handler returns with a `visual` key attached.
68
68
  */
69
- async function fetchBaziChart(components, theme = "dark", conventionFields = {}) {
69
+ async function fetchBaziChart(components, theme = "dark", conventionFields = {}, reconcile = false) {
70
70
  const client = getActiveClient();
71
71
  const body = {
72
72
  ...components,
73
73
  ...conventionFields,
74
74
  include_visual: true,
75
75
  visual_config: { format: "svg", theme, size: 800 },
76
+ // Set only by bazi_recalculate's theme-reconcile path below — waives the
77
+ // visual surcharge server-side for a same-birth-data re-render (#551).
78
+ ...(reconcile ? { _visual_reconcile: true } : {}),
76
79
  };
77
80
  return await client.request("POST", "/chinese/bazi", { data: body });
78
81
  }
@@ -194,7 +197,11 @@ registerTool({
194
197
  // ── Tool: bazi_recalculate [app-only] ────────────────────────────────────────
195
198
  registerTool({
196
199
  name: "bazi_recalculate",
197
- description: "Recalculates a BaZi Four Pillars chart with new birth data or theme.",
200
+ description: "Recalculates a BaZi Four Pillars chart with new birth data or theme. " +
201
+ "App-only: called by the embedded chart itself to reconcile the initial " +
202
+ "server-rendered SVG to the host's actual light/dark theme. Billed at 1 " +
203
+ "credit (base only) — the visual-render surcharge is waived because this " +
204
+ "re-renders already-computed data, not a new chart.",
198
205
  inputSchema: {
199
206
  type: "object",
200
207
  properties: {
@@ -216,7 +223,7 @@ registerTool({
216
223
  handler: async (args) => {
217
224
  const components = parseBaziArgs(args);
218
225
  const theme = args.theme === "light" ? "light" : "dark";
219
- const data = await fetchBaziChart(components, theme);
226
+ const data = await fetchBaziChart(components, theme, {}, true);
220
227
  const payload = buildModelPayload(data, components, theme);
221
228
  return {
222
229
  content: [{ type: "text", text: JSON.stringify({ ...payload, server_version: SERVER_VERSION }) }],
@@ -101,6 +101,9 @@ function buildNatalBody(datetime, lat, lon, houseSystem, timezone) {
101
101
  configuration: {
102
102
  house_system: HOUSE_SYSTEM_MAP[houseSystem] ?? "P",
103
103
  },
104
+ options: {
105
+ include_aspects: true,
106
+ },
104
107
  };
105
108
  if (timezone) {
106
109
  body.subject.birth_location.timezone = { iana_name: timezone };
@@ -474,10 +477,11 @@ const ALL_KNOWN_BODIES = new Set([...CLASSICAL_PLANETS, ...EXTENDED_BODIES]);
474
477
  * solar-return / progressed responses) to the AspectData shape the chart-wheel
475
478
  * UI consumes: { planet1, planet2, type, angle, orb, applying }.
476
479
  *
477
- * The Go API computes aspects by default (options.include_aspects = true),
478
- * including a proper ephemeris-based is_applying flag — so no client-side
479
- * longitude heuristic is needed. Aspects are optionally filtered to the set of
480
- * bodies actually displayed so lines never reference a hidden planet.
480
+ * The Go API only computes aspects when asked (options.include_aspects = true,
481
+ * set in buildNatalBody above), but once requested it includes a proper
482
+ * ephemeris-based is_applying flag — so no client-side longitude heuristic is
483
+ * needed. Aspects are optionally filtered to the set of bodies actually
484
+ * displayed so lines never reference a hidden planet.
481
485
  */
482
486
  function mapServerAspects(rawAspects, allowedNames) {
483
487
  if (!Array.isArray(rawAspects))
@@ -81,7 +81,16 @@ export const DATETIME_CONTRACT_INSTRUCTIONS = "Datetime contract: every clock ti
81
81
  "the naive local time and name the zone in the sibling `timezone` argument (IANA name, " +
82
82
  "e.g. `America/Chicago`). A zone-less clock time is a hard 400 — the engine never " +
83
83
  "guesses UTC, because an unstated zone shifts the Ascendant by ~15° per hour and moves " +
84
- "every house placement. A bare date (no clock time) resolves to 12:00 UTC.";
84
+ "every house placement. A bare date (no clock time) resolves to 12:00 UTC.\n\n" +
85
+ "PREFER the local-time + `timezone` form over converting to UTC yourself. The server " +
86
+ "resolves the IANA zone against the actual date, including historical DST rules — that " +
87
+ "arithmetic (which offset applied on that specific day, in that specific year) is easy " +
88
+ "to get wrong by hand and the server will not catch it: a value that already carries a " +
89
+ "`Z`/±HH:MM suffix is trusted as the stated instant, so a self-computed offset that is " +
90
+ "off by an hour (e.g. daylight vs. standard time) produces a chart that is confidently " +
91
+ "wrong with no error raised. If the user states a local clock time (\"2:30 PM in San " +
92
+ "Francisco\"), pass that wall-clock string verbatim with `timezone` — do not do the " +
93
+ "UTC conversion mentally first.";
85
94
  // ─── Enforcement ─────────────────────────────────────────────────────────────
86
95
  /**
87
96
  * The rejection message. Names both remedies, rewriting the caller's own value