import { z } from "zod"; /** Resolved from package.json at startup — stamped into all app tool payloads. */ export declare const SERVER_VERSION: string; export interface ToolDefinition { name: string; description: string; inputSchema: z.ZodType | Record; /** * MCP spec 2025-11-25 — JSON Schema describing the structured output of this tool. * Smithery uses this to score "Output schemas" in the quality rubric. * All tools return MCP content block arrays; specialized tools declare richer shapes. */ outputSchema?: Record; annotations?: { title?: string; readOnlyHint?: boolean; destructiveHint?: boolean; idempotentHint?: boolean; openWorldHint?: boolean; }; /** * UI metadata — mirrors the ext-apps SDK _meta.ui shape. * When visibility includes "app" only (not "model"), the tool is hidden from * Claude's tool list (ListTools) but remains callable by the UI via callServerTool(). */ _meta?: { ui?: { resourceUri?: string; /** SDK visibility array — use ["app"] to hide from model, ["model"] for model-only. */ visibility?: Array<"app" | "model">; }; }; /** * Tools that operate on process-global state (device-auth credential file, * env-var inspection) are only safe on the single-user stdio transport. The * multi-tenant HTTP server must not expose them: one user's auth_logout * would clear shared credentials, and auth_status could leak the process's * service-key configuration to every connected tenant. */ stdioOnly?: boolean; handler: (args: any) => Promise; } /** * Which slice of the registry a ListTools response advertises. * * - `core`: the curated everyday set (see CORE_TOOL_NAMES). * - `full`: every model-visible tool. * * This is a *view* filter, not a registration filter. Tools outside the core * set stay registered and stay callable by name — CallTool resolves straight * out of toolRegistry (src/index.ts, src/server-sse.ts) and never consults the * surface. Narrowing the surface therefore costs zero capability; it only * shrinks the tool-definition payload the model has to reason over. * * Registration-time filtering (a third ToolProfile) would NOT work here: the * registry is process-global with a one-shot `toolsInitialized` latch, so the * multi-tenant HTTP server cannot vary it per session, and an unregistered * tool becomes uncallable rather than merely hidden. */ export type ToolSurface = "core" | "full" | ToolsetSurface; /** A caller-chosen set of traditions, e.g. `?profile=hd,bazi`. */ export interface ToolsetSurface { readonly toolsets: readonly string[]; } /** * The default advertised surface: everyday astrology work, one tool per job. * * Selection rules: * - every explore_* app (the visual entry point to each tradition), * - the primary data tool per domain, * - geocoding (location_search / timezone_resolve) so charts can be built * from a place name without falling back to dev_read_api, * - the dev escape hatch, which reaches 28 further endpoints via the * allowlist and is how anything outside this set gets discovered. * * Everything omitted here — the Venus family, the typed BaZi derivations, the * comparative and returns long tail, the standalone SVG renderers — remains * fully callable by name, and is listed by `dev_list_allowed` where the * allowlist covers it. Clients that want the whole surface advertised can ask * for it: `?profile=full` on HTTP, `OPENEPHEMERIS_TOOLS=full` on stdio. * * NOTE: scripts/test-mcp-http.ts (the 6-hourly production canary) asserts * dev_read_api, ephemeris_moon_phase, ephemeris_natal_chart and * explore_natal_chart are present. Do not remove those four. */ export declare const CORE_TOOL_NAMES: ReadonlySet; /** * Tool surfaces grouped by tradition, so a caller pays context only for the * work they actually do: `?profile=hd,bazi` on HTTP, `OPENEPHEMERIS_TOOLS=hd` * on stdio. BASE_TOOLSET is always included. * * This is the same *view* filter as `core`/`full` — nothing is unregistered, * every tool stays callable by name — so a toolset is a context-budget choice, * never a capability one. * * Why this exists: the core surface is re-sent on every model pass and sat at * its ceiling with ~0 headroom, and the only levers left were deleting the * disambiguation prose that makes tool selection work. Grouping by tradition * is the structural fix — a Human Design user should not pay for Venus Star * Points on every message. * * Names here are asserted against the registry by a test; a typo fails the * build rather than silently narrowing someone's surface. */ export declare const TOOLSETS: Readonly>; export declare const TOOLSET_NAMES: readonly string[]; /** Resolve a toolset selection to the tool names it advertises. */ export declare function toolsetSelectionNames(names: readonly string[]): Set; /** * Returns tools that should be exposed to Claude in the ListTools response. * Filters out tools that have visibility restricted to the UI (app-only), * for the HTTP transport tools marked stdioOnly, and — when `surface` is * "core" — everything outside CORE_TOOL_NAMES. */ export declare function modelVisibleTools(transport?: "stdio" | "http", surface?: ToolSurface): ToolDefinition[]; /** * Parses a caller-supplied surface hint. * * "full" -> every model-visible tool * "hd" | "hd,bazi" -> those traditions plus BASE_TOOLSET * anything else -> "core" (the curated default) * * Unknown names in a comma list are ignored rather than rejected: a typo * degrades to a smaller surface, never to an error at `initialize` that would * leave the connector dead. If NO name in the list is recognised, fall back to * "core" so a mistyped profile can't strand a caller on BASE alone. */ export declare function parseToolSurface(raw: unknown): ToolSurface; /** Stable label for logs/analytics — an object surface has no useful toString. */ export declare function describeSurface(surface: ToolSurface): string; export declare const toolRegistry: Record; export declare function registerTool(tool: ToolDefinition): void; export type ToolProfile = "dev" | "legacy"; /** * Initializes tool modules. * * - `dev`: registers the allowlist-gated generic call tools AND all specialized * domain tools (natal chart, transits, moon phase, eclipse, synastry, HD). * - `legacy`: registers only the generic tools (back-compat). */ export declare function initTools(profile?: ToolProfile): Promise; /** * Throws an error if any of the required keys are missing or empty. */ export declare function validateRequired(args: any, requiredKeys: string[]): void; /** * Returns `value` only if it is one of `allowed`, otherwise `undefined`. * * Use for any tool argument that gets interpolated into a backend URL/query * string. Even though the inputSchema declares an `enum`, the MCP host does * not guarantee enum enforcement, so a hostile client could smuggle extra * query parameters (e.g. style="dark&admin=1"). Whitelisting here closes that * injection vector at the boundary (DATA-6). */ export declare function pickEnum(value: unknown, allowed: readonly T[]): T | undefined; /** * Throws an error if only one of the coordinate pair is provided. */ export declare function validateCoordinates(args: any, latKey: string, lonKey: string): void; /** * Formats a raw tool response into native MCP content blocks. * Intercepts binary image responses (like Chart Wheels) and returns them as native MCP Images. * Intercepts embedded VisualResult objects (include_visual=true) and returns dual content. * Applies a safety limit to JSON payloads to prevent crashing LLM contexts. */ export declare function formatToolResponse(toolName: string, result: any, durationMs: number): any; /** * Decide whether a thrown tool error is the stdio device-auth-pending case, * which must NOT be flagged isError so the model relays the verification link. * * Everything else — 429, 5xx, network, plain auth failures — is a real error * and MUST report isError: true so the host recovers (retry / OAuth refresh) * instead of treating a failure as a silent success. * * Device-auth-pending is uniquely identified by a BackendError with * code === "auth_required" AND retryable === true (set only when the background * device-auth flow is live and its message carries a verification link). The * plain "disconnect and reconnect" 401 is retryable === false → a real error. */ export declare function isDeviceAuthPendingError(error: unknown): boolean; /** * Build the MCP error content block for a failed tool call. Shared by the stdio * and SSE/HTTP CallTool handlers so the isError policy stays consistent. */ export declare function formatToolError(error: unknown): { content: { type: "text"; text: string; }[]; isError: boolean; };