/** * Generic OAuth client provider for hosted (HTTP) MCP servers. * * Implements the MCP SDK's `OAuthClientProvider` against a terminal app: * tokens, the dynamically-registered client, and the PKCE code verifier are * persisted to `~/.blockrun/mcp-auth/.json` (0600). Authorization uses * the OS browser plus a loopback redirect listener to capture the auth code. * * Nothing here is Base-specific — any hosted MCP doing Dynamic Client * Registration + PKCE + authorization-code (e.g. https://mcp.base.org) works. * The SDK performs discovery, registration, token exchange and refresh; this * class only supplies the storage + browser/loopback plumbing a CLI needs. * * Startup safety: when constructed non-interactively (the path used by * `connectMcpServers` at `franklin start`), `redirectToAuthorization` throws * instead of opening a browser, so a not-yet-authorized server can never pop a * browser tab mid-launch. The interactive flow only runs from `franklin mcp`. */ import type { OAuthClientProvider } from '@modelcontextprotocol/sdk/client/auth.js'; import type { OAuthClientMetadata, OAuthTokens, OAuthClientInformationMixed } from '@modelcontextprotocol/sdk/shared/auth.js'; export interface OAuthProviderOptions { /** Interactive flows (franklin mcp) may open a browser; startup must not. */ interactive?: boolean; /** Called with the authorization URL in interactive mode (open a browser). */ onAuthorizationUrl?: (url: URL) => void; } /** Path to the persisted auth blob for a given server name. */ export declare function authFilePath(serverName: string): string; /** * Does a usable access token exist for this server? Used as the startup gate * in loadMcpConfig — a token-less http server is auto-disabled so it is * silently skipped (never pops a browser, never hangs the agent boot). */ export declare function hasStoredToken(serverName: string): boolean; export declare class McpOAuthProvider implements OAuthClientProvider { private readonly serverName; private readonly serverUrl; readonly interactive: boolean; onAuthorizationUrl?: (url: URL) => void; private readonly file; private _port; private _state?; private server?; private callback?; constructor(serverName: string, serverUrl: string, opts?: OAuthProviderOptions); private read; private persist; get redirectUrl(): string; get clientMetadata(): OAuthClientMetadata; state(): string; clientInformation(): OAuthClientInformationMixed | undefined; saveClientInformation(info: OAuthClientInformationMixed): void; tokens(): OAuthTokens | undefined; saveTokens(tokens: OAuthTokens): void; saveCodeVerifier(verifier: string): void; codeVerifier(): string; redirectToAuthorization(url: URL): void; /** Clear stored credentials so the next login starts clean. */ invalidateCredentials(scope: 'all' | 'client' | 'tokens' | 'verifier'): void; /** * Bind the loopback redirect listener and fix `redirectUrl`'s port BEFORE the * connect attempt, so Dynamic Client Registration registers the right URI. * Tries a stable port first, then an ephemeral one if it's taken. */ startLoopback(): Promise; /** Await the captured authorization code (after the browser redirect). */ waitForCallback(): Promise<{ code: string; state?: string; }>; stopLoopback(): void; } /** Open a URL in the system browser. Returns false if it couldn't be launched. */ export declare function openUrl(url: string): boolean;