@mcp-z/client 2.2.3 → 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 (111) 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 +40 -4
  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 +406 -97
  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/cjs/utils/path-utils.js +7 -4
  62. package/dist/cjs/utils/path-utils.js.map +1 -1
  63. package/dist/esm/auth/capability-discovery.d.ts +3 -1
  64. package/dist/esm/auth/capability-discovery.js +21 -11
  65. package/dist/esm/auth/capability-discovery.js.map +1 -1
  66. package/dist/esm/auth/discovery-fetch.d.ts +2 -0
  67. package/dist/esm/auth/discovery-fetch.js +64 -23
  68. package/dist/esm/auth/discovery-fetch.js.map +1 -1
  69. package/dist/esm/auth/interactive-oauth-flow.d.ts +1 -1
  70. package/dist/esm/auth/interactive-oauth-flow.js +13 -6
  71. package/dist/esm/auth/interactive-oauth-flow.js.map +1 -1
  72. package/dist/esm/auth/oauth-callback-listener.d.ts +1 -1
  73. package/dist/esm/auth/oauth-callback-listener.js +34 -4
  74. package/dist/esm/auth/oauth-callback-listener.js.map +1 -1
  75. package/dist/esm/auth/rfc9728-discovery.d.ts +7 -2
  76. package/dist/esm/auth/rfc9728-discovery.js +40 -16
  77. package/dist/esm/auth/rfc9728-discovery.js.map +1 -1
  78. package/dist/esm/auth/types.d.ts +4 -0
  79. package/dist/esm/auth/types.js.map +1 -1
  80. package/dist/esm/connection/connect-client.d.ts +9 -1
  81. package/dist/esm/connection/connect-client.js +97 -35
  82. package/dist/esm/connection/connect-client.js.map +1 -1
  83. package/dist/esm/connection/existing-process-transport.d.ts +18 -18
  84. package/dist/esm/connection/existing-process-transport.js +132 -70
  85. package/dist/esm/connection/existing-process-transport.js.map +1 -1
  86. package/dist/esm/connection/wait-for-http-ready.d.ts +1 -1
  87. package/dist/esm/connection/wait-for-http-ready.js +19 -4
  88. package/dist/esm/connection/wait-for-http-ready.js.map +1 -1
  89. package/dist/esm/dcr/dcr-authenticator.d.ts +1 -1
  90. package/dist/esm/dcr/dcr-authenticator.js +26 -15
  91. package/dist/esm/dcr/dcr-authenticator.js.map +1 -1
  92. package/dist/esm/dcr/dynamic-client-registrar.js +2 -1
  93. package/dist/esm/dcr/dynamic-client-registrar.js.map +1 -1
  94. package/dist/esm/index.d.ts +1 -1
  95. package/dist/esm/index.js.map +1 -1
  96. package/dist/esm/spawn/owned-process-tree.d.ts +12 -0
  97. package/dist/esm/spawn/owned-process-tree.js +39 -0
  98. package/dist/esm/spawn/owned-process-tree.js.map +1 -0
  99. package/dist/esm/spawn/spawn-server.d.ts +8 -32
  100. package/dist/esm/spawn/spawn-server.js +228 -99
  101. package/dist/esm/spawn/spawn-server.js.map +1 -1
  102. package/dist/esm/spawn/spawn-servers.d.ts +18 -8
  103. package/dist/esm/spawn/spawn-servers.js +180 -55
  104. package/dist/esm/spawn/spawn-servers.js.map +1 -1
  105. package/dist/esm/types.d.ts +1 -1
  106. package/dist/esm/types.js.map +1 -1
  107. package/dist/esm/utils/path-utils.js +7 -4
  108. package/dist/esm/utils/path-utils.js.map +1 -1
  109. package/package.json +8 -3
  110. package/schemas/servers.d.ts +24 -0
  111. package/schemas/servers.schema.json +31 -0
@@ -64,13 +64,14 @@ import { DynamicClientRegistrar } from './dynamic-client-registrar.js';
64
64
  * 'https://example.com/mcp',
65
65
  * capabilities
66
66
  * );
67
- */ async ensureAuthenticated(mcpServerUrl, capabilities) {
67
+ */ async ensureAuthenticated(mcpServerUrl, capabilities, signal) {
68
+ throwIfAborted(signal);
68
69
  // Auto-detect server mode
69
70
  const isSelfHosted = await this.detectSelfHostedMode(mcpServerUrl);
70
71
  if (isSelfHosted) {
71
- return this.ensureAuthenticatedSelfHosted(mcpServerUrl, capabilities);
72
+ return this.ensureAuthenticatedSelfHosted(mcpServerUrl, capabilities, signal);
72
73
  }
73
- return this.ensureAuthenticatedExternal(mcpServerUrl, capabilities);
74
+ return this.ensureAuthenticatedExternal(mcpServerUrl, capabilities, signal);
74
75
  }
75
76
  /**
76
77
  * The three things a server URL is used for here, kept apart on purpose.
@@ -101,7 +102,7 @@ import { DynamicClientRegistrar } from './dynamic-client-registrar.js';
101
102
  /**
102
103
  * Handle authentication for self-hosted DCR servers
103
104
  * Self-hosted servers manage their own token storage via /oauth/verify
104
- */ async ensureAuthenticatedSelfHosted(mcpServerUrl, capabilities) {
105
+ */ async ensureAuthenticatedSelfHosted(mcpServerUrl, capabilities, signal) {
105
106
  // Loopback trust for every discovery-derived fetch below, computed from
106
107
  // the server we're talking to, never from capabilities' endpoints (see discovery-fetch.ts).
107
108
  const allowLoopback = isLoopbackUrl(mcpServerUrl);
@@ -118,7 +119,8 @@ import { DynamicClientRegistrar } from './dynamic-client-registrar.js';
118
119
  headers: {
119
120
  Authorization: `Bearer ${tokens.accessToken}`,
120
121
  Connection: 'close'
121
- }
122
+ },
123
+ signal
122
124
  });
123
125
  if (verifyResponse.ok) {
124
126
  const verifyData = await verifyResponse.json();
@@ -145,10 +147,11 @@ import { DynamicClientRegistrar } from './dynamic-client-registrar.js';
145
147
  this.logger.debug('📝 Registering OAuth client with self-hosted server...');
146
148
  const client = await this.dcrClient.registerClient(capabilities.registrationEndpoint, {
147
149
  redirectUri: this.redirectUri,
148
- allowLoopback
150
+ allowLoopback,
151
+ signal
149
152
  });
150
153
  // Perform OAuth authorization flow with PKCE (RFC 7636)
151
- const flowOptions = this.buildFlowOptions(port, capabilities, issuer, resource, allowLoopback);
154
+ const flowOptions = this.buildFlowOptions(port, capabilities, issuer, resource, allowLoopback, signal);
152
155
  tokens = await this.oauthFlow.performAuthFlow(capabilities.authorizationEndpoint, capabilities.tokenEndpoint, client.clientId, client.clientSecret, flowOptions);
153
156
  // For self-hosted mode, verify the token works with /oauth/verify immediately
154
157
  try {
@@ -157,7 +160,8 @@ import { DynamicClientRegistrar } from './dynamic-client-registrar.js';
157
160
  headers: {
158
161
  Authorization: `Bearer ${tokens.accessToken}`,
159
162
  Connection: 'close'
160
- }
163
+ },
164
+ signal
161
165
  });
162
166
  if (!verifyResponse.ok) {
163
167
  throw new Error(`DCR token verification failed after authentication: ${verifyResponse.status}`);
@@ -179,7 +183,7 @@ import { DynamicClientRegistrar } from './dynamic-client-registrar.js';
179
183
  this.logger.debug('✅ Self-hosted DCR authentication successful, tokens saved');
180
184
  return tokens;
181
185
  }
182
- /** Handles authentication for external (non-self-hosted) OAuth providers. */ async ensureAuthenticatedExternal(mcpServerUrl, capabilities) {
186
+ /** Handles authentication for external (non-self-hosted) OAuth providers. */ async ensureAuthenticatedExternal(mcpServerUrl, capabilities, signal) {
183
187
  // See ensureAuthenticatedSelfHosted - same loopback trust rule.
184
188
  const allowLoopback = isLoopbackUrl(mcpServerUrl);
185
189
  const issuer = requireIssuer(capabilities);
@@ -192,7 +196,7 @@ import { DynamicClientRegistrar } from './dynamic-client-registrar.js';
192
196
  if (tokens.expiresAt < Date.now() + REFRESH_BUFFER_MS) {
193
197
  this.logger.debug('🔄 Refreshing access token...');
194
198
  try {
195
- tokens = await this.refreshTokens(tokens, capabilities.tokenEndpoint, resource, allowLoopback);
199
+ tokens = await this.refreshTokens(tokens, capabilities.tokenEndpoint, resource, allowLoopback, signal);
196
200
  await this.tokenStore.set(tokenKey, {
197
201
  ...tokens,
198
202
  issuer
@@ -220,10 +224,11 @@ import { DynamicClientRegistrar } from './dynamic-client-registrar.js';
220
224
  this.logger.debug('📝 Registering OAuth client...');
221
225
  const client = await this.dcrClient.registerClient(capabilities.registrationEndpoint, {
222
226
  redirectUri: this.redirectUri,
223
- allowLoopback
227
+ allowLoopback,
228
+ signal
224
229
  });
225
230
  // Perform OAuth authorization flow with PKCE (RFC 7636)
226
- const flowOptions = this.buildFlowOptions(port, capabilities, issuer, resource, allowLoopback);
231
+ const flowOptions = this.buildFlowOptions(port, capabilities, issuer, resource, allowLoopback, signal);
227
232
  tokens = await this.oauthFlow.performAuthFlow(capabilities.authorizationEndpoint, capabilities.tokenEndpoint, client.clientId, client.clientSecret, flowOptions);
228
233
  // Save tokens for future use
229
234
  await this.tokenStore.set(tokenKey, {
@@ -237,7 +242,7 @@ import { DynamicClientRegistrar } from './dynamic-client-registrar.js';
237
242
  * Refreshes an access token using a refresh token.
238
243
  * @param resource - Canonical resource server URI, audience-binding the token (RFC 8707).
239
244
  * @param allowLoopback - Loopback trust grant computed from the server actually being talked to (SSRF mitigation, see discovery-fetch.ts). Defaults to `false`.
240
- */ async refreshTokens(tokens, tokenEndpoint, resource, allowLoopback = false) {
245
+ */ async refreshTokens(tokens, tokenEndpoint, resource, allowLoopback = false, signal) {
241
246
  if (!tokenEndpoint) {
242
247
  throw new Error('Token endpoint not available for refresh');
243
248
  }
@@ -247,7 +252,7 @@ import { DynamicClientRegistrar } from './dynamic-client-registrar.js';
247
252
  if (!tokens.clientId || !tokens.clientSecret) {
248
253
  throw new Error('Client credentials not available for refresh');
249
254
  }
250
- return await this.oauthFlow.refreshTokens(tokenEndpoint, tokens.refreshToken, tokens.clientId, tokens.clientSecret, resource, allowLoopback);
255
+ return await this.oauthFlow.refreshTokens(tokenEndpoint, tokens.refreshToken, tokens.clientId, tokens.clientSecret, resource, allowLoopback, signal);
251
256
  }
252
257
  /**
253
258
  * Deletes both stored token families for a server, across every issuer they were bound to.
@@ -277,7 +282,7 @@ import { DynamicClientRegistrar } from './dynamic-client-registrar.js';
277
282
  await this.tokenStore.delete(key);
278
283
  return undefined;
279
284
  }
280
- buildFlowOptions(port, capabilities, issuer, resource, allowLoopback) {
285
+ buildFlowOptions(port, capabilities, issuer, resource, allowLoopback, signal) {
281
286
  var _capabilities_authorizationResponseIssSupported;
282
287
  const flowOptions = {
283
288
  port,
@@ -288,6 +293,9 @@ import { DynamicClientRegistrar } from './dynamic-client-registrar.js';
288
293
  pkce: true,
289
294
  logger: this.logger,
290
295
  allowLoopback,
296
+ ...signal && {
297
+ signal
298
+ },
291
299
  authorizationResponseIssSupported: (_capabilities_authorizationResponseIssSupported = capabilities.authorizationResponseIssSupported) !== null && _capabilities_authorizationResponseIssSupported !== void 0 ? _capabilities_authorizationResponseIssSupported : false
292
300
  };
293
301
  if (capabilities.scopes) {
@@ -319,3 +327,6 @@ import { DynamicClientRegistrar } from './dynamic-client-registrar.js';
319
327
  this.logger = (_options_logger = options.logger) !== null && _options_logger !== void 0 ? _options_logger : defaultLogger;
320
328
  }
321
329
  }
330
+ function throwIfAborted(signal) {
331
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted) throw signal.reason instanceof Error ? signal.reason : new Error('Authentication was cancelled');
332
+ }
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/mcp-z/client/src/dcr/dcr-authenticator.ts"],"sourcesContent":["/**\n * DCR Authenticator\n * Consolidates DCR and OAuth flow logic for MCP HTTP servers\n */\n\nimport path from 'node:path';\nimport * as fs from 'fs';\nimport Keyv from 'keyv';\nimport { KeyvFile } from 'keyv-file';\nimport { isLoopbackUrl } from '../auth/discovery-fetch.ts';\nimport { InteractiveOAuthFlow } from '../auth/interactive-oauth-flow.ts';\nimport type { AuthCapabilities, OAuthFlowOptions, TokenSet } from '../auth/types.ts';\nimport { extractBaseUrl, normalizeUrl } from '../lib/url-utils.ts';\nimport { logger as defaultLogger, type Logger } from '../utils/logger.ts';\nimport { DynamicClientRegistrar } from './dynamic-client-registrar.ts';\n\n/**\n * DcrAuthenticator configuration options\n */\nexport interface DcrAuthenticatorOptions {\n /** Custom Keyv store (for testing) - if not provided, uses default ~/.mcpeasy/tokens.json */\n tokenStore?: Keyv;\n /** Headless mode (don't open browser) */\n headless?: boolean;\n /** Required redirect URI for OAuth callback */\n redirectUri: string;\n /** Optional logger for debug output (defaults to singleton logger) */\n logger?: Logger;\n}\n\n/**\n * Buffer time before token expiry to trigger proactive refresh (5 minutes)\n */\nconst REFRESH_BUFFER_MS = 5 * 60 * 1000;\n\n/** Raised when a credential cannot be bound to, or looked up by, an issuer. */\nclass CredentialBindingError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'CredentialBindingError';\n }\n}\n\n/**\n * The issuer credentials are keyed by, so they can never be presented to a\n * different authorization server even when the resource keeps its URL (SEP-2352).\n */\nfunction requireIssuer(capabilities: AuthCapabilities): string {\n if (!capabilities.issuer) {\n throw new CredentialBindingError('Authorization server metadata has no issuer - credentials cannot be bound to an authorization server (RFC 8414 requires issuer)');\n }\n return capabilities.issuer;\n}\n\n/**\n * DcrAuthenticator manages authentication for MCP HTTP servers\n * Handles DCR registration, OAuth flows, and token management\n */\nexport class DcrAuthenticator {\n private tokenStore: Keyv;\n private dcrClient: DynamicClientRegistrar;\n private oauthFlow: InteractiveOAuthFlow;\n private headless: boolean;\n private redirectUri: string;\n private logger: Logger;\n\n constructor(options: DcrAuthenticatorOptions) {\n if (options.tokenStore) {\n this.tokenStore = options.tokenStore;\n } else {\n // Default CLI store in .mcp-z directory (per-project)\n const storePath = path.join(process.cwd(), '.mcp-z', 'tokens.json');\n\n // Ensure directory exists before creating store\n fs.mkdirSync(path.dirname(storePath), { recursive: true });\n\n this.tokenStore = new Keyv({\n store: new KeyvFile({ filename: storePath }),\n });\n }\n this.dcrClient = new DynamicClientRegistrar();\n this.oauthFlow = new InteractiveOAuthFlow();\n this.headless = options.headless || false;\n this.redirectUri = options.redirectUri;\n this.logger = options.logger ?? defaultLogger;\n }\n\n /**\n * Detect if server is self-hosted DCR (vs external OAuth provider)\n * Self-hosted servers have their own OAuth endpoints and manage token storage\n */\n private async detectSelfHostedMode(mcpServerUrl: string): Promise<boolean> {\n try {\n // Self-hosted DCR servers typically run their own OAuth server\n // Check if this is a self-hosted instance by testing OAuth metadata\n // For now, assume self-hosted if the URL matches common localhost patterns\n // TODO: Implement proper self-hosted detection logic\n return mcpServerUrl.includes('localhost') || mcpServerUrl.includes('127.0.0.1');\n } catch (_error) {\n return false; // Assume external mode if detection fails\n }\n }\n\n /**\n * Ensure server is authenticated, performing DCR and OAuth if needed\n * Proactively refreshes tokens if they're within 5 minutes of expiry\n *\n * @param mcpServerUrl - The MCP server's canonical URL, exactly as configured,\n * with its path intact (`https://example.com/mcp`). Not a deployment root:\n * the path is what identifies the resource an issued token is bound to.\n * @param capabilities - Auth capabilities from .well-known endpoint\n * @returns Valid token set ready to use\n *\n * @throws Error if authentication fails\n *\n * @example\n * const authenticator = new DcrAuthenticator({ redirectUri: 'http://localhost:3000/callback' });\n * const tokens = await authenticator.ensureAuthenticated(\n * 'https://example.com/mcp',\n * capabilities\n * );\n */\n async ensureAuthenticated(mcpServerUrl: string, capabilities: AuthCapabilities): Promise<TokenSet> {\n // Auto-detect server mode\n const isSelfHosted = await this.detectSelfHostedMode(mcpServerUrl);\n\n if (isSelfHosted) {\n return this.ensureAuthenticatedSelfHosted(mcpServerUrl, capabilities);\n }\n return this.ensureAuthenticatedExternal(mcpServerUrl, capabilities);\n }\n\n /**\n * The three things a server URL is used for here, kept apart on purpose.\n *\n * They were one value once, and collapsing them is what sent an authorization\n * server the wrong audience: `resource` was derived from the deployment root,\n * so a server at `https://host/mcp` was asked to mint a token for\n * `https://host`, and any server that validates the indicator answered\n * `invalid_target`.\n *\n * - `serverBaseUrl` builds the server's own endpoints (`/oauth/verify`), so a\n * trailing `/mcp` comes off.\n * - `resource` is the RFC 8707 audience. The resource server names itself in\n * its RFC 9728 metadata; that name wins. Only when no such document exists\n * do we fall back to the URL we were configured with.\n * - `storeKey` identifies the credential locally. It stays the configured URL\n * rather than the discovered `resource`, so it can be computed without a\n * network round trip - `deleteTokens` has only the URL to work from.\n */\n private resolveUrls(mcpServerUrl: string, capabilities: AuthCapabilities): { serverBaseUrl: string; resource: string; storeKey: string } {\n const storeKey = normalizeUrl(mcpServerUrl);\n return { serverBaseUrl: extractBaseUrl(mcpServerUrl), resource: capabilities.resource ?? storeKey, storeKey };\n }\n\n /**\n * Handle authentication for self-hosted DCR servers\n * Self-hosted servers manage their own token storage via /oauth/verify\n */\n private async ensureAuthenticatedSelfHosted(mcpServerUrl: string, capabilities: AuthCapabilities): Promise<TokenSet> {\n // Loopback trust for every discovery-derived fetch below, computed from\n // the server we're talking to, never from capabilities' endpoints (see discovery-fetch.ts).\n const allowLoopback = isLoopbackUrl(mcpServerUrl);\n const issuer = requireIssuer(capabilities);\n const { serverBaseUrl, resource, storeKey } = this.resolveUrls(mcpServerUrl, capabilities);\n const dcrTokenKey = `dcr-tokens:${issuer}:${storeKey}`;\n\n // 1. Check for existing DCR tokens (different from external tokens)\n let tokens = await this.loadTokens(dcrTokenKey, issuer);\n\n if (tokens) {\n // 2. Verify token is still valid by calling /oauth/verify\n try {\n const verifyUrl = `${serverBaseUrl}/oauth/verify`;\n const verifyResponse = await fetch(verifyUrl, {\n headers: { Authorization: `Bearer ${tokens.accessToken}`, Connection: 'close' },\n });\n\n if (verifyResponse.ok) {\n const verifyData = (await verifyResponse.json()) as { token?: string };\n if (verifyData.token === tokens.accessToken) {\n // Token is still valid with the self-hosted server\n return tokens;\n }\n }\n } catch (_error) {\n // Token verification failed - need to re-authenticate\n }\n\n // Token is expired or invalid\n await this.tokenStore.delete(dcrTokenKey);\n tokens = undefined;\n }\n\n // 3. No valid tokens - perform full DCR + OAuth flow\n if (!capabilities.registrationEndpoint || !capabilities.authorizationEndpoint || !capabilities.tokenEndpoint) {\n throw new Error('Server does not provide required OAuth endpoints');\n }\n\n this.logger.debug('🔐 No valid tokens found, starting self-hosted DCR authentication...');\n\n // Extract port from pre-resolved redirectUri\n const port = parseInt(new URL(this.redirectUri).port, 10) || (this.redirectUri.startsWith('https:') ? 443 : 80);\n\n // Register OAuth client via DCR\n this.logger.debug('📝 Registering OAuth client with self-hosted server...');\n const client = await this.dcrClient.registerClient(capabilities.registrationEndpoint, {\n redirectUri: this.redirectUri,\n allowLoopback,\n });\n\n // Perform OAuth authorization flow with PKCE (RFC 7636)\n const flowOptions = this.buildFlowOptions(port, capabilities, issuer, resource, allowLoopback);\n\n tokens = await this.oauthFlow.performAuthFlow(capabilities.authorizationEndpoint, capabilities.tokenEndpoint, client.clientId, client.clientSecret, flowOptions);\n\n // For self-hosted mode, verify the token works with /oauth/verify immediately\n try {\n const verifyUrl = `${serverBaseUrl}/oauth/verify`;\n const verifyResponse = await fetch(verifyUrl, {\n headers: { Authorization: `Bearer ${tokens.accessToken}`, Connection: 'close' },\n });\n\n if (!verifyResponse.ok) {\n throw new Error(`DCR token verification failed after authentication: ${verifyResponse.status}`);\n }\n\n const verifyData = (await verifyResponse.json()) as { token?: string };\n if (verifyData.token !== tokens.accessToken) {\n throw new Error('DCR server returned different token in verification');\n }\n\n this.logger.debug('✅ DCR token verified with self-hosted server');\n } catch (error) {\n this.logger.error('❌ DCR token verification failed:', error instanceof Error ? error.message : String(error));\n throw new Error('Self-hosted DCR authentication completed but token verification failed');\n }\n\n // Save tokens for future use\n await this.tokenStore.set(dcrTokenKey, { ...tokens, issuer });\n this.logger.debug('✅ Self-hosted DCR authentication successful, tokens saved');\n\n return tokens;\n }\n\n /** Handles authentication for external (non-self-hosted) OAuth providers. */\n private async ensureAuthenticatedExternal(mcpServerUrl: string, capabilities: AuthCapabilities): Promise<TokenSet> {\n // See ensureAuthenticatedSelfHosted - same loopback trust rule.\n const allowLoopback = isLoopbackUrl(mcpServerUrl);\n const issuer = requireIssuer(capabilities);\n const { resource, storeKey } = this.resolveUrls(mcpServerUrl, capabilities);\n const tokenKey = `tokens:${issuer}:${storeKey}`;\n\n // 1. Check for existing tokens\n let tokens = await this.loadTokens(tokenKey, issuer);\n\n if (tokens) {\n // 2. Proactive refresh if token expires within 5 minutes\n if (tokens.expiresAt < Date.now() + REFRESH_BUFFER_MS) {\n this.logger.debug('🔄 Refreshing access token...');\n\n try {\n tokens = await this.refreshTokens(tokens, capabilities.tokenEndpoint, resource, allowLoopback);\n await this.tokenStore.set(tokenKey, { ...tokens, issuer });\n this.logger.debug('✅ Token refreshed successfully');\n } catch (_error) {\n // Refresh failed - clear tokens and re-authenticate\n this.logger.warn('⚠️ Token refresh failed, re-authenticating...');\n await this.tokenStore.delete(tokenKey);\n tokens = undefined;\n }\n }\n\n if (tokens) {\n return tokens;\n }\n }\n\n // 3. No valid tokens - perform DCR + OAuth flow\n if (!capabilities.registrationEndpoint || !capabilities.authorizationEndpoint || !capabilities.tokenEndpoint) {\n throw new Error('Server does not provide required OAuth endpoints');\n }\n\n this.logger.debug('🔐 No valid tokens found, starting external OAuth authentication...');\n\n // Extract port from pre-resolved redirectUri\n const port = parseInt(new URL(this.redirectUri).port, 10) || (this.redirectUri.startsWith('https:') ? 443 : 80);\n\n // Register OAuth client via DCR\n this.logger.debug('📝 Registering OAuth client...');\n const client = await this.dcrClient.registerClient(capabilities.registrationEndpoint, {\n redirectUri: this.redirectUri,\n allowLoopback,\n });\n\n // Perform OAuth authorization flow with PKCE (RFC 7636)\n const flowOptions = this.buildFlowOptions(port, capabilities, issuer, resource, allowLoopback);\n\n tokens = await this.oauthFlow.performAuthFlow(capabilities.authorizationEndpoint, capabilities.tokenEndpoint, client.clientId, client.clientSecret, flowOptions);\n\n // Save tokens for future use\n await this.tokenStore.set(tokenKey, { ...tokens, issuer });\n this.logger.debug('✅ Authentication successful, tokens saved');\n\n return tokens;\n }\n\n /**\n * Refreshes an access token using a refresh token.\n * @param resource - Canonical resource server URI, audience-binding the token (RFC 8707).\n * @param allowLoopback - Loopback trust grant computed from the server actually being talked to (SSRF mitigation, see discovery-fetch.ts). Defaults to `false`.\n */\n private async refreshTokens(tokens: TokenSet, tokenEndpoint: string | undefined, resource: string, allowLoopback = false): Promise<TokenSet> {\n if (!tokenEndpoint) {\n throw new Error('Token endpoint not available for refresh');\n }\n\n if (!tokens.refreshToken) {\n throw new Error('No refresh token available');\n }\n\n if (!tokens.clientId || !tokens.clientSecret) {\n throw new Error('Client credentials not available for refresh');\n }\n\n return await this.oauthFlow.refreshTokens(tokenEndpoint, tokens.refreshToken, tokens.clientId, tokens.clientSecret, resource, allowLoopback);\n }\n\n /**\n * Deletes both stored token families for a server, across every issuer they were bound to.\n *\n * Takes the same `mcpServerUrl` that {@link DcrAuthenticator.ensureAuthenticated}\n * was given - keys are built from the configured URL, never from the discovered\n * RFC 8707 resource, precisely so this can find them without doing discovery.\n *\n * @throws CredentialBindingError if the configured store cannot enumerate keys.\n */\n async deleteTokens(mcpServerUrl: string): Promise<void> {\n const suffix = `:${normalizeUrl(mcpServerUrl)}`;\n if (!this.tokenStore.iterator) {\n throw new CredentialBindingError('Token store does not support key enumeration, which issuer-keyed credentials require');\n }\n\n for await (const [key] of this.tokenStore.iterator(this.tokenStore.namespace)) {\n if (typeof key === 'string' && (key.startsWith('tokens:') || key.startsWith('dcr-tokens:')) && key.endsWith(suffix)) {\n await this.tokenStore.delete(key);\n }\n }\n this.logger.debug(`🗑️ Deleted tokens for ${mcpServerUrl}`);\n }\n\n /** Discards a stored credential that is not bound to `issuer` (SEP-2352), including one stored before issuer binding existed. */\n private async loadTokens(key: string, issuer: string): Promise<TokenSet | undefined> {\n const tokens = (await this.tokenStore.get(key)) as TokenSet | undefined;\n if (!tokens) return undefined;\n if (tokens.issuer === issuer) return tokens;\n\n this.logger.debug('🔑 Stored credential is not bound to the discovered issuer, re-authorizing');\n await this.tokenStore.delete(key);\n return undefined;\n }\n\n private buildFlowOptions(port: number, capabilities: AuthCapabilities, issuer: string, resource: string, allowLoopback: boolean): OAuthFlowOptions {\n const flowOptions: OAuthFlowOptions = {\n port,\n issuer,\n resource,\n headless: this.headless,\n redirectUri: this.redirectUri,\n pkce: true,\n logger: this.logger,\n allowLoopback,\n authorizationResponseIssSupported: capabilities.authorizationResponseIssSupported ?? false,\n };\n if (capabilities.scopes) {\n flowOptions.scopes = capabilities.scopes;\n }\n return flowOptions;\n }\n}\n"],"names":["path","fs","Keyv","KeyvFile","isLoopbackUrl","InteractiveOAuthFlow","extractBaseUrl","normalizeUrl","logger","defaultLogger","DynamicClientRegistrar","REFRESH_BUFFER_MS","CredentialBindingError","Error","message","name","requireIssuer","capabilities","issuer","DcrAuthenticator","detectSelfHostedMode","mcpServerUrl","includes","_error","ensureAuthenticated","isSelfHosted","ensureAuthenticatedSelfHosted","ensureAuthenticatedExternal","resolveUrls","storeKey","serverBaseUrl","resource","allowLoopback","dcrTokenKey","tokens","loadTokens","verifyUrl","verifyResponse","fetch","headers","Authorization","accessToken","Connection","ok","verifyData","json","token","tokenStore","delete","undefined","registrationEndpoint","authorizationEndpoint","tokenEndpoint","debug","port","parseInt","URL","redirectUri","startsWith","client","dcrClient","registerClient","flowOptions","buildFlowOptions","oauthFlow","performAuthFlow","clientId","clientSecret","status","error","String","set","tokenKey","expiresAt","Date","now","refreshTokens","warn","refreshToken","deleteTokens","suffix","iterator","key","namespace","endsWith","get","headless","pkce","authorizationResponseIssSupported","scopes","options","storePath","join","process","cwd","mkdirSync","dirname","recursive","store","filename"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/mcp-z/client/src/dcr/dcr-authenticator.ts"],"sourcesContent":["/**\n * DCR Authenticator\n * Consolidates DCR and OAuth flow logic for MCP HTTP servers\n */\n\nimport path from 'node:path';\nimport * as fs from 'fs';\nimport Keyv from 'keyv';\nimport { KeyvFile } from 'keyv-file';\nimport { isLoopbackUrl } from '../auth/discovery-fetch.ts';\nimport { InteractiveOAuthFlow } from '../auth/interactive-oauth-flow.ts';\nimport type { AuthCapabilities, OAuthFlowOptions, TokenSet } from '../auth/types.ts';\nimport { extractBaseUrl, normalizeUrl } from '../lib/url-utils.ts';\nimport { logger as defaultLogger, type Logger } from '../utils/logger.ts';\nimport { DynamicClientRegistrar } from './dynamic-client-registrar.ts';\n\n/**\n * DcrAuthenticator configuration options\n */\nexport interface DcrAuthenticatorOptions {\n /** Custom Keyv store (for testing) - if not provided, uses default ~/.mcpeasy/tokens.json */\n tokenStore?: Keyv;\n /** Headless mode (don't open browser) */\n headless?: boolean;\n /** Required redirect URI for OAuth callback */\n redirectUri: string;\n /** Optional logger for debug output (defaults to singleton logger) */\n logger?: Logger;\n}\n\n/**\n * Buffer time before token expiry to trigger proactive refresh (5 minutes)\n */\nconst REFRESH_BUFFER_MS = 5 * 60 * 1000;\n\n/** Raised when a credential cannot be bound to, or looked up by, an issuer. */\nclass CredentialBindingError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'CredentialBindingError';\n }\n}\n\n/**\n * The issuer credentials are keyed by, so they can never be presented to a\n * different authorization server even when the resource keeps its URL (SEP-2352).\n */\nfunction requireIssuer(capabilities: AuthCapabilities): string {\n if (!capabilities.issuer) {\n throw new CredentialBindingError('Authorization server metadata has no issuer - credentials cannot be bound to an authorization server (RFC 8414 requires issuer)');\n }\n return capabilities.issuer;\n}\n\n/**\n * DcrAuthenticator manages authentication for MCP HTTP servers\n * Handles DCR registration, OAuth flows, and token management\n */\nexport class DcrAuthenticator {\n private tokenStore: Keyv;\n private dcrClient: DynamicClientRegistrar;\n private oauthFlow: InteractiveOAuthFlow;\n private headless: boolean;\n private redirectUri: string;\n private logger: Logger;\n\n constructor(options: DcrAuthenticatorOptions) {\n if (options.tokenStore) {\n this.tokenStore = options.tokenStore;\n } else {\n // Default CLI store in .mcp-z directory (per-project)\n const storePath = path.join(process.cwd(), '.mcp-z', 'tokens.json');\n\n // Ensure directory exists before creating store\n fs.mkdirSync(path.dirname(storePath), { recursive: true });\n\n this.tokenStore = new Keyv({\n store: new KeyvFile({ filename: storePath }),\n });\n }\n this.dcrClient = new DynamicClientRegistrar();\n this.oauthFlow = new InteractiveOAuthFlow();\n this.headless = options.headless || false;\n this.redirectUri = options.redirectUri;\n this.logger = options.logger ?? defaultLogger;\n }\n\n /**\n * Detect if server is self-hosted DCR (vs external OAuth provider)\n * Self-hosted servers have their own OAuth endpoints and manage token storage\n */\n private async detectSelfHostedMode(mcpServerUrl: string): Promise<boolean> {\n try {\n // Self-hosted DCR servers typically run their own OAuth server\n // Check if this is a self-hosted instance by testing OAuth metadata\n // For now, assume self-hosted if the URL matches common localhost patterns\n // TODO: Implement proper self-hosted detection logic\n return mcpServerUrl.includes('localhost') || mcpServerUrl.includes('127.0.0.1');\n } catch (_error) {\n return false; // Assume external mode if detection fails\n }\n }\n\n /**\n * Ensure server is authenticated, performing DCR and OAuth if needed\n * Proactively refreshes tokens if they're within 5 minutes of expiry\n *\n * @param mcpServerUrl - The MCP server's canonical URL, exactly as configured,\n * with its path intact (`https://example.com/mcp`). Not a deployment root:\n * the path is what identifies the resource an issued token is bound to.\n * @param capabilities - Auth capabilities from .well-known endpoint\n * @returns Valid token set ready to use\n *\n * @throws Error if authentication fails\n *\n * @example\n * const authenticator = new DcrAuthenticator({ redirectUri: 'http://localhost:3000/callback' });\n * const tokens = await authenticator.ensureAuthenticated(\n * 'https://example.com/mcp',\n * capabilities\n * );\n */\n async ensureAuthenticated(mcpServerUrl: string, capabilities: AuthCapabilities, signal?: AbortSignal): Promise<TokenSet> {\n throwIfAborted(signal);\n // Auto-detect server mode\n const isSelfHosted = await this.detectSelfHostedMode(mcpServerUrl);\n\n if (isSelfHosted) {\n return this.ensureAuthenticatedSelfHosted(mcpServerUrl, capabilities, signal);\n }\n return this.ensureAuthenticatedExternal(mcpServerUrl, capabilities, signal);\n }\n\n /**\n * The three things a server URL is used for here, kept apart on purpose.\n *\n * They were one value once, and collapsing them is what sent an authorization\n * server the wrong audience: `resource` was derived from the deployment root,\n * so a server at `https://host/mcp` was asked to mint a token for\n * `https://host`, and any server that validates the indicator answered\n * `invalid_target`.\n *\n * - `serverBaseUrl` builds the server's own endpoints (`/oauth/verify`), so a\n * trailing `/mcp` comes off.\n * - `resource` is the RFC 8707 audience. The resource server names itself in\n * its RFC 9728 metadata; that name wins. Only when no such document exists\n * do we fall back to the URL we were configured with.\n * - `storeKey` identifies the credential locally. It stays the configured URL\n * rather than the discovered `resource`, so it can be computed without a\n * network round trip - `deleteTokens` has only the URL to work from.\n */\n private resolveUrls(mcpServerUrl: string, capabilities: AuthCapabilities): { serverBaseUrl: string; resource: string; storeKey: string } {\n const storeKey = normalizeUrl(mcpServerUrl);\n return { serverBaseUrl: extractBaseUrl(mcpServerUrl), resource: capabilities.resource ?? storeKey, storeKey };\n }\n\n /**\n * Handle authentication for self-hosted DCR servers\n * Self-hosted servers manage their own token storage via /oauth/verify\n */\n private async ensureAuthenticatedSelfHosted(mcpServerUrl: string, capabilities: AuthCapabilities, signal?: AbortSignal): Promise<TokenSet> {\n // Loopback trust for every discovery-derived fetch below, computed from\n // the server we're talking to, never from capabilities' endpoints (see discovery-fetch.ts).\n const allowLoopback = isLoopbackUrl(mcpServerUrl);\n const issuer = requireIssuer(capabilities);\n const { serverBaseUrl, resource, storeKey } = this.resolveUrls(mcpServerUrl, capabilities);\n const dcrTokenKey = `dcr-tokens:${issuer}:${storeKey}`;\n\n // 1. Check for existing DCR tokens (different from external tokens)\n let tokens = await this.loadTokens(dcrTokenKey, issuer);\n\n if (tokens) {\n // 2. Verify token is still valid by calling /oauth/verify\n try {\n const verifyUrl = `${serverBaseUrl}/oauth/verify`;\n const verifyResponse = await fetch(verifyUrl, {\n headers: { Authorization: `Bearer ${tokens.accessToken}`, Connection: 'close' },\n signal,\n });\n\n if (verifyResponse.ok) {\n const verifyData = (await verifyResponse.json()) as { token?: string };\n if (verifyData.token === tokens.accessToken) {\n // Token is still valid with the self-hosted server\n return tokens;\n }\n }\n } catch (_error) {\n // Token verification failed - need to re-authenticate\n }\n\n // Token is expired or invalid\n await this.tokenStore.delete(dcrTokenKey);\n tokens = undefined;\n }\n\n // 3. No valid tokens - perform full DCR + OAuth flow\n if (!capabilities.registrationEndpoint || !capabilities.authorizationEndpoint || !capabilities.tokenEndpoint) {\n throw new Error('Server does not provide required OAuth endpoints');\n }\n\n this.logger.debug('🔐 No valid tokens found, starting self-hosted DCR authentication...');\n\n // Extract port from pre-resolved redirectUri\n const port = parseInt(new URL(this.redirectUri).port, 10) || (this.redirectUri.startsWith('https:') ? 443 : 80);\n\n // Register OAuth client via DCR\n this.logger.debug('📝 Registering OAuth client with self-hosted server...');\n const client = await this.dcrClient.registerClient(capabilities.registrationEndpoint, {\n redirectUri: this.redirectUri,\n allowLoopback,\n signal,\n });\n\n // Perform OAuth authorization flow with PKCE (RFC 7636)\n const flowOptions = this.buildFlowOptions(port, capabilities, issuer, resource, allowLoopback, signal);\n\n tokens = await this.oauthFlow.performAuthFlow(capabilities.authorizationEndpoint, capabilities.tokenEndpoint, client.clientId, client.clientSecret, flowOptions);\n\n // For self-hosted mode, verify the token works with /oauth/verify immediately\n try {\n const verifyUrl = `${serverBaseUrl}/oauth/verify`;\n const verifyResponse = await fetch(verifyUrl, {\n headers: { Authorization: `Bearer ${tokens.accessToken}`, Connection: 'close' },\n signal,\n });\n\n if (!verifyResponse.ok) {\n throw new Error(`DCR token verification failed after authentication: ${verifyResponse.status}`);\n }\n\n const verifyData = (await verifyResponse.json()) as { token?: string };\n if (verifyData.token !== tokens.accessToken) {\n throw new Error('DCR server returned different token in verification');\n }\n\n this.logger.debug('✅ DCR token verified with self-hosted server');\n } catch (error) {\n this.logger.error('❌ DCR token verification failed:', error instanceof Error ? error.message : String(error));\n throw new Error('Self-hosted DCR authentication completed but token verification failed');\n }\n\n // Save tokens for future use\n await this.tokenStore.set(dcrTokenKey, { ...tokens, issuer });\n this.logger.debug('✅ Self-hosted DCR authentication successful, tokens saved');\n\n return tokens;\n }\n\n /** Handles authentication for external (non-self-hosted) OAuth providers. */\n private async ensureAuthenticatedExternal(mcpServerUrl: string, capabilities: AuthCapabilities, signal?: AbortSignal): Promise<TokenSet> {\n // See ensureAuthenticatedSelfHosted - same loopback trust rule.\n const allowLoopback = isLoopbackUrl(mcpServerUrl);\n const issuer = requireIssuer(capabilities);\n const { resource, storeKey } = this.resolveUrls(mcpServerUrl, capabilities);\n const tokenKey = `tokens:${issuer}:${storeKey}`;\n\n // 1. Check for existing tokens\n let tokens = await this.loadTokens(tokenKey, issuer);\n\n if (tokens) {\n // 2. Proactive refresh if token expires within 5 minutes\n if (tokens.expiresAt < Date.now() + REFRESH_BUFFER_MS) {\n this.logger.debug('🔄 Refreshing access token...');\n\n try {\n tokens = await this.refreshTokens(tokens, capabilities.tokenEndpoint, resource, allowLoopback, signal);\n await this.tokenStore.set(tokenKey, { ...tokens, issuer });\n this.logger.debug('✅ Token refreshed successfully');\n } catch (_error) {\n // Refresh failed - clear tokens and re-authenticate\n this.logger.warn('⚠️ Token refresh failed, re-authenticating...');\n await this.tokenStore.delete(tokenKey);\n tokens = undefined;\n }\n }\n\n if (tokens) {\n return tokens;\n }\n }\n\n // 3. No valid tokens - perform DCR + OAuth flow\n if (!capabilities.registrationEndpoint || !capabilities.authorizationEndpoint || !capabilities.tokenEndpoint) {\n throw new Error('Server does not provide required OAuth endpoints');\n }\n\n this.logger.debug('🔐 No valid tokens found, starting external OAuth authentication...');\n\n // Extract port from pre-resolved redirectUri\n const port = parseInt(new URL(this.redirectUri).port, 10) || (this.redirectUri.startsWith('https:') ? 443 : 80);\n\n // Register OAuth client via DCR\n this.logger.debug('📝 Registering OAuth client...');\n const client = await this.dcrClient.registerClient(capabilities.registrationEndpoint, {\n redirectUri: this.redirectUri,\n allowLoopback,\n signal,\n });\n\n // Perform OAuth authorization flow with PKCE (RFC 7636)\n const flowOptions = this.buildFlowOptions(port, capabilities, issuer, resource, allowLoopback, signal);\n\n tokens = await this.oauthFlow.performAuthFlow(capabilities.authorizationEndpoint, capabilities.tokenEndpoint, client.clientId, client.clientSecret, flowOptions);\n\n // Save tokens for future use\n await this.tokenStore.set(tokenKey, { ...tokens, issuer });\n this.logger.debug('✅ Authentication successful, tokens saved');\n\n return tokens;\n }\n\n /**\n * Refreshes an access token using a refresh token.\n * @param resource - Canonical resource server URI, audience-binding the token (RFC 8707).\n * @param allowLoopback - Loopback trust grant computed from the server actually being talked to (SSRF mitigation, see discovery-fetch.ts). Defaults to `false`.\n */\n private async refreshTokens(tokens: TokenSet, tokenEndpoint: string | undefined, resource: string, allowLoopback = false, signal?: AbortSignal): Promise<TokenSet> {\n if (!tokenEndpoint) {\n throw new Error('Token endpoint not available for refresh');\n }\n\n if (!tokens.refreshToken) {\n throw new Error('No refresh token available');\n }\n\n if (!tokens.clientId || !tokens.clientSecret) {\n throw new Error('Client credentials not available for refresh');\n }\n\n return await this.oauthFlow.refreshTokens(tokenEndpoint, tokens.refreshToken, tokens.clientId, tokens.clientSecret, resource, allowLoopback, signal);\n }\n\n /**\n * Deletes both stored token families for a server, across every issuer they were bound to.\n *\n * Takes the same `mcpServerUrl` that {@link DcrAuthenticator.ensureAuthenticated}\n * was given - keys are built from the configured URL, never from the discovered\n * RFC 8707 resource, precisely so this can find them without doing discovery.\n *\n * @throws CredentialBindingError if the configured store cannot enumerate keys.\n */\n async deleteTokens(mcpServerUrl: string): Promise<void> {\n const suffix = `:${normalizeUrl(mcpServerUrl)}`;\n if (!this.tokenStore.iterator) {\n throw new CredentialBindingError('Token store does not support key enumeration, which issuer-keyed credentials require');\n }\n\n for await (const [key] of this.tokenStore.iterator(this.tokenStore.namespace)) {\n if (typeof key === 'string' && (key.startsWith('tokens:') || key.startsWith('dcr-tokens:')) && key.endsWith(suffix)) {\n await this.tokenStore.delete(key);\n }\n }\n this.logger.debug(`🗑️ Deleted tokens for ${mcpServerUrl}`);\n }\n\n /** Discards a stored credential that is not bound to `issuer` (SEP-2352), including one stored before issuer binding existed. */\n private async loadTokens(key: string, issuer: string): Promise<TokenSet | undefined> {\n const tokens = (await this.tokenStore.get(key)) as TokenSet | undefined;\n if (!tokens) return undefined;\n if (tokens.issuer === issuer) return tokens;\n\n this.logger.debug('🔑 Stored credential is not bound to the discovered issuer, re-authorizing');\n await this.tokenStore.delete(key);\n return undefined;\n }\n\n private buildFlowOptions(port: number, capabilities: AuthCapabilities, issuer: string, resource: string, allowLoopback: boolean, signal?: AbortSignal): OAuthFlowOptions {\n const flowOptions: OAuthFlowOptions = {\n port,\n issuer,\n resource,\n headless: this.headless,\n redirectUri: this.redirectUri,\n pkce: true,\n logger: this.logger,\n allowLoopback,\n ...(signal && { signal }),\n authorizationResponseIssSupported: capabilities.authorizationResponseIssSupported ?? false,\n };\n if (capabilities.scopes) {\n flowOptions.scopes = capabilities.scopes;\n }\n return flowOptions;\n }\n}\n\nfunction throwIfAborted(signal?: AbortSignal): void {\n if (signal?.aborted) throw signal.reason instanceof Error ? signal.reason : new Error('Authentication was cancelled');\n}\n"],"names":["path","fs","Keyv","KeyvFile","isLoopbackUrl","InteractiveOAuthFlow","extractBaseUrl","normalizeUrl","logger","defaultLogger","DynamicClientRegistrar","REFRESH_BUFFER_MS","CredentialBindingError","Error","message","name","requireIssuer","capabilities","issuer","DcrAuthenticator","detectSelfHostedMode","mcpServerUrl","includes","_error","ensureAuthenticated","signal","throwIfAborted","isSelfHosted","ensureAuthenticatedSelfHosted","ensureAuthenticatedExternal","resolveUrls","storeKey","serverBaseUrl","resource","allowLoopback","dcrTokenKey","tokens","loadTokens","verifyUrl","verifyResponse","fetch","headers","Authorization","accessToken","Connection","ok","verifyData","json","token","tokenStore","delete","undefined","registrationEndpoint","authorizationEndpoint","tokenEndpoint","debug","port","parseInt","URL","redirectUri","startsWith","client","dcrClient","registerClient","flowOptions","buildFlowOptions","oauthFlow","performAuthFlow","clientId","clientSecret","status","error","String","set","tokenKey","expiresAt","Date","now","refreshTokens","warn","refreshToken","deleteTokens","suffix","iterator","key","namespace","endsWith","get","headless","pkce","authorizationResponseIssSupported","scopes","options","storePath","join","process","cwd","mkdirSync","dirname","recursive","store","filename","aborted","reason"],"mappings":"AAAA;;;CAGC,GAED,OAAOA,UAAU,YAAY;AAC7B,YAAYC,QAAQ,KAAK;AACzB,OAAOC,UAAU,OAAO;AACxB,SAASC,QAAQ,QAAQ,YAAY;AACrC,SAASC,aAAa,QAAQ,6BAA6B;AAC3D,SAASC,oBAAoB,QAAQ,oCAAoC;AAEzE,SAASC,cAAc,EAAEC,YAAY,QAAQ,sBAAsB;AACnE,SAASC,UAAUC,aAAa,QAAqB,qBAAqB;AAC1E,SAASC,sBAAsB,QAAQ,gCAAgC;AAgBvE;;CAEC,GACD,MAAMC,oBAAoB,IAAI,KAAK;AAEnC,6EAA6E,GAC7E,IAAA,AAAMC,yBAAN,MAAMA,+BAA+BC;IACnC,YAAYC,OAAe,CAAE;QAC3B,KAAK,CAACA;QACN,IAAI,CAACC,IAAI,GAAG;IACd;AACF;AAEA;;;CAGC,GACD,SAASC,cAAcC,YAA8B;IACnD,IAAI,CAACA,aAAaC,MAAM,EAAE;QACxB,MAAM,IAAIN,uBAAuB;IACnC;IACA,OAAOK,aAAaC,MAAM;AAC5B;AAEA;;;CAGC,GACD,OAAO,MAAMC;IA6BX;;;GAGC,GACD,MAAcC,qBAAqBC,YAAoB,EAAoB;QACzE,IAAI;YACF,+DAA+D;YAC/D,oEAAoE;YACpE,2EAA2E;YAC3E,qDAAqD;YACrD,OAAOA,aAAaC,QAAQ,CAAC,gBAAgBD,aAAaC,QAAQ,CAAC;QACrE,EAAE,OAAOC,QAAQ;YACf,OAAO,OAAO,0CAA0C;QAC1D;IACF;IAEA;;;;;;;;;;;;;;;;;;GAkBC,GACD,MAAMC,oBAAoBH,YAAoB,EAAEJ,YAA8B,EAAEQ,MAAoB,EAAqB;QACvHC,eAAeD;QACf,0BAA0B;QAC1B,MAAME,eAAe,MAAM,IAAI,CAACP,oBAAoB,CAACC;QAErD,IAAIM,cAAc;YAChB,OAAO,IAAI,CAACC,6BAA6B,CAACP,cAAcJ,cAAcQ;QACxE;QACA,OAAO,IAAI,CAACI,2BAA2B,CAACR,cAAcJ,cAAcQ;IACtE;IAEA;;;;;;;;;;;;;;;;;GAiBC,GACD,AAAQK,YAAYT,YAAoB,EAAEJ,YAA8B,EAAiE;YAEvEA;QADhE,MAAMc,WAAWxB,aAAac;QAC9B,OAAO;YAAEW,eAAe1B,eAAee;YAAeY,QAAQ,GAAEhB,yBAAAA,aAAagB,QAAQ,cAArBhB,oCAAAA,yBAAyBc;YAAUA;QAAS;IAC9G;IAEA;;;GAGC,GACD,MAAcH,8BAA8BP,YAAoB,EAAEJ,YAA8B,EAAEQ,MAAoB,EAAqB;QACzI,wEAAwE;QACxE,4FAA4F;QAC5F,MAAMS,gBAAgB9B,cAAciB;QACpC,MAAMH,SAASF,cAAcC;QAC7B,MAAM,EAAEe,aAAa,EAAEC,QAAQ,EAAEF,QAAQ,EAAE,GAAG,IAAI,CAACD,WAAW,CAACT,cAAcJ;QAC7E,MAAMkB,cAAc,CAAC,WAAW,EAAEjB,OAAO,CAAC,EAAEa,UAAU;QAEtD,oEAAoE;QACpE,IAAIK,SAAS,MAAM,IAAI,CAACC,UAAU,CAACF,aAAajB;QAEhD,IAAIkB,QAAQ;YACV,0DAA0D;YAC1D,IAAI;gBACF,MAAME,YAAY,GAAGN,cAAc,aAAa,CAAC;gBACjD,MAAMO,iBAAiB,MAAMC,MAAMF,WAAW;oBAC5CG,SAAS;wBAAEC,eAAe,CAAC,OAAO,EAAEN,OAAOO,WAAW,EAAE;wBAAEC,YAAY;oBAAQ;oBAC9EnB;gBACF;gBAEA,IAAIc,eAAeM,EAAE,EAAE;oBACrB,MAAMC,aAAc,MAAMP,eAAeQ,IAAI;oBAC7C,IAAID,WAAWE,KAAK,KAAKZ,OAAOO,WAAW,EAAE;wBAC3C,mDAAmD;wBACnD,OAAOP;oBACT;gBACF;YACF,EAAE,OAAOb,QAAQ;YACf,sDAAsD;YACxD;YAEA,8BAA8B;YAC9B,MAAM,IAAI,CAAC0B,UAAU,CAACC,MAAM,CAACf;YAC7BC,SAASe;QACX;QAEA,qDAAqD;QACrD,IAAI,CAAClC,aAAamC,oBAAoB,IAAI,CAACnC,aAAaoC,qBAAqB,IAAI,CAACpC,aAAaqC,aAAa,EAAE;YAC5G,MAAM,IAAIzC,MAAM;QAClB;QAEA,IAAI,CAACL,MAAM,CAAC+C,KAAK,CAAC;QAElB,6CAA6C;QAC7C,MAAMC,OAAOC,SAAS,IAAIC,IAAI,IAAI,CAACC,WAAW,EAAEH,IAAI,EAAE,OAAQ,CAAA,IAAI,CAACG,WAAW,CAACC,UAAU,CAAC,YAAY,MAAM,EAAC;QAE7G,gCAAgC;QAChC,IAAI,CAACpD,MAAM,CAAC+C,KAAK,CAAC;QAClB,MAAMM,SAAS,MAAM,IAAI,CAACC,SAAS,CAACC,cAAc,CAAC9C,aAAamC,oBAAoB,EAAE;YACpFO,aAAa,IAAI,CAACA,WAAW;YAC7BzB;YACAT;QACF;QAEA,wDAAwD;QACxD,MAAMuC,cAAc,IAAI,CAACC,gBAAgB,CAACT,MAAMvC,cAAcC,QAAQe,UAAUC,eAAeT;QAE/FW,SAAS,MAAM,IAAI,CAAC8B,SAAS,CAACC,eAAe,CAAClD,aAAaoC,qBAAqB,EAAEpC,aAAaqC,aAAa,EAAEO,OAAOO,QAAQ,EAAEP,OAAOQ,YAAY,EAAEL;QAEpJ,8EAA8E;QAC9E,IAAI;YACF,MAAM1B,YAAY,GAAGN,cAAc,aAAa,CAAC;YACjD,MAAMO,iBAAiB,MAAMC,MAAMF,WAAW;gBAC5CG,SAAS;oBAAEC,eAAe,CAAC,OAAO,EAAEN,OAAOO,WAAW,EAAE;oBAAEC,YAAY;gBAAQ;gBAC9EnB;YACF;YAEA,IAAI,CAACc,eAAeM,EAAE,EAAE;gBACtB,MAAM,IAAIhC,MAAM,CAAC,oDAAoD,EAAE0B,eAAe+B,MAAM,EAAE;YAChG;YAEA,MAAMxB,aAAc,MAAMP,eAAeQ,IAAI;YAC7C,IAAID,WAAWE,KAAK,KAAKZ,OAAOO,WAAW,EAAE;gBAC3C,MAAM,IAAI9B,MAAM;YAClB;YAEA,IAAI,CAACL,MAAM,CAAC+C,KAAK,CAAC;QACpB,EAAE,OAAOgB,OAAO;YACd,IAAI,CAAC/D,MAAM,CAAC+D,KAAK,CAAC,oCAAoCA,iBAAiB1D,QAAQ0D,MAAMzD,OAAO,GAAG0D,OAAOD;YACtG,MAAM,IAAI1D,MAAM;QAClB;QAEA,6BAA6B;QAC7B,MAAM,IAAI,CAACoC,UAAU,CAACwB,GAAG,CAACtC,aAAa;YAAE,GAAGC,MAAM;YAAElB;QAAO;QAC3D,IAAI,CAACV,MAAM,CAAC+C,KAAK,CAAC;QAElB,OAAOnB;IACT;IAEA,2EAA2E,GAC3E,MAAcP,4BAA4BR,YAAoB,EAAEJ,YAA8B,EAAEQ,MAAoB,EAAqB;QACvI,gEAAgE;QAChE,MAAMS,gBAAgB9B,cAAciB;QACpC,MAAMH,SAASF,cAAcC;QAC7B,MAAM,EAAEgB,QAAQ,EAAEF,QAAQ,EAAE,GAAG,IAAI,CAACD,WAAW,CAACT,cAAcJ;QAC9D,MAAMyD,WAAW,CAAC,OAAO,EAAExD,OAAO,CAAC,EAAEa,UAAU;QAE/C,+BAA+B;QAC/B,IAAIK,SAAS,MAAM,IAAI,CAACC,UAAU,CAACqC,UAAUxD;QAE7C,IAAIkB,QAAQ;YACV,yDAAyD;YACzD,IAAIA,OAAOuC,SAAS,GAAGC,KAAKC,GAAG,KAAKlE,mBAAmB;gBACrD,IAAI,CAACH,MAAM,CAAC+C,KAAK,CAAC;gBAElB,IAAI;oBACFnB,SAAS,MAAM,IAAI,CAAC0C,aAAa,CAAC1C,QAAQnB,aAAaqC,aAAa,EAAErB,UAAUC,eAAeT;oBAC/F,MAAM,IAAI,CAACwB,UAAU,CAACwB,GAAG,CAACC,UAAU;wBAAE,GAAGtC,MAAM;wBAAElB;oBAAO;oBACxD,IAAI,CAACV,MAAM,CAAC+C,KAAK,CAAC;gBACpB,EAAE,OAAOhC,QAAQ;oBACf,oDAAoD;oBACpD,IAAI,CAACf,MAAM,CAACuE,IAAI,CAAC;oBACjB,MAAM,IAAI,CAAC9B,UAAU,CAACC,MAAM,CAACwB;oBAC7BtC,SAASe;gBACX;YACF;YAEA,IAAIf,QAAQ;gBACV,OAAOA;YACT;QACF;QAEA,gDAAgD;QAChD,IAAI,CAACnB,aAAamC,oBAAoB,IAAI,CAACnC,aAAaoC,qBAAqB,IAAI,CAACpC,aAAaqC,aAAa,EAAE;YAC5G,MAAM,IAAIzC,MAAM;QAClB;QAEA,IAAI,CAACL,MAAM,CAAC+C,KAAK,CAAC;QAElB,6CAA6C;QAC7C,MAAMC,OAAOC,SAAS,IAAIC,IAAI,IAAI,CAACC,WAAW,EAAEH,IAAI,EAAE,OAAQ,CAAA,IAAI,CAACG,WAAW,CAACC,UAAU,CAAC,YAAY,MAAM,EAAC;QAE7G,gCAAgC;QAChC,IAAI,CAACpD,MAAM,CAAC+C,KAAK,CAAC;QAClB,MAAMM,SAAS,MAAM,IAAI,CAACC,SAAS,CAACC,cAAc,CAAC9C,aAAamC,oBAAoB,EAAE;YACpFO,aAAa,IAAI,CAACA,WAAW;YAC7BzB;YACAT;QACF;QAEA,wDAAwD;QACxD,MAAMuC,cAAc,IAAI,CAACC,gBAAgB,CAACT,MAAMvC,cAAcC,QAAQe,UAAUC,eAAeT;QAE/FW,SAAS,MAAM,IAAI,CAAC8B,SAAS,CAACC,eAAe,CAAClD,aAAaoC,qBAAqB,EAAEpC,aAAaqC,aAAa,EAAEO,OAAOO,QAAQ,EAAEP,OAAOQ,YAAY,EAAEL;QAEpJ,6BAA6B;QAC7B,MAAM,IAAI,CAACf,UAAU,CAACwB,GAAG,CAACC,UAAU;YAAE,GAAGtC,MAAM;YAAElB;QAAO;QACxD,IAAI,CAACV,MAAM,CAAC+C,KAAK,CAAC;QAElB,OAAOnB;IACT;IAEA;;;;GAIC,GACD,MAAc0C,cAAc1C,MAAgB,EAAEkB,aAAiC,EAAErB,QAAgB,EAAEC,gBAAgB,KAAK,EAAET,MAAoB,EAAqB;QACjK,IAAI,CAAC6B,eAAe;YAClB,MAAM,IAAIzC,MAAM;QAClB;QAEA,IAAI,CAACuB,OAAO4C,YAAY,EAAE;YACxB,MAAM,IAAInE,MAAM;QAClB;QAEA,IAAI,CAACuB,OAAOgC,QAAQ,IAAI,CAAChC,OAAOiC,YAAY,EAAE;YAC5C,MAAM,IAAIxD,MAAM;QAClB;QAEA,OAAO,MAAM,IAAI,CAACqD,SAAS,CAACY,aAAa,CAACxB,eAAelB,OAAO4C,YAAY,EAAE5C,OAAOgC,QAAQ,EAAEhC,OAAOiC,YAAY,EAAEpC,UAAUC,eAAeT;IAC/I;IAEA;;;;;;;;GAQC,GACD,MAAMwD,aAAa5D,YAAoB,EAAiB;QACtD,MAAM6D,SAAS,CAAC,CAAC,EAAE3E,aAAac,eAAe;QAC/C,IAAI,CAAC,IAAI,CAAC4B,UAAU,CAACkC,QAAQ,EAAE;YAC7B,MAAM,IAAIvE,uBAAuB;QACnC;QAEA,WAAW,MAAM,CAACwE,IAAI,IAAI,IAAI,CAACnC,UAAU,CAACkC,QAAQ,CAAC,IAAI,CAAClC,UAAU,CAACoC,SAAS,EAAG;YAC7E,IAAI,OAAOD,QAAQ,YAAaA,CAAAA,IAAIxB,UAAU,CAAC,cAAcwB,IAAIxB,UAAU,CAAC,cAAa,KAAMwB,IAAIE,QAAQ,CAACJ,SAAS;gBACnH,MAAM,IAAI,CAACjC,UAAU,CAACC,MAAM,CAACkC;YAC/B;QACF;QACA,IAAI,CAAC5E,MAAM,CAAC+C,KAAK,CAAC,CAAC,wBAAwB,EAAElC,cAAc;IAC7D;IAEA,+HAA+H,GAC/H,MAAcgB,WAAW+C,GAAW,EAAElE,MAAc,EAAiC;QACnF,MAAMkB,SAAU,MAAM,IAAI,CAACa,UAAU,CAACsC,GAAG,CAACH;QAC1C,IAAI,CAAChD,QAAQ,OAAOe;QACpB,IAAIf,OAAOlB,MAAM,KAAKA,QAAQ,OAAOkB;QAErC,IAAI,CAAC5B,MAAM,CAAC+C,KAAK,CAAC;QAClB,MAAM,IAAI,CAACN,UAAU,CAACC,MAAM,CAACkC;QAC7B,OAAOjC;IACT;IAEQc,iBAAiBT,IAAY,EAAEvC,YAA8B,EAAEC,MAAc,EAAEe,QAAgB,EAAEC,aAAsB,EAAET,MAAoB,EAAoB;YAWlIR;QAVrC,MAAM+C,cAAgC;YACpCR;YACAtC;YACAe;YACAuD,UAAU,IAAI,CAACA,QAAQ;YACvB7B,aAAa,IAAI,CAACA,WAAW;YAC7B8B,MAAM;YACNjF,QAAQ,IAAI,CAACA,MAAM;YACnB0B;YACA,GAAIT,UAAU;gBAAEA;YAAO,CAAC;YACxBiE,iCAAiC,GAAEzE,kDAAAA,aAAayE,iCAAiC,cAA9CzE,6DAAAA,kDAAkD;QACvF;QACA,IAAIA,aAAa0E,MAAM,EAAE;YACvB3B,YAAY2B,MAAM,GAAG1E,aAAa0E,MAAM;QAC1C;QACA,OAAO3B;IACT;IA9TA,YAAY4B,OAAgC,CAAE;YAkB9BA;QAjBd,IAAIA,QAAQ3C,UAAU,EAAE;YACtB,IAAI,CAACA,UAAU,GAAG2C,QAAQ3C,UAAU;QACtC,OAAO;YACL,sDAAsD;YACtD,MAAM4C,YAAY7F,KAAK8F,IAAI,CAACC,QAAQC,GAAG,IAAI,UAAU;YAErD,gDAAgD;YAChD/F,GAAGgG,SAAS,CAACjG,KAAKkG,OAAO,CAACL,YAAY;gBAAEM,WAAW;YAAK;YAExD,IAAI,CAAClD,UAAU,GAAG,IAAI/C,KAAK;gBACzBkG,OAAO,IAAIjG,SAAS;oBAAEkG,UAAUR;gBAAU;YAC5C;QACF;QACA,IAAI,CAAC/B,SAAS,GAAG,IAAIpD;QACrB,IAAI,CAACwD,SAAS,GAAG,IAAI7D;QACrB,IAAI,CAACmF,QAAQ,GAAGI,QAAQJ,QAAQ,IAAI;QACpC,IAAI,CAAC7B,WAAW,GAAGiC,QAAQjC,WAAW;QACtC,IAAI,CAACnD,MAAM,IAAGoF,kBAAAA,QAAQpF,MAAM,cAAdoF,6BAAAA,kBAAkBnF;IAClC;AA4SF;AAEA,SAASiB,eAAeD,MAAoB;IAC1C,IAAIA,mBAAAA,6BAAAA,OAAQ6E,OAAO,EAAE,MAAM7E,OAAO8E,MAAM,YAAY1F,QAAQY,OAAO8E,MAAM,GAAG,IAAI1F,MAAM;AACxF"}
@@ -38,7 +38,8 @@
38
38
  Accept: 'application/json',
39
39
  Connection: 'close'
40
40
  },
41
- body: JSON.stringify(requestBody)
41
+ body: JSON.stringify(requestBody),
42
+ signal: options.signal
42
43
  }, 'registration endpoint', {
43
44
  allowLoopback: (_options_allowLoopback = options.allowLoopback) !== null && _options_allowLoopback !== void 0 ? _options_allowLoopback : false
44
45
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/mcp-z/client/src/dcr/dynamic-client-registrar.ts"],"sourcesContent":["/**\n * Dynamic Client Registration (DCR) Client\n * Implements RFC 7591 for OAuth client registration\n */\n\nimport { discoveryFetch } from '../auth/discovery-fetch.ts';\nimport type { ClientCredentials, DcrRegistrationOptions } from '../auth/types.ts';\n\n/** DCR Registration Request (RFC 7591) */\ninterface DcrRegistrationRequest {\n client_name?: string;\n redirect_uris?: string[];\n grant_types?: string[];\n response_types?: string[];\n token_endpoint_auth_method?: string;\n application_type?: string;\n}\n\n/** DCR Registration Response (RFC 7591) */\ninterface DcrRegistrationResponse {\n client_id: string;\n client_secret?: string;\n client_id_issued_at?: number;\n client_secret_expires_at?: number;\n}\n\n/** Handles Dynamic Client Registration with OAuth servers. */\nexport class DynamicClientRegistrar {\n /**\n * Registers a new OAuth client with the authorization server (RFC 7591).\n *\n * @param registrationEndpoint - Often sourced from remote-controlled AS metadata; pass `options.allowLoopback` explicitly. Defaults to `false`.\n * @param options - Registration options (redirect URI, client name, loopback trust).\n * @returns Client credentials (client ID and secret).\n * @throws Error if registration fails or the server returns an error.\n */\n async registerClient(registrationEndpoint: string, options: DcrRegistrationOptions): Promise<ClientCredentials> {\n const requestBody: DcrRegistrationRequest = {\n client_name: options.clientName || '@mcp-z/client',\n redirect_uris: [options.redirectUri],\n grant_types: ['authorization_code', 'refresh_token'],\n response_types: ['code'],\n token_endpoint_auth_method: 'client_secret_basic',\n // The callback listener always binds an OS-assigned loopback port, so the\n // redirect URI is one an authorization server rejects for a web client (SEP-837, RFC 8252).\n application_type: 'native',\n };\n\n // registrationEndpoint is remote-controlled discovery data; allowLoopback\n // must come from the caller's own trust, never from registrationEndpoint.\n const response = await discoveryFetch(\n registrationEndpoint,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n Connection: 'close',\n },\n body: JSON.stringify(requestBody),\n },\n 'registration endpoint',\n { allowLoopback: options.allowLoopback ?? false }\n );\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`DCR registration failed (${response.status}): ${errorText}`);\n }\n\n const data = (await response.json()) as DcrRegistrationResponse;\n\n if (!data.client_id) {\n throw new Error('DCR registration response missing client_id');\n }\n\n const credentials: ClientCredentials = {\n clientId: data.client_id,\n clientSecret: data.client_secret || '',\n };\n\n if (data.client_id_issued_at) {\n credentials.issuedAt = data.client_id_issued_at;\n }\n\n return credentials;\n }\n}\n"],"names":["discoveryFetch","DynamicClientRegistrar","registerClient","registrationEndpoint","options","requestBody","client_name","clientName","redirect_uris","redirectUri","grant_types","response_types","token_endpoint_auth_method","application_type","response","method","headers","Accept","Connection","body","JSON","stringify","allowLoopback","ok","errorText","text","Error","status","data","json","client_id","credentials","clientId","clientSecret","client_secret","client_id_issued_at","issuedAt"],"mappings":"AAAA;;;CAGC,GAED,SAASA,cAAc,QAAQ,6BAA6B;AAqB5D,4DAA4D,GAC5D,OAAO,MAAMC;IACX;;;;;;;GAOC,GACD,MAAMC,eAAeC,oBAA4B,EAAEC,OAA+B,EAA8B;YA0B3FA;QAzBnB,MAAMC,cAAsC;YAC1CC,aAAaF,QAAQG,UAAU,IAAI;YACnCC,eAAe;gBAACJ,QAAQK,WAAW;aAAC;YACpCC,aAAa;gBAAC;gBAAsB;aAAgB;YACpDC,gBAAgB;gBAAC;aAAO;YACxBC,4BAA4B;YAC5B,0EAA0E;YAC1E,4FAA4F;YAC5FC,kBAAkB;QACpB;QAEA,0EAA0E;QAC1E,0EAA0E;QAC1E,MAAMC,WAAW,MAAMd,eACrBG,sBACA;YACEY,QAAQ;YACRC,SAAS;gBACP,gBAAgB;gBAChBC,QAAQ;gBACRC,YAAY;YACd;YACAC,MAAMC,KAAKC,SAAS,CAAChB;QACvB,GACA,yBACA;YAAEiB,aAAa,GAAElB,yBAAAA,QAAQkB,aAAa,cAArBlB,oCAAAA,yBAAyB;QAAM;QAGlD,IAAI,CAACU,SAASS,EAAE,EAAE;YAChB,MAAMC,YAAY,MAAMV,SAASW,IAAI;YACrC,MAAM,IAAIC,MAAM,CAAC,yBAAyB,EAAEZ,SAASa,MAAM,CAAC,GAAG,EAAEH,WAAW;QAC9E;QAEA,MAAMI,OAAQ,MAAMd,SAASe,IAAI;QAEjC,IAAI,CAACD,KAAKE,SAAS,EAAE;YACnB,MAAM,IAAIJ,MAAM;QAClB;QAEA,MAAMK,cAAiC;YACrCC,UAAUJ,KAAKE,SAAS;YACxBG,cAAcL,KAAKM,aAAa,IAAI;QACtC;QAEA,IAAIN,KAAKO,mBAAmB,EAAE;YAC5BJ,YAAYK,QAAQ,GAAGR,KAAKO,mBAAmB;QACjD;QAEA,OAAOJ;IACT;AACF"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/mcp-z/client/src/dcr/dynamic-client-registrar.ts"],"sourcesContent":["/**\n * Dynamic Client Registration (DCR) Client\n * Implements RFC 7591 for OAuth client registration\n */\n\nimport { discoveryFetch } from '../auth/discovery-fetch.ts';\nimport type { ClientCredentials, DcrRegistrationOptions } from '../auth/types.ts';\n\n/** DCR Registration Request (RFC 7591) */\ninterface DcrRegistrationRequest {\n client_name?: string;\n redirect_uris?: string[];\n grant_types?: string[];\n response_types?: string[];\n token_endpoint_auth_method?: string;\n application_type?: string;\n}\n\n/** DCR Registration Response (RFC 7591) */\ninterface DcrRegistrationResponse {\n client_id: string;\n client_secret?: string;\n client_id_issued_at?: number;\n client_secret_expires_at?: number;\n}\n\n/** Handles Dynamic Client Registration with OAuth servers. */\nexport class DynamicClientRegistrar {\n /**\n * Registers a new OAuth client with the authorization server (RFC 7591).\n *\n * @param registrationEndpoint - Often sourced from remote-controlled AS metadata; pass `options.allowLoopback` explicitly. Defaults to `false`.\n * @param options - Registration options (redirect URI, client name, loopback trust).\n * @returns Client credentials (client ID and secret).\n * @throws Error if registration fails or the server returns an error.\n */\n async registerClient(registrationEndpoint: string, options: DcrRegistrationOptions): Promise<ClientCredentials> {\n const requestBody: DcrRegistrationRequest = {\n client_name: options.clientName || '@mcp-z/client',\n redirect_uris: [options.redirectUri],\n grant_types: ['authorization_code', 'refresh_token'],\n response_types: ['code'],\n token_endpoint_auth_method: 'client_secret_basic',\n // The callback listener always binds an OS-assigned loopback port, so the\n // redirect URI is one an authorization server rejects for a web client (SEP-837, RFC 8252).\n application_type: 'native',\n };\n\n // registrationEndpoint is remote-controlled discovery data; allowLoopback\n // must come from the caller's own trust, never from registrationEndpoint.\n const response = await discoveryFetch(\n registrationEndpoint,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n Connection: 'close',\n },\n body: JSON.stringify(requestBody),\n signal: options.signal,\n },\n 'registration endpoint',\n { allowLoopback: options.allowLoopback ?? false }\n );\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`DCR registration failed (${response.status}): ${errorText}`);\n }\n\n const data = (await response.json()) as DcrRegistrationResponse;\n\n if (!data.client_id) {\n throw new Error('DCR registration response missing client_id');\n }\n\n const credentials: ClientCredentials = {\n clientId: data.client_id,\n clientSecret: data.client_secret || '',\n };\n\n if (data.client_id_issued_at) {\n credentials.issuedAt = data.client_id_issued_at;\n }\n\n return credentials;\n }\n}\n"],"names":["discoveryFetch","DynamicClientRegistrar","registerClient","registrationEndpoint","options","requestBody","client_name","clientName","redirect_uris","redirectUri","grant_types","response_types","token_endpoint_auth_method","application_type","response","method","headers","Accept","Connection","body","JSON","stringify","signal","allowLoopback","ok","errorText","text","Error","status","data","json","client_id","credentials","clientId","clientSecret","client_secret","client_id_issued_at","issuedAt"],"mappings":"AAAA;;;CAGC,GAED,SAASA,cAAc,QAAQ,6BAA6B;AAqB5D,4DAA4D,GAC5D,OAAO,MAAMC;IACX;;;;;;;GAOC,GACD,MAAMC,eAAeC,oBAA4B,EAAEC,OAA+B,EAA8B;YA2B3FA;QA1BnB,MAAMC,cAAsC;YAC1CC,aAAaF,QAAQG,UAAU,IAAI;YACnCC,eAAe;gBAACJ,QAAQK,WAAW;aAAC;YACpCC,aAAa;gBAAC;gBAAsB;aAAgB;YACpDC,gBAAgB;gBAAC;aAAO;YACxBC,4BAA4B;YAC5B,0EAA0E;YAC1E,4FAA4F;YAC5FC,kBAAkB;QACpB;QAEA,0EAA0E;QAC1E,0EAA0E;QAC1E,MAAMC,WAAW,MAAMd,eACrBG,sBACA;YACEY,QAAQ;YACRC,SAAS;gBACP,gBAAgB;gBAChBC,QAAQ;gBACRC,YAAY;YACd;YACAC,MAAMC,KAAKC,SAAS,CAAChB;YACrBiB,QAAQlB,QAAQkB,MAAM;QACxB,GACA,yBACA;YAAEC,aAAa,GAAEnB,yBAAAA,QAAQmB,aAAa,cAArBnB,oCAAAA,yBAAyB;QAAM;QAGlD,IAAI,CAACU,SAASU,EAAE,EAAE;YAChB,MAAMC,YAAY,MAAMX,SAASY,IAAI;YACrC,MAAM,IAAIC,MAAM,CAAC,yBAAyB,EAAEb,SAASc,MAAM,CAAC,GAAG,EAAEH,WAAW;QAC9E;QAEA,MAAMI,OAAQ,MAAMf,SAASgB,IAAI;QAEjC,IAAI,CAACD,KAAKE,SAAS,EAAE;YACnB,MAAM,IAAIJ,MAAM;QAClB;QAEA,MAAMK,cAAiC;YACrCC,UAAUJ,KAAKE,SAAS;YACxBG,cAAcL,KAAKM,aAAa,IAAI;QACtC;QAEA,IAAIN,KAAKO,mBAAmB,EAAE;YAC5BJ,YAAYK,QAAQ,GAAGR,KAAKO,mBAAmB;QACjD;QAEA,OAAOJ;IACT;AACF"}
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export type { VersionNegotiationOptions } from '@modelcontextprotocol/client';
5
5
  export { SdkError, SdkErrorCode } from '@modelcontextprotocol/client';
6
- export type { McpServerEntry, StartConfig } from '../schemas/servers.d.js';
6
+ export type { McpServerEntry, StartConfig, StopCommandConfig } from '../schemas/servers.d.js';
7
7
  export { probeAuthCapabilities } from './auth/capability-discovery.js';
8
8
  export { DiscoveryFetchError, isLoopbackUrl } from './auth/discovery-fetch.js';
9
9
  export type { AuthCapabilities, CallbackResult, OAuthCallbackListenerOptions, OAuthFlowOptions, TokenSet } from './auth/index.js';
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/mcp-z/client/src/index.ts"],"sourcesContent":["/**\n * @mcp-z/client - MCP Client Library\n */\n\nexport type { VersionNegotiationOptions } from '@modelcontextprotocol/client';\n// SDK re-exports for protocol version negotiation: the connect-option type and the typed\n// errors a negotiation can fail with, so callers can handle era mismatch without\n// depending on the SDK themselves.\nexport { SdkError, SdkErrorCode } from '@modelcontextprotocol/client';\n// Config types (from schema)\nexport type { McpServerEntry, StartConfig } from '../schemas/servers.d.ts';\n// Auth - OAuth utilities\nexport { probeAuthCapabilities } from './auth/capability-discovery.ts';\nexport { DiscoveryFetchError, isLoopbackUrl } from './auth/discovery-fetch.ts';\nexport type { AuthCapabilities, CallbackResult, OAuthCallbackListenerOptions, OAuthFlowOptions, TokenSet } from './auth/index.ts';\nexport { InteractiveOAuthFlow } from './auth/interactive-oauth-flow.ts';\nexport { OAuthCallbackListener } from './auth/oauth-callback-listener.ts';\n// Client helpers and lightweight overloads\nexport { decorateClient, type ManagedClient, type PromptArguments, type WrappedCallToolReturn, type WrappedGetPromptReturn, type WrappedReadResourceReturn } from './client-helpers.ts';\n// Config - Configuration validation\nexport { type ValidationResult, validateServers } from './config/validate-config.ts';\n// Connection - MCP client connection utilities (internal helpers exposed for advanced use)\nexport type { JsonValue, PromptArgument, ToolArguments } from './connection/types.ts';\n// DCR - Dynamic Client Registration utilities\nexport { DcrAuthenticator } from './dcr/dcr-authenticator.ts';\nexport { DynamicClientRegistrar } from './dcr/dynamic-client-registrar.ts';\nexport type { ClientCredentials, DcrAuthenticatorOptions, DcrRegistrationOptions } from './dcr/index.ts';\nexport {\n type JsonValidator,\n type NativeCallToolResponse,\n type NativeGetPromptResponse,\n type NativeReadResourceResponse,\n PromptResponseError,\n PromptResponseWrapper,\n ResourceResponseError,\n ResourceResponseWrapper,\n ToolResponseError,\n ToolResponseWrapper,\n} from './response-wrappers.ts';\nexport type { CapabilityClient, CapabilityIndex, CapabilityType, IndexedCapability, IndexedPrompt, IndexedResource, IndexedTool, SearchField, SearchOptions, SearchResponse, SearchResult } from './search/index.ts';\n// Search - Capability discovery\nexport { buildCapabilityIndex, search, searchCapabilities } from './search/index.ts';\n// Spawn - Server registry (v3 API)\nexport { type CloseResult, type CreateServerRegistryOptions, createServerRegistry, type Dialect, type ServerRegistry, type ServersConfig } from './spawn/spawn-servers.ts';\nexport type { TransportType } from './types.ts';\n// Utils - Shared utilities\nexport { getLogLevel, type Logger, type LogLevel, logger, setLogLevel } from './utils/logger.ts';\nexport { resolveArgsPaths, resolvePath } from './utils/path-utils.ts';\n"],"names":["SdkError","SdkErrorCode","probeAuthCapabilities","DiscoveryFetchError","isLoopbackUrl","InteractiveOAuthFlow","OAuthCallbackListener","decorateClient","validateServers","DcrAuthenticator","DynamicClientRegistrar","PromptResponseError","PromptResponseWrapper","ResourceResponseError","ResourceResponseWrapper","ToolResponseError","ToolResponseWrapper","buildCapabilityIndex","search","searchCapabilities","createServerRegistry","getLogLevel","logger","setLogLevel","resolveArgsPaths","resolvePath"],"mappings":"AAAA;;CAEC,GAGD,yFAAyF;AACzF,iFAAiF;AACjF,mCAAmC;AACnC,SAASA,QAAQ,EAAEC,YAAY,QAAQ,+BAA+B;AAGtE,yBAAyB;AACzB,SAASC,qBAAqB,QAAQ,iCAAiC;AACvE,SAASC,mBAAmB,EAAEC,aAAa,QAAQ,4BAA4B;AAE/E,SAASC,oBAAoB,QAAQ,mCAAmC;AACxE,SAASC,qBAAqB,QAAQ,oCAAoC;AAC1E,2CAA2C;AAC3C,SAASC,cAAc,QAA2I,sBAAsB;AACxL,oCAAoC;AACpC,SAAgCC,eAAe,QAAQ,8BAA8B;AAGrF,8CAA8C;AAC9C,SAASC,gBAAgB,QAAQ,6BAA6B;AAC9D,SAASC,sBAAsB,QAAQ,oCAAoC;AAE3E,SAKEC,mBAAmB,EACnBC,qBAAqB,EACrBC,qBAAqB,EACrBC,uBAAuB,EACvBC,iBAAiB,EACjBC,mBAAmB,QACd,yBAAyB;AAEhC,gCAAgC;AAChC,SAASC,oBAAoB,EAAEC,MAAM,EAAEC,kBAAkB,QAAQ,oBAAoB;AACrF,mCAAmC;AACnC,SAA6DC,oBAAoB,QAA+D,2BAA2B;AAE3K,2BAA2B;AAC3B,SAASC,WAAW,EAA8BC,MAAM,EAAEC,WAAW,QAAQ,oBAAoB;AACjG,SAASC,gBAAgB,EAAEC,WAAW,QAAQ,wBAAwB"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/mcp-z/client/src/index.ts"],"sourcesContent":["/**\n * @mcp-z/client - MCP Client Library\n */\n\nexport type { VersionNegotiationOptions } from '@modelcontextprotocol/client';\n// SDK re-exports for protocol version negotiation: the connect-option type and the typed\n// errors a negotiation can fail with, so callers can handle era mismatch without\n// depending on the SDK themselves.\nexport { SdkError, SdkErrorCode } from '@modelcontextprotocol/client';\n// Config types (from schema)\nexport type { McpServerEntry, StartConfig, StopCommandConfig } from '../schemas/servers.d.ts';\n// Auth - OAuth utilities\nexport { probeAuthCapabilities } from './auth/capability-discovery.ts';\nexport { DiscoveryFetchError, isLoopbackUrl } from './auth/discovery-fetch.ts';\nexport type { AuthCapabilities, CallbackResult, OAuthCallbackListenerOptions, OAuthFlowOptions, TokenSet } from './auth/index.ts';\nexport { InteractiveOAuthFlow } from './auth/interactive-oauth-flow.ts';\nexport { OAuthCallbackListener } from './auth/oauth-callback-listener.ts';\n// Client helpers and lightweight overloads\nexport { decorateClient, type ManagedClient, type PromptArguments, type WrappedCallToolReturn, type WrappedGetPromptReturn, type WrappedReadResourceReturn } from './client-helpers.ts';\n// Config - Configuration validation\nexport { type ValidationResult, validateServers } from './config/validate-config.ts';\n// Connection - MCP client connection utilities (internal helpers exposed for advanced use)\nexport type { JsonValue, PromptArgument, ToolArguments } from './connection/types.ts';\n// DCR - Dynamic Client Registration utilities\nexport { DcrAuthenticator } from './dcr/dcr-authenticator.ts';\nexport { DynamicClientRegistrar } from './dcr/dynamic-client-registrar.ts';\nexport type { ClientCredentials, DcrAuthenticatorOptions, DcrRegistrationOptions } from './dcr/index.ts';\nexport {\n type JsonValidator,\n type NativeCallToolResponse,\n type NativeGetPromptResponse,\n type NativeReadResourceResponse,\n PromptResponseError,\n PromptResponseWrapper,\n ResourceResponseError,\n ResourceResponseWrapper,\n ToolResponseError,\n ToolResponseWrapper,\n} from './response-wrappers.ts';\nexport type { CapabilityClient, CapabilityIndex, CapabilityType, IndexedCapability, IndexedPrompt, IndexedResource, IndexedTool, SearchField, SearchOptions, SearchResponse, SearchResult } from './search/index.ts';\n// Search - Capability discovery\nexport { buildCapabilityIndex, search, searchCapabilities } from './search/index.ts';\n// Spawn - Server registry (v3 API)\nexport { type CloseResult, type CreateServerRegistryOptions, createServerRegistry, type Dialect, type ServerRegistry, type ServersConfig } from './spawn/spawn-servers.ts';\nexport type { TransportType } from './types.ts';\n// Utils - Shared utilities\nexport { getLogLevel, type Logger, type LogLevel, logger, setLogLevel } from './utils/logger.ts';\nexport { resolveArgsPaths, resolvePath } from './utils/path-utils.ts';\n"],"names":["SdkError","SdkErrorCode","probeAuthCapabilities","DiscoveryFetchError","isLoopbackUrl","InteractiveOAuthFlow","OAuthCallbackListener","decorateClient","validateServers","DcrAuthenticator","DynamicClientRegistrar","PromptResponseError","PromptResponseWrapper","ResourceResponseError","ResourceResponseWrapper","ToolResponseError","ToolResponseWrapper","buildCapabilityIndex","search","searchCapabilities","createServerRegistry","getLogLevel","logger","setLogLevel","resolveArgsPaths","resolvePath"],"mappings":"AAAA;;CAEC,GAGD,yFAAyF;AACzF,iFAAiF;AACjF,mCAAmC;AACnC,SAASA,QAAQ,EAAEC,YAAY,QAAQ,+BAA+B;AAGtE,yBAAyB;AACzB,SAASC,qBAAqB,QAAQ,iCAAiC;AACvE,SAASC,mBAAmB,EAAEC,aAAa,QAAQ,4BAA4B;AAE/E,SAASC,oBAAoB,QAAQ,mCAAmC;AACxE,SAASC,qBAAqB,QAAQ,oCAAoC;AAC1E,2CAA2C;AAC3C,SAASC,cAAc,QAA2I,sBAAsB;AACxL,oCAAoC;AACpC,SAAgCC,eAAe,QAAQ,8BAA8B;AAGrF,8CAA8C;AAC9C,SAASC,gBAAgB,QAAQ,6BAA6B;AAC9D,SAASC,sBAAsB,QAAQ,oCAAoC;AAE3E,SAKEC,mBAAmB,EACnBC,qBAAqB,EACrBC,qBAAqB,EACrBC,uBAAuB,EACvBC,iBAAiB,EACjBC,mBAAmB,QACd,yBAAyB;AAEhC,gCAAgC;AAChC,SAASC,oBAAoB,EAAEC,MAAM,EAAEC,kBAAkB,QAAQ,oBAAoB;AACrF,mCAAmC;AACnC,SAA6DC,oBAAoB,QAA+D,2BAA2B;AAE3K,2BAA2B;AAC3B,SAASC,WAAW,EAA8BC,MAAM,EAAEC,WAAW,QAAQ,oBAAoB;AACjG,SAASC,gBAAgB,EAAEC,WAAW,QAAQ,wBAAwB"}
@@ -0,0 +1,12 @@
1
+ interface TerminationResult {
2
+ killed: boolean;
3
+ error?: Error;
4
+ }
5
+ /** Tracks the detached process group used for owned processes on POSIX. */
6
+ export declare class OwnedProcessTree {
7
+ private readonly _rootPid;
8
+ constructor(rootPid: number);
9
+ isEmpty(): boolean;
10
+ terminate(): TerminationResult;
11
+ }
12
+ export {};
@@ -0,0 +1,39 @@
1
+ import * as process from 'process';
2
+ /** Tracks the detached process group used for owned processes on POSIX. */ export class OwnedProcessTree {
3
+ isEmpty() {
4
+ try {
5
+ process.kill(-this._rootPid, 0);
6
+ return false;
7
+ } catch (error) {
8
+ if (isErrno(error, 'ESRCH')) return true;
9
+ if (isErrno(error, 'EPERM')) return false;
10
+ throw toError(error);
11
+ }
12
+ }
13
+ terminate() {
14
+ try {
15
+ process.kill(-this._rootPid, 'SIGKILL');
16
+ return {
17
+ killed: true
18
+ };
19
+ } catch (error) {
20
+ if (isErrno(error, 'ESRCH')) return {
21
+ killed: false
22
+ };
23
+ return {
24
+ killed: false,
25
+ error: toError(error)
26
+ };
27
+ }
28
+ }
29
+ constructor(rootPid){
30
+ if (process.platform === 'win32') throw new Error('Owned process groups are not available on Windows');
31
+ this._rootPid = rootPid;
32
+ }
33
+ }
34
+ function isErrno(error, code) {
35
+ return error instanceof Error && 'code' in error && error.code === code;
36
+ }
37
+ function toError(error) {
38
+ return error instanceof Error ? error : new Error(String(error));
39
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/mcp-z/client/src/spawn/owned-process-tree.ts"],"sourcesContent":["import * as process from 'process';\n\ninterface TerminationResult {\n killed: boolean;\n error?: Error;\n}\n\n/** Tracks the detached process group used for owned processes on POSIX. */\nexport class OwnedProcessTree {\n private readonly _rootPid: number;\n\n constructor(rootPid: number) {\n if (process.platform === 'win32') throw new Error('Owned process groups are not available on Windows');\n this._rootPid = rootPid;\n }\n\n isEmpty(): boolean {\n try {\n process.kill(-this._rootPid, 0);\n return false;\n } catch (error) {\n if (isErrno(error, 'ESRCH')) return true;\n if (isErrno(error, 'EPERM')) return false;\n throw toError(error);\n }\n }\n\n terminate(): TerminationResult {\n try {\n process.kill(-this._rootPid, 'SIGKILL');\n return { killed: true };\n } catch (error) {\n if (isErrno(error, 'ESRCH')) return { killed: false };\n return { killed: false, error: toError(error) };\n }\n }\n}\n\nfunction isErrno(error: unknown, code: string): boolean {\n return error instanceof Error && 'code' in error && error.code === code;\n}\n\nfunction toError(error: unknown): Error {\n return error instanceof Error ? error : new Error(String(error));\n}\n"],"names":["process","OwnedProcessTree","isEmpty","kill","_rootPid","error","isErrno","toError","terminate","killed","rootPid","platform","Error","code","String"],"mappings":"AAAA,YAAYA,aAAa,UAAU;AAOnC,yEAAyE,GACzE,OAAO,MAAMC;IAQXC,UAAmB;QACjB,IAAI;YACFF,QAAQG,IAAI,CAAC,CAAC,IAAI,CAACC,QAAQ,EAAE;YAC7B,OAAO;QACT,EAAE,OAAOC,OAAO;YACd,IAAIC,QAAQD,OAAO,UAAU,OAAO;YACpC,IAAIC,QAAQD,OAAO,UAAU,OAAO;YACpC,MAAME,QAAQF;QAChB;IACF;IAEAG,YAA+B;QAC7B,IAAI;YACFR,QAAQG,IAAI,CAAC,CAAC,IAAI,CAACC,QAAQ,EAAE;YAC7B,OAAO;gBAAEK,QAAQ;YAAK;QACxB,EAAE,OAAOJ,OAAO;YACd,IAAIC,QAAQD,OAAO,UAAU,OAAO;gBAAEI,QAAQ;YAAM;YACpD,OAAO;gBAAEA,QAAQ;gBAAOJ,OAAOE,QAAQF;YAAO;QAChD;IACF;IAxBA,YAAYK,OAAe,CAAE;QAC3B,IAAIV,QAAQW,QAAQ,KAAK,SAAS,MAAM,IAAIC,MAAM;QAClD,IAAI,CAACR,QAAQ,GAAGM;IAClB;AAsBF;AAEA,SAASJ,QAAQD,KAAc,EAAEQ,IAAY;IAC3C,OAAOR,iBAAiBO,SAAS,UAAUP,SAASA,MAAMQ,IAAI,KAAKA;AACrE;AAEA,SAASN,QAAQF,KAAc;IAC7B,OAAOA,iBAAiBO,QAAQP,QAAQ,IAAIO,MAAME,OAAOT;AAC3D"}
@@ -2,7 +2,8 @@
2
2
  * Low-level single server spawning utilities.
3
3
  * Provides core process spawning with path resolution, environment management, and lifecycle control.
4
4
  */
5
- import { type ChildProcess, type StdioOptions } from 'child_process';
5
+ import type { ChildProcess, StdioOptions } from 'child_process';
6
+ import type { StopCommandConfig } from '../types.js';
6
7
  /**
7
8
  * Options for spawning a single server process.
8
9
  * @internal
@@ -18,10 +19,12 @@ export interface SpawnProcessOptions {
18
19
  cwd?: string;
19
20
  /** Additional environment variables (merged with process.env) */
20
21
  env?: Record<string, string>;
22
+ /** Whether to add process.env to env; registry callers provide a pre-merged environment */
23
+ inheritEnv?: boolean;
21
24
  /** Standard I/O configuration */
22
25
  stdio?: StdioOptions;
23
- /** Use shell for command execution (default: false, true on Windows) */
24
- shell?: boolean;
26
+ /** Application-specific shutdown command for an owned HTTP server */
27
+ stop?: StopCommandConfig;
25
28
  }
26
29
  /**
27
30
  * Handle to a spawned server process.
@@ -29,10 +32,6 @@ export interface SpawnProcessOptions {
29
32
  * @hidden
30
33
  */
31
34
  export interface ServerProcess {
32
- /**
33
- * The resolved server configuration that was actually used.
34
- * Useful for debugging and understanding what was spawned.
35
- */
36
35
  config: {
37
36
  name: string;
38
37
  command: string;
@@ -40,20 +39,9 @@ export interface ServerProcess {
40
39
  cwd: string;
41
40
  env: Record<string, string>;
42
41
  stdio: StdioOptions;
43
- shell: boolean;
42
+ shell: false;
44
43
  };
45
- /**
46
- * The spawned child process.
47
- */
48
44
  process: ChildProcess;
49
- /**
50
- * Close the server gracefully.
51
- * Sends the specified signal (default: SIGINT), then SIGKILL after timeout.
52
- *
53
- * @param signal - Signal to send (default: SIGINT)
54
- * @param opts - Options including timeout
55
- * @returns Promise resolving to whether the process timed out and was force-killed
56
- */
57
45
  close: (signal?: NodeJS.Signals, opts?: {
58
46
  timeoutMs?: number;
59
47
  }) => Promise<{
@@ -66,18 +54,6 @@ export interface ServerProcess {
66
54
  *
67
55
  * @internal
68
56
  * @param opts - Server spawn options
69
- * @returns ServerProcess handle with resolved config, process, and stop function
70
- *
71
- * @example
72
- * const handle = spawnProcess({
73
- * name: 'echo',
74
- * command: 'node',
75
- * args: ['./bin/server.js', '--port', '3000'],
76
- * cwd: '/home/user/project/test/lib/servers/echo',
77
- * env: { LOG_LEVEL: 'error' }
78
- * });
79
- *
80
- * // Later...
81
- * await handle.close();
57
+ * @returns ServerProcess handle with resolved config and lifecycle control
82
58
  */
83
59
  export declare function spawnProcess(opts: SpawnProcessOptions): ServerProcess;