@mcp-z/client 1.1.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +5 -1
  2. package/dist/cjs/auth/capability-discovery.js +13 -7
  3. package/dist/cjs/auth/capability-discovery.js.map +1 -1
  4. package/dist/cjs/auth/discovery-fetch.d.cts +35 -0
  5. package/dist/cjs/auth/discovery-fetch.d.ts +35 -0
  6. package/dist/cjs/auth/discovery-fetch.js +658 -0
  7. package/dist/cjs/auth/discovery-fetch.js.map +1 -0
  8. package/dist/cjs/auth/index.d.cts +1 -0
  9. package/dist/cjs/auth/index.d.ts +1 -0
  10. package/dist/cjs/auth/index.js +7 -0
  11. package/dist/cjs/auth/index.js.map +1 -1
  12. package/dist/cjs/auth/interactive-oauth-flow.d.cts +12 -12
  13. package/dist/cjs/auth/interactive-oauth-flow.d.ts +12 -12
  14. package/dist/cjs/auth/interactive-oauth-flow.js +22 -16
  15. package/dist/cjs/auth/interactive-oauth-flow.js.map +1 -1
  16. package/dist/cjs/auth/rfc9728-discovery.d.cts +12 -25
  17. package/dist/cjs/auth/rfc9728-discovery.d.ts +12 -25
  18. package/dist/cjs/auth/rfc9728-discovery.js +59 -48
  19. package/dist/cjs/auth/rfc9728-discovery.js.map +1 -1
  20. package/dist/cjs/auth/types.d.cts +16 -0
  21. package/dist/cjs/auth/types.d.ts +16 -0
  22. package/dist/cjs/auth/types.js.map +1 -1
  23. package/dist/cjs/dcr/dcr-authenticator.d.cts +3 -4
  24. package/dist/cjs/dcr/dcr-authenticator.d.ts +3 -4
  25. package/dist/cjs/dcr/dcr-authenticator.js +22 -12
  26. package/dist/cjs/dcr/dcr-authenticator.js.map +1 -1
  27. package/dist/cjs/dcr/dynamic-client-registrar.d.cts +6 -17
  28. package/dist/cjs/dcr/dynamic-client-registrar.d.ts +6 -17
  29. package/dist/cjs/dcr/dynamic-client-registrar.js +10 -16
  30. package/dist/cjs/dcr/dynamic-client-registrar.js.map +1 -1
  31. package/dist/cjs/index.d.cts +1 -0
  32. package/dist/cjs/index.d.ts +1 -0
  33. package/dist/cjs/index.js +7 -0
  34. package/dist/cjs/index.js.map +1 -1
  35. package/dist/esm/auth/capability-discovery.js +12 -6
  36. package/dist/esm/auth/capability-discovery.js.map +1 -1
  37. package/dist/esm/auth/discovery-fetch.d.ts +35 -0
  38. package/dist/esm/auth/discovery-fetch.js +184 -0
  39. package/dist/esm/auth/discovery-fetch.js.map +1 -0
  40. package/dist/esm/auth/index.d.ts +1 -0
  41. package/dist/esm/auth/index.js +1 -0
  42. package/dist/esm/auth/index.js.map +1 -1
  43. package/dist/esm/auth/interactive-oauth-flow.d.ts +12 -12
  44. package/dist/esm/auth/interactive-oauth-flow.js +25 -16
  45. package/dist/esm/auth/interactive-oauth-flow.js.map +1 -1
  46. package/dist/esm/auth/rfc9728-discovery.d.ts +12 -25
  47. package/dist/esm/auth/rfc9728-discovery.js +49 -56
  48. package/dist/esm/auth/rfc9728-discovery.js.map +1 -1
  49. package/dist/esm/auth/types.d.ts +16 -0
  50. package/dist/esm/auth/types.js.map +1 -1
  51. package/dist/esm/dcr/dcr-authenticator.d.ts +3 -4
  52. package/dist/esm/dcr/dcr-authenticator.js +20 -11
  53. package/dist/esm/dcr/dcr-authenticator.js.map +1 -1
  54. package/dist/esm/dcr/dynamic-client-registrar.d.ts +6 -17
  55. package/dist/esm/dcr/dynamic-client-registrar.js +13 -18
  56. package/dist/esm/dcr/dynamic-client-registrar.js.map +1 -1
  57. package/dist/esm/index.d.ts +1 -0
  58. package/dist/esm/index.js +1 -0
  59. package/dist/esm/index.js.map +1 -1
  60. package/package.json +2 -7
@@ -3,6 +3,7 @@
3
3
  * Exports public API for OAuth authentication only (DCR moved to ../dcr/)
4
4
  */
5
5
  export { probeAuthCapabilities } from './capability-discovery.js';
6
+ export { DiscoveryFetchError, isLoopbackUrl } from './discovery-fetch.js';
6
7
  export { InteractiveOAuthFlow } from './interactive-oauth-flow.js';
7
8
  export type { OAuthCallbackListenerOptions } from './oauth-callback-listener.js';
8
9
  export { OAuthCallbackListener } from './oauth-callback-listener.js';
@@ -3,6 +3,7 @@
3
3
  * Exports public API for OAuth authentication only (DCR moved to ../dcr/)
4
4
  */
5
5
  export { probeAuthCapabilities } from './capability-discovery.js';
6
+ export { DiscoveryFetchError, isLoopbackUrl } from './discovery-fetch.js';
6
7
  export { InteractiveOAuthFlow } from './interactive-oauth-flow.js';
7
8
  export type { OAuthCallbackListenerOptions } from './oauth-callback-listener.js';
8
9
  export { OAuthCallbackListener } from './oauth-callback-listener.js';
@@ -12,17 +12,24 @@ function _export(target, all) {
12
12
  });
13
13
  }
14
14
  _export(exports, {
15
+ get DiscoveryFetchError () {
16
+ return _discoveryfetchts.DiscoveryFetchError;
17
+ },
15
18
  get InteractiveOAuthFlow () {
16
19
  return _interactiveoauthflowts.InteractiveOAuthFlow;
17
20
  },
18
21
  get OAuthCallbackListener () {
19
22
  return _oauthcallbacklistenerts.OAuthCallbackListener;
20
23
  },
24
+ get isLoopbackUrl () {
25
+ return _discoveryfetchts.isLoopbackUrl;
26
+ },
21
27
  get probeAuthCapabilities () {
22
28
  return _capabilitydiscoveryts.probeAuthCapabilities;
23
29
  }
24
30
  });
25
31
  var _capabilitydiscoveryts = require("./capability-discovery.js");
32
+ var _discoveryfetchts = require("./discovery-fetch.js");
26
33
  var _interactiveoauthflowts = require("./interactive-oauth-flow.js");
27
34
  var _oauthcallbacklistenerts = require("./oauth-callback-listener.js");
28
35
  /* CJS INTEROP */ if (exports.__esModule && exports.default) { try { Object.defineProperty(exports.default, '__esModule', { value: true }); for (var key in exports) { exports.default[key] = exports[key]; } } catch (_) {}; module.exports = exports.default; }
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/mcp-z/client/src/auth/index.ts"],"sourcesContent":["/**\n * Authentication Module\n * Exports public API for OAuth authentication only (DCR moved to ../dcr/)\n */\n\nexport { probeAuthCapabilities } from './capability-discovery.ts';\nexport { InteractiveOAuthFlow } from './interactive-oauth-flow.ts';\nexport type { OAuthCallbackListenerOptions } from './oauth-callback-listener.ts';\nexport { OAuthCallbackListener } from './oauth-callback-listener.ts';\nexport type { AuthCapabilities, CallbackResult, OAuthFlowOptions, TokenSet } from './types.ts';\n"],"names":["InteractiveOAuthFlow","OAuthCallbackListener","probeAuthCapabilities"],"mappings":"AAAA;;;CAGC;;;;;;;;;;;QAGQA;eAAAA,4CAAoB;;QAEpBC;eAAAA,8CAAqB;;QAHrBC;eAAAA,4CAAqB;;;qCAAQ;sCACD;uCAEC"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/mcp-z/client/src/auth/index.ts"],"sourcesContent":["/**\n * Authentication Module\n * Exports public API for OAuth authentication only (DCR moved to ../dcr/)\n */\n\nexport { probeAuthCapabilities } from './capability-discovery.ts';\nexport { DiscoveryFetchError, isLoopbackUrl } from './discovery-fetch.ts';\nexport { InteractiveOAuthFlow } from './interactive-oauth-flow.ts';\nexport type { OAuthCallbackListenerOptions } from './oauth-callback-listener.ts';\nexport { OAuthCallbackListener } from './oauth-callback-listener.ts';\nexport type { AuthCapabilities, CallbackResult, OAuthFlowOptions, TokenSet } from './types.ts';\n"],"names":["DiscoveryFetchError","InteractiveOAuthFlow","OAuthCallbackListener","isLoopbackUrl","probeAuthCapabilities"],"mappings":"AAAA;;;CAGC;;;;;;;;;;;QAGQA;eAAAA,qCAAmB;;QACnBC;eAAAA,4CAAoB;;QAEpBC;eAAAA,8CAAqB;;QAHAC;eAAAA,+BAAa;;QADlCC;eAAAA,4CAAqB;;;qCAAQ;gCACa;sCACd;uCAEC"}
@@ -34,22 +34,22 @@ export declare class InteractiveOAuthFlow {
34
34
  */
35
35
  performAuthFlow(authorizationEndpoint: string, tokenEndpoint: string, clientId: string, clientSecret: string, options: OAuthFlowOptions): Promise<TokenSet>;
36
36
  /**
37
- * Exchange authorization code for access and refresh tokens
38
- * @param codeVerifier - Optional PKCE code verifier (RFC 7636)
37
+ * Exchanges an authorization code for access and refresh tokens.
38
+ * @param allowLoopback - Loopback trust grant computed by the caller from the server it is actually talking to, never from `tokenEndpoint`.
39
+ * @param codeVerifier - Optional PKCE code verifier (RFC 7636).
39
40
  */
40
41
  private exchangeCodeForTokens;
41
42
  /**
42
- * Refresh access token using refresh token
43
- *
44
- * @param tokenEndpoint - OAuth token endpoint URL
45
- * @param refreshToken - Refresh token from previous token set
46
- * @param clientId - OAuth client ID
47
- * @param clientSecret - OAuth client secret
48
- * @returns New token set with refreshed access token
49
- *
50
- * @throws Error if refresh fails
43
+ * Refreshes an access token using a refresh token.
44
+ * @param tokenEndpoint - OAuth token endpoint URL.
45
+ * @param refreshToken - Refresh token from a previous token set.
46
+ * @param clientId - OAuth client ID.
47
+ * @param clientSecret - OAuth client secret.
48
+ * @param allowLoopback - Loopback trust grant computed by the caller from the server it is actually talking to, never from `tokenEndpoint`. Defaults to `false`.
49
+ * @returns New token set with a refreshed access token.
50
+ * @throws Error if refresh fails.
51
51
  */
52
- refreshTokens(tokenEndpoint: string, refreshToken: string, clientId: string, clientSecret: string): Promise<TokenSet>;
52
+ refreshTokens(tokenEndpoint: string, refreshToken: string, clientId: string, clientSecret: string, allowLoopback?: boolean): Promise<TokenSet>;
53
53
  /**
54
54
  * Open browser to authorization URL
55
55
  * Uses platform-specific command to open default browser
@@ -34,22 +34,22 @@ export declare class InteractiveOAuthFlow {
34
34
  */
35
35
  performAuthFlow(authorizationEndpoint: string, tokenEndpoint: string, clientId: string, clientSecret: string, options: OAuthFlowOptions): Promise<TokenSet>;
36
36
  /**
37
- * Exchange authorization code for access and refresh tokens
38
- * @param codeVerifier - Optional PKCE code verifier (RFC 7636)
37
+ * Exchanges an authorization code for access and refresh tokens.
38
+ * @param allowLoopback - Loopback trust grant computed by the caller from the server it is actually talking to, never from `tokenEndpoint`.
39
+ * @param codeVerifier - Optional PKCE code verifier (RFC 7636).
39
40
  */
40
41
  private exchangeCodeForTokens;
41
42
  /**
42
- * Refresh access token using refresh token
43
- *
44
- * @param tokenEndpoint - OAuth token endpoint URL
45
- * @param refreshToken - Refresh token from previous token set
46
- * @param clientId - OAuth client ID
47
- * @param clientSecret - OAuth client secret
48
- * @returns New token set with refreshed access token
49
- *
50
- * @throws Error if refresh fails
43
+ * Refreshes an access token using a refresh token.
44
+ * @param tokenEndpoint - OAuth token endpoint URL.
45
+ * @param refreshToken - Refresh token from a previous token set.
46
+ * @param clientId - OAuth client ID.
47
+ * @param clientSecret - OAuth client secret.
48
+ * @param allowLoopback - Loopback trust grant computed by the caller from the server it is actually talking to, never from `tokenEndpoint`. Defaults to `false`.
49
+ * @returns New token set with a refreshed access token.
50
+ * @throws Error if refresh fails.
51
51
  */
52
- refreshTokens(tokenEndpoint: string, refreshToken: string, clientId: string, clientSecret: string): Promise<TokenSet>;
52
+ refreshTokens(tokenEndpoint: string, refreshToken: string, clientId: string, clientSecret: string, allowLoopback?: boolean): Promise<TokenSet>;
53
53
  /**
54
54
  * Open browser to authorization URL
55
55
  * Uses platform-specific command to open default browser
@@ -13,6 +13,7 @@ Object.defineProperty(exports, "InteractiveOAuthFlow", {
13
13
  });
14
14
  var _nodechild_process = /*#__PURE__*/ _interop_require_wildcard(require("node:child_process"));
15
15
  var _loggerts = require("../utils/logger.js");
16
+ var _discoveryfetchts = require("./discovery-fetch.js");
16
17
  var _oauthcallbacklistenerts = require("./oauth-callback-listener.js");
17
18
  var _pkcets = require("./pkce.js");
18
19
  function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
@@ -211,7 +212,7 @@ var InteractiveOAuthFlow = /*#__PURE__*/ function() {
211
212
  * );
212
213
  */ _proto.performAuthFlow = function performAuthFlow(authorizationEndpoint, tokenEndpoint, clientId, clientSecret, options) {
213
214
  return _async_to_generator(function() {
214
- var _options_logger, logger, callbackListener, pkce, redirectUri, authUrl, timeout, result, tokens, error;
215
+ var _options_logger, logger, callbackListener, pkce, _options_allowLoopback, redirectUri, authUrl, timeout, result, tokens, error;
215
216
  return _ts_generator(this, function(_state) {
216
217
  switch(_state.label){
217
218
  case 0:
@@ -297,7 +298,7 @@ var InteractiveOAuthFlow = /*#__PURE__*/ function() {
297
298
  result = _state.sent();
298
299
  return [
299
300
  4,
300
- this.exchangeCodeForTokens(tokenEndpoint, result.code, clientId, clientSecret, redirectUri, pkce === null || pkce === void 0 ? void 0 : pkce.codeVerifier)
301
+ this.exchangeCodeForTokens(tokenEndpoint, result.code, clientId, clientSecret, redirectUri, (_options_allowLoopback = options.allowLoopback) !== null && _options_allowLoopback !== void 0 ? _options_allowLoopback : false, pkce === null || pkce === void 0 ? void 0 : pkce.codeVerifier)
301
302
  ];
302
303
  case 8:
303
304
  tokens = _state.sent();
@@ -329,9 +330,10 @@ var InteractiveOAuthFlow = /*#__PURE__*/ function() {
329
330
  }).call(this);
330
331
  };
331
332
  /**
332
- * Exchange authorization code for access and refresh tokens
333
- * @param codeVerifier - Optional PKCE code verifier (RFC 7636)
334
- */ _proto.exchangeCodeForTokens = function exchangeCodeForTokens(tokenEndpoint, code, clientId, clientSecret, redirectUri, codeVerifier) {
333
+ * Exchanges an authorization code for access and refresh tokens.
334
+ * @param allowLoopback - Loopback trust grant computed by the caller from the server it is actually talking to, never from `tokenEndpoint`.
335
+ * @param codeVerifier - Optional PKCE code verifier (RFC 7636).
336
+ */ _proto.exchangeCodeForTokens = function exchangeCodeForTokens(tokenEndpoint, code, clientId, clientSecret, redirectUri, allowLoopback, codeVerifier) {
335
337
  return _async_to_generator(function() {
336
338
  var params, response, errorText, data, tokenSet;
337
339
  return _ts_generator(this, function(_state) {
@@ -350,7 +352,7 @@ var InteractiveOAuthFlow = /*#__PURE__*/ function() {
350
352
  }
351
353
  return [
352
354
  4,
353
- fetch(tokenEndpoint, {
355
+ (0, _discoveryfetchts.discoveryFetch)(tokenEndpoint, {
354
356
  method: 'POST',
355
357
  headers: {
356
358
  'Content-Type': 'application/x-www-form-urlencoded',
@@ -358,6 +360,8 @@ var InteractiveOAuthFlow = /*#__PURE__*/ function() {
358
360
  Connection: 'close'
359
361
  },
360
362
  body: params
363
+ }, 'token endpoint', {
364
+ allowLoopback: allowLoopback
361
365
  })
362
366
  ];
363
367
  case 1:
@@ -402,16 +406,16 @@ var InteractiveOAuthFlow = /*#__PURE__*/ function() {
402
406
  })();
403
407
  };
404
408
  /**
405
- * Refresh access token using refresh token
406
- *
407
- * @param tokenEndpoint - OAuth token endpoint URL
408
- * @param refreshToken - Refresh token from previous token set
409
- * @param clientId - OAuth client ID
410
- * @param clientSecret - OAuth client secret
411
- * @returns New token set with refreshed access token
412
- *
413
- * @throws Error if refresh fails
409
+ * Refreshes an access token using a refresh token.
410
+ * @param tokenEndpoint - OAuth token endpoint URL.
411
+ * @param refreshToken - Refresh token from a previous token set.
412
+ * @param clientId - OAuth client ID.
413
+ * @param clientSecret - OAuth client secret.
414
+ * @param allowLoopback - Loopback trust grant computed by the caller from the server it is actually talking to, never from `tokenEndpoint`. Defaults to `false`.
415
+ * @returns New token set with a refreshed access token.
416
+ * @throws Error if refresh fails.
414
417
  */ _proto.refreshTokens = function refreshTokens(tokenEndpoint, refreshToken, clientId, clientSecret) {
418
+ var allowLoopback = arguments.length > 4 && arguments[4] !== void 0 ? arguments[4] : false;
415
419
  return _async_to_generator(function() {
416
420
  var response, errorText, data, tokenSet;
417
421
  return _ts_generator(this, function(_state) {
@@ -419,7 +423,7 @@ var InteractiveOAuthFlow = /*#__PURE__*/ function() {
419
423
  case 0:
420
424
  return [
421
425
  4,
422
- fetch(tokenEndpoint, {
426
+ (0, _discoveryfetchts.discoveryFetch)(tokenEndpoint, {
423
427
  method: 'POST',
424
428
  headers: {
425
429
  'Content-Type': 'application/x-www-form-urlencoded',
@@ -432,6 +436,8 @@ var InteractiveOAuthFlow = /*#__PURE__*/ function() {
432
436
  client_id: clientId,
433
437
  client_secret: clientSecret
434
438
  })
439
+ }, 'token endpoint', {
440
+ allowLoopback: allowLoopback
435
441
  })
436
442
  ];
437
443
  case 1:
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/mcp-z/client/src/auth/interactive-oauth-flow.ts"],"sourcesContent":["/**\n * OAuth Authorization Flow Handler\n * Manages browser-based OAuth flows and token exchange with PKCE support\n */\n\nimport * as child_process from 'node:child_process';\nimport { logger as defaultLogger } from '../utils/logger.ts';\nimport { OAuthCallbackListener } from './oauth-callback-listener.ts';\nimport { generatePkce } from './pkce.ts';\nimport type { OAuthFlowOptions, PkceParams, TokenSet } from './types.ts';\n\n/**\n * OAuth token response from token endpoint\n */\ninterface TokenResponse {\n access_token: string;\n refresh_token?: string;\n expires_in: number;\n scope?: string;\n token_type?: string;\n}\n\n/**\n * InteractiveOAuthFlow manages the complete OAuth authorization code flow\n */\nexport class InteractiveOAuthFlow {\n /**\n * Perform OAuth authorization code flow\n *\n * @param authorizationEndpoint - OAuth authorization endpoint URL\n * @param tokenEndpoint - OAuth token endpoint URL\n * @param clientId - OAuth client ID\n * @param clientSecret - OAuth client secret\n * @param options - Flow options (port is required - use get-port to find available port)\n * @returns Token set with access and refresh tokens\n *\n * @throws Error if flow fails or times out\n *\n * @example\n * import getPort from 'get-port';\n *\n * const flow = new InteractiveOAuthFlow();\n * const port = await getPort();\n * const tokens = await flow.performAuthFlow(\n * 'https://example.com/oauth/authorize',\n * 'https://example.com/oauth/token',\n * 'client-id',\n * 'client-secret',\n * { port, scopes: ['read', 'write'] }\n * );\n */\n async performAuthFlow(authorizationEndpoint: string, tokenEndpoint: string, clientId: string, clientSecret: string, options: OAuthFlowOptions): Promise<TokenSet> {\n const logger = options.logger ?? defaultLogger;\n const callbackListener = new OAuthCallbackListener({ port: options.port, logger });\n\n // Generate PKCE parameters if requested (RFC 7636)\n let pkce: PkceParams | undefined;\n if (options.pkce) {\n logger.debug('🔐 Generating PKCE parameters...');\n pkce = await generatePkce();\n }\n\n try {\n // Start callback server\n await callbackListener.start();\n\n // Build redirect URI\n const redirectUri = options.redirectUri || `http://localhost:${options.port}/callback`;\n\n // Build authorization URL\n const authUrl = new URL(authorizationEndpoint);\n authUrl.searchParams.set('client_id', clientId);\n authUrl.searchParams.set('redirect_uri', redirectUri);\n authUrl.searchParams.set('response_type', 'code');\n\n if (options.scopes && options.scopes.length > 0) {\n authUrl.searchParams.set('scope', options.scopes.join(' '));\n }\n\n // Add resource parameter if specified (RFC 8707)\n if (options.resource) {\n authUrl.searchParams.set('resource', options.resource);\n }\n\n // Add PKCE parameters if generated (RFC 7636)\n if (pkce) {\n authUrl.searchParams.set('code_challenge', pkce.codeChallenge);\n authUrl.searchParams.set('code_challenge_method', pkce.codeChallengeMethod);\n }\n\n // Open browser or print URL for headless mode\n if (options.headless) {\n logger.info('🔗 Please visit this URL to authorize:');\n logger.info(authUrl.toString());\n logger.info('Waiting for callback...');\n } else {\n logger.debug('🌐 Opening browser for OAuth authorization...');\n // Try to open browser (requires 'open' package or native command)\n await this.openBrowser(authUrl.toString());\n }\n\n // Wait for callback with timeout\n const timeout = options.timeout || (options.headless ? 60000 : 300000);\n const result = await callbackListener.waitForCallback(timeout);\n\n // Exchange authorization code for tokens (with PKCE verifier if used)\n const tokens = await this.exchangeCodeForTokens(tokenEndpoint, result.code, clientId, clientSecret, redirectUri, pkce?.codeVerifier);\n\n return tokens;\n } catch (error) {\n logger.error('❌ OAuth flow failed:', error instanceof Error ? error.message : String(error));\n throw error;\n } finally {\n // Always close callback server\n await callbackListener.stop();\n }\n }\n\n /**\n * Exchange authorization code for access and refresh tokens\n * @param codeVerifier - Optional PKCE code verifier (RFC 7636)\n */\n private async exchangeCodeForTokens(tokenEndpoint: string, code: string, clientId: string, clientSecret: string, redirectUri: string, codeVerifier?: string): Promise<TokenSet> {\n const params = new URLSearchParams({\n grant_type: 'authorization_code',\n code,\n redirect_uri: redirectUri,\n client_id: clientId,\n client_secret: clientSecret,\n });\n\n // Add PKCE code verifier if provided (RFC 7636)\n if (codeVerifier) {\n params.set('code_verifier', codeVerifier);\n }\n\n const response = await fetch(tokenEndpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n Accept: 'application/json',\n Connection: 'close',\n },\n body: params,\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Token exchange failed (${response.status}): ${errorText}`);\n }\n\n const data = (await response.json()) as TokenResponse;\n\n if (!data.access_token) {\n throw new Error('Token response missing access_token');\n }\n\n const tokenSet: TokenSet = {\n accessToken: data.access_token,\n refreshToken: data.refresh_token || '',\n expiresAt: Date.now() + data.expires_in * 1000,\n clientId,\n clientSecret,\n };\n\n if (data.scope) {\n tokenSet.scopes = data.scope.split(' ');\n }\n\n return tokenSet;\n }\n\n /**\n * Refresh access token using refresh token\n *\n * @param tokenEndpoint - OAuth token endpoint URL\n * @param refreshToken - Refresh token from previous token set\n * @param clientId - OAuth client ID\n * @param clientSecret - OAuth client secret\n * @returns New token set with refreshed access token\n *\n * @throws Error if refresh fails\n */\n async refreshTokens(tokenEndpoint: string, refreshToken: string, clientId: string, clientSecret: string): Promise<TokenSet> {\n const response = await fetch(tokenEndpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n Accept: 'application/json',\n Connection: 'close',\n },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n refresh_token: refreshToken,\n client_id: clientId,\n client_secret: clientSecret,\n }),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Token refresh failed (${response.status}): ${errorText}`);\n }\n\n const data = (await response.json()) as TokenResponse;\n\n if (!data.access_token) {\n throw new Error('Token refresh response missing access_token');\n }\n\n const tokenSet: TokenSet = {\n accessToken: data.access_token,\n refreshToken: data.refresh_token || refreshToken, // Reuse old refresh token if not provided\n expiresAt: Date.now() + data.expires_in * 1000,\n clientId,\n clientSecret,\n };\n\n if (data.scope) {\n tokenSet.scopes = data.scope.split(' ');\n }\n\n return tokenSet;\n }\n\n /**\n * Open browser to authorization URL\n * Uses platform-specific command to open default browser\n */\n private async openBrowser(url: string): Promise<void> {\n // Determine platform-specific command\n const platform = process.platform;\n let command: string;\n let args: string[];\n\n if (platform === 'darwin') {\n command = 'open';\n args = [url];\n } else if (platform === 'win32') {\n command = 'cmd';\n args = ['/c', 'start', url];\n } else {\n // Linux and others\n command = 'xdg-open';\n args = [url];\n }\n\n // Spawn browser process\n const child = child_process.spawn(command, args, {\n detached: true,\n stdio: 'ignore',\n });\n\n child.unref();\n }\n}\n"],"names":["InteractiveOAuthFlow","performAuthFlow","authorizationEndpoint","tokenEndpoint","clientId","clientSecret","options","logger","callbackListener","pkce","redirectUri","authUrl","timeout","result","tokens","error","defaultLogger","OAuthCallbackListener","port","debug","generatePkce","start","URL","searchParams","set","scopes","length","join","resource","codeChallenge","codeChallengeMethod","headless","info","toString","openBrowser","waitForCallback","exchangeCodeForTokens","code","codeVerifier","Error","message","String","stop","params","response","errorText","data","tokenSet","URLSearchParams","grant_type","redirect_uri","client_id","client_secret","fetch","method","headers","Accept","Connection","body","ok","text","status","json","access_token","accessToken","refreshToken","refresh_token","expiresAt","Date","now","expires_in","scope","split","refreshTokens","url","platform","command","args","child","process","child_process","spawn","detached","stdio","unref"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/mcp-z/client/src/auth/interactive-oauth-flow.ts"],"sourcesContent":["/**\n * OAuth Authorization Flow Handler\n * Manages browser-based OAuth flows and token exchange with PKCE support\n */\n\nimport * as child_process from 'node:child_process';\nimport { logger as defaultLogger } from '../utils/logger.ts';\nimport { discoveryFetch } from './discovery-fetch.ts';\nimport { OAuthCallbackListener } from './oauth-callback-listener.ts';\nimport { generatePkce } from './pkce.ts';\nimport type { OAuthFlowOptions, PkceParams, TokenSet } from './types.ts';\n\n/**\n * OAuth token response from token endpoint\n */\ninterface TokenResponse {\n access_token: string;\n refresh_token?: string;\n expires_in: number;\n scope?: string;\n token_type?: string;\n}\n\n/**\n * InteractiveOAuthFlow manages the complete OAuth authorization code flow\n */\nexport class InteractiveOAuthFlow {\n /**\n * Perform OAuth authorization code flow\n *\n * @param authorizationEndpoint - OAuth authorization endpoint URL\n * @param tokenEndpoint - OAuth token endpoint URL\n * @param clientId - OAuth client ID\n * @param clientSecret - OAuth client secret\n * @param options - Flow options (port is required - use get-port to find available port)\n * @returns Token set with access and refresh tokens\n *\n * @throws Error if flow fails or times out\n *\n * @example\n * import getPort from 'get-port';\n *\n * const flow = new InteractiveOAuthFlow();\n * const port = await getPort();\n * const tokens = await flow.performAuthFlow(\n * 'https://example.com/oauth/authorize',\n * 'https://example.com/oauth/token',\n * 'client-id',\n * 'client-secret',\n * { port, scopes: ['read', 'write'] }\n * );\n */\n async performAuthFlow(authorizationEndpoint: string, tokenEndpoint: string, clientId: string, clientSecret: string, options: OAuthFlowOptions): Promise<TokenSet> {\n const logger = options.logger ?? defaultLogger;\n const callbackListener = new OAuthCallbackListener({ port: options.port, logger });\n\n // Generate PKCE parameters if requested (RFC 7636)\n let pkce: PkceParams | undefined;\n if (options.pkce) {\n logger.debug('🔐 Generating PKCE parameters...');\n pkce = await generatePkce();\n }\n\n try {\n // Start callback server\n await callbackListener.start();\n\n // Build redirect URI\n const redirectUri = options.redirectUri || `http://localhost:${options.port}/callback`;\n\n // Build authorization URL\n const authUrl = new URL(authorizationEndpoint);\n authUrl.searchParams.set('client_id', clientId);\n authUrl.searchParams.set('redirect_uri', redirectUri);\n authUrl.searchParams.set('response_type', 'code');\n\n if (options.scopes && options.scopes.length > 0) {\n authUrl.searchParams.set('scope', options.scopes.join(' '));\n }\n\n // Add resource parameter if specified (RFC 8707)\n if (options.resource) {\n authUrl.searchParams.set('resource', options.resource);\n }\n\n // Add PKCE parameters if generated (RFC 7636)\n if (pkce) {\n authUrl.searchParams.set('code_challenge', pkce.codeChallenge);\n authUrl.searchParams.set('code_challenge_method', pkce.codeChallengeMethod);\n }\n\n // Open browser or print URL for headless mode\n if (options.headless) {\n logger.info('🔗 Please visit this URL to authorize:');\n logger.info(authUrl.toString());\n logger.info('Waiting for callback...');\n } else {\n logger.debug('🌐 Opening browser for OAuth authorization...');\n // Try to open browser (requires 'open' package or native command)\n await this.openBrowser(authUrl.toString());\n }\n\n // Wait for callback with timeout\n const timeout = options.timeout || (options.headless ? 60000 : 300000);\n const result = await callbackListener.waitForCallback(timeout);\n\n // Exchange authorization code for tokens (with PKCE verifier if used)\n const tokens = await this.exchangeCodeForTokens(tokenEndpoint, result.code, clientId, clientSecret, redirectUri, options.allowLoopback ?? false, pkce?.codeVerifier);\n\n return tokens;\n } catch (error) {\n logger.error('❌ OAuth flow failed:', error instanceof Error ? error.message : String(error));\n throw error;\n } finally {\n // Always close callback server\n await callbackListener.stop();\n }\n }\n\n /**\n * Exchanges an authorization code for access and refresh tokens.\n * @param allowLoopback - Loopback trust grant computed by the caller from the server it is actually talking to, never from `tokenEndpoint`.\n * @param codeVerifier - Optional PKCE code verifier (RFC 7636).\n */\n private async exchangeCodeForTokens(tokenEndpoint: string, code: string, clientId: string, clientSecret: string, redirectUri: string, allowLoopback: boolean, codeVerifier?: string): Promise<TokenSet> {\n const params = new URLSearchParams({\n grant_type: 'authorization_code',\n code,\n redirect_uri: redirectUri,\n client_id: clientId,\n client_secret: clientSecret,\n });\n\n // Add PKCE code verifier if provided (RFC 7636)\n if (codeVerifier) {\n params.set('code_verifier', codeVerifier);\n }\n\n // tokenEndpoint is remote-controlled discovery data; discoveryFetch blocks\n // a private/internal target before the client secret is sent to it.\n const response = await discoveryFetch(\n tokenEndpoint,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n Accept: 'application/json',\n Connection: 'close',\n },\n body: params,\n },\n 'token endpoint',\n { allowLoopback }\n );\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Token exchange failed (${response.status}): ${errorText}`);\n }\n\n const data = (await response.json()) as TokenResponse;\n\n if (!data.access_token) {\n throw new Error('Token response missing access_token');\n }\n\n const tokenSet: TokenSet = {\n accessToken: data.access_token,\n refreshToken: data.refresh_token || '',\n expiresAt: Date.now() + data.expires_in * 1000,\n clientId,\n clientSecret,\n };\n\n if (data.scope) {\n tokenSet.scopes = data.scope.split(' ');\n }\n\n return tokenSet;\n }\n\n /**\n * Refreshes an access token using a refresh token.\n * @param tokenEndpoint - OAuth token endpoint URL.\n * @param refreshToken - Refresh token from a previous token set.\n * @param clientId - OAuth client ID.\n * @param clientSecret - OAuth client secret.\n * @param allowLoopback - Loopback trust grant computed by the caller from the server it is actually talking to, never from `tokenEndpoint`. Defaults to `false`.\n * @returns New token set with a refreshed access token.\n * @throws Error if refresh fails.\n */\n async refreshTokens(tokenEndpoint: string, refreshToken: string, clientId: string, clientSecret: string, allowLoopback = false): Promise<TokenSet> {\n // See exchangeCodeForTokens - tokenEndpoint is remote-controlled discovery data.\n const response = await discoveryFetch(\n tokenEndpoint,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n Accept: 'application/json',\n Connection: 'close',\n },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n refresh_token: refreshToken,\n client_id: clientId,\n client_secret: clientSecret,\n }),\n },\n 'token endpoint',\n { allowLoopback }\n );\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Token refresh failed (${response.status}): ${errorText}`);\n }\n\n const data = (await response.json()) as TokenResponse;\n\n if (!data.access_token) {\n throw new Error('Token refresh response missing access_token');\n }\n\n const tokenSet: TokenSet = {\n accessToken: data.access_token,\n refreshToken: data.refresh_token || refreshToken, // Reuse old refresh token if not provided\n expiresAt: Date.now() + data.expires_in * 1000,\n clientId,\n clientSecret,\n };\n\n if (data.scope) {\n tokenSet.scopes = data.scope.split(' ');\n }\n\n return tokenSet;\n }\n\n /**\n * Open browser to authorization URL\n * Uses platform-specific command to open default browser\n */\n private async openBrowser(url: string): Promise<void> {\n // Determine platform-specific command\n const platform = process.platform;\n let command: string;\n let args: string[];\n\n if (platform === 'darwin') {\n command = 'open';\n args = [url];\n } else if (platform === 'win32') {\n command = 'cmd';\n args = ['/c', 'start', url];\n } else {\n // Linux and others\n command = 'xdg-open';\n args = [url];\n }\n\n // Spawn browser process\n const child = child_process.spawn(command, args, {\n detached: true,\n stdio: 'ignore',\n });\n\n child.unref();\n }\n}\n"],"names":["InteractiveOAuthFlow","performAuthFlow","authorizationEndpoint","tokenEndpoint","clientId","clientSecret","options","logger","callbackListener","pkce","redirectUri","authUrl","timeout","result","tokens","error","defaultLogger","OAuthCallbackListener","port","debug","generatePkce","start","URL","searchParams","set","scopes","length","join","resource","codeChallenge","codeChallengeMethod","headless","info","toString","openBrowser","waitForCallback","exchangeCodeForTokens","code","allowLoopback","codeVerifier","Error","message","String","stop","params","response","errorText","data","tokenSet","URLSearchParams","grant_type","redirect_uri","client_id","client_secret","discoveryFetch","method","headers","Accept","Connection","body","ok","text","status","json","access_token","accessToken","refreshToken","refresh_token","expiresAt","Date","now","expires_in","scope","split","refreshTokens","url","platform","command","args","child","process","child_process","spawn","detached","stdio","unref"],"mappings":"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"}
@@ -4,37 +4,24 @@
4
4
  */
5
5
  import type { AuthorizationServerMetadata, ProtectedResourceMetadata } from './types.js';
6
6
  /**
7
- * Normalize a resource URL by stripping query/hash and trailing slashes.
8
- */
9
- /**
10
- * Discover OAuth 2.0 Protected Resource Metadata (RFC 9728)
11
- * Probes .well-known/oauth-protected-resource endpoint
7
+ * Discovers RFC 9728 Protected Resource Metadata: the `WWW-Authenticate`
8
+ * header first, then `.well-known/oauth-protected-resource` at the origin root and, for a path-prefixed resource, its sub-path variant.
12
9
  *
13
- * Discovery Strategy:
14
- * 1. Try origin root: {origin}/.well-known/oauth-protected-resource
15
- * 2. If 404, try sub-path: {origin}/.well-known/oauth-protected-resource{path}
16
- *
17
- * @param resourceUrl - URL of the protected resource (e.g., https://ai.todoist.net/mcp)
18
- * @returns ProtectedResourceMetadata if discovered, null otherwise
19
- *
20
- * @example
21
- * // Todoist case: MCP at ai.todoist.net/mcp, OAuth at todoist.com
22
- * const metadata = await discoverProtectedResourceMetadata('https://ai.todoist.net/mcp');
23
- * // Returns: { resource: "https://ai.todoist.net/mcp", authorization_servers: ["https://todoist.com"] }
10
+ * @param resourceUrl - URL of the protected resource (e.g. `https://ai.todoist.net/mcp`).
11
+ * @returns Discovered metadata, or `null` if none is found.
24
12
  */
25
13
  export declare function discoverProtectedResourceMetadata(resourceUrl: string): Promise<ProtectedResourceMetadata | null>;
26
14
  /**
27
- * Discover OAuth 2.0 Authorization Server Metadata (RFC 8414)
28
- * Probes .well-known/oauth-authorization-server endpoint
29
- *
30
- * @param authServerUrl - URL of the authorization server (typically from RFC 9728 discovery)
31
- * @returns AuthorizationServerMetadata if discovered, null otherwise
15
+ * Discovers RFC 8414 Authorization Server Metadata at
16
+ * `.well-known/oauth-authorization-server`, path-local variant first.
32
17
  *
33
- * @example
34
- * const metadata = await discoverAuthorizationServerMetadata('https://todoist.com');
35
- * // Returns: { issuer: "https://todoist.com", authorization_endpoint: "...", ... }
18
+ * @param authServerUrl - URL of the authorization server (typically from RFC 9728 discovery).
19
+ * @param options.allowLoopback - Loopback trust grant computed from the server the caller is actually talking to, never from `authServerUrl` itself. Defaults to `false`.
20
+ * @returns Discovered metadata, or `null` if none is found.
36
21
  */
37
- export declare function discoverAuthorizationServerMetadata(authServerUrl: string): Promise<AuthorizationServerMetadata | null>;
22
+ export declare function discoverAuthorizationServerMetadata(authServerUrl: string, options?: {
23
+ allowLoopback?: boolean;
24
+ }): Promise<AuthorizationServerMetadata | null>;
38
25
  /**
39
26
  * Discover OAuth Authorization Server Issuer from resource response (RFC 9207)
40
27
  *
@@ -4,37 +4,24 @@
4
4
  */
5
5
  import type { AuthorizationServerMetadata, ProtectedResourceMetadata } from './types.js';
6
6
  /**
7
- * Normalize a resource URL by stripping query/hash and trailing slashes.
8
- */
9
- /**
10
- * Discover OAuth 2.0 Protected Resource Metadata (RFC 9728)
11
- * Probes .well-known/oauth-protected-resource endpoint
7
+ * Discovers RFC 9728 Protected Resource Metadata: the `WWW-Authenticate`
8
+ * header first, then `.well-known/oauth-protected-resource` at the origin root and, for a path-prefixed resource, its sub-path variant.
12
9
  *
13
- * Discovery Strategy:
14
- * 1. Try origin root: {origin}/.well-known/oauth-protected-resource
15
- * 2. If 404, try sub-path: {origin}/.well-known/oauth-protected-resource{path}
16
- *
17
- * @param resourceUrl - URL of the protected resource (e.g., https://ai.todoist.net/mcp)
18
- * @returns ProtectedResourceMetadata if discovered, null otherwise
19
- *
20
- * @example
21
- * // Todoist case: MCP at ai.todoist.net/mcp, OAuth at todoist.com
22
- * const metadata = await discoverProtectedResourceMetadata('https://ai.todoist.net/mcp');
23
- * // Returns: { resource: "https://ai.todoist.net/mcp", authorization_servers: ["https://todoist.com"] }
10
+ * @param resourceUrl - URL of the protected resource (e.g. `https://ai.todoist.net/mcp`).
11
+ * @returns Discovered metadata, or `null` if none is found.
24
12
  */
25
13
  export declare function discoverProtectedResourceMetadata(resourceUrl: string): Promise<ProtectedResourceMetadata | null>;
26
14
  /**
27
- * Discover OAuth 2.0 Authorization Server Metadata (RFC 8414)
28
- * Probes .well-known/oauth-authorization-server endpoint
29
- *
30
- * @param authServerUrl - URL of the authorization server (typically from RFC 9728 discovery)
31
- * @returns AuthorizationServerMetadata if discovered, null otherwise
15
+ * Discovers RFC 8414 Authorization Server Metadata at
16
+ * `.well-known/oauth-authorization-server`, path-local variant first.
32
17
  *
33
- * @example
34
- * const metadata = await discoverAuthorizationServerMetadata('https://todoist.com');
35
- * // Returns: { issuer: "https://todoist.com", authorization_endpoint: "...", ... }
18
+ * @param authServerUrl - URL of the authorization server (typically from RFC 9728 discovery).
19
+ * @param options.allowLoopback - Loopback trust grant computed from the server the caller is actually talking to, never from `authServerUrl` itself. Defaults to `false`.
20
+ * @returns Discovered metadata, or `null` if none is found.
36
21
  */
37
- export declare function discoverAuthorizationServerMetadata(authServerUrl: string): Promise<AuthorizationServerMetadata | null>;
22
+ export declare function discoverAuthorizationServerMetadata(authServerUrl: string, options?: {
23
+ allowLoopback?: boolean;
24
+ }): Promise<AuthorizationServerMetadata | null>;
38
25
  /**
39
26
  * Discover OAuth Authorization Server Issuer from resource response (RFC 9207)
40
27
  *