@openephemeris/mcp-server 3.19.0 → 3.21.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 +45 -0
- package/README.md +1 -1
- package/dist/analytics.d.ts +3 -0
- package/dist/analytics.js +44 -0
- package/dist/backend/client.js +17 -8
- package/dist/event-store.d.ts +11 -0
- package/dist/event-store.js +61 -0
- package/dist/oauth/dcr.js +25 -0
- package/dist/oauth/rate-limit.js +10 -11
- package/dist/prompts.js +7 -5
- package/dist/server-sse.d.ts +7 -1
- package/dist/server-sse.js +98 -157
- package/dist/tools/apps/bi-wheel-app.js +11 -7
- package/dist/tools/apps/bodygraph-app.js +6 -3
- package/dist/tools/apps/chart-wheel-app.js +40 -53
- package/dist/tools/apps/location-tools.js +2 -2
- package/dist/tools/apps/moon-phase-app.js +1 -0
- package/dist/tools/auth.js +6 -1
- package/dist/tools/index.d.ts +11 -2
- package/dist/tools/index.js +5 -2
- package/dist/tools/specialized/ephemeris_extended.js +4 -2
- package/dist/tools/specialized/natal.js +2 -1
- package/dist/ui/bi-wheel.html +16 -11
- package/dist/ui/bodygraph.html +33 -6
- package/dist/ui/chart-wheel.html +8 -5
- package/dist/ui/moon-phase.html +15 -8
- package/package.json +5 -3
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,51 @@ Version numbering follows [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [3.21.0] — 2026-07-09
|
|
11
|
+
|
|
12
|
+
Launch-readiness wave from the three-perspective master review (directory vetting, first-run UX, revenue red-team).
|
|
13
|
+
|
|
14
|
+
### Security (directory-listing conditions)
|
|
15
|
+
- **`auth_*` device tools removed from the HTTP transport.** They mutate process-global state (shared credential file, env inspection) — on the multi-tenant remote server one user's `auth_logout` cleared shared credentials and `auth_status` could report the process service-key config to any tenant. Now `stdioOnly` and hidden/blocked on `/mcp`; unchanged for local stdio installs.
|
|
16
|
+
- **Rate limiters no longer trust `X-Forwarded-For`** (leftmost value is attacker-controlled and let callers rotate fake IPs past every throttle). Client IP now comes from Fly's trusted `Fly-Client-IP` hop, with a spoof-resistance test.
|
|
17
|
+
- **DCR redirect hardening:** `redirect_uris` must be https or http loopback (RFC 8252) — non-conforming registrations are rejected.
|
|
18
|
+
|
|
19
|
+
### Conversion & instrumentation
|
|
20
|
+
- **PostHog events from the MCP server** (`src/analytics.ts`, fire-and-forget, no-ops without `POSTHOG_API_KEY`): `mcp_session_init` (auth type), `mcp_tool_call` (tool, duration), `mcp_tool_error` (tool, HTTP status — makes 402 paywall hits and 429s countable). Identity is a SHA-256 prefix of the credential, never the raw key; all events tagged `source: mcp`.
|
|
21
|
+
- **402 paywall fixed:** paid tiers are no longer told to "enable overages" (overage billing is switched off) — they get the next tier with exact price and a `/pricing?plan=…` deep link; free-tier 402s deep-link to the wallet top-up and `/pricing?plan=developer`.
|
|
22
|
+
- **Credit costs disclosed** on the five app tools that lacked them (verified against `usage_meter.go`); `ephemeris_natal_batch` now states its Startup-tier gating and warns that N subjects = N credits.
|
|
23
|
+
- **Return hooks:** `current_sky_snapshot` and `transit_forecast` prompts now end with when to check back (next Moon sign change / next exact transit).
|
|
24
|
+
|
|
25
|
+
### First-run UX
|
|
26
|
+
- **Routing:** `explore_natal_chart` is now explicitly the PRIMARY tool for chart requests; `ephemeris_natal_chart` marks itself raw-JSON and defers to it; the `natal_chart_reading` prompt and the astrology-mcp landing page now point at the interactive explorers.
|
|
27
|
+
- **Welcome rewritten** from a 12-bullet brochure to a two-option hook (instant no-birth-data sky snapshot, or an interactive birth chart); server `instructions` now steer the model to `explore_*` tools for anything visual.
|
|
28
|
+
- **Loader copy de-jargoned** ("Computing ephemeris…" → "Mapping the sky at your birth moment…" etc.); every terminal iframe error now offers "Tap to try again" wired to a real retry; `auth_login` leads with "free tier, no credit card needed"; expired-OAuth copy tells users exactly where to reconnect.
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
- **Local visual harness (`npm run harness`) was broken** by an orphaned vite process squatting port 5180: `visual-check.mjs` now kills the full process tree on Windows (`taskkill /T`), and the harness uses `strictPort` so a squatted port fails loudly instead of silently drifting. Harness index/fixtures cleaned of the deleted dormant apps; the dead bodygraph fixture was rebuilt with the real Go-SVG contract — which exposed and fixed two genuine a11y bugs (`role="img"` on an SVG containing buttons; hardcoded low-contrast gold on action buttons).
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## [3.20.0] — 2026-07-09
|
|
36
|
+
|
|
37
|
+
Second wave of the transport/iframe review: legacy transport retirement, resumability, and remaining hardening/quality items.
|
|
38
|
+
|
|
39
|
+
### Removed
|
|
40
|
+
- **Legacy SSE transport retired.** `GET /sse` + `POST /message` (pre-2025 spec, SDK-deprecated, no cross-machine replay, zero live sessions) now return `410 Gone` pointing at `/mcp`. `scripts/test-sse-client.ts` and the `test:sse` script removed; docs updated.
|
|
41
|
+
|
|
42
|
+
### Added
|
|
43
|
+
- **`Last-Event-ID` resumability.** Bounded in-memory `EventStore` (200 events/stream, 2h TTL) on the Streamable HTTP transport — a reconnecting SSE stream replays missed events instead of losing them. Per-process is safe because sessions are machine-pinned via fly-replay.
|
|
44
|
+
- **Server-computed natal aspects.** `explore_natal_chart` was discarding the `aspects` array the natal endpoint already returns (with true ephemeris-derived `is_applying`) and re-deriving aspects client-side with an approximate heuristic — `computeAspects` deleted, server aspects mapped through. Bi-wheel cross-aspects stay client-side (two charts, no single endpoint) but the applying/separating heuristic now uses real longitude speeds instead of defaulted guesses.
|
|
45
|
+
- **CSP + keyboard accessibility** on the four iframe apps: inline-only `Content-Security-Policy` meta on each shell; interactive SVG elements (planets, houses, aspects, centers/gates/channels) get `tabindex`/`role`/`aria-label`, Enter/Space activation, and visible focus indicators.
|
|
46
|
+
- **Real-handler test coverage.** `createSseApp()` is now exported and `test/server-sse-real-app.test.ts` drives the actual Express app: 401/WWW-Authenticate contract, expired-JWT refresh signal, session issuance with machine prefix, cross-machine `fly-replay` 307 + loop-guard 404, resume-auth requirement, Host allowlist, and the 410 legacy responses.
|
|
47
|
+
- **`check:plugin` release gate** (`scripts/plugin-audit.ts`): plugin.json version must match the package (it had rotted at 3.1.0) and `openephemeris-plugin.zip` must match the plugin tree by content; `regen:plugin` rebuilds both. Wired into `verify:release`.
|
|
48
|
+
- **CI:** `validate:visual` (Playwright + axe over the app harness) added to the validate workflow; the live canary now sends a **Telegram alert on failure** (needs `TELEGRAM_BOT_TOKEN`/`TELEGRAM_CHAT_ID` secrets; no-ops without them).
|
|
49
|
+
|
|
50
|
+
### Changed
|
|
51
|
+
- Resume-path auth now runs **before** the session lookup so an unauthenticated probe cannot learn whether a session id exists.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
10
55
|
## [3.19.0] — 2026-07-09
|
|
11
56
|
|
|
12
57
|
Transport hardening + E2E release verification, from a full review of the streaming HTTP transport and iframe app capability.
|
package/README.md
CHANGED
|
@@ -187,7 +187,7 @@ The server is hosted at `https://mcp.openephemeris.com/mcp` with full Streamable
|
|
|
187
187
|
|
|
188
188
|
- **Claude Web**: Add `https://mcp.openephemeris.com/mcp` as a custom connector URL — leave OAuth Client ID and Secret **blank**. The server uses OAuth 2.1 + PKCE (Dynamic Client Registration), so Claude handles authentication via a browser popup automatically.
|
|
189
189
|
- **Via Smithery**: Use the [Smithery listing](https://smithery.ai/servers/open-ephemeris/openephemeris) for managed connections with any client
|
|
190
|
-
- **Legacy SSE**:
|
|
190
|
+
- **Legacy SSE**: retired in 3.20.0 — use Streamable HTTP at `/mcp`
|
|
191
191
|
|
|
192
192
|
### Auth and upgrade behavior in MCP clients
|
|
193
193
|
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* analytics.ts — fire-and-forget PostHog events for the remote MCP server.
|
|
3
|
+
*
|
|
4
|
+
* The launch was previously unmeasurable from the MCP side: no way to see how
|
|
5
|
+
* many sessions initialized, which tools drove usage, or how many users hit
|
|
6
|
+
* the 402 paywall. These events close that gap. Everything is best-effort:
|
|
7
|
+
* analytics must never slow down or fail a tool call.
|
|
8
|
+
*
|
|
9
|
+
* Configuration (Fly secrets / env):
|
|
10
|
+
* POSTHOG_API_KEY — the project API key (phc_...); absent → all no-ops.
|
|
11
|
+
* POSTHOG_HOST — capture host, default https://us.i.posthog.com.
|
|
12
|
+
*
|
|
13
|
+
* Identity: we never send raw API keys or JWTs. The distinct_id is a SHA-256
|
|
14
|
+
* prefix of the credential, stable per user across sessions but useless for
|
|
15
|
+
* authentication.
|
|
16
|
+
*/
|
|
17
|
+
import { createHash } from "node:crypto";
|
|
18
|
+
const POSTHOG_HOST = process.env.POSTHOG_HOST || "https://us.i.posthog.com";
|
|
19
|
+
function apiKey() {
|
|
20
|
+
return process.env.POSTHOG_API_KEY || undefined;
|
|
21
|
+
}
|
|
22
|
+
/** Stable, non-reversible identity from a credential. */
|
|
23
|
+
export function distinctIdFor(credential) {
|
|
24
|
+
if (!credential)
|
|
25
|
+
return "anonymous";
|
|
26
|
+
return "mcp_" + createHash("sha256").update(credential).digest("hex").slice(0, 24);
|
|
27
|
+
}
|
|
28
|
+
export function captureEvent(event, distinctId, properties = {}) {
|
|
29
|
+
const key = apiKey();
|
|
30
|
+
if (!key)
|
|
31
|
+
return;
|
|
32
|
+
// Fire-and-forget: no await, swallow every failure.
|
|
33
|
+
fetch(`${POSTHOG_HOST}/capture/`, {
|
|
34
|
+
method: "POST",
|
|
35
|
+
headers: { "Content-Type": "application/json" },
|
|
36
|
+
body: JSON.stringify({
|
|
37
|
+
api_key: key,
|
|
38
|
+
event,
|
|
39
|
+
distinct_id: distinctId,
|
|
40
|
+
properties: { source: "mcp", ...properties },
|
|
41
|
+
timestamp: new Date().toISOString(),
|
|
42
|
+
}),
|
|
43
|
+
}).catch(() => { });
|
|
44
|
+
}
|
package/dist/backend/client.js
CHANGED
|
@@ -266,25 +266,33 @@ export class BackendClient {
|
|
|
266
266
|
}
|
|
267
267
|
return new BackendError(`Authentication required: ${msg}. Run auth.login to connect your account, ` +
|
|
268
268
|
`or set OPENEPHEMERIS_API_KEY in your MCP server config. If you connected ` +
|
|
269
|
-
`through Claude's connector settings,
|
|
270
|
-
`OpenEphemeris
|
|
269
|
+
`through Claude's connector settings, your session expired: in Claude's connector ` +
|
|
270
|
+
`settings, toggle OpenEphemeris off and on to reconnect — this only takes a moment.`, 401, "auth_required", false, LOGIN_SIGNUP_URL);
|
|
271
271
|
}
|
|
272
272
|
if (status === 402) {
|
|
273
273
|
// PRICING SSOT: apps/web/config/billing-plans.ts — Explorer grant
|
|
274
274
|
// (EXPLORER.creditsNumeric = 150, one-time), Pro/`developer`
|
|
275
275
|
// (monthlyPrice = 29, credits = 75,000), and CREDIT_TOPUPS
|
|
276
|
-
// ($5→150, $10→350, $20→900)
|
|
277
|
-
//
|
|
276
|
+
// ($5→150, $10→350, $20→900), Startup ($79, 500k), Scale ($199, 2M).
|
|
277
|
+
// Overage billing is OFF (OVERAGE_BILLING_AVAILABLE=false) — never
|
|
278
|
+
// suggest enabling overages; suggest the next tier instead. Keep every
|
|
279
|
+
// number below in sync with that file; do NOT hand-edit independently.
|
|
278
280
|
const dataObj = data && typeof data === "object" ? data : {};
|
|
279
|
-
const walletUrl = dataObj.upgrade?.wallets_url || "https://openephemeris.com/wallet";
|
|
281
|
+
const walletUrl = dataObj.upgrade?.wallets_url || "https://openephemeris.com/dashboard?tab=wallet";
|
|
280
282
|
const currentTier = dataObj.upgrade?.current_tier || dataObj.tier || "explorer";
|
|
283
|
+
// Next-tier upgrade path (SSOT: BILLING_PLANS order in billing-plans.ts).
|
|
284
|
+
const NEXT_TIER = {
|
|
285
|
+
developer: { plan: "startup", label: "Startup ($79/mo, 500,000 credits)" },
|
|
286
|
+
startup: { plan: "scale", label: "Scale ($199/mo, 2,000,000 credits)" },
|
|
287
|
+
scale: { plan: "enterprise", label: "Enterprise (custom volume)" },
|
|
288
|
+
};
|
|
281
289
|
let upsellMsg = "";
|
|
282
290
|
if (currentTier === 'explorer') {
|
|
283
291
|
upsellMsg =
|
|
284
292
|
`Let the user know warmly: Their 150 free credits (one-time grant) are used up. ` +
|
|
285
293
|
`They can top up their wallet starting at $5 for 150 credits at ` +
|
|
286
294
|
`${walletUrl} — or upgrade to Pro ($29/mo) ` +
|
|
287
|
-
`for 75,000 credits/month at ${UPGRADE_URL}`;
|
|
295
|
+
`for 75,000 credits/month at ${UPGRADE_URL}?plan=developer`;
|
|
288
296
|
}
|
|
289
297
|
else if (currentTier === 'payg') {
|
|
290
298
|
upsellMsg =
|
|
@@ -293,9 +301,10 @@ export class BackendClient {
|
|
|
293
301
|
`$10 for 350, or $20 for 900.`;
|
|
294
302
|
}
|
|
295
303
|
else {
|
|
304
|
+
const next = NEXT_TIER[currentTier] || NEXT_TIER.developer;
|
|
296
305
|
upsellMsg =
|
|
297
|
-
`Let the user know warmly: Their monthly credits are exhausted. They can
|
|
298
|
-
|
|
306
|
+
`Let the user know warmly: Their monthly credits are exhausted. They can upgrade to ` +
|
|
307
|
+
`${next.label} at ${UPGRADE_URL}?plan=${next.plan}`;
|
|
299
308
|
}
|
|
300
309
|
return new BackendError(`⚠️ CREDIT LIMIT REACHED — DO NOT FABRICATE DATA.\n\n` +
|
|
301
310
|
`${msg}\n\n` +
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
import type { EventStore, EventId, StreamId } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
3
|
+
export declare class InMemoryEventStore implements EventStore {
|
|
4
|
+
private streams;
|
|
5
|
+
storeEvent(streamId: StreamId, message: JSONRPCMessage): Promise<EventId>;
|
|
6
|
+
replayEventsAfter(lastEventId: EventId, { send }: {
|
|
7
|
+
send: (eventId: EventId, message: JSONRPCMessage) => Promise<void>;
|
|
8
|
+
}): Promise<StreamId>;
|
|
9
|
+
/** Drop streams idle past the TTL so abandoned sessions don't leak memory. */
|
|
10
|
+
private reapStale;
|
|
11
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* event-store.ts — bounded in-memory EventStore for Streamable HTTP
|
|
3
|
+
* resumability (Last-Event-ID).
|
|
4
|
+
*
|
|
5
|
+
* Without an event store the transport cannot replay server→client messages
|
|
6
|
+
* missed across an SSE reconnect — a dropped connection silently loses any
|
|
7
|
+
* buffered events. This store keeps a small per-session ring of recent events
|
|
8
|
+
* so a client reconnecting with Last-Event-ID resumes cleanly.
|
|
9
|
+
*
|
|
10
|
+
* Deliberately in-memory and per-process: sessions are already machine-pinned
|
|
11
|
+
* (fly-replay routes a session's requests to its owning machine), so the
|
|
12
|
+
* events for a session always live on the machine handling its reconnect.
|
|
13
|
+
*/
|
|
14
|
+
import { randomUUID } from "node:crypto";
|
|
15
|
+
const MAX_EVENTS_PER_STREAM = 200;
|
|
16
|
+
const STREAM_TTL_MS = 2 * 60 * 60 * 1000; // matches the session idle TTL
|
|
17
|
+
export class InMemoryEventStore {
|
|
18
|
+
streams = new Map();
|
|
19
|
+
async storeEvent(streamId, message) {
|
|
20
|
+
// Event IDs embed the stream ID so replay can locate the stream without a
|
|
21
|
+
// secondary index: "<streamId>__<uuid>".
|
|
22
|
+
const eventId = `${streamId}__${randomUUID()}`;
|
|
23
|
+
let stream = this.streams.get(streamId);
|
|
24
|
+
if (!stream) {
|
|
25
|
+
stream = { events: [], lastTouched: Date.now() };
|
|
26
|
+
this.streams.set(streamId, stream);
|
|
27
|
+
}
|
|
28
|
+
stream.events.push({ eventId, message });
|
|
29
|
+
stream.lastTouched = Date.now();
|
|
30
|
+
if (stream.events.length > MAX_EVENTS_PER_STREAM) {
|
|
31
|
+
stream.events.splice(0, stream.events.length - MAX_EVENTS_PER_STREAM);
|
|
32
|
+
}
|
|
33
|
+
this.reapStale();
|
|
34
|
+
return eventId;
|
|
35
|
+
}
|
|
36
|
+
async replayEventsAfter(lastEventId, { send }) {
|
|
37
|
+
const sep = lastEventId.lastIndexOf("__");
|
|
38
|
+
const streamId = sep > 0 ? lastEventId.slice(0, sep) : "";
|
|
39
|
+
const stream = this.streams.get(streamId);
|
|
40
|
+
if (!stream)
|
|
41
|
+
return streamId;
|
|
42
|
+
const idx = stream.events.findIndex((e) => e.eventId === lastEventId);
|
|
43
|
+
// If the event aged out of the ring, replay nothing rather than
|
|
44
|
+
// duplicating from an unknown point — the client re-requests as needed.
|
|
45
|
+
if (idx === -1)
|
|
46
|
+
return streamId;
|
|
47
|
+
for (const event of stream.events.slice(idx + 1)) {
|
|
48
|
+
await send(event.eventId, event.message);
|
|
49
|
+
}
|
|
50
|
+
stream.lastTouched = Date.now();
|
|
51
|
+
return streamId;
|
|
52
|
+
}
|
|
53
|
+
/** Drop streams idle past the TTL so abandoned sessions don't leak memory. */
|
|
54
|
+
reapStale() {
|
|
55
|
+
const cutoff = Date.now() - STREAM_TTL_MS;
|
|
56
|
+
for (const [id, stream] of this.streams) {
|
|
57
|
+
if (stream.lastTouched < cutoff)
|
|
58
|
+
this.streams.delete(id);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
package/dist/oauth/dcr.js
CHANGED
|
@@ -32,6 +32,31 @@ oauthDcrRouter.post("/oauth/register", (req, res) => {
|
|
|
32
32
|
return;
|
|
33
33
|
}
|
|
34
34
|
const redirect_uris = body.redirect_uris;
|
|
35
|
+
// OAuth 2.1 hardening: public clients may only register https redirect
|
|
36
|
+
// targets or http loopback addresses (RFC 8252) — reject anything else so a
|
|
37
|
+
// malicious registration can't point authorization codes at http or
|
|
38
|
+
// custom-scheme interceptors.
|
|
39
|
+
const isAllowedRedirect = (uri) => {
|
|
40
|
+
try {
|
|
41
|
+
const u = new URL(uri);
|
|
42
|
+
if (u.protocol === "https:")
|
|
43
|
+
return true;
|
|
44
|
+
if (u.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(u.hostname))
|
|
45
|
+
return true;
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
const badUri = redirect_uris.find((u) => typeof u !== "string" || !isAllowedRedirect(u));
|
|
53
|
+
if (badUri !== undefined) {
|
|
54
|
+
res.status(400).json({
|
|
55
|
+
error: "invalid_redirect_uri",
|
|
56
|
+
error_description: `redirect_uris must be https URLs or http loopback addresses (got: ${String(badUri)}).`,
|
|
57
|
+
});
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
35
60
|
const client_name = body.client_name || "Unknown Client";
|
|
36
61
|
// Deterministic client_id: SHA-256 of sorted redirect URIs
|
|
37
62
|
const uriFingerprint = [...redirect_uris].sort().join("\n");
|
package/dist/oauth/rate-limit.js
CHANGED
|
@@ -26,19 +26,18 @@ export function createRateLimiter(options) {
|
|
|
26
26
|
if (cleanupTimer.unref)
|
|
27
27
|
cleanupTimer.unref();
|
|
28
28
|
return (req, res, next) => {
|
|
29
|
-
// Determine client IP
|
|
29
|
+
// Determine client IP. The leftmost X-Forwarded-For value is
|
|
30
|
+
// attacker-controlled (a client can prepend anything), which would let a
|
|
31
|
+
// caller rotate fake IPs to bypass every limiter. Fly's proxy sets
|
|
32
|
+
// Fly-Client-IP to the true peer address — trust only that; otherwise
|
|
33
|
+
// fall back to the socket address.
|
|
30
34
|
let ip;
|
|
31
35
|
if (trustProxy) {
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
ip = forwarded[0].split(",")[0].trim();
|
|
38
|
-
}
|
|
39
|
-
else {
|
|
40
|
-
ip = req.ip || req.socket.remoteAddress || "unknown";
|
|
41
|
-
}
|
|
36
|
+
const flyClientIp = req.headers["fly-client-ip"];
|
|
37
|
+
ip =
|
|
38
|
+
(typeof flyClientIp === "string" && flyClientIp.trim()) ||
|
|
39
|
+
req.socket.remoteAddress ||
|
|
40
|
+
"unknown";
|
|
42
41
|
}
|
|
43
42
|
else {
|
|
44
43
|
ip = req.ip || req.socket.remoteAddress || "unknown";
|
package/dist/prompts.js
CHANGED
|
@@ -76,13 +76,11 @@ export const PROMPTS = [
|
|
|
76
76
|
"- Format: pass `datetime` as local ISO 8601 (e.g. `1990-04-15T14:30:00`) " +
|
|
77
77
|
"and `timezone` as the IANA name returned by the lookup. Do NOT append Z to a local birth time.\n\n" +
|
|
78
78
|
"## Step 3 — Tool Call\n" +
|
|
79
|
-
"Make a single call to `
|
|
79
|
+
"Make a single call to `explore_natal_chart` (the interactive chart wheel) with:\n" +
|
|
80
80
|
"- `datetime`: local ISO 8601 (no Z)\n" +
|
|
81
81
|
"- `timezone`: IANA name (e.g. `America/Chicago`)\n" +
|
|
82
82
|
"- `latitude` / `longitude`: decimal coordinates\n" +
|
|
83
|
-
"- `house_system`: user's choice or `'placidus'`\n" +
|
|
84
|
-
"- `format`: `'llm'`\n" +
|
|
85
|
-
"- `include_visual`: `true` (renders the interactive chart wheel)\n\n" +
|
|
83
|
+
"- `house_system`: user's choice or `'placidus'`\n\n" +
|
|
86
84
|
"Deliver the full reading from this single response. Do not call additional tools before presenting the reading.\n\n" +
|
|
87
85
|
"## Step 4 — Structure the Reading\n" +
|
|
88
86
|
"Deliver the interpretation in this sequence:\n\n" +
|
|
@@ -180,6 +178,8 @@ export const PROMPTS = [
|
|
|
180
178
|
"**Tone**: Empowering but honest. Frame transits as conditions that can be worked with, " +
|
|
181
179
|
"not inevitable forces. Avoid fatalism, but also avoid implying the person has total control over transit-level events. " +
|
|
182
180
|
"Difficult transits warrant clear, specific preparation — not vague reassurance.\n" +
|
|
181
|
+
"End the forecast with one natural sentence telling them when to check back — " +
|
|
182
|
+
"the next exact transit date from the data (e.g. 'Check back around March 14, when Saturn perfects the square to your Sun').\n" +
|
|
183
183
|
"**Disclaimer**: Astrological interpretation is symbolic and interpretive, not predictive or diagnostic. " +
|
|
184
184
|
"Never present transit analysis as a substitute for professional advice.\n\n" +
|
|
185
185
|
"## Step 5 — Follow-Up Offers\n" +
|
|
@@ -746,7 +746,9 @@ export const PROMPTS = [
|
|
|
746
746
|
"If they shared a concern or life area in Step 1: a dedicated paragraph applying today's sky " +
|
|
747
747
|
"specifically to their situation.\n\n" +
|
|
748
748
|
"**Tone**: Bright, grounded, and modern. Like a weather briefing, but for energy and intention. " +
|
|
749
|
-
"Keep it practical — every insight should connect to a real decision or action
|
|
749
|
+
"Keep it practical — every insight should connect to a real decision or action.\n\n" +
|
|
750
|
+
"Close with one natural sentence noting when the sky next shifts and it's worth checking back — " +
|
|
751
|
+
"the next Moon sign change or next lunar phase date from the data.",
|
|
750
752
|
},
|
|
751
753
|
// ─────────────────────────────────────────────────────────────────────────
|
|
752
754
|
// 11. Solar Return Year Ahead
|
package/dist/server-sse.d.ts
CHANGED
|
@@ -1 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
import express from "express";
|
|
2
|
+
/**
|
|
3
|
+
* Build the full Express app (auth gate, OAuth routes, /mcp Streamable HTTP,
|
|
4
|
+
* health, resources). Exported so tests can drive the REAL handlers with
|
|
5
|
+
* supertest instead of a re-implemented stub. Does not listen.
|
|
6
|
+
*/
|
|
7
|
+
export declare function createSseApp(): Promise<express.Application>;
|