( serverName: string, url: URL, kind: "http" | "sse", oauth: McpOAuthConfig, requestInit: RequestInit, intercept?: AuthIntercept, )
| 400 | * and how the code is obtained (TUI with copy + paste fallback). When omitted, |
| 401 | * the flow auto-waits for the loopback callback server (headless mode). */ |
| 402 | export function createAuthTransport( |
| 403 | serverName: string, |
| 404 | url: URL, |
| 405 | kind: "http" | "sse", |
| 406 | oauth: McpOAuthConfig, |
| 407 | requestInit: RequestInit, |
| 408 | intercept?: AuthIntercept, |
| 409 | ): AuthTransport { |
| 410 | const { provider, callbackPort } = buildAuthProvider(serverName, oauth) |
| 411 | const fileProvider = provider instanceof FileOAuthProvider ? provider : undefined |
| 412 | |
| 413 | const transport = |
| 414 | kind === "http" |
| 415 | ? new StreamableHTTPClientTransport(url, { authProvider: provider, requestInit }) |
| 416 | : new SSEClientTransport(url, { authProvider: provider, requestInit }) |
| 417 | |
| 418 | const authenticate = async (): Promise<void> => { |
| 419 | // M2M providers (ClientCredentialsProvider, PrivateKeyJwtProvider) don't |
| 420 | // need a browser — the auth() call handles token acquisition directly. |
| 421 | if (!fileProvider) { |
| 422 | const result = await auth(provider, { serverUrl: url }) |
| 423 | if (result === "REDIRECT") { |
| 424 | throw new Error("M2M provider requested a browser redirect — unexpected.") |
| 425 | } |
| 426 | return |
| 427 | } |
| 428 | |
| 429 | // Interactive flow: use the standalone auth() orchestrator. |
| 430 | // If we already have valid tokens, auth() returns AUTHORIZED immediately. |
| 431 | // If not, it returns REDIRECT (after calling redirectToAuthorization, |
| 432 | // which opens the browser and captures the URL). |
| 433 | const serverUrlStr = url.toString() |
| 434 | const result = await auth(provider, { serverUrl: serverUrlStr }) |
| 435 | |
| 436 | if (result === "AUTHORIZED") return // tokens are valid |
| 437 | |
| 438 | // REDIRECT: the provider's redirectToAuthorization was called, which |
| 439 | // captured the auth URL and opened the browser. Surface it to the TUI. |
| 440 | const authUrl = fileProvider.getAuthUrl() |
| 441 | if (intercept && authUrl) intercept.onAuthUrl(authUrl) |
| 442 | |
| 443 | // Start the callback server (the browser redirect may arrive here). |
| 444 | const callbackServer = startCallbackServer(callbackPort!, (code) => fileProvider.resolveCode(code)) |
| 445 | |
| 446 | try { |
| 447 | let code: string |
| 448 | if (intercept) { |
| 449 | // Interactive: race the callback against a manual paste. |
| 450 | code = await Promise.race([intercept.getCode(), fileProvider.waitForCode()]) |
| 451 | } else { |
| 452 | code = await fileProvider.waitForCode() |
| 453 | } |
| 454 | |
| 455 | // Exchange the code for tokens via the standalone auth() call. |
| 456 | await auth(provider, { serverUrl: serverUrlStr, authorizationCode: code }) |
| 457 | } finally { |
| 458 | callbackServer.close() |
| 459 | } |
no test coverage detected