@openephemeris/mcp-server 4.11.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,29 @@ 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
|
+
|
|
10
33
|
## [4.11.0] — 2026-08-07
|
|
11
34
|
|
|
12
35
|
### 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
|
|
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 }) }],
|
package/dist/tools/datetime.js
CHANGED
|
@@ -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
|