export interface BackendConfig { baseURL: string; userId?: string; jwt?: string; serviceKey?: string; apiKey?: string; /** Back-compat: previous name for JWT token (Bearer). */ authToken?: string; /** * When false, the client uses ONLY the credentials passed in this config and * never falls back to the process environment (OPENEPHEMERIS_SERVICE_KEY, * OPENEPHEMERIS_API_KEY, OPENEPHEMERIS_JWT and their legacy aliases). * * The hosted HTTP server builds one client per user session and MUST pass * false: the interceptor sends X-Service-Key ahead of the user's own key, so * an env credential inherited by a per-session client would make every * user's calls unmetered (service key) or billed to one account (API key / * JWT). Defaults to true so the stdio/local server keeps reading its config * from the environment exactly as before. */ inheritEnvCredentials?: boolean; } /** Env vars holding a service key — bypasses metering; never valid on the hosted server. */ export declare const SERVICE_KEY_ENV_VARS: readonly ["OPENEPHEMERIS_SERVICE_KEY", "ASTROMCP_SERVICE_KEY", "MERIDIAN_SERVICE_KEY"]; /** Env vars holding one user's credential — on the hosted server they would bill every session to one account. */ export declare const USER_CREDENTIAL_ENV_VARS: readonly ["OPENEPHEMERIS_API_KEY", "ASTROMCP_API_KEY", "MERIDIAN_API_KEY", "OPENEPHEMERIS_JWT", "ASTROMCP_JWT", "MERIDIAN_AUTH_TOKEN"]; /** Names of the credential env vars that are set (non-empty) in `env`. */ export declare function presentCredentialEnvVars(env?: NodeJS.ProcessEnv): { serviceKeys: string[]; userCredentials: string[]; }; export type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE"; export interface BackendRequestOptions { params?: Record; data?: unknown; headers?: Record; /** Override per-request timeout in milliseconds. */ timeoutMs?: number; } export interface BinaryBackendResponse { content_type: string; content_length: number; encoding: "base64"; data_base64: string; } export declare const DASHBOARD_ACCOUNT_URL = "https://openephemeris.com/dashboard?tab=account"; export declare const LOGIN_SIGNUP_URL = "https://openephemeris.com/login?signup=true&redirect=%2Fdashboard%3Ftab%3Daccount"; export declare const UPGRADE_URL = "https://openephemeris.com/pricing"; /** One-tap $5 → 150-credit top-up (signs the user in if needed, then redirects to the prefilled Stripe Payment Link). */ export declare const TOPUP_URL = "https://openephemeris.com/topup?pack=payg_5&src=mcp_402"; /** * The API's 402 carries `upgrade.topup_url`: the same /topup link, signed with * the caller's account so it opens Stripe checkout without a sign-in step * (most people hitting the wall are in ChatGPT/Claude and not signed in on the * site). Use it when present, re-tagged src=mcp_402 so the funnel can tell MCP * clicks from raw API ones; otherwise fall back to the unsigned link. */ export declare function resolveTopupUrl(apiTopupUrl: unknown): string; export declare const WALLET_TOPUP_URL = "https://openephemeris.com/wallet"; /** * Whether an error body (typically a 422) is really the credit wall: the * detail / type / field codes mention insufficient credits or an exceeded * quota. Checked over the whole serialized body because the signal can sit in * `detail`, `type`, or a nested `errors[].code`. */ export declare function looksLikeCreditExhaustion(data: unknown, msg?: string): boolean; /** * True for errors that mean "this account cannot be charged / is not allowed" * — the credit wall (402), missing auth (401), or a tier gate (403). Tools * that fan out or degrade gracefully must rethrow these instead of swallowing * them: a placeholder result at zero credits hides the paywall (and its * top-up link) from the assistant and reads as real data. */ export declare function isBillingOrAuthError(err: unknown): err is BackendError; /** Rethrows the first billing/auth BackendError among settled results, if any. */ export declare function rethrowBillingOrAuth(results: PromiseSettledResult[]): void; export declare class BackendError extends Error { readonly status: number; readonly code: string; readonly retryable: boolean; readonly upgradeUrl?: string | undefined; constructor(message: string, status: number, code: string, retryable: boolean, upgradeUrl?: string | undefined); } /** * OpenAI's plugin guidelines (developers.openai.com/plugins/plugin-guidelines, * "Commerce and monetization") forbid selling credits, showing plans or * prices, and linking to checkout from inside ChatGPT or Codex. They allow * saying a feature needs more credits and linking an informational page. * Hosts matching this get that message; every other host keeps the direct * top-up link. ChatGPT connects as "openai-mcp", Codex as "openai-mcp (Codex)". */ export declare function isOpenAIHost(hostName: string | undefined): boolean; export declare class BackendClient { /** MCP clientInfo.name of the connected host, set after the handshake. */ hostName?: string; private client; private userId; private jwt?; private serviceKey?; private apiKey?; private credentialManager; private _pendingAuthFlow; private _authFlowResult; constructor(config: BackendConfig); /** * Override the API key at runtime. Used by the SSE server to inject the * per-session user's key so that all tool calls are authenticated and * metered against the correct user account. */ setApiKey(key: string): void; /** * Override the stored JWT at runtime. Used by the SSE/HTTP server to refresh * a session's Bearer token when the host (e.g. claude.ai) rotates it via * /oauth/token. Self-signed Supabase JWTs expire after 1h, so without this a * long-lived session would keep sending the frozen expired token and 401 on * every tool call. Only takes effect when no API key / service key is set * (JWT is the lowest-priority credential in the interceptor). */ setJwt(jwt: string): void; /** * Drop every credential this client holds (service key, API key, JWT). The * hosted HTTP server calls this on the module singleton at startup: the * singleton is what getActiveClient() falls back to outside a session * context, and it was built from the environment at import time. */ clearCredentials(): void; /** Which configured credential the interceptor will send (diagnostics/tests). */ credentialKind(): "service_key" | "api_key" | "jwt" | null; /** * Start the device auth flow in the background if not already running. * Called automatically when no credentials are found in the interceptor. * Idempotent — only starts once per client lifetime. */ private autoStartDeviceAuth; /** The actual device-auth start + background poll. Kicked off by (and stored * on) autoStartDeviceAuth so the guard is race-safe. */ private _runDeviceAuth; private expectsBinaryResponse; private toBuffer; private decodePayload; private extractMessage; private normalizeContentType; private isBinaryContentType; /** Maps an AxiosError to a structured BackendError. */ private mapError; get(path: string, params?: Record, timeoutMs?: number): Promise; post(path: string, data?: any, timeoutMs?: number): Promise; delete(path: string): Promise; request(method: HttpMethod, path: string, options?: BackendRequestOptions): Promise; getUserId(): string; } export declare const backendClient: BackendClient; /** * Run `fn` in an async context where `getActiveClient()` returns `client`. * Used by the SSE server to scope each connection to its own BackendClient. */ export declare function runWithClient(client: BackendClient, fn: () => T): T; /** * Returns the per-session BackendClient when called inside `runWithClient`, * otherwise falls back to the module-level singleton (stdio mode). */ export declare function getActiveClient(): BackendClient;