import type { OAuthController } from "@oh-my-pi/pi-ai/oauth/types"; import { MCPManager } from "./manager.js"; import { type OAuthEndpoints } from "./oauth-discovery.js"; import type { AuthStorage } from "../session/auth-storage.js"; import type { MCPAuthChallenge, MCPServerConfig } from "./types.js"; export declare function withTimeout(promise: Promise, timeoutMs: number, message: string, onTimeout?: () => void): Promise; /** * Reject as soon as `signal` aborts, even when `promise` cannot observe it * (e.g. an in-flight `fetch` before the flow's own callback wait starts). * `promise` stays observed, so its later rejection is never unhandled. */ export declare function raceAbortSignal(promise: Promise, signal: AbortSignal | undefined, createError: () => Error): Promise; export interface OAuthFlowResult { credentialId: string; clientId?: string; resource?: string; } /** * Thrown when the user (or a caller-supplied {@link AbortSignal}) cancels the * in-flight flow. Distinct from network/timeout failures so callers can * surface a neutral "cancelled" status instead of an error banner. */ export declare class MCPOAuthCancelledError extends Error { constructor(message?: string); } /** * User-actionable refusal (stdio transport, disabled server, anonymous * server, …): the message is the guidance itself, so non-interactive callers * surface it verbatim instead of wrapping it in a failure prefix. */ export declare class McpReauthRefusalError extends Error { } export interface FoundMcpServer { filePath: string; scope: "user" | "project"; config: MCPServerConfig; /** Live-manager entry that lives in no writable config file; writes go to user scope. */ discovered: boolean; /** * The manager entry belongs to an ACP-isolated session: the client declared * this server at `session/new` and re-declares it (headers included) every * session, so its definition is session-owned — never a candidate for * user-config write-back. */ acpOwned?: boolean; } /** * Resolve a server for an auth/test operation. Project wins over user on a * name collision — runtime discovery enumerates project paths first and * deduplicates first-wins, so the project entry is the server actually * invoked — then falls back to standalone `mcp.json` / `.mcp.json` (the * mcp-json discovery paths), then to runtime-discovered servers (`/mcp list` * surfaces marketplace/Claude-Code-plugin entries that live in no writable * config). For a discovered server, persisted changes are written into the * user config under the same namespaced name; the native provider * (priority 100) shadows the discovered entry after the next reload. * * `preferManager` flips the first step — but only for ACP-isolated managers: * ACP declares servers at `session/new` (source `provider: "acp"`), and * isolation means the manager entry is the server actually active while a * same-named file entry is inert. Print/RPC managers enumerate the same files * the lookup below reads, and their entries are env-expanded copies with * synthesized user-config paths — preferring them would shadow the real * project/user source, serialize expanded `${...}` placeholders, and write * fresh bindings to the wrong file. */ export declare function findMcpServerForAuth(name: string, cwd?: string, mcpManager?: MCPManager, options?: { preferManager?: boolean; enableProjectConfig?: boolean; }): Promise; /** Strip the `auth` block so a probe or flow starts from the raw server config. */ export declare function stripMcpOAuthAuth(config: MCPServerConfig): MCPServerConfig; /** * Drop a server definition's client-supplied auth surface so the next * `MCPManager` auth resolution picks up the fresh url-keyed credential row a * reauth just wrote: while an `Authorization` header is present the manager * disables the url-keyed credential fallback, and a stale `auth` block * answers first — either would keep the expired credential in charge of the * reconnect. */ export declare function clearMcpClientAuthSurface(config: MCPServerConfig): MCPServerConfig; /** * The host manager's entry for `name`, but only when it is the same server * definition a reauth just refreshed (`selected`). A name collision must not * reconnect an unrelated manager entry: file/config lookups deliberately * prefer user/project entries over the manager fallback, so an ACP client * declaring `foo` at URL B while a config file carries a different `foo` at * URL A would otherwise have its live entry torn down and reconnected with * no matching credential. Definitions correspond when both are http/sse with * the same URL (raw or env-expanded — the manager stores the expanded form). */ export declare function matchingManagerServerConfig(manager: MCPManager | undefined, name: string, selected: MCPServerConfig): MCPServerConfig | undefined; /** * Reconnect the host manager's entry for a reauthorized server and report the * post-reconnect state: `undefined` when the manager does not own the * selected definition (callers fall back to restart guidance). * * Clears the entry's client-supplied auth surface first (a preserved bearer * header would keep the fresh url-keyed credential row out of the reconnect), * awaits a pending automatic reconnect — it captured the old definition and * aborts on the identity check, and joining it would report "disconnected, * retries continue" with no retries left — then starts a fresh manual attempt * against the updated definition. */ export declare function reconnectMcpManagerServer(manager: MCPManager | undefined, name: string, selected: MCPServerConfig): Promise<"connected" | "connecting" | "disconnected" | undefined>; /** * Throwaway connection used to probe a server's auth requirements. Uses the * host's manager when available (its `prepareConfig` honors live state), * else a temporary manager bound to the caller's auth storage. */ export declare function probeMcpConnection(config: MCPServerConfig, options: { oauth?: boolean; authStorage: AuthStorage; mcpManager?: MCPManager; cwd?: string; }): Promise; /** * Resolve the OAuth endpoints a reauth needs from the server itself: * probe anonymously first (nothing destructive has happened yet), then either * take discovery metadata (a clean `initialize` is only weak evidence per the * MCP spec, so honor the explicit reauth when the server advertises OAuth) or * analyze the auth failure and discover from it. A tool-level challenge * overrides the clean probe result. */ export declare function resolveMcpOAuthEndpointsFromServer(config: MCPServerConfig, opts: { authChallenge?: MCPAuthChallenge; signal?: AbortSignal; authStorage: AuthStorage; mcpManager?: MCPManager; cwd?: string; }): Promise; export type McpOAuthFlowPresentation = Pick & { onFlowComplete?: () => void; /** * Release the manual-input claim taken by {@link onManualCodeInput}. The * engine calls this in its `finally` *before* the single-flight claim * releases, so a superseding flow (which wakes on the single-flight * completion) observes the pending manual input already gone instead of * refusing to start. */ releaseManualInput?: () => void; }; export interface McpOAuthFlowRunOptions extends McpOAuthFlowPresentation { authUrl: string; tokenUrl: string; clientId: string; clientSecret: string; scopes: string; serverUrl?: string; registrationUrl?: string; issuerUrl?: string; resource?: string; stripSameOriginResource?: boolean; callbackPort?: number; callbackPath?: string; redirectUri?: string; prompt?: string; authStorage: AuthStorage; /** * External cancellation: host abort, Esc, or a wizard's controller. Aborts * the in-flight flow and throws {@link MCPOAuthCancelledError}. */ signal?: AbortSignal; /** Single-flight lifecycle key; defaults to a module-level owner. */ flowOwner?: object; /** Busy manual-input provider name, to fail fast before starting the flow. */ pendingInput?: () => string | undefined; /** * Fires once the flow's single-flight claim resolves, before any browser * work starts. Hosts that install UI around the flow (the TUI's Esc hook) * do it here, not before the claim: a flow superseded while queued must * install — and later restore — nothing, or it resurrects a dead * predecessor's UI over the live default. */ onClaimed?: () => void; } /** * Run one MCP OAuth browser flow and persist its credential row. * Normalizes the client secret (`""` means "none" so a DCR-issued secret * reaches the token exchange), races the login against the abort signal, and * tears the flow down on the 5-minute deadline. */ export declare function runMcpOAuthFlow(opts: McpOAuthFlowRunOptions): Promise; /** * Fold a completed OAuth flow back into a server config. Owns the persistence * policy in one place: the auth block records the credential pointer plus * refresh material, the oauth block echoes the client id for pre-auth reuse, * and only a user-supplied client secret is ever written — DCR-issued secrets * stay embedded in the stored credential so they cannot leak into (possibly * shared/committed) config files. */ export declare function persistMcpOAuthResult(config: MCPServerConfig, result: OAuthFlowResult, opts: { tokenUrl: string; resource?: string; stripSameOriginResource?: boolean; clientId?: string; persistOAuthClientId?: boolean; userClientSecret?: string; }): MCPServerConfig; export interface McpReauthOptions extends McpOAuthFlowPresentation { authStorage: AuthStorage; /** Host cancellation; wired before the anonymous probe so the whole reauth is abortable. */ signal?: AbortSignal; /** Tool-level `_meta["mcp/www_authenticate"]` challenge that triggered an auto-reauth. */ authChallenge?: MCPAuthChallenge; /** Host's live manager, used to prepare the anonymous probe config. */ mcpManager?: MCPManager; cwd?: string; /** Suppresses the "Reauthorizing…" notice (auto-reauth on a 401 challenge). */ silent?: boolean; /** Presentation for the pre-flow notice. */ onReauthorizing?: () => void; flowOwner?: object; pendingInput?: () => string | undefined; /** * Fires once the flow's single-flight claim resolves (see * {@link McpOAuthFlowRunOptions.onClaimed}); hosts install flow UI here so * a superseded flow installs — and restores — nothing. */ onClaimed?: () => void; } export interface McpReauthOutcome { /** Config with the fresh auth block (or the stripped base when no write-back was needed). */ config: MCPServerConfig; /** Whether the server definition was written back to its config file. */ persisted: boolean; oauth: OAuthEndpoints; credentialId: string; } /** * Reauthorize one configured MCP server end to end: strip the auth block, * resolve OAuth endpoints from the server, assemble client credentials * (configured > persisted > stored > metadata-advertised, with DCR preferred * whenever the authorization server offers it), run the browser flow, GC the * superseded credential row, and write the config back under the TUI's * persistence policy. Definition-only entries resolve through the url-keyed * credential binding alone, so a committed project mcp.json stays clean. * * Throws {@link McpReauthRefusalError} for actionable refusals (stdio * transport, disabled server, anonymous server) and * {@link MCPOAuthCancelledError} when the host aborts mid-flow. */ export declare function reauthorizeMcpServer(name: string, found: FoundMcpServer, opts: McpReauthOptions): Promise;