@mcp-z/client 2.2.4 → 2.3.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 (107) hide show
  1. package/README.md +12 -1
  2. package/dist/cjs/auth/capability-discovery.d.cts +3 -1
  3. package/dist/cjs/auth/capability-discovery.d.ts +3 -1
  4. package/dist/cjs/auth/capability-discovery.js +46 -32
  5. package/dist/cjs/auth/capability-discovery.js.map +1 -1
  6. package/dist/cjs/auth/discovery-fetch.d.cts +2 -0
  7. package/dist/cjs/auth/discovery-fetch.d.ts +2 -0
  8. package/dist/cjs/auth/discovery-fetch.js +85 -31
  9. package/dist/cjs/auth/discovery-fetch.js.map +1 -1
  10. package/dist/cjs/auth/interactive-oauth-flow.d.cts +1 -1
  11. package/dist/cjs/auth/interactive-oauth-flow.d.ts +1 -1
  12. package/dist/cjs/auth/interactive-oauth-flow.js +13 -6
  13. package/dist/cjs/auth/interactive-oauth-flow.js.map +1 -1
  14. package/dist/cjs/auth/oauth-callback-listener.d.cts +1 -1
  15. package/dist/cjs/auth/oauth-callback-listener.d.ts +1 -1
  16. package/dist/cjs/auth/oauth-callback-listener.js +38 -2
  17. package/dist/cjs/auth/oauth-callback-listener.js.map +1 -1
  18. package/dist/cjs/auth/rfc9728-discovery.d.cts +7 -2
  19. package/dist/cjs/auth/rfc9728-discovery.d.ts +7 -2
  20. package/dist/cjs/auth/rfc9728-discovery.js +120 -88
  21. package/dist/cjs/auth/rfc9728-discovery.js.map +1 -1
  22. package/dist/cjs/auth/types.d.cts +4 -0
  23. package/dist/cjs/auth/types.d.ts +4 -0
  24. package/dist/cjs/auth/types.js.map +1 -1
  25. package/dist/cjs/connection/connect-client.d.cts +9 -1
  26. package/dist/cjs/connection/connect-client.d.ts +9 -1
  27. package/dist/cjs/connection/connect-client.js +282 -75
  28. package/dist/cjs/connection/connect-client.js.map +1 -1
  29. package/dist/cjs/connection/existing-process-transport.d.cts +18 -18
  30. package/dist/cjs/connection/existing-process-transport.d.ts +18 -18
  31. package/dist/cjs/connection/existing-process-transport.js +187 -82
  32. package/dist/cjs/connection/existing-process-transport.js.map +1 -1
  33. package/dist/cjs/connection/wait-for-http-ready.d.cts +1 -1
  34. package/dist/cjs/connection/wait-for-http-ready.d.ts +1 -1
  35. package/dist/cjs/connection/wait-for-http-ready.js +30 -5
  36. package/dist/cjs/connection/wait-for-http-ready.js.map +1 -1
  37. package/dist/cjs/dcr/dcr-authenticator.d.cts +1 -1
  38. package/dist/cjs/dcr/dcr-authenticator.d.ts +1 -1
  39. package/dist/cjs/dcr/dcr-authenticator.js +29 -18
  40. package/dist/cjs/dcr/dcr-authenticator.js.map +1 -1
  41. package/dist/cjs/dcr/dynamic-client-registrar.js +2 -1
  42. package/dist/cjs/dcr/dynamic-client-registrar.js.map +1 -1
  43. package/dist/cjs/index.d.cts +1 -1
  44. package/dist/cjs/index.d.ts +1 -1
  45. package/dist/cjs/index.js.map +1 -1
  46. package/dist/cjs/spawn/owned-process-tree.d.cts +12 -0
  47. package/dist/cjs/spawn/owned-process-tree.d.ts +12 -0
  48. package/dist/cjs/spawn/owned-process-tree.js +93 -0
  49. package/dist/cjs/spawn/owned-process-tree.js.map +1 -0
  50. package/dist/cjs/spawn/spawn-server.d.cts +8 -32
  51. package/dist/cjs/spawn/spawn-server.d.ts +8 -32
  52. package/dist/cjs/spawn/spawn-server.js +398 -212
  53. package/dist/cjs/spawn/spawn-server.js.map +1 -1
  54. package/dist/cjs/spawn/spawn-servers.d.cts +18 -8
  55. package/dist/cjs/spawn/spawn-servers.d.ts +18 -8
  56. package/dist/cjs/spawn/spawn-servers.js +342 -113
  57. package/dist/cjs/spawn/spawn-servers.js.map +1 -1
  58. package/dist/cjs/types.d.cts +1 -1
  59. package/dist/cjs/types.d.ts +1 -1
  60. package/dist/cjs/types.js.map +1 -1
  61. package/dist/esm/auth/capability-discovery.d.ts +3 -1
  62. package/dist/esm/auth/capability-discovery.js +21 -11
  63. package/dist/esm/auth/capability-discovery.js.map +1 -1
  64. package/dist/esm/auth/discovery-fetch.d.ts +2 -0
  65. package/dist/esm/auth/discovery-fetch.js +64 -23
  66. package/dist/esm/auth/discovery-fetch.js.map +1 -1
  67. package/dist/esm/auth/interactive-oauth-flow.d.ts +1 -1
  68. package/dist/esm/auth/interactive-oauth-flow.js +13 -6
  69. package/dist/esm/auth/interactive-oauth-flow.js.map +1 -1
  70. package/dist/esm/auth/oauth-callback-listener.d.ts +1 -1
  71. package/dist/esm/auth/oauth-callback-listener.js +32 -2
  72. package/dist/esm/auth/oauth-callback-listener.js.map +1 -1
  73. package/dist/esm/auth/rfc9728-discovery.d.ts +7 -2
  74. package/dist/esm/auth/rfc9728-discovery.js +40 -16
  75. package/dist/esm/auth/rfc9728-discovery.js.map +1 -1
  76. package/dist/esm/auth/types.d.ts +4 -0
  77. package/dist/esm/auth/types.js.map +1 -1
  78. package/dist/esm/connection/connect-client.d.ts +9 -1
  79. package/dist/esm/connection/connect-client.js +97 -35
  80. package/dist/esm/connection/connect-client.js.map +1 -1
  81. package/dist/esm/connection/existing-process-transport.d.ts +18 -18
  82. package/dist/esm/connection/existing-process-transport.js +132 -70
  83. package/dist/esm/connection/existing-process-transport.js.map +1 -1
  84. package/dist/esm/connection/wait-for-http-ready.d.ts +1 -1
  85. package/dist/esm/connection/wait-for-http-ready.js +19 -4
  86. package/dist/esm/connection/wait-for-http-ready.js.map +1 -1
  87. package/dist/esm/dcr/dcr-authenticator.d.ts +1 -1
  88. package/dist/esm/dcr/dcr-authenticator.js +26 -15
  89. package/dist/esm/dcr/dcr-authenticator.js.map +1 -1
  90. package/dist/esm/dcr/dynamic-client-registrar.js +2 -1
  91. package/dist/esm/dcr/dynamic-client-registrar.js.map +1 -1
  92. package/dist/esm/index.d.ts +1 -1
  93. package/dist/esm/index.js.map +1 -1
  94. package/dist/esm/spawn/owned-process-tree.d.ts +12 -0
  95. package/dist/esm/spawn/owned-process-tree.js +39 -0
  96. package/dist/esm/spawn/owned-process-tree.js.map +1 -0
  97. package/dist/esm/spawn/spawn-server.d.ts +8 -32
  98. package/dist/esm/spawn/spawn-server.js +224 -130
  99. package/dist/esm/spawn/spawn-server.js.map +1 -1
  100. package/dist/esm/spawn/spawn-servers.d.ts +18 -8
  101. package/dist/esm/spawn/spawn-servers.js +180 -55
  102. package/dist/esm/spawn/spawn-servers.js.map +1 -1
  103. package/dist/esm/types.d.ts +1 -1
  104. package/dist/esm/types.js.map +1 -1
  105. package/package.json +8 -5
  106. package/schemas/servers.d.ts +24 -0
  107. package/schemas/servers.schema.json +31 -0
package/README.md CHANGED
@@ -86,13 +86,24 @@ const registry = createServerRegistry(
86
86
  api: {
87
87
  type: 'http',
88
88
  url: 'http://localhost:3000/mcp',
89
- start: { command: 'node', args: ['server.js'] }
89
+ start: {
90
+ command: 'node',
91
+ args: ['server.js'],
92
+ stop: {
93
+ command: 'node',
94
+ args: ['request-server-stop.js', 'http://localhost:3000/admin/shutdown']
95
+ }
96
+ }
90
97
  }
91
98
  },
92
99
  { dialects: ['start'] }
93
100
  );
94
101
  ```
95
102
 
103
+ `start.stop` is an optional application-specific cooperative shutdown command for a server started by this registry. It runs once during `registry.close()`, before the registry waits for the owned process to close. The stop command must use a mechanism implemented by that server; MCP HTTP has no server shutdown operation. Commands use argument arrays without a shell. `timeoutMs` bounds each cooperative wait for the stop command and owned process (five seconds by default); emergency termination has its own bounded wait. Without a stop command there is no portable HTTP shutdown protocol: POSIX requests the configured signal, while Windows waits for the direct child and then uses emergency termination. External HTTP servers and client lease closure do not run the stop command.
104
+
105
+ Stdio shutdown sends EOF to the owned process first. If it remains open, POSIX sends the requested signal and waits again; Windows proceeds to bounded termination of the direct child. POSIX shutdown tracks the detached process group. Windows descendant ownership is not verified, so a wrapper that exits while leaving descendants may leave those processes running. `timedOut` reports a missed cooperative window, and `killedCount` counts direct-child or process-group emergency terminations.
106
+
96
107
  ## API overview
97
108
 
98
109
  ### Registry
@@ -3,4 +3,6 @@
3
3
  * Probes RFC 9728 (Protected Resource) and RFC 8414 (Authorization Server) metadata
4
4
  */
5
5
  import type { AuthCapabilities } from './types.js';
6
- export declare function probeAuthCapabilities(baseUrl: string): Promise<AuthCapabilities>;
6
+ export declare function probeAuthCapabilities(baseUrl: string, options?: {
7
+ signal?: AbortSignal;
8
+ }): Promise<AuthCapabilities>;
@@ -3,4 +3,6 @@
3
3
  * Probes RFC 9728 (Protected Resource) and RFC 8414 (Authorization Server) metadata
4
4
  */
5
5
  import type { AuthCapabilities } from './types.js';
6
- export declare function probeAuthCapabilities(baseUrl: string): Promise<AuthCapabilities>;
6
+ export declare function probeAuthCapabilities(baseUrl: string, options?: {
7
+ signal?: AbortSignal;
8
+ }): Promise<AuthCapabilities>;
@@ -205,7 +205,7 @@ function _ts_generator(thisArg, body) {
205
205
  }
206
206
  return capabilities;
207
207
  }
208
- function resolveCapabilitiesFromAuthorizationServer(authServerUrl, scopes, allowLoopback, resource) {
208
+ function resolveCapabilitiesFromAuthorizationServer(authServerUrl, scopes, allowLoopback, resource, signal) {
209
209
  return _async_to_generator(function() {
210
210
  var metadata;
211
211
  return _ts_generator(this, function(_state) {
@@ -214,7 +214,8 @@ function resolveCapabilitiesFromAuthorizationServer(authServerUrl, scopes, allow
214
214
  return [
215
215
  4,
216
216
  (0, _rfc9728discoveryts.discoverAuthorizationServerMetadata)(authServerUrl, {
217
- allowLoopback: allowLoopback
217
+ allowLoopback: allowLoopback,
218
+ signal: signal
218
219
  })
219
220
  ];
220
221
  case 1:
@@ -231,31 +232,39 @@ function resolveCapabilitiesFromAuthorizationServer(authServerUrl, scopes, allow
231
232
  });
232
233
  })();
233
234
  }
234
- function probeAuthCapabilities(baseUrl) {
235
- return _async_to_generator(function() {
236
- var normalizedBaseUrl, allowLoopback, resourceMetadata, authServerUrl, capabilities, issuer, issuerCapabilities, issuer1, issuerCapabilities1, origin, originCapabilities, _error;
235
+ function probeAuthCapabilities(_0) {
236
+ return _async_to_generator(function(baseUrl) {
237
+ var options, signal, normalizedBaseUrl, allowLoopback, resourceMetadata, authServerUrl, capabilities, issuer, issuerCapabilities, issuer1, issuerCapabilities1, origin, originCapabilities, _error;
238
+ var _arguments = arguments;
237
239
  return _ts_generator(this, function(_state) {
238
240
  switch(_state.label){
239
241
  case 0:
242
+ options = _arguments.length > 1 && _arguments[1] !== void 0 ? _arguments[1] : {};
243
+ signal = options.signal;
244
+ _state.label = 1;
245
+ case 1:
240
246
  _state.trys.push([
241
- 0,
242
- 10,
247
+ 1,
248
+ 11,
243
249
  ,
244
- 11
250
+ 12
245
251
  ]);
252
+ (0, _discoveryfetchts.throwIfAborted)(signal);
246
253
  normalizedBaseUrl = (0, _urlutilsts.normalizeUrl)(baseUrl);
247
254
  // Trust signal for every authorization-server fetch below: whether the
248
255
  // configured MCP server is itself loopback, never a remote-supplied URL.
249
256
  allowLoopback = (0, _discoveryfetchts.isLoopbackUrl)(normalizedBaseUrl);
250
257
  return [
251
258
  4,
252
- (0, _rfc9728discoveryts.discoverProtectedResourceMetadata)(normalizedBaseUrl)
259
+ (0, _rfc9728discoveryts.discoverProtectedResourceMetadata)(normalizedBaseUrl, {
260
+ signal: signal
261
+ })
253
262
  ];
254
- case 1:
263
+ case 2:
255
264
  resourceMetadata = _state.sent();
256
265
  if (!(resourceMetadata && resourceMetadata.authorization_servers.length > 0)) return [
257
266
  3,
258
- 5
267
+ 6
259
268
  ];
260
269
  // Found protected resource metadata with authorization servers
261
270
  // Discover the authorization server's metadata (RFC 8414)
@@ -271,9 +280,9 @@ function probeAuthCapabilities(baseUrl) {
271
280
  }
272
281
  return [
273
282
  4,
274
- resolveCapabilitiesFromAuthorizationServer(authServerUrl, resourceMetadata.scopes_supported, allowLoopback, resourceMetadata.resource)
283
+ resolveCapabilitiesFromAuthorizationServer(authServerUrl, resourceMetadata.scopes_supported, allowLoopback, resourceMetadata.resource, signal)
275
284
  ];
276
- case 2:
285
+ case 3:
277
286
  capabilities = _state.sent();
278
287
  if (capabilities) {
279
288
  return [
@@ -283,56 +292,60 @@ function probeAuthCapabilities(baseUrl) {
283
292
  }
284
293
  return [
285
294
  4,
286
- (0, _rfc9728discoveryts.discoverAuthorizationServerIssuer)(baseUrl)
295
+ (0, _rfc9728discoveryts.discoverAuthorizationServerIssuer)(baseUrl, {
296
+ signal: signal
297
+ })
287
298
  ];
288
- case 3:
299
+ case 4:
289
300
  issuer = _state.sent();
290
301
  if (!issuer) return [
291
302
  3,
292
- 5
303
+ 6
293
304
  ];
294
305
  return [
295
306
  4,
296
- resolveCapabilitiesFromAuthorizationServer(issuer, resourceMetadata.scopes_supported, allowLoopback, resourceMetadata.resource)
307
+ resolveCapabilitiesFromAuthorizationServer(issuer, resourceMetadata.scopes_supported, allowLoopback, resourceMetadata.resource, signal)
297
308
  ];
298
- case 4:
309
+ case 5:
299
310
  issuerCapabilities = _state.sent();
300
311
  if (issuerCapabilities) return [
301
312
  2,
302
313
  issuerCapabilities
303
314
  ];
304
- _state.label = 5;
305
- case 5:
315
+ _state.label = 6;
316
+ case 6:
306
317
  return [
307
318
  4,
308
- (0, _rfc9728discoveryts.discoverAuthorizationServerIssuer)(normalizedBaseUrl)
319
+ (0, _rfc9728discoveryts.discoverAuthorizationServerIssuer)(normalizedBaseUrl, {
320
+ signal: signal
321
+ })
309
322
  ];
310
- case 6:
323
+ case 7:
311
324
  issuer1 = _state.sent();
312
325
  if (!issuer1) return [
313
326
  3,
314
- 8
327
+ 9
315
328
  ];
316
329
  return [
317
330
  4,
318
- resolveCapabilitiesFromAuthorizationServer(issuer1, undefined, allowLoopback)
331
+ resolveCapabilitiesFromAuthorizationServer(issuer1, undefined, allowLoopback, undefined, signal)
319
332
  ];
320
- case 7:
333
+ case 8:
321
334
  issuerCapabilities1 = _state.sent();
322
335
  if (issuerCapabilities1) return [
323
336
  2,
324
337
  issuerCapabilities1
325
338
  ];
326
- _state.label = 8;
327
- case 8:
339
+ _state.label = 9;
340
+ case 9:
328
341
  // Strategy 2: Fall back to direct RFC 8414 discovery at resource origin
329
342
  // This handles same-domain OAuth (traditional setup)
330
343
  origin = getOrigin(normalizedBaseUrl);
331
344
  return [
332
345
  4,
333
- resolveCapabilitiesFromAuthorizationServer(origin, undefined, allowLoopback)
346
+ resolveCapabilitiesFromAuthorizationServer(origin, undefined, allowLoopback, undefined, signal)
334
347
  ];
335
- case 9:
348
+ case 10:
336
349
  originCapabilities = _state.sent();
337
350
  if (originCapabilities) return [
338
351
  2,
@@ -345,8 +358,9 @@ function probeAuthCapabilities(baseUrl) {
345
358
  supportsDcr: false
346
359
  }
347
360
  ];
348
- case 10:
361
+ case 11:
349
362
  _error = _state.sent();
363
+ (0, _discoveryfetchts.throwIfAborted)(signal);
350
364
  // Network error, invalid JSON, or other fetch failure
351
365
  // Gracefully degrade - assume no DCR support
352
366
  return [
@@ -355,12 +369,12 @@ function probeAuthCapabilities(baseUrl) {
355
369
  supportsDcr: false
356
370
  }
357
371
  ];
358
- case 11:
372
+ case 12:
359
373
  return [
360
374
  2
361
375
  ];
362
376
  }
363
377
  });
364
- })();
378
+ }).apply(this, arguments);
365
379
  }
366
380
  /* 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/capability-discovery.ts"],"sourcesContent":["/**\n * OAuth Server Capability Discovery\n * Probes RFC 9728 (Protected Resource) and RFC 8414 (Authorization Server) metadata\n */\n\nimport { normalizeUrl } from '../lib/url-utils.ts';\nimport { isLoopbackUrl } from './discovery-fetch.ts';\nimport { discoverAuthorizationServerIssuer, discoverAuthorizationServerMetadata, discoverProtectedResourceMetadata } from './rfc9728-discovery.ts';\nimport type { AuthCapabilities, AuthorizationServerMetadata } from './types.ts';\n\n/**\n * Extract origin (protocol + host) from a URL\n * @param url - Full URL that may include a path\n * @returns Origin (e.g., \"https://example.com\") or original string if invalid URL\n *\n * @example\n * getOrigin('https://example.com/mcp') // → 'https://example.com'\n * getOrigin('http://localhost:9999/api/v1/mcp') // → 'http://localhost:9999'\n */\nfunction getOrigin(url: string): string {\n try {\n return new URL(url).origin;\n } catch {\n // Invalid URL - return as-is for graceful degradation\n return url;\n }\n}\n\n/**\n * Probe OAuth server capabilities using RFC 9728 → RFC 8414 discovery chain\n * Returns capabilities including DCR support detection\n *\n * Discovery Strategy:\n * 1. Try RFC 9728 Protected Resource Metadata (supports cross-domain OAuth)\n * 2. If found, use first authorization_server to discover RFC 8414 Authorization Server Metadata\n * 3. Fall back to direct RFC 8414 discovery at resource origin\n *\n * @param baseUrl - Base URL of the protected resource (e.g., https://ai.todoist.net/mcp)\n * @returns AuthCapabilities object with discovered endpoints and features\n *\n * @example\n * // Todoist case: MCP at ai.todoist.net/mcp, OAuth at todoist.com\n * const caps = await probeAuthCapabilities('https://ai.todoist.net/mcp');\n * if (caps.supportsDcr) {\n * console.log('Registration endpoint:', caps.registrationEndpoint);\n * }\n */\nfunction buildCapabilities(metadata: AuthorizationServerMetadata, scopes?: string[], resource?: string): AuthCapabilities {\n const supportsDcr = !!metadata.registration_endpoint;\n const capabilities: AuthCapabilities = { supportsDcr, authorizationResponseIssSupported: metadata.authorization_response_iss_parameter_supported === true };\n\n if (metadata.issuer) {\n capabilities.issuer = metadata.issuer;\n }\n\n // Carried from the RFC 9728 document, not from the URL we were given: the\n // resource server names itself, and that name is what RFC 8707 audience-binds\n // a token to.\n if (resource) {\n capabilities.resource = resource;\n }\n\n if (metadata.registration_endpoint) {\n capabilities.registrationEndpoint = metadata.registration_endpoint;\n }\n if (metadata.authorization_endpoint) {\n capabilities.authorizationEndpoint = metadata.authorization_endpoint;\n }\n if (metadata.token_endpoint) capabilities.tokenEndpoint = metadata.token_endpoint;\n if (metadata.introspection_endpoint) {\n capabilities.introspectionEndpoint = metadata.introspection_endpoint;\n }\n\n if (scopes && scopes.length > 0) {\n capabilities.scopes = scopes;\n } else if (metadata.scopes_supported) {\n capabilities.scopes = metadata.scopes_supported;\n }\n\n return capabilities;\n}\n\nasync function resolveCapabilitiesFromAuthorizationServer(authServerUrl: string, scopes: string[] | undefined, allowLoopback: boolean, resource?: string): Promise<AuthCapabilities | null> {\n const metadata = await discoverAuthorizationServerMetadata(authServerUrl, { allowLoopback });\n if (!metadata) return null;\n return buildCapabilities(metadata, scopes, resource);\n}\n\nexport async function probeAuthCapabilities(baseUrl: string): Promise<AuthCapabilities> {\n try {\n const normalizedBaseUrl = normalizeUrl(baseUrl);\n // Trust signal for every authorization-server fetch below: whether the\n // configured MCP server is itself loopback, never a remote-supplied URL.\n const allowLoopback = isLoopbackUrl(normalizedBaseUrl);\n // Strategy 1: Try RFC 9728 Protected Resource Metadata discovery\n // This handles cross-domain OAuth (e.g., Todoist: ai.todoist.net/mcp → todoist.com)\n const resourceMetadata = await discoverProtectedResourceMetadata(normalizedBaseUrl);\n\n if (resourceMetadata && resourceMetadata.authorization_servers.length > 0) {\n // Found protected resource metadata with authorization servers\n // Discover the authorization server's metadata (RFC 8414)\n const authServerUrl = resourceMetadata.authorization_servers[0];\n if (!authServerUrl) {\n // Array has length > 0 but first element is undefined/null - skip this path\n return { supportsDcr: false };\n }\n const capabilities = await resolveCapabilitiesFromAuthorizationServer(authServerUrl, resourceMetadata.scopes_supported, allowLoopback, resourceMetadata.resource);\n if (capabilities) {\n return capabilities;\n }\n\n const issuer = await discoverAuthorizationServerIssuer(baseUrl);\n if (issuer) {\n const issuerCapabilities = await resolveCapabilitiesFromAuthorizationServer(issuer, resourceMetadata.scopes_supported, allowLoopback, resourceMetadata.resource);\n if (issuerCapabilities) return issuerCapabilities;\n }\n }\n\n const issuer = await discoverAuthorizationServerIssuer(normalizedBaseUrl);\n if (issuer) {\n const issuerCapabilities = await resolveCapabilitiesFromAuthorizationServer(issuer, undefined, allowLoopback);\n if (issuerCapabilities) return issuerCapabilities;\n }\n\n // Strategy 2: Fall back to direct RFC 8414 discovery at resource origin\n // This handles same-domain OAuth (traditional setup)\n const origin = getOrigin(normalizedBaseUrl);\n const originCapabilities = await resolveCapabilitiesFromAuthorizationServer(origin, undefined, allowLoopback);\n if (originCapabilities) return originCapabilities;\n\n // No OAuth metadata found\n return { supportsDcr: false };\n } catch (_error) {\n // Network error, invalid JSON, or other fetch failure\n // Gracefully degrade - assume no DCR support\n return { supportsDcr: false };\n }\n}\n"],"names":["probeAuthCapabilities","getOrigin","url","URL","origin","buildCapabilities","metadata","scopes","resource","supportsDcr","registration_endpoint","capabilities","authorizationResponseIssSupported","authorization_response_iss_parameter_supported","issuer","registrationEndpoint","authorization_endpoint","authorizationEndpoint","token_endpoint","tokenEndpoint","introspection_endpoint","introspectionEndpoint","length","scopes_supported","resolveCapabilitiesFromAuthorizationServer","authServerUrl","allowLoopback","discoverAuthorizationServerMetadata","baseUrl","normalizedBaseUrl","resourceMetadata","issuerCapabilities","originCapabilities","_error","normalizeUrl","isLoopbackUrl","discoverProtectedResourceMetadata","authorization_servers","discoverAuthorizationServerIssuer","undefined"],"mappings":"AAAA;;;CAGC;;;;+BAqFqBA;;;eAAAA;;;0BAnFO;gCACC;kCAC4F;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAG1H;;;;;;;;CAQC,GACD,SAASC,UAAUC,GAAW;IAC5B,IAAI;QACF,OAAO,IAAIC,IAAID,KAAKE,MAAM;IAC5B,EAAE,eAAM;QACN,sDAAsD;QACtD,OAAOF;IACT;AACF;AAEA;;;;;;;;;;;;;;;;;;CAkBC,GACD,SAASG,kBAAkBC,QAAqC,EAAEC,MAAiB,EAAEC,QAAiB;IACpG,IAAMC,cAAc,CAAC,CAACH,SAASI,qBAAqB;IACpD,IAAMC,eAAiC;QAAEF,aAAAA;QAAaG,mCAAmCN,SAASO,8CAA8C,KAAK;IAAK;IAE1J,IAAIP,SAASQ,MAAM,EAAE;QACnBH,aAAaG,MAAM,GAAGR,SAASQ,MAAM;IACvC;IAEA,0EAA0E;IAC1E,8EAA8E;IAC9E,cAAc;IACd,IAAIN,UAAU;QACZG,aAAaH,QAAQ,GAAGA;IAC1B;IAEA,IAAIF,SAASI,qBAAqB,EAAE;QAClCC,aAAaI,oBAAoB,GAAGT,SAASI,qBAAqB;IACpE;IACA,IAAIJ,SAASU,sBAAsB,EAAE;QACnCL,aAAaM,qBAAqB,GAAGX,SAASU,sBAAsB;IACtE;IACA,IAAIV,SAASY,cAAc,EAAEP,aAAaQ,aAAa,GAAGb,SAASY,cAAc;IACjF,IAAIZ,SAASc,sBAAsB,EAAE;QACnCT,aAAaU,qBAAqB,GAAGf,SAASc,sBAAsB;IACtE;IAEA,IAAIb,UAAUA,OAAOe,MAAM,GAAG,GAAG;QAC/BX,aAAaJ,MAAM,GAAGA;IACxB,OAAO,IAAID,SAASiB,gBAAgB,EAAE;QACpCZ,aAAaJ,MAAM,GAAGD,SAASiB,gBAAgB;IACjD;IAEA,OAAOZ;AACT;AAEA,SAAea,2CAA2CC,aAAqB,EAAElB,MAA4B,EAAEmB,aAAsB,EAAElB,QAAiB;;YAChJF;;;;oBAAW;;wBAAMqB,IAAAA,uDAAmC,EAACF,eAAe;4BAAEC,eAAAA;wBAAc;;;oBAApFpB,WAAW;oBACjB,IAAI,CAACA,UAAU;;wBAAO;;oBACtB;;wBAAOD,kBAAkBC,UAAUC,QAAQC;;;;IAC7C;;AAEO,SAAeR,sBAAsB4B,OAAe;;YAEjDC,mBAGAH,eAGAI,kBAKEL,eAKAd,cAKAG,QAEEiB,oBAKJjB,SAEEiB,qBAMF3B,QACA4B,oBAKCC;;;;;;;;;;oBA1CDJ,oBAAoBK,IAAAA,wBAAY,EAACN;oBACvC,uEAAuE;oBACvE,yEAAyE;oBACnEF,gBAAgBS,IAAAA,+BAAa,EAACN;oBAGX;;wBAAMO,IAAAA,qDAAiC,EAACP;;;oBAA3DC,mBAAmB;yBAErBA,CAAAA,oBAAoBA,iBAAiBO,qBAAqB,CAACf,MAAM,GAAG,CAAA,GAApEQ;;;;oBACF,+DAA+D;oBAC/D,0DAA0D;oBACpDL,gBAAgBK,iBAAiBO,qBAAqB,CAAC,EAAE;oBAC/D,IAAI,CAACZ,eAAe;wBAClB,4EAA4E;wBAC5E;;4BAAO;gCAAEhB,aAAa;4BAAM;;oBAC9B;oBACqB;;wBAAMe,2CAA2CC,eAAeK,iBAAiBP,gBAAgB,EAAEG,eAAeI,iBAAiBtB,QAAQ;;;oBAA1JG,eAAe;oBACrB,IAAIA,cAAc;wBAChB;;4BAAOA;;oBACT;oBAEe;;wBAAM2B,IAAAA,qDAAiC,EAACV;;;oBAAjDd,SAAS;yBACXA,QAAAA;;;;oBACyB;;wBAAMU,2CAA2CV,QAAQgB,iBAAiBP,gBAAgB,EAAEG,eAAeI,iBAAiBtB,QAAQ;;;oBAAzJuB,qBAAqB;oBAC3B,IAAIA,oBAAoB;;wBAAOA;;;;oBAIpB;;wBAAMO,IAAAA,qDAAiC,EAACT;;;oBAAjDf,UAAS;yBACXA,SAAAA;;;;oBACyB;;wBAAMU,2CAA2CV,SAAQyB,WAAWb;;;oBAAzFK,sBAAqB;oBAC3B,IAAIA,qBAAoB;;wBAAOA;;;;oBAGjC,wEAAwE;oBACxE,qDAAqD;oBAC/C3B,SAASH,UAAU4B;oBACE;;wBAAML,2CAA2CpB,QAAQmC,WAAWb;;;oBAAzFM,qBAAqB;oBAC3B,IAAIA,oBAAoB;;wBAAOA;;oBAE/B,0BAA0B;oBAC1B;;wBAAO;4BAAEvB,aAAa;wBAAM;;;oBACrBwB;oBACP,sDAAsD;oBACtD,6CAA6C;oBAC7C;;wBAAO;4BAAExB,aAAa;wBAAM;;;;;;;;IAEhC"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/mcp-z/client/src/auth/capability-discovery.ts"],"sourcesContent":["/**\n * OAuth Server Capability Discovery\n * Probes RFC 9728 (Protected Resource) and RFC 8414 (Authorization Server) metadata\n */\n\nimport { normalizeUrl } from '../lib/url-utils.ts';\nimport { isLoopbackUrl, throwIfAborted } from './discovery-fetch.ts';\nimport { discoverAuthorizationServerIssuer, discoverAuthorizationServerMetadata, discoverProtectedResourceMetadata } from './rfc9728-discovery.ts';\nimport type { AuthCapabilities, AuthorizationServerMetadata } from './types.ts';\n\n/**\n * Extract origin (protocol + host) from a URL\n * @param url - Full URL that may include a path\n * @returns Origin (e.g., \"https://example.com\") or original string if invalid URL\n *\n * @example\n * getOrigin('https://example.com/mcp') // → 'https://example.com'\n * getOrigin('http://localhost:9999/api/v1/mcp') // → 'http://localhost:9999'\n */\nfunction getOrigin(url: string): string {\n try {\n return new URL(url).origin;\n } catch {\n // Invalid URL - return as-is for graceful degradation\n return url;\n }\n}\n\n/**\n * Probe OAuth server capabilities using RFC 9728 → RFC 8414 discovery chain\n * Returns capabilities including DCR support detection\n *\n * Discovery Strategy:\n * 1. Try RFC 9728 Protected Resource Metadata (supports cross-domain OAuth)\n * 2. If found, use first authorization_server to discover RFC 8414 Authorization Server Metadata\n * 3. Fall back to direct RFC 8414 discovery at resource origin\n *\n * @param baseUrl - Base URL of the protected resource (e.g., https://ai.todoist.net/mcp)\n * @returns AuthCapabilities object with discovered endpoints and features\n *\n * @example\n * // Todoist case: MCP at ai.todoist.net/mcp, OAuth at todoist.com\n * const caps = await probeAuthCapabilities('https://ai.todoist.net/mcp');\n * if (caps.supportsDcr) {\n * console.log('Registration endpoint:', caps.registrationEndpoint);\n * }\n */\nfunction buildCapabilities(metadata: AuthorizationServerMetadata, scopes?: string[], resource?: string): AuthCapabilities {\n const supportsDcr = !!metadata.registration_endpoint;\n const capabilities: AuthCapabilities = { supportsDcr, authorizationResponseIssSupported: metadata.authorization_response_iss_parameter_supported === true };\n\n if (metadata.issuer) {\n capabilities.issuer = metadata.issuer;\n }\n\n // Carried from the RFC 9728 document, not from the URL we were given: the\n // resource server names itself, and that name is what RFC 8707 audience-binds\n // a token to.\n if (resource) {\n capabilities.resource = resource;\n }\n\n if (metadata.registration_endpoint) {\n capabilities.registrationEndpoint = metadata.registration_endpoint;\n }\n if (metadata.authorization_endpoint) {\n capabilities.authorizationEndpoint = metadata.authorization_endpoint;\n }\n if (metadata.token_endpoint) capabilities.tokenEndpoint = metadata.token_endpoint;\n if (metadata.introspection_endpoint) {\n capabilities.introspectionEndpoint = metadata.introspection_endpoint;\n }\n\n if (scopes && scopes.length > 0) {\n capabilities.scopes = scopes;\n } else if (metadata.scopes_supported) {\n capabilities.scopes = metadata.scopes_supported;\n }\n\n return capabilities;\n}\n\nasync function resolveCapabilitiesFromAuthorizationServer(authServerUrl: string, scopes: string[] | undefined, allowLoopback: boolean, resource?: string, signal?: AbortSignal): Promise<AuthCapabilities | null> {\n const metadata = await discoverAuthorizationServerMetadata(authServerUrl, { allowLoopback, signal });\n if (!metadata) return null;\n return buildCapabilities(metadata, scopes, resource);\n}\n\nexport async function probeAuthCapabilities(baseUrl: string, options: { signal?: AbortSignal } = {}): Promise<AuthCapabilities> {\n const { signal } = options;\n try {\n throwIfAborted(signal);\n const normalizedBaseUrl = normalizeUrl(baseUrl);\n // Trust signal for every authorization-server fetch below: whether the\n // configured MCP server is itself loopback, never a remote-supplied URL.\n const allowLoopback = isLoopbackUrl(normalizedBaseUrl);\n // Strategy 1: Try RFC 9728 Protected Resource Metadata discovery\n // This handles cross-domain OAuth (e.g., Todoist: ai.todoist.net/mcp → todoist.com)\n const resourceMetadata = await discoverProtectedResourceMetadata(normalizedBaseUrl, { signal });\n\n if (resourceMetadata && resourceMetadata.authorization_servers.length > 0) {\n // Found protected resource metadata with authorization servers\n // Discover the authorization server's metadata (RFC 8414)\n const authServerUrl = resourceMetadata.authorization_servers[0];\n if (!authServerUrl) {\n // Array has length > 0 but first element is undefined/null - skip this path\n return { supportsDcr: false };\n }\n const capabilities = await resolveCapabilitiesFromAuthorizationServer(authServerUrl, resourceMetadata.scopes_supported, allowLoopback, resourceMetadata.resource, signal);\n if (capabilities) {\n return capabilities;\n }\n\n const issuer = await discoverAuthorizationServerIssuer(baseUrl, { signal });\n if (issuer) {\n const issuerCapabilities = await resolveCapabilitiesFromAuthorizationServer(issuer, resourceMetadata.scopes_supported, allowLoopback, resourceMetadata.resource, signal);\n if (issuerCapabilities) return issuerCapabilities;\n }\n }\n\n const issuer = await discoverAuthorizationServerIssuer(normalizedBaseUrl, { signal });\n if (issuer) {\n const issuerCapabilities = await resolveCapabilitiesFromAuthorizationServer(issuer, undefined, allowLoopback, undefined, signal);\n if (issuerCapabilities) return issuerCapabilities;\n }\n\n // Strategy 2: Fall back to direct RFC 8414 discovery at resource origin\n // This handles same-domain OAuth (traditional setup)\n const origin = getOrigin(normalizedBaseUrl);\n const originCapabilities = await resolveCapabilitiesFromAuthorizationServer(origin, undefined, allowLoopback, undefined, signal);\n if (originCapabilities) return originCapabilities;\n\n // No OAuth metadata found\n return { supportsDcr: false };\n } catch (_error) {\n throwIfAborted(signal);\n // Network error, invalid JSON, or other fetch failure\n // Gracefully degrade - assume no DCR support\n return { supportsDcr: false };\n }\n}\n"],"names":["probeAuthCapabilities","getOrigin","url","URL","origin","buildCapabilities","metadata","scopes","resource","supportsDcr","registration_endpoint","capabilities","authorizationResponseIssSupported","authorization_response_iss_parameter_supported","issuer","registrationEndpoint","authorization_endpoint","authorizationEndpoint","token_endpoint","tokenEndpoint","introspection_endpoint","introspectionEndpoint","length","scopes_supported","resolveCapabilitiesFromAuthorizationServer","authServerUrl","allowLoopback","signal","discoverAuthorizationServerMetadata","baseUrl","options","normalizedBaseUrl","resourceMetadata","issuerCapabilities","originCapabilities","_error","throwIfAborted","normalizeUrl","isLoopbackUrl","discoverProtectedResourceMetadata","authorization_servers","discoverAuthorizationServerIssuer","undefined"],"mappings":"AAAA;;;CAGC;;;;+BAqFqBA;;;eAAAA;;;0BAnFO;gCACiB;kCAC4E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAG1H;;;;;;;;CAQC,GACD,SAASC,UAAUC,GAAW;IAC5B,IAAI;QACF,OAAO,IAAIC,IAAID,KAAKE,MAAM;IAC5B,EAAE,eAAM;QACN,sDAAsD;QACtD,OAAOF;IACT;AACF;AAEA;;;;;;;;;;;;;;;;;;CAkBC,GACD,SAASG,kBAAkBC,QAAqC,EAAEC,MAAiB,EAAEC,QAAiB;IACpG,IAAMC,cAAc,CAAC,CAACH,SAASI,qBAAqB;IACpD,IAAMC,eAAiC;QAAEF,aAAAA;QAAaG,mCAAmCN,SAASO,8CAA8C,KAAK;IAAK;IAE1J,IAAIP,SAASQ,MAAM,EAAE;QACnBH,aAAaG,MAAM,GAAGR,SAASQ,MAAM;IACvC;IAEA,0EAA0E;IAC1E,8EAA8E;IAC9E,cAAc;IACd,IAAIN,UAAU;QACZG,aAAaH,QAAQ,GAAGA;IAC1B;IAEA,IAAIF,SAASI,qBAAqB,EAAE;QAClCC,aAAaI,oBAAoB,GAAGT,SAASI,qBAAqB;IACpE;IACA,IAAIJ,SAASU,sBAAsB,EAAE;QACnCL,aAAaM,qBAAqB,GAAGX,SAASU,sBAAsB;IACtE;IACA,IAAIV,SAASY,cAAc,EAAEP,aAAaQ,aAAa,GAAGb,SAASY,cAAc;IACjF,IAAIZ,SAASc,sBAAsB,EAAE;QACnCT,aAAaU,qBAAqB,GAAGf,SAASc,sBAAsB;IACtE;IAEA,IAAIb,UAAUA,OAAOe,MAAM,GAAG,GAAG;QAC/BX,aAAaJ,MAAM,GAAGA;IACxB,OAAO,IAAID,SAASiB,gBAAgB,EAAE;QACpCZ,aAAaJ,MAAM,GAAGD,SAASiB,gBAAgB;IACjD;IAEA,OAAOZ;AACT;AAEA,SAAea,2CAA2CC,aAAqB,EAAElB,MAA4B,EAAEmB,aAAsB,EAAElB,QAAiB,EAAEmB,MAAoB;;YACtKrB;;;;oBAAW;;wBAAMsB,IAAAA,uDAAmC,EAACH,eAAe;4BAAEC,eAAAA;4BAAeC,QAAAA;wBAAO;;;oBAA5FrB,WAAW;oBACjB,IAAI,CAACA,UAAU;;wBAAO;;oBACtB;;wBAAOD,kBAAkBC,UAAUC,QAAQC;;;;IAC7C;;AAEO,SAAeR;wCAAsB6B,OAAe;YAAEC,SACnDH,QAGAI,mBAGAL,eAGAM,kBAKEP,eAKAd,cAKAG,QAEEmB,oBAKJnB,SAEEmB,qBAMF7B,QACA8B,oBAKCC;;;;;oBA9CkDL,UAAAA,oEAAoC,CAAC;oBACxFH,SAAWG,QAAXH;;;;;;;;;oBAENS,IAAAA,gCAAc,EAACT;oBACTI,oBAAoBM,IAAAA,wBAAY,EAACR;oBACvC,uEAAuE;oBACvE,yEAAyE;oBACnEH,gBAAgBY,IAAAA,+BAAa,EAACP;oBAGX;;wBAAMQ,IAAAA,qDAAiC,EAACR,mBAAmB;4BAAEJ,QAAAA;wBAAO;;;oBAAvFK,mBAAmB;yBAErBA,CAAAA,oBAAoBA,iBAAiBQ,qBAAqB,CAAClB,MAAM,GAAG,CAAA,GAApEU;;;;oBACF,+DAA+D;oBAC/D,0DAA0D;oBACpDP,gBAAgBO,iBAAiBQ,qBAAqB,CAAC,EAAE;oBAC/D,IAAI,CAACf,eAAe;wBAClB,4EAA4E;wBAC5E;;4BAAO;gCAAEhB,aAAa;4BAAM;;oBAC9B;oBACqB;;wBAAMe,2CAA2CC,eAAeO,iBAAiBT,gBAAgB,EAAEG,eAAeM,iBAAiBxB,QAAQ,EAAEmB;;;oBAA5JhB,eAAe;oBACrB,IAAIA,cAAc;wBAChB;;4BAAOA;;oBACT;oBAEe;;wBAAM8B,IAAAA,qDAAiC,EAACZ,SAAS;4BAAEF,QAAAA;wBAAO;;;oBAAnEb,SAAS;yBACXA,QAAAA;;;;oBACyB;;wBAAMU,2CAA2CV,QAAQkB,iBAAiBT,gBAAgB,EAAEG,eAAeM,iBAAiBxB,QAAQ,EAAEmB;;;oBAA3JM,qBAAqB;oBAC3B,IAAIA,oBAAoB;;wBAAOA;;;;oBAIpB;;wBAAMQ,IAAAA,qDAAiC,EAACV,mBAAmB;4BAAEJ,QAAAA;wBAAO;;;oBAA7Eb,UAAS;yBACXA,SAAAA;;;;oBACyB;;wBAAMU,2CAA2CV,SAAQ4B,WAAWhB,eAAegB,WAAWf;;;oBAAnHM,sBAAqB;oBAC3B,IAAIA,qBAAoB;;wBAAOA;;;;oBAGjC,wEAAwE;oBACxE,qDAAqD;oBAC/C7B,SAASH,UAAU8B;oBACE;;wBAAMP,2CAA2CpB,QAAQsC,WAAWhB,eAAegB,WAAWf;;;oBAAnHO,qBAAqB;oBAC3B,IAAIA,oBAAoB;;wBAAOA;;oBAE/B,0BAA0B;oBAC1B;;wBAAO;4BAAEzB,aAAa;wBAAM;;;oBACrB0B;oBACPC,IAAAA,gCAAc,EAACT;oBACf,sDAAsD;oBACtD,6CAA6C;oBAC7C;;wBAAO;4BAAElB,aAAa;wBAAM;;;;;;;;IAEhC"}
@@ -1,6 +1,8 @@
1
1
  export declare class DiscoveryFetchError extends Error {
2
2
  constructor(message: string);
3
3
  }
4
+ /** Preserve cancellation while discovery falls back across endpoints. */
5
+ export declare function throwIfAborted(signal?: AbortSignal | null): void;
4
6
  type LookupRecord = {
5
7
  address: string;
6
8
  family: number;
@@ -1,6 +1,8 @@
1
1
  export declare class DiscoveryFetchError extends Error {
2
2
  constructor(message: string);
3
3
  }
4
+ /** Preserve cancellation while discovery falls back across endpoints. */
5
+ export declare function throwIfAborted(signal?: AbortSignal | null): void;
4
6
  type LookupRecord = {
5
7
  address: string;
6
8
  family: number;
@@ -23,6 +23,9 @@ _export(exports, {
23
23
  },
24
24
  get readDiscoveryJson () {
25
25
  return readDiscoveryJson;
26
+ },
27
+ get throwIfAborted () {
28
+ return throwIfAborted;
26
29
  }
27
30
  });
28
31
  var _nodedns = /*#__PURE__*/ _interop_require_default(require("node:dns"));
@@ -325,6 +328,9 @@ var DiscoveryFetchError = /*#__PURE__*/ function(Error1) {
325
328
  var DEFAULT_TIMEOUT_MS = 10000;
326
329
  var MAX_REDIRECTS = 5;
327
330
  var MAX_BODY_BYTES = 1000000; // 1 MB - discovery documents are small JSON
331
+ function throwIfAborted(signal) {
332
+ signal === null || signal === void 0 ? void 0 : signal.throwIfAborted();
333
+ }
328
334
  function stripBrackets(hostname) {
329
335
  return hostname.startsWith('[') && hostname.endsWith(']') ? hostname.slice(1, -1) : hostname;
330
336
  }
@@ -386,32 +392,47 @@ function isLoopbackAddress(address) {
386
392
  }
387
393
  return url;
388
394
  }
389
- function lookupAll(hostname, lookup, timeoutMs) {
395
+ function lookupAll(hostname, lookup, timeoutMs, signal) {
390
396
  return new Promise(function(resolve, reject) {
391
397
  var settled = false;
398
+ var finish = function finish(complete) {
399
+ if (settled) return;
400
+ settled = true;
401
+ clearTimeout(deadline);
402
+ signal === null || signal === void 0 ? void 0 : signal.removeEventListener('abort', onAbort);
403
+ complete();
404
+ };
405
+ var onAbort = function onAbort() {
406
+ return finish(function() {
407
+ return reject(signal === null || signal === void 0 ? void 0 : signal.reason);
408
+ });
409
+ };
392
410
  var deadline = setTimeout(function() {
393
- if (!settled) {
394
- settled = true;
395
- reject(new Error('DNS resolution timed out'));
396
- }
411
+ finish(function() {
412
+ return reject(new Error('DNS resolution timed out'));
413
+ });
397
414
  }, timeoutMs);
415
+ signal === null || signal === void 0 ? void 0 : signal.addEventListener('abort', onAbort, {
416
+ once: true
417
+ });
418
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
419
+ onAbort();
420
+ return;
421
+ }
398
422
  try {
399
423
  lookup(hostname, {
400
424
  all: true,
401
425
  verbatim: true
402
426
  }, function(error, addresses) {
403
- if (settled) return;
404
- settled = true;
405
- clearTimeout(deadline);
406
- if (error || addresses.length === 0) reject(new Error('DNS resolution failed'));
407
- else resolve(addresses);
427
+ finish(function() {
428
+ if (error || addresses.length === 0) reject(new Error('DNS resolution failed'));
429
+ else resolve(addresses);
430
+ });
408
431
  });
409
432
  } catch (unused) {
410
- if (!settled) {
411
- settled = true;
412
- clearTimeout(deadline);
413
- reject(new Error('DNS resolution failed'));
414
- }
433
+ finish(function() {
434
+ return reject(new Error('DNS resolution failed'));
435
+ });
415
436
  }
416
437
  });
417
438
  }
@@ -421,12 +442,13 @@ function lookupAll(hostname, lookup, timeoutMs) {
421
442
  * request itself (see `requestOnce`), which is what closes the DNS-rebinding
422
443
  * TOCTOU: the transport can only dial addresses that were already validated,
423
444
  * and it never resolves the hostname a second time.
424
- */ function resolveSafeAddresses(url, context, allowLoopback, lookup, timeoutMs) {
445
+ */ function resolveSafeAddresses(url, context, allowLoopback, lookup, timeoutMs, signal) {
425
446
  return _async_to_generator(function() {
426
- var host, loopbackGrant, addresses, unused;
447
+ var host, loopbackGrant, addresses, _error;
427
448
  return _ts_generator(this, function(_state) {
428
449
  switch(_state.label){
429
450
  case 0:
451
+ throwIfAborted(signal);
430
452
  host = stripBrackets(url.hostname);
431
453
  if ((0, _nodenet.isIP)(host)) return [
432
454
  2,
@@ -451,7 +473,7 @@ function lookupAll(hostname, lookup, timeoutMs) {
451
473
  ]);
452
474
  return [
453
475
  4,
454
- lookupAll(host, lookup, timeoutMs)
476
+ lookupAll(host, lookup, timeoutMs, signal)
455
477
  ];
456
478
  case 2:
457
479
  addresses = _state.sent();
@@ -460,7 +482,8 @@ function lookupAll(hostname, lookup, timeoutMs) {
460
482
  4
461
483
  ];
462
484
  case 3:
463
- unused = _state.sent();
485
+ _error = _state.sent();
486
+ throwIfAborted(signal);
464
487
  throw new DiscoveryFetchError("Refusing to fetch ".concat(context, ": host could not be resolved"));
465
488
  case 4:
466
489
  if (loopbackGrant) {
@@ -684,55 +707,84 @@ function toResponse(status, statusText, headers, body) {
684
707
  }
685
708
  function requestOnce(url, init, context, addresses, timeoutMs) {
686
709
  return _async_to_generator(function() {
687
- var _init_method, body, unused, transport, options;
710
+ var _init_method, _init_signal, body, unused, transport, options;
688
711
  return _ts_generator(this, function(_state) {
689
712
  switch(_state.label){
690
713
  case 0:
714
+ throwIfAborted(init.signal);
715
+ _state.label = 1;
716
+ case 1:
691
717
  _state.trys.push([
692
- 0,
693
- 2,
718
+ 1,
719
+ 3,
694
720
  ,
695
- 3
721
+ 4
696
722
  ]);
697
723
  return [
698
724
  4,
699
725
  toRequestBody(init.body)
700
726
  ];
701
- case 1:
727
+ case 2:
702
728
  body = _state.sent();
703
729
  return [
704
730
  3,
705
- 3
731
+ 4
706
732
  ];
707
- case 2:
733
+ case 3:
708
734
  unused = _state.sent();
709
735
  throw new DiscoveryFetchError("Failed to fetch ".concat(context));
710
- case 3:
736
+ case 4:
711
737
  transport = url.protocol === 'https:' ? _nodehttps.default : _nodehttp.default;
712
738
  options = {
713
739
  method: ((_init_method = init.method) !== null && _init_method !== void 0 ? _init_method : 'GET').toUpperCase(),
714
740
  headers: toRequestHeaders(init),
715
741
  agent: false,
716
- lookup: pinnedLookup(addresses)
742
+ lookup: pinnedLookup(addresses),
743
+ signal: (_init_signal = init.signal) !== null && _init_signal !== void 0 ? _init_signal : undefined
717
744
  };
718
745
  return [
719
746
  2,
720
747
  new Promise(function(resolve, reject) {
748
+ var _init_signal;
721
749
  var settled = false;
722
750
  var req;
723
751
  var res;
752
+ var signal = (_init_signal = init.signal) !== null && _init_signal !== void 0 ? _init_signal : undefined;
753
+ var cleanup = function cleanup() {
754
+ clearTimeout(deadline);
755
+ signal === null || signal === void 0 ? void 0 : signal.removeEventListener('abort', onAbort);
756
+ };
724
757
  var fail = function fail(message) {
725
758
  if (!settled) {
726
759
  settled = true;
727
- clearTimeout(deadline);
760
+ cleanup();
728
761
  reject(new DiscoveryFetchError(message));
729
762
  }
730
763
  };
764
+ var failWith = function failWith(error) {
765
+ if (!settled) {
766
+ settled = true;
767
+ cleanup();
768
+ reject(error);
769
+ }
770
+ };
731
771
  var deadline = setTimeout(function() {
732
772
  res === null || res === void 0 ? void 0 : res.destroy();
733
773
  req === null || req === void 0 ? void 0 : req.destroy();
734
774
  fail("Failed to fetch ".concat(context));
735
775
  }, timeoutMs);
776
+ var onAbort = function onAbort() {
777
+ res === null || res === void 0 ? void 0 : res.destroy();
778
+ req === null || req === void 0 ? void 0 : req.destroy();
779
+ failWith(signal === null || signal === void 0 ? void 0 : signal.reason);
780
+ };
781
+ signal === null || signal === void 0 ? void 0 : signal.addEventListener('abort', onAbort, {
782
+ once: true
783
+ });
784
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
785
+ onAbort();
786
+ return;
787
+ }
736
788
  try {
737
789
  req = transport.request(url, options, function(response) {
738
790
  var _response_statusMessage;
@@ -761,7 +813,7 @@ function requestOnce(url, init, context, addresses, timeoutMs) {
761
813
  response.once('end', function() {
762
814
  if (settled) return;
763
815
  settled = true;
764
- clearTimeout(deadline);
816
+ cleanup();
765
817
  resolve(toResponse(status, statusText, response.headers, Buffer.concat(chunks)));
766
818
  });
767
819
  });
@@ -785,9 +837,10 @@ function fetchOnce(url, init, context, allowLoopback, timeoutMs, lookup) {
785
837
  return _ts_generator(this, function(_state) {
786
838
  switch(_state.label){
787
839
  case 0:
840
+ throwIfAborted(init.signal);
788
841
  return [
789
842
  4,
790
- resolveSafeAddresses(url, context, allowLoopback, lookup, timeoutMs)
843
+ resolveSafeAddresses(url, context, allowLoopback, lookup, timeoutMs, init.signal)
791
844
  ];
792
845
  case 1:
793
846
  addresses = _state.sent();
@@ -807,6 +860,7 @@ function discoveryFetch(_0) {
807
860
  switch(_state.label){
808
861
  case 0:
809
862
  init = _arguments.length > 1 && _arguments[1] !== void 0 ? _arguments[1] : {}, context = _arguments.length > 2 && _arguments[2] !== void 0 ? _arguments[2] : 'discovery URL', options = _arguments.length > 3 && _arguments[3] !== void 0 ? _arguments[3] : {};
863
+ throwIfAborted(init.signal);
810
864
  _options_allowLoopback = options.allowLoopback, allowLoopback = _options_allowLoopback === void 0 ? false : _options_allowLoopback, _options_timeoutMs = options.timeoutMs, timeoutMs = _options_timeoutMs === void 0 ? DEFAULT_TIMEOUT_MS : _options_timeoutMs, _options_lookup = options.lookup, lookup = _options_lookup === void 0 ? _nodedns.default.lookup : _options_lookup;
811
865
  url = assertSafeUrl(rawUrl, context, allowLoopback);
812
866
  return [