/** * RFC 8693 token-exchange client (unified-machine-auth 04 §4, O2) — derives the plugin-plane * family from a freshly minted agent access token at the existing `/oauth/token` endpoint. The * wire shape is FROZEN by the server-side a5 implementation (AGS PR #592) and pinned by tests: * * - request: `grant_type=urn:ietf:params:oauth:grant-type:token-exchange`, * `subject_token=` (PATs are rejected server-side), * `subject_token_type=urn:ietf:params:oauth:token-type:access_token` (exact URN), * `client_id=`, `scope=mcp:plugin` (anything else → `invalid_scope`); * - `audience` / `resource` may be sent ONLY as the exact {@link HUB_AUDIENCE} (`urn:agd:hub`) — * this client sends the exact value when asked and otherwise omits the parameter entirely; * - response keys exactly: `access_token`, `issued_token_type`, `token_type=Bearer`, * `expires_in`, `refresh_token` (RFC 8693 §2.2.1's documented exception), `scope`, `sub`. * * The derived family is stamped with the presented `client_id` — the exchanging client's OWN id, * never a synthetic one (O2/D8). This module is the HTTP seam only: the login-commit helper * (`login-commit.ts`) owns the two-lock-hold F1/F2 write sequence around it. * * It **fails closed**: any non-success, missing access token, invalid input, or exception becomes * a {@link TokenExchangeResult} failure — never a throw past the boundary, and it never logs * token material. */ /** RFC 8693 token-exchange grant type (frozen a5 shape). */ export declare const TOKEN_EXCHANGE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:token-exchange"; /** RFC 8693 subject-token type for an access token — the EXACT URN the server requires (a5). */ export declare const TOKEN_TYPE_ACCESS_TOKEN = "urn:ietf:params:oauth:token-type:access_token"; /** The ONLY `audience`/`resource` value the exchange endpoint accepts (send exact or omit — a5). */ export declare const HUB_AUDIENCE = "urn:agd:hub"; /** Server-enforced maximum `client_id` length on the exchange request (a5). */ export declare const TOKEN_EXCHANGE_MAX_CLIENT_ID_LENGTH = 64; /** RFC 8693 token-exchange response document (frozen a5 keys) + RFC 6749 §5.2 error shape. */ export interface TokenExchangeResponse { access_token?: string; issued_token_type?: string; token_type?: string; expires_in?: number; refresh_token?: string; scope?: string; sub?: string; error?: string; error_description?: string; } /** One exchange request: the fresh agent access token + the exchanging client's own id. */ export interface TokenExchangeRequest { /** A FRESH agent-plane ES256 access token (the `subject_token`; PATs are rejected server-side). */ subjectToken: string; /** The exchanging client's OWN OAuth client id (≤64 chars) — stamped onto the derived family. */ clientId: string; /** The credential's server target (AS root or `/mcp` hub URL — normalized before use). */ serverTarget?: string; /** * Optional RFC 8693 `audience`. The server accepts ONLY the exact {@link HUB_AUDIENCE}; any * other value fails closed here before the network. Omit (default) to let the server default. */ audience?: string; /** Cancellation. */ signal?: AbortSignal; } /** The result of an exchange attempt — a value, never a throw. */ export type TokenExchangeResult = { ok: true; accessToken: string; refreshToken?: string; expiresAt?: string; scope?: string; sub?: string; issuedTokenType?: string; tokenType?: string; } | { ok: false; reason: string; }; /** The exchange transport seam (injectable for tests and for the login-commit helper). */ export interface TokenExchangeClient { exchange(request: TokenExchangeRequest): Promise; } /** * Build the frozen a5 exchange form: exactly `grant_type` + `subject_token` + * `subject_token_type` + `client_id` + `scope=mcp:plugin`, plus `audience` ONLY when provided * (callers must pass the exact {@link HUB_AUDIENCE}). Nothing else is ever added. */ export declare function buildTokenExchangeForm(subjectToken: string, clientId: string, audience?: string): URLSearchParams; /** Turn a parsed exchange response into a {@link TokenExchangeResult}. */ export declare function buildTokenExchangeResult(isSuccessStatus: boolean, statusCode: number, parsed: TokenExchangeResponse | null, now?: () => number): TokenExchangeResult; /** Options for the default fetch-backed exchange client. */ export interface HttpTokenExchangeClientOptions { /** The AS root used when a request carries no `serverTarget`. */ defaultServerBaseUrl: string; /** Injectable for tests; defaults to the global `fetch`. */ fetchImpl?: typeof fetch; /** Per-request network timeout (ms). Defaults to {@link REFRESH_HTTP_TIMEOUT} (15 s). */ timeoutMs?: number; /** Injectable clock (ms since epoch); defaults to `Date.now`. */ now?: () => number; } /** The default {@link TokenExchangeClient}: a form-encoded POST to `{base}/oauth/token`. */ export declare class HttpTokenExchangeClient implements TokenExchangeClient { private readonly _defaultBase; private readonly _fetch; private readonly _timeoutMs; private readonly _now; constructor(options: HttpTokenExchangeClientOptions); exchange(request: TokenExchangeRequest): Promise; } //# sourceMappingURL=token-exchange.d.ts.map