/** * Delegated OAuth 2.1 Authorization Server as an HTTP request handler. * * Unified abstraction for upstream OAuth providers. Fronts two upstream * flows behind a single local OAuth 2.1 PKCE facade for the MCP client: * * - ``redirect`` flow -- classic authorization_code redirect (Notion, * Grafana, GitHub, ...). The local server proxies ``/authorize`` to the * upstream authorize endpoint, handles the callback, exchanges the * upstream code for upstream tokens via ``upstream.tokenUrl``, forwards * the tokens to the consumer via ``onTokenReceived``, then finishes the * local PKCE exchange so the MCP client receives a local JWT. * - ``device_code`` flow (RFC 8628) -- used by GDrive, Outlook, etc. The * local server initiates the upstream device authorization, renders a * page showing ``user_code`` + ``verification_url``, runs a background * polling task on ``upstream.tokenUrl`` until the upstream grants a * token, invokes ``onTokenReceived``, and signals completion via the * setup-status endpoint. * * This is a TypeScript port of core-py's ``delegated_oauth_app.py``. * Behavior, protocol, and TTL constants are kept identical for parity. */ import { JWTIssuer } from '../oauth/jwt-issuer.js'; import { type RequestHandler } from './router.js'; import { type SessionKv } from './session-store.js'; export type FlowType = 'device_code' | 'redirect'; export type TokenEndpointAuthMethod = 'client_secret_basic' | 'client_secret_post'; export interface UpstreamOAuthConfig { tokenUrl: string; clientId: string; clientSecret?: string; scopes?: string[]; /** * Extra query parameters merged verbatim into the upstream /authorize * redirect (redirect flow only). Providers like Google require * `access_type=offline` + `prompt=consent` here to be granted a * refresh_token. Keys set here are applied after the standard params. */ authorizeParams?: Record; /** * How to pass client credentials to the upstream token endpoint. * Defaults to ``client_secret_basic`` per RFC 6749 ยง2.3.1 which mandates * basic-auth support. Notion, GitHub, Microsoft identity platform all * require basic; Google and Slack accept both. */ tokenEndpointAuthMethod?: TokenEndpointAuthMethod; authorizeUrl?: string; callbackPath?: string; deviceAuthUrl?: string; pollIntervalMs?: number; } export type OAuthTokens = Record; /** * Called after upstream token exchange completes. Return the subject * identifier (e.g. provider user id) to use as JWT `sub` in the bearer * token issued to the MCP client. Returning `void` / `undefined` falls * back to `'local-user'` (single-user mode). */ export type TokenCallback = (tokens: OAuthTokens) => string | undefined | void | Promise; export interface DelegatedOAuthAppOptions { serverName: string; flow: FlowType; upstream: UpstreamOAuthConfig; onTokenReceived: TokenCallback; jwtIssuer?: JWTIssuer; /** * Optional durable KV for the delegated-flow handshake state (pending * sessions + issued auth codes). Inject in serverless/container deploys so * the state survives a cold-start/restart between /authorize and /callback; * omit for stdio/single-process (falls back to an in-process map). * * The caller (not this module) owns constructing this -- typically * ``wrapKvBackendAsSessionKv(backendFromEnv(), ':')``. This * module cannot safely guess an app-scoped KV key prefix: a CF Container's * ``kv.internal`` outbound handler commonly allowlists keys by app-specific * prefix, so an auto-detected generic prefix would be silently rejected * (403) by that allowlist. See ``wrapKvBackendAsSessionKv`` in * ``session-store.ts``. */ sessionKv?: SessionKv; } export interface DelegatedOAuthAppResult { handler: RequestHandler; jwtIssuer: JWTIssuer; markSetupComplete: (key?: string) => void; /** Shut down background polling tasks (device_code flow). */ shutdown: () => Promise; } export declare function createDelegatedOAuthApp(options: DelegatedOAuthAppOptions): Promise; //# sourceMappingURL=delegated-oauth-app.d.ts.map