@mcp-z/client 2.0.0 → 2.1.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 (62) hide show
  1. package/dist/cjs/auth/capability-discovery.js +11 -5
  2. package/dist/cjs/auth/capability-discovery.js.map +1 -1
  3. package/dist/cjs/auth/types.d.cts +11 -0
  4. package/dist/cjs/auth/types.d.ts +11 -0
  5. package/dist/cjs/auth/types.js.map +1 -1
  6. package/dist/cjs/client-helpers.d.cts +5 -6
  7. package/dist/cjs/client-helpers.d.ts +5 -6
  8. package/dist/cjs/client-helpers.js +8 -8
  9. package/dist/cjs/client-helpers.js.map +1 -1
  10. package/dist/cjs/connection/connect-client.d.cts +1 -11
  11. package/dist/cjs/connection/connect-client.d.ts +1 -11
  12. package/dist/cjs/connection/connect-client.js +20 -41
  13. package/dist/cjs/connection/connect-client.js.map +1 -1
  14. package/dist/cjs/connection/existing-process-transport.d.cts +1 -2
  15. package/dist/cjs/connection/existing-process-transport.d.ts +1 -2
  16. package/dist/cjs/connection/existing-process-transport.js +3 -3
  17. package/dist/cjs/connection/existing-process-transport.js.map +1 -1
  18. package/dist/cjs/dcr/dcr-authenticator.d.cts +30 -4
  19. package/dist/cjs/dcr/dcr-authenticator.d.ts +30 -4
  20. package/dist/cjs/dcr/dcr-authenticator.js +57 -24
  21. package/dist/cjs/dcr/dcr-authenticator.js.map +1 -1
  22. package/dist/cjs/lib/url-utils.d.cts +17 -0
  23. package/dist/cjs/lib/url-utils.d.ts +17 -0
  24. package/dist/cjs/lib/url-utils.js +20 -0
  25. package/dist/cjs/lib/url-utils.js.map +1 -1
  26. package/dist/cjs/response-wrappers.d.cts +1 -1
  27. package/dist/cjs/response-wrappers.d.ts +1 -1
  28. package/dist/cjs/response-wrappers.js.map +1 -1
  29. package/dist/cjs/search/search.d.cts +1 -1
  30. package/dist/cjs/search/search.d.ts +1 -1
  31. package/dist/cjs/search/search.js.map +1 -1
  32. package/dist/esm/auth/capability-discovery.js +11 -5
  33. package/dist/esm/auth/capability-discovery.js.map +1 -1
  34. package/dist/esm/auth/types.d.ts +11 -0
  35. package/dist/esm/auth/types.js.map +1 -1
  36. package/dist/esm/client-helpers.d.ts +5 -6
  37. package/dist/esm/client-helpers.js +8 -8
  38. package/dist/esm/client-helpers.js.map +1 -1
  39. package/dist/esm/connection/connect-client.d.ts +1 -11
  40. package/dist/esm/connection/connect-client.js +12 -32
  41. package/dist/esm/connection/connect-client.js.map +1 -1
  42. package/dist/esm/connection/existing-process-transport.d.ts +1 -2
  43. package/dist/esm/connection/existing-process-transport.js +1 -1
  44. package/dist/esm/connection/existing-process-transport.js.map +1 -1
  45. package/dist/esm/dcr/dcr-authenticator.d.ts +30 -4
  46. package/dist/esm/dcr/dcr-authenticator.js +56 -23
  47. package/dist/esm/dcr/dcr-authenticator.js.map +1 -1
  48. package/dist/esm/lib/url-utils.d.ts +17 -0
  49. package/dist/esm/lib/url-utils.js +32 -0
  50. package/dist/esm/lib/url-utils.js.map +1 -1
  51. package/dist/esm/response-wrappers.d.ts +1 -1
  52. package/dist/esm/response-wrappers.js.map +1 -1
  53. package/dist/esm/search/search.d.ts +1 -1
  54. package/dist/esm/search/search.js.map +1 -1
  55. package/package.json +3 -3
  56. package/dist/cjs/monkey-patches.d.cts +0 -6
  57. package/dist/cjs/monkey-patches.d.ts +0 -6
  58. package/dist/cjs/monkey-patches.js +0 -233
  59. package/dist/cjs/monkey-patches.js.map +0 -1
  60. package/dist/esm/monkey-patches.d.ts +0 -6
  61. package/dist/esm/monkey-patches.js +0 -32
  62. package/dist/esm/monkey-patches.js.map +0 -1
@@ -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 { 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(baseUrl: 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 baseUrl matches common localhost patterns\n // TODO: Implement proper self-hosted detection logic\n return baseUrl.includes('localhost') || baseUrl.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 baseUrl - Base URL of the server (e.g., https://example.com)\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',\n * capabilities\n * );\n */\n async ensureAuthenticated(baseUrl: string, capabilities: AuthCapabilities): Promise<TokenSet> {\n // Auto-detect server mode\n const isSelfHosted = await this.detectSelfHostedMode(baseUrl);\n\n if (isSelfHosted) {\n return this.ensureAuthenticatedSelfHosted(baseUrl, capabilities);\n }\n return this.ensureAuthenticatedExternal(baseUrl, capabilities);\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(baseUrl: 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(baseUrl);\n const issuer = requireIssuer(capabilities);\n const resource = normalizeUrl(baseUrl);\n const dcrTokenKey = `dcr-tokens:${issuer}:${resource}`;\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 = `${baseUrl}/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 = `${baseUrl}/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(baseUrl: string, capabilities: AuthCapabilities): Promise<TokenSet> {\n // See ensureAuthenticatedSelfHosted - same loopback trust rule.\n const allowLoopback = isLoopbackUrl(baseUrl);\n const issuer = requireIssuer(capabilities);\n const resource = normalizeUrl(baseUrl);\n const tokenKey = `tokens:${issuer}:${resource}`;\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 * @throws CredentialBindingError if the configured store cannot enumerate keys.\n */\n async deleteTokens(baseUrl: string): Promise<void> {\n const suffix = `:${normalizeUrl(baseUrl)}`;\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 ${baseUrl}`);\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":["DcrAuthenticator","REFRESH_BUFFER_MS","CredentialBindingError","message","name","Error","requireIssuer","capabilities","issuer","options","tokenStore","storePath","path","join","process","cwd","fs","mkdirSync","dirname","recursive","Keyv","store","KeyvFile","filename","dcrClient","DynamicClientRegistrar","oauthFlow","InteractiveOAuthFlow","headless","redirectUri","logger","defaultLogger","detectSelfHostedMode","baseUrl","includes","_error","ensureAuthenticated","isSelfHosted","ensureAuthenticatedSelfHosted","ensureAuthenticatedExternal","allowLoopback","resource","dcrTokenKey","tokens","verifyUrl","verifyResponse","verifyData","port","client","flowOptions","error","isLoopbackUrl","normalizeUrl","loadTokens","fetch","headers","Authorization","accessToken","Connection","ok","json","token","delete","undefined","registrationEndpoint","authorizationEndpoint","tokenEndpoint","debug","parseInt","URL","startsWith","registerClient","buildFlowOptions","performAuthFlow","clientId","clientSecret","status","String","set","tokenKey","expiresAt","Date","now","refreshTokens","warn","refreshToken","deleteTokens","suffix","key","iterator","namespace","endsWith","get","pkce","authorizationResponseIssSupported","scopes"],"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): 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":["DcrAuthenticator","REFRESH_BUFFER_MS","CredentialBindingError","message","name","Error","requireIssuer","capabilities","issuer","options","tokenStore","storePath","path","join","process","cwd","fs","mkdirSync","dirname","recursive","Keyv","store","KeyvFile","filename","dcrClient","DynamicClientRegistrar","oauthFlow","InteractiveOAuthFlow","headless","redirectUri","logger","defaultLogger","detectSelfHostedMode","mcpServerUrl","includes","_error","ensureAuthenticated","isSelfHosted","ensureAuthenticatedSelfHosted","ensureAuthenticatedExternal","resolveUrls","storeKey","normalizeUrl","serverBaseUrl","extractBaseUrl","resource","allowLoopback","dcrTokenKey","tokens","verifyUrl","verifyResponse","verifyData","port","client","flowOptions","error","isLoopbackUrl","loadTokens","fetch","headers","Authorization","accessToken","Connection","ok","json","token","delete","undefined","registrationEndpoint","authorizationEndpoint","tokenEndpoint","debug","parseInt","URL","startsWith","registerClient","buildFlowOptions","performAuthFlow","clientId","clientSecret","status","String","set","tokenKey","expiresAt","Date","now","refreshTokens","warn","refreshToken","deleteTokens","suffix","key","iterator","namespace","endsWith","get","pkce","authorizationResponseIssSupported","scopes"],"mappings":"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"}
@@ -1,2 +1,19 @@
1
1
  export declare function normalizeUrl(input: string): string;
2
2
  export declare function joinWellKnown(baseUrl: string, suffix: string): string;
3
+ /**
4
+ * Extract the "server base" - where a server's own endpoints live, NOT its identity.
5
+ *
6
+ * The `/mcp` segment names the protocol endpoint; everything before it is the
7
+ * deployment root that `/oauth/verify` and friends hang off. Stripping it is
8
+ * right for building those URLs and wrong for anything that identifies the
9
+ * server: an RFC 8707 `resource` indicator or a credential store key must use
10
+ * the full URL, because the stripped form names a different resource (or none).
11
+ *
12
+ * Original shape by removing a trailing `/mcp` path segment if present.
13
+ * Examples:
14
+ * - https://example.com/mcp -> https://example.com
15
+ * - https://example.com/sheets/mcp -> https://example.com/sheets
16
+ * - https://example.com/sheets/mcp/ -> https://example.com/sheets
17
+ * - https://example.com/sheets -> https://example.com/sheets
18
+ */
19
+ export declare function extractBaseUrl(mcpUrl: string): string;
@@ -1,2 +1,19 @@
1
1
  export declare function normalizeUrl(input: string): string;
2
2
  export declare function joinWellKnown(baseUrl: string, suffix: string): string;
3
+ /**
4
+ * Extract the "server base" - where a server's own endpoints live, NOT its identity.
5
+ *
6
+ * The `/mcp` segment names the protocol endpoint; everything before it is the
7
+ * deployment root that `/oauth/verify` and friends hang off. Stripping it is
8
+ * right for building those URLs and wrong for anything that identifies the
9
+ * server: an RFC 8707 `resource` indicator or a credential store key must use
10
+ * the full URL, because the stripped form names a different resource (or none).
11
+ *
12
+ * Original shape by removing a trailing `/mcp` path segment if present.
13
+ * Examples:
14
+ * - https://example.com/mcp -> https://example.com
15
+ * - https://example.com/sheets/mcp -> https://example.com/sheets
16
+ * - https://example.com/sheets/mcp/ -> https://example.com/sheets
17
+ * - https://example.com/sheets -> https://example.com/sheets
18
+ */
19
+ export declare function extractBaseUrl(mcpUrl: string): string;
@@ -9,6 +9,9 @@ function _export(target, all) {
9
9
  });
10
10
  }
11
11
  _export(exports, {
12
+ get extractBaseUrl () {
13
+ return extractBaseUrl;
14
+ },
12
15
  get joinWellKnown () {
13
16
  return joinWellKnown;
14
17
  },
@@ -30,4 +33,21 @@ function normalizeUrl(input) {
30
33
  function joinWellKnown(baseUrl, suffix) {
31
34
  return "".concat(normalizeUrl(baseUrl)).concat(suffix);
32
35
  }
36
+ function extractBaseUrl(mcpUrl) {
37
+ var url = new URL(mcpUrl);
38
+ // Ignore query/hash for base URL purposes
39
+ url.search = '';
40
+ url.hash = '';
41
+ // Normalize path segments (removes empty segments from leading/trailing slashes)
42
+ var segments = url.pathname.split('/').filter(Boolean);
43
+ // If last segment is exactly "mcp", drop it
44
+ if (segments[segments.length - 1] === 'mcp') {
45
+ segments.pop();
46
+ }
47
+ // Rebuild pathname; empty means root
48
+ url.pathname = segments.length ? "/".concat(segments.join('/')) : '';
49
+ // Return without trailing slash (except root origin)
50
+ var out = url.origin + url.pathname;
51
+ return out === url.origin ? out : out.replace(/\/+$/, '');
52
+ }
33
53
  /* 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/lib/url-utils.ts"],"sourcesContent":["export function normalizeUrl(input: string): string {\n try {\n const url = new URL(input);\n url.search = '';\n url.hash = '';\n // Strip after joining: assigning an empty pathname puts the '/' straight back.\n return (url.origin + url.pathname).replace(/\\/+$/, '');\n } catch {\n return input.replace(/\\/+$/, '');\n }\n}\n\nexport function joinWellKnown(baseUrl: string, suffix: string): string {\n return `${normalizeUrl(baseUrl)}${suffix}`;\n}\n"],"names":["joinWellKnown","normalizeUrl","input","url","URL","search","hash","origin","pathname","replace","baseUrl","suffix"],"mappings":";;;;;;;;;;;QAYgBA;eAAAA;;QAZAC;eAAAA;;;AAAT,SAASA,aAAaC,KAAa;IACxC,IAAI;QACF,IAAMC,MAAM,IAAIC,IAAIF;QACpBC,IAAIE,MAAM,GAAG;QACbF,IAAIG,IAAI,GAAG;QACX,+EAA+E;QAC/E,OAAO,AAACH,CAAAA,IAAII,MAAM,GAAGJ,IAAIK,QAAQ,AAAD,EAAGC,OAAO,CAAC,QAAQ;IACrD,EAAE,eAAM;QACN,OAAOP,MAAMO,OAAO,CAAC,QAAQ;IAC/B;AACF;AAEO,SAAST,cAAcU,OAAe,EAAEC,MAAc;IAC3D,OAAO,AAAC,GAA0BA,OAAxBV,aAAaS,UAAkB,OAAPC;AACpC"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/mcp-z/client/src/lib/url-utils.ts"],"sourcesContent":["export function normalizeUrl(input: string): string {\n try {\n const url = new URL(input);\n url.search = '';\n url.hash = '';\n // Strip after joining: assigning an empty pathname puts the '/' straight back.\n return (url.origin + url.pathname).replace(/\\/+$/, '');\n } catch {\n return input.replace(/\\/+$/, '');\n }\n}\n\nexport function joinWellKnown(baseUrl: string, suffix: string): string {\n return `${normalizeUrl(baseUrl)}${suffix}`;\n}\n\n/**\n * Extract the \"server base\" - where a server's own endpoints live, NOT its identity.\n *\n * The `/mcp` segment names the protocol endpoint; everything before it is the\n * deployment root that `/oauth/verify` and friends hang off. Stripping it is\n * right for building those URLs and wrong for anything that identifies the\n * server: an RFC 8707 `resource` indicator or a credential store key must use\n * the full URL, because the stripped form names a different resource (or none).\n *\n * Original shape by removing a trailing `/mcp` path segment if present.\n * Examples:\n * - https://example.com/mcp -> https://example.com\n * - https://example.com/sheets/mcp -> https://example.com/sheets\n * - https://example.com/sheets/mcp/ -> https://example.com/sheets\n * - https://example.com/sheets -> https://example.com/sheets\n */\nexport function extractBaseUrl(mcpUrl: string): string {\n const url = new URL(mcpUrl);\n\n // Ignore query/hash for base URL purposes\n url.search = '';\n url.hash = '';\n\n // Normalize path segments (removes empty segments from leading/trailing slashes)\n const segments = url.pathname.split('/').filter(Boolean);\n\n // If last segment is exactly \"mcp\", drop it\n if (segments[segments.length - 1] === 'mcp') {\n segments.pop();\n }\n\n // Rebuild pathname; empty means root\n url.pathname = segments.length ? `/${segments.join('/')}` : '';\n\n // Return without trailing slash (except root origin)\n const out = url.origin + url.pathname;\n return out === url.origin ? out : out.replace(/\\/+$/, '');\n}\n"],"names":["extractBaseUrl","joinWellKnown","normalizeUrl","input","url","URL","search","hash","origin","pathname","replace","baseUrl","suffix","mcpUrl","segments","split","filter","Boolean","length","pop","join","out"],"mappings":";;;;;;;;;;;QAgCgBA;eAAAA;;QApBAC;eAAAA;;QAZAC;eAAAA;;;AAAT,SAASA,aAAaC,KAAa;IACxC,IAAI;QACF,IAAMC,MAAM,IAAIC,IAAIF;QACpBC,IAAIE,MAAM,GAAG;QACbF,IAAIG,IAAI,GAAG;QACX,+EAA+E;QAC/E,OAAO,AAACH,CAAAA,IAAII,MAAM,GAAGJ,IAAIK,QAAQ,AAAD,EAAGC,OAAO,CAAC,QAAQ;IACrD,EAAE,eAAM;QACN,OAAOP,MAAMO,OAAO,CAAC,QAAQ;IAC/B;AACF;AAEO,SAAST,cAAcU,OAAe,EAAEC,MAAc;IAC3D,OAAO,AAAC,GAA0BA,OAAxBV,aAAaS,UAAkB,OAAPC;AACpC;AAkBO,SAASZ,eAAea,MAAc;IAC3C,IAAMT,MAAM,IAAIC,IAAIQ;IAEpB,0CAA0C;IAC1CT,IAAIE,MAAM,GAAG;IACbF,IAAIG,IAAI,GAAG;IAEX,iFAAiF;IACjF,IAAMO,WAAWV,IAAIK,QAAQ,CAACM,KAAK,CAAC,KAAKC,MAAM,CAACC;IAEhD,4CAA4C;IAC5C,IAAIH,QAAQ,CAACA,SAASI,MAAM,GAAG,EAAE,KAAK,OAAO;QAC3CJ,SAASK,GAAG;IACd;IAEA,qCAAqC;IACrCf,IAAIK,QAAQ,GAAGK,SAASI,MAAM,GAAG,AAAC,IAAsB,OAAnBJ,SAASM,IAAI,CAAC,QAAS;IAE5D,qDAAqD;IACrD,IAAMC,MAAMjB,IAAII,MAAM,GAAGJ,IAAIK,QAAQ;IACrC,OAAOY,QAAQjB,IAAII,MAAM,GAAGa,MAAMA,IAAIX,OAAO,CAAC,QAAQ;AACxD"}
@@ -1,4 +1,4 @@
1
- import type { Client } from '@modelcontextprotocol/sdk/client/index.js';
1
+ import type { Client } from '@modelcontextprotocol/client';
2
2
  export type NativeCallToolResponse = Awaited<ReturnType<Client['callTool']>>;
3
3
  export type NativeGetPromptResponse = Awaited<ReturnType<Client['getPrompt']>>;
4
4
  export type NativeReadResourceResponse = Awaited<ReturnType<Client['readResource']>>;
@@ -1,4 +1,4 @@
1
- import type { Client } from '@modelcontextprotocol/sdk/client/index.js';
1
+ import type { Client } from '@modelcontextprotocol/client';
2
2
  export type NativeCallToolResponse = Awaited<ReturnType<Client['callTool']>>;
3
3
  export type NativeGetPromptResponse = Awaited<ReturnType<Client['getPrompt']>>;
4
4
  export type NativeReadResourceResponse = Awaited<ReturnType<Client['readResource']>>;
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/mcp-z/client/src/response-wrappers.ts"],"sourcesContent":["import { Buffer } from 'node:buffer';\nimport type { Client } from '@modelcontextprotocol/sdk/client/index.js';\nimport type { ContentBlock, PromptMessage, TextContent } from '@modelcontextprotocol/sdk/types.js';\n\nexport type NativeCallToolResponse = Awaited<ReturnType<Client['callTool']>>;\nexport type NativeGetPromptResponse = Awaited<ReturnType<Client['getPrompt']>>;\nexport type NativeReadResourceResponse = Awaited<ReturnType<Client['readResource']>>;\n\nexport type JsonValidator<T> = (value: unknown) => asserts value is T;\n\nexport class ToolResponseError extends Error {\n readonly response: NativeCallToolResponse;\n\n constructor(message: string, response: NativeCallToolResponse) {\n super(message);\n this.name = 'ToolResponseError';\n this.response = response;\n }\n}\n\nexport class ToolResponseWrapper {\n private readonly payload: NativeCallToolResponse;\n\n constructor(payload: NativeCallToolResponse) {\n this.payload = payload;\n }\n\n raw(): NativeCallToolResponse {\n return this.payload;\n }\n\n json<T = unknown>(validator?: JsonValidator<T>): T {\n const value = this.resolveJsonPayload();\n if (validator) {\n const assertValid: JsonValidator<T> = validator;\n assertValid(value);\n }\n return value as T;\n }\n\n text(): string {\n if (isCompatibilityResult(this.payload)) {\n if (typeof this.payload.toolResult === 'string') {\n return this.payload.toolResult;\n }\n throw new ToolResponseError('Compatibility tool result is not text', this.payload);\n }\n\n this.throwIfError();\n\n const textBlock = findFirstTextBlock(this.payload.content ?? []);\n if (!textBlock) {\n throw new ToolResponseError('Tool response did not include text content', this.payload);\n }\n\n return textBlock.text;\n }\n\n private resolveJsonPayload(): unknown {\n if (isCompatibilityResult(this.payload)) {\n return this.payload.toolResult;\n }\n\n this.throwIfError();\n\n if (hasStructuredContent(this.payload)) {\n return this.payload.structuredContent;\n }\n\n const textBlock = findFirstTextBlock(this.payload.content ?? []);\n if (!textBlock) {\n throw new ToolResponseError('Tool response did not include structuredContent or text content', this.payload);\n }\n\n try {\n return JSON.parse(textBlock.text) as unknown;\n } catch (error) {\n const reason = formatErrorReason(error);\n throw new ToolResponseError(`Failed to parse tool text content as JSON: ${reason}`, this.payload);\n }\n }\n\n private throwIfError(): void {\n if ('isError' in this.payload && this.payload.isError) {\n let detail = typeof this.payload.error === 'object' && this.payload.error && 'message' in this.payload.error ? String((this.payload.error as { message?: unknown }).message ?? '') : '';\n if (!detail) {\n const textBlock = findFirstTextBlock((this.payload.content ?? []) as ContentBlock[]);\n if (textBlock?.text) {\n detail = textBlock.text;\n }\n }\n const message = detail ? `Tool invocation returned an error result: ${detail}` : 'Tool invocation returned an error result';\n throw new ToolResponseError(message, this.payload);\n }\n }\n}\n\nexport class PromptResponseError extends Error {\n readonly response: NativeGetPromptResponse;\n\n constructor(message: string, response: NativeGetPromptResponse) {\n super(message);\n this.name = 'PromptResponseError';\n this.response = response;\n }\n}\n\nexport class PromptResponseWrapper {\n private readonly payload: NativeGetPromptResponse;\n\n constructor(payload: NativeGetPromptResponse) {\n this.payload = payload;\n }\n\n raw(): NativeGetPromptResponse {\n return this.payload;\n }\n\n text(): string {\n const segments = collectPromptText(this.payload.messages);\n if (!segments.length) {\n throw new PromptResponseError('Prompt response did not include text content', this.payload);\n }\n return segments.join('\\n\\n');\n }\n\n json<T = unknown>(validator?: JsonValidator<T>): T {\n const textValue = this.text();\n try {\n const parsed = JSON.parse(textValue) as unknown;\n if (validator) {\n const assertValid: JsonValidator<T> = validator;\n assertValid(parsed);\n }\n return parsed as T;\n } catch (error) {\n const reason = formatErrorReason(error);\n throw new PromptResponseError(`Failed to parse prompt text as JSON: ${reason}`, this.payload);\n }\n }\n}\n\nexport class ResourceResponseError extends Error {\n readonly response: NativeReadResourceResponse;\n\n constructor(message: string, response: NativeReadResourceResponse) {\n super(message);\n this.name = 'ResourceResponseError';\n this.response = response;\n }\n}\n\nexport class ResourceResponseWrapper {\n private readonly payload: NativeReadResourceResponse;\n\n constructor(payload: NativeReadResourceResponse) {\n this.payload = payload;\n }\n\n raw(): NativeReadResourceResponse {\n return this.payload;\n }\n\n text(): string {\n const entry = this.firstEntry();\n if ('text' in entry && typeof entry.text === 'string') {\n return entry.text;\n }\n if ('blob' in entry && typeof entry.blob === 'string') {\n try {\n return Buffer.from(entry.blob, 'base64').toString('utf8');\n } catch (error) {\n const reason = formatErrorReason(error);\n throw new ResourceResponseError(`Failed to decode resource blob as UTF-8 text: ${reason}`, this.payload);\n }\n }\n throw new ResourceResponseError('Resource content does not include text or blob data', this.payload);\n }\n\n json<T = unknown>(validator?: JsonValidator<T>): T {\n const textValue = this.text();\n try {\n const parsed = JSON.parse(textValue) as unknown;\n if (validator) {\n const assertValid: JsonValidator<T> = validator;\n assertValid(parsed);\n }\n return parsed as T;\n } catch (error) {\n const reason = formatErrorReason(error);\n throw new ResourceResponseError(`Failed to parse resource text as JSON: ${reason}`, this.payload);\n }\n }\n\n private firstEntry(): NativeReadResourceResponse['contents'][number] {\n const [entry] = this.payload.contents ?? [];\n if (!entry) {\n throw new ResourceResponseError('Resource response did not include any contents', this.payload);\n }\n return entry;\n }\n}\n\nfunction hasStructuredContent(response: NativeCallToolResponse): response is NativeCallToolResponse & { structuredContent: Record<string, unknown> } {\n return Boolean((response as { structuredContent?: unknown }).structuredContent);\n}\n\nfunction isCompatibilityResult(response: NativeCallToolResponse): response is NativeCallToolResponse & { toolResult: unknown } {\n return hasOwn(response, 'toolResult');\n}\n\nfunction findFirstTextBlock(blocks: ContentBlock[] | undefined): TextContent | undefined {\n if (!Array.isArray(blocks)) {\n return undefined;\n }\n return blocks.find(isTextContent);\n}\n\nfunction collectPromptText(messages: PromptMessage[]): string[] {\n const segments: string[] = [];\n for (const message of messages) {\n const textBlock = isTextContent(message.content) ? message.content : undefined;\n if (textBlock) {\n segments.push(textBlock.text);\n }\n }\n return segments;\n}\n\nfunction isTextContent(block: unknown): block is TextContent {\n return Boolean(block) && typeof block === 'object' && (block as { type?: string }).type === 'text';\n}\n\nconst protoHasOwn = Object.prototype.hasOwnProperty;\n\nfunction hasOwn(target: object, key: PropertyKey): boolean {\n return protoHasOwn.call(target, key);\n}\n\nfunction formatErrorReason(error: unknown): string {\n if (error instanceof Error && typeof error.message === 'string') {\n return error.message;\n }\n if (typeof error === 'string') {\n return error;\n }\n try {\n return JSON.stringify(error);\n } catch {\n return String(error);\n }\n}\n"],"names":["PromptResponseError","PromptResponseWrapper","ResourceResponseError","ResourceResponseWrapper","ToolResponseError","ToolResponseWrapper","message","response","name","Error","payload","raw","json","validator","value","resolveJsonPayload","assertValid","text","isCompatibilityResult","toolResult","throwIfError","textBlock","findFirstTextBlock","content","hasStructuredContent","structuredContent","JSON","parse","error","reason","formatErrorReason","isError","detail","String","segments","collectPromptText","messages","length","join","textValue","parsed","entry","firstEntry","blob","Buffer","from","toString","contents","Boolean","hasOwn","blocks","Array","isArray","undefined","find","isTextContent","push","block","type","protoHasOwn","Object","prototype","hasOwnProperty","target","key","call","stringify"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/mcp-z/client/src/response-wrappers.ts"],"sourcesContent":["import { Buffer } from 'node:buffer';\nimport type { Client, ContentBlock, PromptMessage, TextContent } from '@modelcontextprotocol/client';\n\nexport type NativeCallToolResponse = Awaited<ReturnType<Client['callTool']>>;\nexport type NativeGetPromptResponse = Awaited<ReturnType<Client['getPrompt']>>;\nexport type NativeReadResourceResponse = Awaited<ReturnType<Client['readResource']>>;\n\nexport type JsonValidator<T> = (value: unknown) => asserts value is T;\n\nexport class ToolResponseError extends Error {\n readonly response: NativeCallToolResponse;\n\n constructor(message: string, response: NativeCallToolResponse) {\n super(message);\n this.name = 'ToolResponseError';\n this.response = response;\n }\n}\n\nexport class ToolResponseWrapper {\n private readonly payload: NativeCallToolResponse;\n\n constructor(payload: NativeCallToolResponse) {\n this.payload = payload;\n }\n\n raw(): NativeCallToolResponse {\n return this.payload;\n }\n\n json<T = unknown>(validator?: JsonValidator<T>): T {\n const value = this.resolveJsonPayload();\n if (validator) {\n const assertValid: JsonValidator<T> = validator;\n assertValid(value);\n }\n return value as T;\n }\n\n text(): string {\n if (isCompatibilityResult(this.payload)) {\n if (typeof this.payload.toolResult === 'string') {\n return this.payload.toolResult;\n }\n throw new ToolResponseError('Compatibility tool result is not text', this.payload);\n }\n\n this.throwIfError();\n\n const textBlock = findFirstTextBlock(this.payload.content ?? []);\n if (!textBlock) {\n throw new ToolResponseError('Tool response did not include text content', this.payload);\n }\n\n return textBlock.text;\n }\n\n private resolveJsonPayload(): unknown {\n if (isCompatibilityResult(this.payload)) {\n return this.payload.toolResult;\n }\n\n this.throwIfError();\n\n if (hasStructuredContent(this.payload)) {\n return this.payload.structuredContent;\n }\n\n const textBlock = findFirstTextBlock(this.payload.content ?? []);\n if (!textBlock) {\n throw new ToolResponseError('Tool response did not include structuredContent or text content', this.payload);\n }\n\n try {\n return JSON.parse(textBlock.text) as unknown;\n } catch (error) {\n const reason = formatErrorReason(error);\n throw new ToolResponseError(`Failed to parse tool text content as JSON: ${reason}`, this.payload);\n }\n }\n\n private throwIfError(): void {\n if ('isError' in this.payload && this.payload.isError) {\n let detail = typeof this.payload.error === 'object' && this.payload.error && 'message' in this.payload.error ? String((this.payload.error as { message?: unknown }).message ?? '') : '';\n if (!detail) {\n const textBlock = findFirstTextBlock((this.payload.content ?? []) as ContentBlock[]);\n if (textBlock?.text) {\n detail = textBlock.text;\n }\n }\n const message = detail ? `Tool invocation returned an error result: ${detail}` : 'Tool invocation returned an error result';\n throw new ToolResponseError(message, this.payload);\n }\n }\n}\n\nexport class PromptResponseError extends Error {\n readonly response: NativeGetPromptResponse;\n\n constructor(message: string, response: NativeGetPromptResponse) {\n super(message);\n this.name = 'PromptResponseError';\n this.response = response;\n }\n}\n\nexport class PromptResponseWrapper {\n private readonly payload: NativeGetPromptResponse;\n\n constructor(payload: NativeGetPromptResponse) {\n this.payload = payload;\n }\n\n raw(): NativeGetPromptResponse {\n return this.payload;\n }\n\n text(): string {\n const segments = collectPromptText(this.payload.messages);\n if (!segments.length) {\n throw new PromptResponseError('Prompt response did not include text content', this.payload);\n }\n return segments.join('\\n\\n');\n }\n\n json<T = unknown>(validator?: JsonValidator<T>): T {\n const textValue = this.text();\n try {\n const parsed = JSON.parse(textValue) as unknown;\n if (validator) {\n const assertValid: JsonValidator<T> = validator;\n assertValid(parsed);\n }\n return parsed as T;\n } catch (error) {\n const reason = formatErrorReason(error);\n throw new PromptResponseError(`Failed to parse prompt text as JSON: ${reason}`, this.payload);\n }\n }\n}\n\nexport class ResourceResponseError extends Error {\n readonly response: NativeReadResourceResponse;\n\n constructor(message: string, response: NativeReadResourceResponse) {\n super(message);\n this.name = 'ResourceResponseError';\n this.response = response;\n }\n}\n\nexport class ResourceResponseWrapper {\n private readonly payload: NativeReadResourceResponse;\n\n constructor(payload: NativeReadResourceResponse) {\n this.payload = payload;\n }\n\n raw(): NativeReadResourceResponse {\n return this.payload;\n }\n\n text(): string {\n const entry = this.firstEntry();\n if ('text' in entry && typeof entry.text === 'string') {\n return entry.text;\n }\n if ('blob' in entry && typeof entry.blob === 'string') {\n try {\n return Buffer.from(entry.blob, 'base64').toString('utf8');\n } catch (error) {\n const reason = formatErrorReason(error);\n throw new ResourceResponseError(`Failed to decode resource blob as UTF-8 text: ${reason}`, this.payload);\n }\n }\n throw new ResourceResponseError('Resource content does not include text or blob data', this.payload);\n }\n\n json<T = unknown>(validator?: JsonValidator<T>): T {\n const textValue = this.text();\n try {\n const parsed = JSON.parse(textValue) as unknown;\n if (validator) {\n const assertValid: JsonValidator<T> = validator;\n assertValid(parsed);\n }\n return parsed as T;\n } catch (error) {\n const reason = formatErrorReason(error);\n throw new ResourceResponseError(`Failed to parse resource text as JSON: ${reason}`, this.payload);\n }\n }\n\n private firstEntry(): NativeReadResourceResponse['contents'][number] {\n const [entry] = this.payload.contents ?? [];\n if (!entry) {\n throw new ResourceResponseError('Resource response did not include any contents', this.payload);\n }\n return entry;\n }\n}\n\nfunction hasStructuredContent(response: NativeCallToolResponse): response is NativeCallToolResponse & { structuredContent: Record<string, unknown> } {\n return Boolean((response as { structuredContent?: unknown }).structuredContent);\n}\n\nfunction isCompatibilityResult(response: NativeCallToolResponse): response is NativeCallToolResponse & { toolResult: unknown } {\n return hasOwn(response, 'toolResult');\n}\n\nfunction findFirstTextBlock(blocks: ContentBlock[] | undefined): TextContent | undefined {\n if (!Array.isArray(blocks)) {\n return undefined;\n }\n return blocks.find(isTextContent);\n}\n\nfunction collectPromptText(messages: PromptMessage[]): string[] {\n const segments: string[] = [];\n for (const message of messages) {\n const textBlock = isTextContent(message.content) ? message.content : undefined;\n if (textBlock) {\n segments.push(textBlock.text);\n }\n }\n return segments;\n}\n\nfunction isTextContent(block: unknown): block is TextContent {\n return Boolean(block) && typeof block === 'object' && (block as { type?: string }).type === 'text';\n}\n\nconst protoHasOwn = Object.prototype.hasOwnProperty;\n\nfunction hasOwn(target: object, key: PropertyKey): boolean {\n return protoHasOwn.call(target, key);\n}\n\nfunction formatErrorReason(error: unknown): string {\n if (error instanceof Error && typeof error.message === 'string') {\n return error.message;\n }\n if (typeof error === 'string') {\n return error;\n }\n try {\n return JSON.stringify(error);\n } catch {\n return String(error);\n }\n}\n"],"names":["PromptResponseError","PromptResponseWrapper","ResourceResponseError","ResourceResponseWrapper","ToolResponseError","ToolResponseWrapper","message","response","name","Error","payload","raw","json","validator","value","resolveJsonPayload","assertValid","text","isCompatibilityResult","toolResult","throwIfError","textBlock","findFirstTextBlock","content","hasStructuredContent","structuredContent","JSON","parse","error","reason","formatErrorReason","isError","detail","String","segments","collectPromptText","messages","length","join","textValue","parsed","entry","firstEntry","blob","Buffer","from","toString","contents","Boolean","hasOwn","blocks","Array","isArray","undefined","find","isTextContent","push","block","type","protoHasOwn","Object","prototype","hasOwnProperty","target","key","call","stringify"],"mappings":"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"}
@@ -4,7 +4,7 @@
4
4
  * Provides text-based search across tools, prompts, and resources
5
5
  * from connected MCP servers.
6
6
  */
7
- import type { Client } from '@modelcontextprotocol/sdk/client/index.js';
7
+ import type { Client } from '@modelcontextprotocol/client';
8
8
  import type { CapabilityIndex, SearchOptions, SearchResponse } from './types.js';
9
9
  export type CapabilityClient = Pick<Client, 'listTools' | 'listPrompts' | 'listResources'>;
10
10
  /**
@@ -4,7 +4,7 @@
4
4
  * Provides text-based search across tools, prompts, and resources
5
5
  * from connected MCP servers.
6
6
  */
7
- import type { Client } from '@modelcontextprotocol/sdk/client/index.js';
7
+ import type { Client } from '@modelcontextprotocol/client';
8
8
  import type { CapabilityIndex, SearchOptions, SearchResponse } from './types.js';
9
9
  export type CapabilityClient = Pick<Client, 'listTools' | 'listPrompts' | 'listResources'>;
10
10
  /**
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/mcp-z/client/src/search/search.ts"],"sourcesContent":["/**\n * Search implementation for MCP capability discovery\n *\n * Provides text-based search across tools, prompts, and resources\n * from connected MCP servers.\n */\n\nimport type { Client } from '@modelcontextprotocol/sdk/client/index.js';\nimport type { PromptArgument } from '../connection/types.ts';\nimport type { CapabilityIndex, CapabilityType, IndexedCapability, IndexedPrompt, IndexedResource, IndexedTool, SearchField, SearchOptions, SearchResponse, SearchResult } from './types.ts';\n\nexport type CapabilityClient = Pick<Client, 'listTools' | 'listPrompts' | 'listResources'>;\n\nconst DEFAULT_LIMIT = 20;\nconst DEFAULT_THRESHOLD = 0;\nconst DEFAULT_TYPES: CapabilityType[] = ['tool', 'prompt', 'resource'];\nconst DEFAULT_SEARCH_FIELDS: SearchField[] = ['name', 'description', 'schema'];\n\n/**\n * Extract searchable text from a JSON Schema's property descriptions\n */\nfunction extractSchemaText(inputSchema: unknown): string {\n if (!inputSchema || typeof inputSchema !== 'object') {\n return '';\n }\n\n const schema = inputSchema as {\n properties?: Record<string, { description?: string; name?: string }>;\n description?: string;\n };\n\n const parts: string[] = [];\n\n // Add schema-level description if present\n if (schema.description) {\n parts.push(schema.description);\n }\n\n // Add property names and descriptions\n if (schema.properties) {\n for (const [propName, prop] of Object.entries(schema.properties)) {\n parts.push(propName);\n if (prop && typeof prop === 'object' && prop.description) {\n parts.push(prop.description);\n }\n }\n }\n\n return parts.join(' ');\n}\n\n/**\n * Extract searchable text from prompt arguments\n */\nfunction extractArgumentsText(args: PromptArgument[] | undefined): string {\n if (!args || !Array.isArray(args)) {\n return '';\n }\n\n return args\n .map((arg) => {\n const parts = [arg.name];\n if (arg.description) {\n parts.push(arg.description);\n }\n return parts.join(' ');\n })\n .join(' ');\n}\n\n/**\n * Build an index of capabilities from connected MCP clients\n */\nexport async function buildCapabilityIndex(clients: Map<string, CapabilityClient>): Promise<CapabilityIndex> {\n const capabilities: IndexedCapability[] = [];\n const servers: string[] = [];\n\n for (const [serverName, client] of clients) {\n servers.push(serverName);\n\n // Fetch all capabilities in parallel, handling errors gracefully\n const [toolsResult, promptsResult, resourcesResult] = await Promise.all([client.listTools().catch(() => null), client.listPrompts().catch(() => null), client.listResources().catch(() => null)]);\n\n // Index tools\n if (toolsResult?.tools) {\n for (const tool of toolsResult.tools) {\n capabilities.push({\n type: 'tool',\n server: serverName,\n name: tool.name,\n description: tool.description,\n schemaText: extractSchemaText(tool.inputSchema),\n } satisfies IndexedTool);\n }\n }\n\n // Index prompts\n if (promptsResult?.prompts) {\n for (const prompt of promptsResult.prompts) {\n capabilities.push({\n type: 'prompt',\n server: serverName,\n name: prompt.name,\n description: prompt.description,\n argumentsText: extractArgumentsText(prompt.arguments as PromptArgument[] | undefined),\n arguments: prompt.arguments as PromptArgument[] | undefined,\n } satisfies IndexedPrompt);\n }\n }\n\n // Index resources\n if (resourcesResult?.resources) {\n for (const resource of resourcesResult.resources) {\n capabilities.push({\n type: 'resource',\n server: serverName,\n name: resource.name,\n description: resource.description,\n uri: resource.uri,\n mimeType: resource.mimeType,\n } satisfies IndexedResource);\n }\n }\n }\n\n return {\n capabilities,\n servers,\n indexedAt: new Date(),\n };\n}\n\n/**\n * Calculate relevance score and matched fields for a capability against a query\n */\nfunction scoreCapability(capability: IndexedCapability, queryTerms: string[], searchFields: SearchField[]): { score: number; matchedOn: string[] } {\n const matchedOn: string[] = [];\n let totalScore = 0;\n\n // Weights for different match types\n const EXACT_NAME_WEIGHT = 1.0;\n const PARTIAL_NAME_WEIGHT = 0.8;\n const DESCRIPTION_WEIGHT = 0.6;\n const SCHEMA_WEIGHT = 0.4;\n const SERVER_WEIGHT = 0.3;\n\n const nameLower = capability.name.toLowerCase();\n const descLower = (capability.description || '').toLowerCase();\n const serverLower = capability.server.toLowerCase();\n\n // Get schema/arguments text based on type\n let schemaTextLower = '';\n if (capability.type === 'tool') {\n schemaTextLower = capability.schemaText.toLowerCase();\n } else if (capability.type === 'prompt') {\n schemaTextLower = capability.argumentsText.toLowerCase();\n } else if (capability.type === 'resource') {\n // For resources, include URI and mimeType in searchable text\n schemaTextLower = `${capability.uri} ${capability.mimeType || ''}`.toLowerCase();\n }\n\n for (const term of queryTerms) {\n const termLower = term.toLowerCase();\n\n // Check name matches\n if (searchFields.includes('name')) {\n if (nameLower === termLower) {\n totalScore += EXACT_NAME_WEIGHT;\n if (!matchedOn.includes('name')) matchedOn.push('name');\n } else if (nameLower.includes(termLower)) {\n totalScore += PARTIAL_NAME_WEIGHT;\n if (!matchedOn.includes('name')) matchedOn.push('name');\n }\n }\n\n // Check description matches\n if (searchFields.includes('description') && descLower.includes(termLower)) {\n totalScore += DESCRIPTION_WEIGHT;\n if (!matchedOn.includes('description')) matchedOn.push('description');\n }\n\n // Check schema/arguments matches\n if (searchFields.includes('schema') && schemaTextLower.includes(termLower)) {\n totalScore += SCHEMA_WEIGHT;\n const fieldName = capability.type === 'tool' ? 'inputSchema' : capability.type === 'prompt' ? 'arguments' : 'uri';\n if (!matchedOn.includes(fieldName)) matchedOn.push(fieldName);\n }\n\n // Check server name matches\n if (searchFields.includes('server') && serverLower.includes(termLower)) {\n totalScore += SERVER_WEIGHT;\n if (!matchedOn.includes('server')) matchedOn.push('server');\n }\n }\n\n // Normalize score to 0-1 range based on number of terms\n const normalizedScore = queryTerms.length > 0 ? Math.min(1, totalScore / queryTerms.length) : 0;\n\n return { score: normalizedScore, matchedOn };\n}\n\n/**\n * Search for capabilities matching a query string\n */\nexport function searchCapabilities(index: CapabilityIndex, query: string, options: SearchOptions = {}): SearchResponse {\n const { types = DEFAULT_TYPES, servers, searchFields = DEFAULT_SEARCH_FIELDS, limit = DEFAULT_LIMIT, threshold = DEFAULT_THRESHOLD } = options;\n\n // Tokenize query into search terms\n const queryTerms = query\n .toLowerCase()\n .split(/\\s+/)\n .filter((term) => term.length > 0);\n\n // If empty query, return empty results\n if (queryTerms.length === 0) {\n return { query, results: [], total: 0 };\n }\n\n // Filter and score capabilities\n const scoredResults: Array<{ capability: IndexedCapability; score: number; matchedOn: string[] }> = [];\n\n for (const capability of index.capabilities) {\n // Filter by type\n if (!types.includes(capability.type)) {\n continue;\n }\n\n // Filter by server\n if (servers && servers.length > 0 && !servers.includes(capability.server)) {\n continue;\n }\n\n // Score the capability\n const { score, matchedOn } = scoreCapability(capability, queryTerms, searchFields);\n\n // Apply threshold filter\n if (score >= threshold && matchedOn.length > 0) {\n scoredResults.push({ capability, score, matchedOn });\n }\n }\n\n // Sort by score descending\n scoredResults.sort((a, b) => b.score - a.score);\n\n // Get total before limiting\n const total = scoredResults.length;\n\n // Apply limit and transform to SearchResult\n const results: SearchResult[] = scoredResults.slice(0, limit).map(({ capability, score, matchedOn }) => ({\n type: capability.type,\n server: capability.server,\n name: capability.name,\n description: capability.description,\n matchedOn,\n score,\n }));\n\n return { query, results, total };\n}\n\n/**\n * Convenience function to search directly from connected clients\n * Builds index and performs search in one call\n */\nexport async function search(clients: Map<string, CapabilityClient>, query: string, options: SearchOptions = {}): Promise<SearchResponse> {\n const index = await buildCapabilityIndex(clients);\n return searchCapabilities(index, query, options);\n}\n"],"names":["buildCapabilityIndex","search","searchCapabilities","DEFAULT_LIMIT","DEFAULT_THRESHOLD","DEFAULT_TYPES","DEFAULT_SEARCH_FIELDS","extractSchemaText","inputSchema","schema","parts","description","push","properties","Object","entries","propName","prop","join","extractArgumentsText","args","Array","isArray","map","arg","name","clients","capabilities","servers","serverName","client","toolsResult","promptsResult","resourcesResult","tool","prompt","resource","Promise","all","listTools","catch","listPrompts","listResources","tools","type","server","schemaText","prompts","argumentsText","arguments","resources","uri","mimeType","indexedAt","Date","scoreCapability","capability","queryTerms","searchFields","matchedOn","totalScore","EXACT_NAME_WEIGHT","PARTIAL_NAME_WEIGHT","DESCRIPTION_WEIGHT","SCHEMA_WEIGHT","SERVER_WEIGHT","nameLower","toLowerCase","descLower","serverLower","schemaTextLower","term","termLower","includes","fieldName","normalizedScore","length","Math","min","score","index","query","options","types","limit","threshold","split","filter","results","total","scoredResults","sort","a","b","slice"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/mcp-z/client/src/search/search.ts"],"sourcesContent":["/**\n * Search implementation for MCP capability discovery\n *\n * Provides text-based search across tools, prompts, and resources\n * from connected MCP servers.\n */\n\nimport type { Client } from '@modelcontextprotocol/client';\nimport type { PromptArgument } from '../connection/types.ts';\nimport type { CapabilityIndex, CapabilityType, IndexedCapability, IndexedPrompt, IndexedResource, IndexedTool, SearchField, SearchOptions, SearchResponse, SearchResult } from './types.ts';\n\nexport type CapabilityClient = Pick<Client, 'listTools' | 'listPrompts' | 'listResources'>;\n\nconst DEFAULT_LIMIT = 20;\nconst DEFAULT_THRESHOLD = 0;\nconst DEFAULT_TYPES: CapabilityType[] = ['tool', 'prompt', 'resource'];\nconst DEFAULT_SEARCH_FIELDS: SearchField[] = ['name', 'description', 'schema'];\n\n/**\n * Extract searchable text from a JSON Schema's property descriptions\n */\nfunction extractSchemaText(inputSchema: unknown): string {\n if (!inputSchema || typeof inputSchema !== 'object') {\n return '';\n }\n\n const schema = inputSchema as {\n properties?: Record<string, { description?: string; name?: string }>;\n description?: string;\n };\n\n const parts: string[] = [];\n\n // Add schema-level description if present\n if (schema.description) {\n parts.push(schema.description);\n }\n\n // Add property names and descriptions\n if (schema.properties) {\n for (const [propName, prop] of Object.entries(schema.properties)) {\n parts.push(propName);\n if (prop && typeof prop === 'object' && prop.description) {\n parts.push(prop.description);\n }\n }\n }\n\n return parts.join(' ');\n}\n\n/**\n * Extract searchable text from prompt arguments\n */\nfunction extractArgumentsText(args: PromptArgument[] | undefined): string {\n if (!args || !Array.isArray(args)) {\n return '';\n }\n\n return args\n .map((arg) => {\n const parts = [arg.name];\n if (arg.description) {\n parts.push(arg.description);\n }\n return parts.join(' ');\n })\n .join(' ');\n}\n\n/**\n * Build an index of capabilities from connected MCP clients\n */\nexport async function buildCapabilityIndex(clients: Map<string, CapabilityClient>): Promise<CapabilityIndex> {\n const capabilities: IndexedCapability[] = [];\n const servers: string[] = [];\n\n for (const [serverName, client] of clients) {\n servers.push(serverName);\n\n // Fetch all capabilities in parallel, handling errors gracefully\n const [toolsResult, promptsResult, resourcesResult] = await Promise.all([client.listTools().catch(() => null), client.listPrompts().catch(() => null), client.listResources().catch(() => null)]);\n\n // Index tools\n if (toolsResult?.tools) {\n for (const tool of toolsResult.tools) {\n capabilities.push({\n type: 'tool',\n server: serverName,\n name: tool.name,\n description: tool.description,\n schemaText: extractSchemaText(tool.inputSchema),\n } satisfies IndexedTool);\n }\n }\n\n // Index prompts\n if (promptsResult?.prompts) {\n for (const prompt of promptsResult.prompts) {\n capabilities.push({\n type: 'prompt',\n server: serverName,\n name: prompt.name,\n description: prompt.description,\n argumentsText: extractArgumentsText(prompt.arguments as PromptArgument[] | undefined),\n arguments: prompt.arguments as PromptArgument[] | undefined,\n } satisfies IndexedPrompt);\n }\n }\n\n // Index resources\n if (resourcesResult?.resources) {\n for (const resource of resourcesResult.resources) {\n capabilities.push({\n type: 'resource',\n server: serverName,\n name: resource.name,\n description: resource.description,\n uri: resource.uri,\n mimeType: resource.mimeType,\n } satisfies IndexedResource);\n }\n }\n }\n\n return {\n capabilities,\n servers,\n indexedAt: new Date(),\n };\n}\n\n/**\n * Calculate relevance score and matched fields for a capability against a query\n */\nfunction scoreCapability(capability: IndexedCapability, queryTerms: string[], searchFields: SearchField[]): { score: number; matchedOn: string[] } {\n const matchedOn: string[] = [];\n let totalScore = 0;\n\n // Weights for different match types\n const EXACT_NAME_WEIGHT = 1.0;\n const PARTIAL_NAME_WEIGHT = 0.8;\n const DESCRIPTION_WEIGHT = 0.6;\n const SCHEMA_WEIGHT = 0.4;\n const SERVER_WEIGHT = 0.3;\n\n const nameLower = capability.name.toLowerCase();\n const descLower = (capability.description || '').toLowerCase();\n const serverLower = capability.server.toLowerCase();\n\n // Get schema/arguments text based on type\n let schemaTextLower = '';\n if (capability.type === 'tool') {\n schemaTextLower = capability.schemaText.toLowerCase();\n } else if (capability.type === 'prompt') {\n schemaTextLower = capability.argumentsText.toLowerCase();\n } else if (capability.type === 'resource') {\n // For resources, include URI and mimeType in searchable text\n schemaTextLower = `${capability.uri} ${capability.mimeType || ''}`.toLowerCase();\n }\n\n for (const term of queryTerms) {\n const termLower = term.toLowerCase();\n\n // Check name matches\n if (searchFields.includes('name')) {\n if (nameLower === termLower) {\n totalScore += EXACT_NAME_WEIGHT;\n if (!matchedOn.includes('name')) matchedOn.push('name');\n } else if (nameLower.includes(termLower)) {\n totalScore += PARTIAL_NAME_WEIGHT;\n if (!matchedOn.includes('name')) matchedOn.push('name');\n }\n }\n\n // Check description matches\n if (searchFields.includes('description') && descLower.includes(termLower)) {\n totalScore += DESCRIPTION_WEIGHT;\n if (!matchedOn.includes('description')) matchedOn.push('description');\n }\n\n // Check schema/arguments matches\n if (searchFields.includes('schema') && schemaTextLower.includes(termLower)) {\n totalScore += SCHEMA_WEIGHT;\n const fieldName = capability.type === 'tool' ? 'inputSchema' : capability.type === 'prompt' ? 'arguments' : 'uri';\n if (!matchedOn.includes(fieldName)) matchedOn.push(fieldName);\n }\n\n // Check server name matches\n if (searchFields.includes('server') && serverLower.includes(termLower)) {\n totalScore += SERVER_WEIGHT;\n if (!matchedOn.includes('server')) matchedOn.push('server');\n }\n }\n\n // Normalize score to 0-1 range based on number of terms\n const normalizedScore = queryTerms.length > 0 ? Math.min(1, totalScore / queryTerms.length) : 0;\n\n return { score: normalizedScore, matchedOn };\n}\n\n/**\n * Search for capabilities matching a query string\n */\nexport function searchCapabilities(index: CapabilityIndex, query: string, options: SearchOptions = {}): SearchResponse {\n const { types = DEFAULT_TYPES, servers, searchFields = DEFAULT_SEARCH_FIELDS, limit = DEFAULT_LIMIT, threshold = DEFAULT_THRESHOLD } = options;\n\n // Tokenize query into search terms\n const queryTerms = query\n .toLowerCase()\n .split(/\\s+/)\n .filter((term) => term.length > 0);\n\n // If empty query, return empty results\n if (queryTerms.length === 0) {\n return { query, results: [], total: 0 };\n }\n\n // Filter and score capabilities\n const scoredResults: Array<{ capability: IndexedCapability; score: number; matchedOn: string[] }> = [];\n\n for (const capability of index.capabilities) {\n // Filter by type\n if (!types.includes(capability.type)) {\n continue;\n }\n\n // Filter by server\n if (servers && servers.length > 0 && !servers.includes(capability.server)) {\n continue;\n }\n\n // Score the capability\n const { score, matchedOn } = scoreCapability(capability, queryTerms, searchFields);\n\n // Apply threshold filter\n if (score >= threshold && matchedOn.length > 0) {\n scoredResults.push({ capability, score, matchedOn });\n }\n }\n\n // Sort by score descending\n scoredResults.sort((a, b) => b.score - a.score);\n\n // Get total before limiting\n const total = scoredResults.length;\n\n // Apply limit and transform to SearchResult\n const results: SearchResult[] = scoredResults.slice(0, limit).map(({ capability, score, matchedOn }) => ({\n type: capability.type,\n server: capability.server,\n name: capability.name,\n description: capability.description,\n matchedOn,\n score,\n }));\n\n return { query, results, total };\n}\n\n/**\n * Convenience function to search directly from connected clients\n * Builds index and performs search in one call\n */\nexport async function search(clients: Map<string, CapabilityClient>, query: string, options: SearchOptions = {}): Promise<SearchResponse> {\n const index = await buildCapabilityIndex(clients);\n return searchCapabilities(index, query, options);\n}\n"],"names":["buildCapabilityIndex","search","searchCapabilities","DEFAULT_LIMIT","DEFAULT_THRESHOLD","DEFAULT_TYPES","DEFAULT_SEARCH_FIELDS","extractSchemaText","inputSchema","schema","parts","description","push","properties","Object","entries","propName","prop","join","extractArgumentsText","args","Array","isArray","map","arg","name","clients","capabilities","servers","serverName","client","toolsResult","promptsResult","resourcesResult","tool","prompt","resource","Promise","all","listTools","catch","listPrompts","listResources","tools","type","server","schemaText","prompts","argumentsText","arguments","resources","uri","mimeType","indexedAt","Date","scoreCapability","capability","queryTerms","searchFields","matchedOn","totalScore","EXACT_NAME_WEIGHT","PARTIAL_NAME_WEIGHT","DESCRIPTION_WEIGHT","SCHEMA_WEIGHT","SERVER_WEIGHT","nameLower","toLowerCase","descLower","serverLower","schemaTextLower","term","termLower","includes","fieldName","normalizedScore","length","Math","min","score","index","query","options","types","limit","threshold","split","filter","results","total","scoredResults","sort","a","b","slice"],"mappings":"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"}
@@ -38,7 +38,7 @@ import { discoverAuthorizationServerIssuer, discoverAuthorizationServerMetadata,
38
38
  * if (caps.supportsDcr) {
39
39
  * console.log('Registration endpoint:', caps.registrationEndpoint);
40
40
  * }
41
- */ function buildCapabilities(metadata, scopes) {
41
+ */ function buildCapabilities(metadata, scopes, resource) {
42
42
  const supportsDcr = !!metadata.registration_endpoint;
43
43
  const capabilities = {
44
44
  supportsDcr,
@@ -47,6 +47,12 @@ import { discoverAuthorizationServerIssuer, discoverAuthorizationServerMetadata,
47
47
  if (metadata.issuer) {
48
48
  capabilities.issuer = metadata.issuer;
49
49
  }
50
+ // Carried from the RFC 9728 document, not from the URL we were given: the
51
+ // resource server names itself, and that name is what RFC 8707 audience-binds
52
+ // a token to.
53
+ if (resource) {
54
+ capabilities.resource = resource;
55
+ }
50
56
  if (metadata.registration_endpoint) {
51
57
  capabilities.registrationEndpoint = metadata.registration_endpoint;
52
58
  }
@@ -64,12 +70,12 @@ import { discoverAuthorizationServerIssuer, discoverAuthorizationServerMetadata,
64
70
  }
65
71
  return capabilities;
66
72
  }
67
- async function resolveCapabilitiesFromAuthorizationServer(authServerUrl, scopes, allowLoopback) {
73
+ async function resolveCapabilitiesFromAuthorizationServer(authServerUrl, scopes, allowLoopback, resource) {
68
74
  const metadata = await discoverAuthorizationServerMetadata(authServerUrl, {
69
75
  allowLoopback
70
76
  });
71
77
  if (!metadata) return null;
72
- return buildCapabilities(metadata, scopes);
78
+ return buildCapabilities(metadata, scopes, resource);
73
79
  }
74
80
  export async function probeAuthCapabilities(baseUrl) {
75
81
  try {
@@ -90,13 +96,13 @@ export async function probeAuthCapabilities(baseUrl) {
90
96
  supportsDcr: false
91
97
  };
92
98
  }
93
- const capabilities = await resolveCapabilitiesFromAuthorizationServer(authServerUrl, resourceMetadata.scopes_supported, allowLoopback);
99
+ const capabilities = await resolveCapabilitiesFromAuthorizationServer(authServerUrl, resourceMetadata.scopes_supported, allowLoopback, resourceMetadata.resource);
94
100
  if (capabilities) {
95
101
  return capabilities;
96
102
  }
97
103
  const issuer = await discoverAuthorizationServerIssuer(baseUrl);
98
104
  if (issuer) {
99
- const issuerCapabilities = await resolveCapabilitiesFromAuthorizationServer(issuer, resourceMetadata.scopes_supported, allowLoopback);
105
+ const issuerCapabilities = await resolveCapabilitiesFromAuthorizationServer(issuer, resourceMetadata.scopes_supported, allowLoopback, resourceMetadata.resource);
100
106
  if (issuerCapabilities) return issuerCapabilities;
101
107
  }
102
108
  }
@@ -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[]): 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 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): Promise<AuthCapabilities | null> {\n const metadata = await discoverAuthorizationServerMetadata(authServerUrl, { allowLoopback });\n if (!metadata) return null;\n return buildCapabilities(metadata, scopes);\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);\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);\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":["normalizeUrl","isLoopbackUrl","discoverAuthorizationServerIssuer","discoverAuthorizationServerMetadata","discoverProtectedResourceMetadata","getOrigin","url","URL","origin","buildCapabilities","metadata","scopes","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","probeAuthCapabilities","baseUrl","normalizedBaseUrl","resourceMetadata","authorization_servers","issuerCapabilities","undefined","originCapabilities","_error"],"mappings":"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"}
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":["normalizeUrl","isLoopbackUrl","discoverAuthorizationServerIssuer","discoverAuthorizationServerMetadata","discoverProtectedResourceMetadata","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","probeAuthCapabilities","baseUrl","normalizedBaseUrl","resourceMetadata","authorization_servers","issuerCapabilities","undefined","originCapabilities","_error"],"mappings":"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"}
@@ -91,6 +91,17 @@ export interface AuthCapabilities {
91
91
  supportsDcr: boolean;
92
92
  /** Issuer identifier from the authorization server metadata (RFC 8414) */
93
93
  issuer?: string;
94
+ /**
95
+ * The protected resource's canonical identifier, from the RFC 9728 metadata
96
+ * document's `resource` field. This is what an RFC 8707 `resource` indicator
97
+ * must carry, and it is the resource server's own statement of its identity -
98
+ * not the URL we happened to dial, and never the base URL discovery was
99
+ * performed against, which has any `/mcp` segment stripped off it.
100
+ *
101
+ * Absent when no protected-resource metadata was published and the
102
+ * authorization server was reached by direct RFC 8414 discovery instead.
103
+ */
104
+ resource?: string;
94
105
  /** Whether the authorization response carries an `iss` parameter (RFC 9207) */
95
106
  authorizationResponseIssSupported?: boolean;
96
107
  /** DCR client registration endpoint */
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/mcp-z/client/src/auth/types.ts"],"sourcesContent":["/**\n * Shared types for OAuth and DCR authentication\n */\n\n/**\n * OAuth callback result from authorization server\n */\nexport interface CallbackResult {\n /** Authorization code from OAuth server */\n code: string;\n /** State parameter for CSRF protection */\n state?: string;\n /** Issuer identifier of the authorization server that minted the response (RFC 9207) */\n iss?: string;\n}\n\n/**\n * PKCE (Proof Key for Code Exchange) parameters (RFC 7636)\n * Used to secure OAuth 2.0 authorization code flow for public clients\n */\nexport interface PkceParams {\n /** Code verifier - cryptographically random string (43-128 characters) */\n codeVerifier: string;\n /** Code challenge - derived from code verifier using challenge method */\n codeChallenge: string;\n /** Code challenge method - S256 (SHA-256) or plain */\n codeChallengeMethod: 'S256' | 'plain';\n}\n\n/**\n * OAuth token set with access and refresh tokens\n */\nexport interface TokenSet {\n /** Access token for API requests */\n accessToken: string;\n /** Refresh token for obtaining new access tokens */\n refreshToken: string;\n /** Timestamp when access token expires (milliseconds since epoch) */\n expiresAt: number;\n /** Scopes granted for this token set */\n scopes?: string[];\n /** Client ID used for DCR registration (stored for future use) */\n clientId?: string;\n /** Client secret used for DCR registration (stored for future use) */\n clientSecret?: string;\n /** Issuer identifier of the authorization server these credentials belong to (SEP-2352) */\n issuer?: string;\n}\n\n/**\n * OAuth 2.0 Protected Resource Metadata (RFC 9728)\n * Response from .well-known/oauth-protected-resource endpoint\n */\nexport interface ProtectedResourceMetadata {\n /** The protected resource identifier */\n resource: string;\n /** List of authorization server URLs that can issue tokens for this resource */\n authorization_servers: string[];\n /** Optional list of scopes supported by this resource */\n scopes_supported?: string[];\n /** Optional list of bearer token methods supported (header, query, body) */\n bearer_methods_supported?: string[];\n}\n\n/**\n * OAuth 2.0 Authorization Server Metadata (RFC 8414)\n * Response from .well-known/oauth-authorization-server endpoint\n */\nexport interface AuthorizationServerMetadata {\n /** The authorization server's issuer identifier */\n issuer?: string;\n /** URL of the authorization endpoint */\n authorization_endpoint?: string;\n /** URL of the token endpoint */\n token_endpoint?: string;\n /** URL of the client registration endpoint (DCR - RFC 7591) */\n registration_endpoint?: string;\n /** URL of the token introspection endpoint */\n introspection_endpoint?: string;\n /** List of OAuth scopes supported by the authorization server */\n scopes_supported?: string[];\n /** Response types supported (code, token, etc.) */\n response_types_supported?: string[];\n /** Grant types supported (authorization_code, refresh_token, etc.) */\n grant_types_supported?: string[];\n /** Token endpoint authentication methods supported */\n token_endpoint_auth_methods_supported?: string[];\n /** Whether the authorization response carries an `iss` parameter (RFC 9207) */\n authorization_response_iss_parameter_supported?: boolean;\n}\n\n/**\n * OAuth server capabilities discovered from .well-known endpoint\n */\nexport interface AuthCapabilities {\n /** Whether the server supports Dynamic Client Registration (RFC 7591) */\n supportsDcr: boolean;\n /** Issuer identifier from the authorization server metadata (RFC 8414) */\n issuer?: string;\n /** Whether the authorization response carries an `iss` parameter (RFC 9207) */\n authorizationResponseIssSupported?: boolean;\n /** DCR client registration endpoint */\n registrationEndpoint?: string;\n /** OAuth authorization endpoint */\n authorizationEndpoint?: string;\n /** OAuth token endpoint */\n tokenEndpoint?: string;\n /** Token introspection endpoint */\n introspectionEndpoint?: string;\n /** Supported OAuth scopes */\n scopes?: string[];\n}\n\n/**\n * Client credentials from DCR registration\n */\nexport interface ClientCredentials {\n /** OAuth client ID */\n clientId: string;\n /** OAuth client secret */\n clientSecret: string;\n /** Timestamp when client was registered */\n issuedAt?: number;\n}\n\n/**\n * Options for DCR client registration\n */\nexport interface DcrRegistrationOptions {\n /** Redirect URI for OAuth callback, from the loopback listener the caller has already bound (RFC 8252) */\n redirectUri: string;\n /** Client name to register */\n clientName?: string;\n /**\n * Loopback trust grant for the registration_endpoint fetch (SSRF\n * mitigation - see `src/auth/discovery-fetch.ts`). Compute this from the\n * MCP server the caller is actually talking to, never from\n * `registrationEndpoint` itself (which is typically sourced from\n * remote-controlled AS metadata). Defaults to `false`.\n */\n allowLoopback?: boolean;\n}\n\n/**\n * Options for OAuth authorization flow\n */\nexport interface OAuthFlowOptions {\n /** Port for OAuth callback listener (required - use get-port to find available port) */\n port: number;\n /** Issuer identifier discovered before the flow starts; the `iss` in the authorization response must match it (RFC 9207) */\n issuer: string;\n /** Canonical resource server URI, sent as `resource` on the authorization, token, and refresh requests (RFC 8707) */\n resource: string;\n /** Redirect URI for OAuth callback (optional - will be built from port if not provided) */\n redirectUri?: string;\n /** OAuth scopes to request */\n scopes?: string[];\n /** Whether the authorization server advertises `authorization_response_iss_parameter_supported` (RFC 9207) */\n authorizationResponseIssSupported?: boolean;\n /** Enable PKCE (RFC 7636) - recommended for all clients, required for public clients */\n pkce?: boolean;\n /** Headless mode (don't open browser) */\n headless?: boolean;\n /** Timeout for callback (milliseconds) */\n timeout?: number;\n /** Optional logger for debug output (defaults to singleton logger) */\n logger?: import('../utils/logger.ts').Logger;\n /**\n * Loopback trust grant for the token endpoint fetch (SSRF mitigation - see\n * `src/auth/discovery-fetch.ts`). Compute this from the MCP server the\n * caller is actually talking to, never from `tokenEndpoint` itself (which\n * is typically sourced from remote-controlled AS metadata). Defaults to\n * `false`.\n */\n allowLoopback?: boolean;\n}\n"],"names":[],"mappings":"AAAA;;CAEC,GAED;;CAEC,GAyID;;CAEC,GACD,WA6BC"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/mcp-z/client/src/auth/types.ts"],"sourcesContent":["/**\n * Shared types for OAuth and DCR authentication\n */\n\n/**\n * OAuth callback result from authorization server\n */\nexport interface CallbackResult {\n /** Authorization code from OAuth server */\n code: string;\n /** State parameter for CSRF protection */\n state?: string;\n /** Issuer identifier of the authorization server that minted the response (RFC 9207) */\n iss?: string;\n}\n\n/**\n * PKCE (Proof Key for Code Exchange) parameters (RFC 7636)\n * Used to secure OAuth 2.0 authorization code flow for public clients\n */\nexport interface PkceParams {\n /** Code verifier - cryptographically random string (43-128 characters) */\n codeVerifier: string;\n /** Code challenge - derived from code verifier using challenge method */\n codeChallenge: string;\n /** Code challenge method - S256 (SHA-256) or plain */\n codeChallengeMethod: 'S256' | 'plain';\n}\n\n/**\n * OAuth token set with access and refresh tokens\n */\nexport interface TokenSet {\n /** Access token for API requests */\n accessToken: string;\n /** Refresh token for obtaining new access tokens */\n refreshToken: string;\n /** Timestamp when access token expires (milliseconds since epoch) */\n expiresAt: number;\n /** Scopes granted for this token set */\n scopes?: string[];\n /** Client ID used for DCR registration (stored for future use) */\n clientId?: string;\n /** Client secret used for DCR registration (stored for future use) */\n clientSecret?: string;\n /** Issuer identifier of the authorization server these credentials belong to (SEP-2352) */\n issuer?: string;\n}\n\n/**\n * OAuth 2.0 Protected Resource Metadata (RFC 9728)\n * Response from .well-known/oauth-protected-resource endpoint\n */\nexport interface ProtectedResourceMetadata {\n /** The protected resource identifier */\n resource: string;\n /** List of authorization server URLs that can issue tokens for this resource */\n authorization_servers: string[];\n /** Optional list of scopes supported by this resource */\n scopes_supported?: string[];\n /** Optional list of bearer token methods supported (header, query, body) */\n bearer_methods_supported?: string[];\n}\n\n/**\n * OAuth 2.0 Authorization Server Metadata (RFC 8414)\n * Response from .well-known/oauth-authorization-server endpoint\n */\nexport interface AuthorizationServerMetadata {\n /** The authorization server's issuer identifier */\n issuer?: string;\n /** URL of the authorization endpoint */\n authorization_endpoint?: string;\n /** URL of the token endpoint */\n token_endpoint?: string;\n /** URL of the client registration endpoint (DCR - RFC 7591) */\n registration_endpoint?: string;\n /** URL of the token introspection endpoint */\n introspection_endpoint?: string;\n /** List of OAuth scopes supported by the authorization server */\n scopes_supported?: string[];\n /** Response types supported (code, token, etc.) */\n response_types_supported?: string[];\n /** Grant types supported (authorization_code, refresh_token, etc.) */\n grant_types_supported?: string[];\n /** Token endpoint authentication methods supported */\n token_endpoint_auth_methods_supported?: string[];\n /** Whether the authorization response carries an `iss` parameter (RFC 9207) */\n authorization_response_iss_parameter_supported?: boolean;\n}\n\n/**\n * OAuth server capabilities discovered from .well-known endpoint\n */\nexport interface AuthCapabilities {\n /** Whether the server supports Dynamic Client Registration (RFC 7591) */\n supportsDcr: boolean;\n /** Issuer identifier from the authorization server metadata (RFC 8414) */\n issuer?: string;\n /**\n * The protected resource's canonical identifier, from the RFC 9728 metadata\n * document's `resource` field. This is what an RFC 8707 `resource` indicator\n * must carry, and it is the resource server's own statement of its identity -\n * not the URL we happened to dial, and never the base URL discovery was\n * performed against, which has any `/mcp` segment stripped off it.\n *\n * Absent when no protected-resource metadata was published and the\n * authorization server was reached by direct RFC 8414 discovery instead.\n */\n resource?: string;\n /** Whether the authorization response carries an `iss` parameter (RFC 9207) */\n authorizationResponseIssSupported?: boolean;\n /** DCR client registration endpoint */\n registrationEndpoint?: string;\n /** OAuth authorization endpoint */\n authorizationEndpoint?: string;\n /** OAuth token endpoint */\n tokenEndpoint?: string;\n /** Token introspection endpoint */\n introspectionEndpoint?: string;\n /** Supported OAuth scopes */\n scopes?: string[];\n}\n\n/**\n * Client credentials from DCR registration\n */\nexport interface ClientCredentials {\n /** OAuth client ID */\n clientId: string;\n /** OAuth client secret */\n clientSecret: string;\n /** Timestamp when client was registered */\n issuedAt?: number;\n}\n\n/**\n * Options for DCR client registration\n */\nexport interface DcrRegistrationOptions {\n /** Redirect URI for OAuth callback, from the loopback listener the caller has already bound (RFC 8252) */\n redirectUri: string;\n /** Client name to register */\n clientName?: string;\n /**\n * Loopback trust grant for the registration_endpoint fetch (SSRF\n * mitigation - see `src/auth/discovery-fetch.ts`). Compute this from the\n * MCP server the caller is actually talking to, never from\n * `registrationEndpoint` itself (which is typically sourced from\n * remote-controlled AS metadata). Defaults to `false`.\n */\n allowLoopback?: boolean;\n}\n\n/**\n * Options for OAuth authorization flow\n */\nexport interface OAuthFlowOptions {\n /** Port for OAuth callback listener (required - use get-port to find available port) */\n port: number;\n /** Issuer identifier discovered before the flow starts; the `iss` in the authorization response must match it (RFC 9207) */\n issuer: string;\n /** Canonical resource server URI, sent as `resource` on the authorization, token, and refresh requests (RFC 8707) */\n resource: string;\n /** Redirect URI for OAuth callback (optional - will be built from port if not provided) */\n redirectUri?: string;\n /** OAuth scopes to request */\n scopes?: string[];\n /** Whether the authorization server advertises `authorization_response_iss_parameter_supported` (RFC 9207) */\n authorizationResponseIssSupported?: boolean;\n /** Enable PKCE (RFC 7636) - recommended for all clients, required for public clients */\n pkce?: boolean;\n /** Headless mode (don't open browser) */\n headless?: boolean;\n /** Timeout for callback (milliseconds) */\n timeout?: number;\n /** Optional logger for debug output (defaults to singleton logger) */\n logger?: import('../utils/logger.ts').Logger;\n /**\n * Loopback trust grant for the token endpoint fetch (SSRF mitigation - see\n * `src/auth/discovery-fetch.ts`). Compute this from the MCP server the\n * caller is actually talking to, never from `tokenEndpoint` itself (which\n * is typically sourced from remote-controlled AS metadata). Defaults to\n * `false`.\n */\n allowLoopback?: boolean;\n}\n"],"names":[],"mappings":"AAAA;;CAEC,GAED;;CAEC,GAoJD;;CAEC,GACD,WA6BC"}