@mcp-z/client 1.1.1 → 2.0.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 (68) hide show
  1. package/README.md +5 -1
  2. package/dist/cjs/auth/capability-discovery.js +18 -8
  3. package/dist/cjs/auth/capability-discovery.js.map +1 -1
  4. package/dist/cjs/auth/discovery-fetch.d.cts +35 -0
  5. package/dist/cjs/auth/discovery-fetch.d.ts +35 -0
  6. package/dist/cjs/auth/discovery-fetch.js +658 -0
  7. package/dist/cjs/auth/discovery-fetch.js.map +1 -0
  8. package/dist/cjs/auth/index.d.cts +1 -0
  9. package/dist/cjs/auth/index.d.ts +1 -0
  10. package/dist/cjs/auth/index.js +7 -0
  11. package/dist/cjs/auth/index.js.map +1 -1
  12. package/dist/cjs/auth/interactive-oauth-flow.d.cts +20 -13
  13. package/dist/cjs/auth/interactive-oauth-flow.d.ts +20 -13
  14. package/dist/cjs/auth/interactive-oauth-flow.js +48 -24
  15. package/dist/cjs/auth/interactive-oauth-flow.js.map +1 -1
  16. package/dist/cjs/auth/oauth-callback-listener.js +4 -0
  17. package/dist/cjs/auth/oauth-callback-listener.js.map +1 -1
  18. package/dist/cjs/auth/rfc9728-discovery.d.cts +12 -25
  19. package/dist/cjs/auth/rfc9728-discovery.d.ts +12 -25
  20. package/dist/cjs/auth/rfc9728-discovery.js +59 -48
  21. package/dist/cjs/auth/rfc9728-discovery.js.map +1 -1
  22. package/dist/cjs/auth/types.d.cts +34 -4
  23. package/dist/cjs/auth/types.d.ts +34 -4
  24. package/dist/cjs/auth/types.js.map +1 -1
  25. package/dist/cjs/dcr/dcr-authenticator.d.cts +9 -5
  26. package/dist/cjs/dcr/dcr-authenticator.d.ts +9 -5
  27. package/dist/cjs/dcr/dcr-authenticator.js +436 -43
  28. package/dist/cjs/dcr/dcr-authenticator.js.map +1 -1
  29. package/dist/cjs/dcr/dynamic-client-registrar.d.cts +7 -18
  30. package/dist/cjs/dcr/dynamic-client-registrar.d.ts +7 -18
  31. package/dist/cjs/dcr/dynamic-client-registrar.js +18 -25
  32. package/dist/cjs/dcr/dynamic-client-registrar.js.map +1 -1
  33. package/dist/cjs/index.d.cts +1 -0
  34. package/dist/cjs/index.d.ts +1 -0
  35. package/dist/cjs/index.js +7 -0
  36. package/dist/cjs/index.js.map +1 -1
  37. package/dist/cjs/lib/url-utils.js +2 -2
  38. package/dist/cjs/lib/url-utils.js.map +1 -1
  39. package/dist/esm/auth/capability-discovery.js +17 -7
  40. package/dist/esm/auth/capability-discovery.js.map +1 -1
  41. package/dist/esm/auth/discovery-fetch.d.ts +35 -0
  42. package/dist/esm/auth/discovery-fetch.js +184 -0
  43. package/dist/esm/auth/discovery-fetch.js.map +1 -0
  44. package/dist/esm/auth/index.d.ts +1 -0
  45. package/dist/esm/auth/index.js +1 -0
  46. package/dist/esm/auth/index.js.map +1 -1
  47. package/dist/esm/auth/interactive-oauth-flow.d.ts +20 -13
  48. package/dist/esm/auth/interactive-oauth-flow.js +50 -23
  49. package/dist/esm/auth/interactive-oauth-flow.js.map +1 -1
  50. package/dist/esm/auth/oauth-callback-listener.js +4 -0
  51. package/dist/esm/auth/oauth-callback-listener.js.map +1 -1
  52. package/dist/esm/auth/rfc9728-discovery.d.ts +12 -25
  53. package/dist/esm/auth/rfc9728-discovery.js +49 -56
  54. package/dist/esm/auth/rfc9728-discovery.js.map +1 -1
  55. package/dist/esm/auth/types.d.ts +34 -4
  56. package/dist/esm/auth/types.js.map +1 -1
  57. package/dist/esm/dcr/dcr-authenticator.d.ts +9 -5
  58. package/dist/esm/dcr/dcr-authenticator.js +92 -39
  59. package/dist/esm/dcr/dcr-authenticator.js.map +1 -1
  60. package/dist/esm/dcr/dynamic-client-registrar.d.ts +7 -18
  61. package/dist/esm/dcr/dynamic-client-registrar.js +19 -23
  62. package/dist/esm/dcr/dynamic-client-registrar.js.map +1 -1
  63. package/dist/esm/index.d.ts +1 -0
  64. package/dist/esm/index.js +1 -0
  65. package/dist/esm/index.js.map +1 -1
  66. package/dist/esm/lib/url-utils.js +2 -2
  67. package/dist/esm/lib/url-utils.js.map +1 -1
  68. package/package.json +3 -8
@@ -4,37 +4,24 @@
4
4
  */
5
5
  import type { AuthorizationServerMetadata, ProtectedResourceMetadata } from './types.js';
6
6
  /**
7
- * Normalize a resource URL by stripping query/hash and trailing slashes.
8
- */
9
- /**
10
- * Discover OAuth 2.0 Protected Resource Metadata (RFC 9728)
11
- * Probes .well-known/oauth-protected-resource endpoint
7
+ * Discovers RFC 9728 Protected Resource Metadata: the `WWW-Authenticate`
8
+ * header first, then `.well-known/oauth-protected-resource` at the origin root and, for a path-prefixed resource, its sub-path variant.
12
9
  *
13
- * Discovery Strategy:
14
- * 1. Try origin root: {origin}/.well-known/oauth-protected-resource
15
- * 2. If 404, try sub-path: {origin}/.well-known/oauth-protected-resource{path}
16
- *
17
- * @param resourceUrl - URL of the protected resource (e.g., https://ai.todoist.net/mcp)
18
- * @returns ProtectedResourceMetadata if discovered, null otherwise
19
- *
20
- * @example
21
- * // Todoist case: MCP at ai.todoist.net/mcp, OAuth at todoist.com
22
- * const metadata = await discoverProtectedResourceMetadata('https://ai.todoist.net/mcp');
23
- * // Returns: { resource: "https://ai.todoist.net/mcp", authorization_servers: ["https://todoist.com"] }
10
+ * @param resourceUrl - URL of the protected resource (e.g. `https://ai.todoist.net/mcp`).
11
+ * @returns Discovered metadata, or `null` if none is found.
24
12
  */
25
13
  export declare function discoverProtectedResourceMetadata(resourceUrl: string): Promise<ProtectedResourceMetadata | null>;
26
14
  /**
27
- * Discover OAuth 2.0 Authorization Server Metadata (RFC 8414)
28
- * Probes .well-known/oauth-authorization-server endpoint
29
- *
30
- * @param authServerUrl - URL of the authorization server (typically from RFC 9728 discovery)
31
- * @returns AuthorizationServerMetadata if discovered, null otherwise
15
+ * Discovers RFC 8414 Authorization Server Metadata at
16
+ * `.well-known/oauth-authorization-server`, path-local variant first.
32
17
  *
33
- * @example
34
- * const metadata = await discoverAuthorizationServerMetadata('https://todoist.com');
35
- * // Returns: { issuer: "https://todoist.com", authorization_endpoint: "...", ... }
18
+ * @param authServerUrl - URL of the authorization server (typically from RFC 9728 discovery).
19
+ * @param options.allowLoopback - Loopback trust grant computed from the server the caller is actually talking to, never from `authServerUrl` itself. Defaults to `false`.
20
+ * @returns Discovered metadata, or `null` if none is found.
36
21
  */
37
- export declare function discoverAuthorizationServerMetadata(authServerUrl: string): Promise<AuthorizationServerMetadata | null>;
22
+ export declare function discoverAuthorizationServerMetadata(authServerUrl: string, options?: {
23
+ allowLoopback?: boolean;
24
+ }): Promise<AuthorizationServerMetadata | null>;
38
25
  /**
39
26
  * Discover OAuth Authorization Server Issuer from resource response (RFC 9207)
40
27
  *
@@ -2,69 +2,50 @@
2
2
  * RFC 9728 Protected Resource Metadata Discovery
3
3
  * Probes .well-known/oauth-protected-resource endpoint
4
4
  */ import { joinWellKnown, normalizeUrl } from '../lib/url-utils.js';
5
- /**
6
- * Extract origin (protocol + host) from a URL
7
- * @param url - Full URL that may include a path
8
- * @returns Origin (e.g., "https://example.com") or original string if invalid URL
9
- *
10
- * @example
11
- * getOrigin('https://example.com/mcp') // → 'https://example.com'
12
- * getOrigin('http://localhost:9999/api/v1/mcp') // → 'http://localhost:9999'
13
- */ function getOrigin(url) {
5
+ import { discoveryFetch, isLoopbackUrl, readDiscoveryJson } from './discovery-fetch.js';
6
+ /** Returns `url`'s origin (protocol + host), or the original string if it doesn't parse. */ function getOrigin(url) {
14
7
  try {
15
8
  return new URL(url).origin;
16
9
  } catch {
17
- // Invalid URL - return as-is for graceful degradation
18
10
  return url;
19
11
  }
20
12
  }
21
- /**
22
- * Extract path from a URL (without origin)
23
- * @param url - Full URL
24
- * @returns Path component (e.g., "/mcp", "/api/v1/mcp") or empty string if no path
25
- */ function getPath(url) {
13
+ /** Returns `url`'s pathname, or `''` for the root path or an unparseable URL. */ function getPath(url) {
26
14
  try {
27
15
  const parsed = new URL(url);
28
- // pathname includes leading slash, e.g., "/mcp"
29
16
  return parsed.pathname === '/' ? '' : parsed.pathname;
30
17
  } catch {
31
18
  return '';
32
19
  }
33
20
  }
34
21
  /**
35
- * Normalize a resource URL by stripping query/hash and trailing slashes.
36
- */ /**
37
- * Discover OAuth 2.0 Protected Resource Metadata (RFC 9728)
38
- * Probes .well-known/oauth-protected-resource endpoint
22
+ * Discovers RFC 9728 Protected Resource Metadata: the `WWW-Authenticate`
23
+ * header first, then `.well-known/oauth-protected-resource` at the origin root and, for a path-prefixed resource, its sub-path variant.
39
24
  *
40
- * Discovery Strategy:
41
- * 1. Try origin root: {origin}/.well-known/oauth-protected-resource
42
- * 2. If 404, try sub-path: {origin}/.well-known/oauth-protected-resource{path}
43
- *
44
- * @param resourceUrl - URL of the protected resource (e.g., https://ai.todoist.net/mcp)
45
- * @returns ProtectedResourceMetadata if discovered, null otherwise
46
- *
47
- * @example
48
- * // Todoist case: MCP at ai.todoist.net/mcp, OAuth at todoist.com
49
- * const metadata = await discoverProtectedResourceMetadata('https://ai.todoist.net/mcp');
50
- * // Returns: { resource: "https://ai.todoist.net/mcp", authorization_servers: ["https://todoist.com"] }
25
+ * @param resourceUrl - URL of the protected resource (e.g. `https://ai.todoist.net/mcp`).
26
+ * @returns Discovered metadata, or `null` if none is found.
51
27
  */ export async function discoverProtectedResourceMetadata(resourceUrl) {
52
28
  try {
53
29
  const normalizedResourceUrl = normalizeUrl(resourceUrl);
54
- const headerMetadata = await discoverProtectedResourceMetadataFromHeader(normalizedResourceUrl);
30
+ // resourceUrl is the server the caller configured (never remote-supplied);
31
+ // its loopback-ness is the trust signal every fetch below relies on.
32
+ const allowLoopback = isLoopbackUrl(normalizedResourceUrl);
33
+ const headerMetadata = await discoverProtectedResourceMetadataFromHeader(normalizedResourceUrl, allowLoopback);
55
34
  if (headerMetadata) return headerMetadata;
56
35
  // Strategy 0: Try path-local well-known (supports path-prefixed deployments like /outlook)
57
36
  const localWellKnownUrl = joinWellKnown(normalizedResourceUrl, '/.well-known/oauth-protected-resource');
58
37
  try {
59
- const response = await fetch(localWellKnownUrl, {
38
+ const response = await discoveryFetch(localWellKnownUrl, {
60
39
  method: 'GET',
61
40
  headers: {
62
41
  Accept: 'application/json',
63
42
  Connection: 'close'
64
43
  }
44
+ }, 'protected resource metadata (path-local)', {
45
+ allowLoopback
65
46
  });
66
47
  if (response.ok) {
67
- return await response.json();
48
+ return await readDiscoveryJson(response, 'protected resource metadata (path-local)');
68
49
  }
69
50
  } catch {
70
51
  // Continue to origin-based discovery
@@ -74,15 +55,17 @@
74
55
  // Strategy 1: Try root location (REQUIRED by RFC 9728)
75
56
  const rootUrl = `${origin}/.well-known/oauth-protected-resource`;
76
57
  try {
77
- const response = await fetch(rootUrl, {
58
+ const response = await discoveryFetch(rootUrl, {
78
59
  method: 'GET',
79
60
  headers: {
80
61
  Accept: 'application/json',
81
62
  Connection: 'close'
82
63
  }
64
+ }, 'protected resource metadata (root)', {
65
+ allowLoopback
83
66
  });
84
67
  if (response.ok) {
85
- const metadata = await response.json();
68
+ const metadata = await readDiscoveryJson(response, 'protected resource metadata (root)');
86
69
  // Check if the discovered resource matches what we're looking for
87
70
  if (metadata.resource === normalizedResourceUrl) {
88
71
  return metadata;
@@ -101,15 +84,17 @@
101
84
  // Try sub-path location for more specific metadata
102
85
  const subPathUrl = `${origin}/.well-known/oauth-protected-resource${path}`;
103
86
  try {
104
- const subPathResponse = await fetch(subPathUrl, {
87
+ const subPathResponse = await discoveryFetch(subPathUrl, {
105
88
  method: 'GET',
106
89
  headers: {
107
90
  Accept: 'application/json',
108
91
  Connection: 'close'
109
92
  }
93
+ }, 'protected resource metadata (sub-path)', {
94
+ allowLoopback
110
95
  });
111
96
  if (subPathResponse.ok) {
112
- return await subPathResponse.json();
97
+ return await readDiscoveryJson(subPathResponse, 'protected resource metadata (sub-path)');
113
98
  }
114
99
  } catch {
115
100
  // Sub-path failed, use root metadata
@@ -127,15 +112,17 @@
127
112
  if (path) {
128
113
  const subPathUrl = `${origin}/.well-known/oauth-protected-resource${path}`;
129
114
  try {
130
- const response = await fetch(subPathUrl, {
115
+ const response = await discoveryFetch(subPathUrl, {
131
116
  method: 'GET',
132
117
  headers: {
133
118
  Accept: 'application/json',
134
119
  Connection: 'close'
135
120
  }
121
+ }, 'protected resource metadata (sub-path)', {
122
+ allowLoopback
136
123
  });
137
124
  if (response.ok) {
138
- return await response.json();
125
+ return await readDiscoveryJson(response, 'protected resource metadata (sub-path)');
139
126
  }
140
127
  } catch {
141
128
  // Fall through to return null
@@ -148,7 +135,7 @@
148
135
  return null;
149
136
  }
150
137
  }
151
- async function discoverProtectedResourceMetadataFromHeader(resourceUrl) {
138
+ async function discoverProtectedResourceMetadataFromHeader(resourceUrl, allowLoopback) {
152
139
  try {
153
140
  const response = await fetch(resourceUrl, {
154
141
  method: 'GET',
@@ -173,59 +160,65 @@ async function discoverProtectedResourceMetadataFromHeader(resourceUrl) {
173
160
  if (!header) return null;
174
161
  const match = header.match(/resource_metadata="([^"]+)"/i);
175
162
  if (!match || !match[1]) return null;
163
+ // metadataUrl is remote-server-chosen (the primary SSRF vector here);
164
+ // allowLoopback reflects trust in resourceUrl, not in metadataUrl.
176
165
  const metadataUrl = match[1];
177
- const metadataResponse = await fetch(metadataUrl, {
166
+ const metadataResponse = await discoveryFetch(metadataUrl, {
178
167
  method: 'GET',
179
168
  headers: {
180
169
  Accept: 'application/json',
181
170
  Connection: 'close'
182
171
  }
172
+ }, 'resource_metadata URL', {
173
+ allowLoopback
183
174
  });
184
175
  if (!metadataResponse.ok) {
185
176
  return null;
186
177
  }
187
- return await metadataResponse.json();
178
+ return await readDiscoveryJson(metadataResponse, 'resource_metadata URL');
188
179
  } catch (_error) {
189
180
  return null;
190
181
  }
191
182
  }
192
183
  /**
193
- * Discover OAuth 2.0 Authorization Server Metadata (RFC 8414)
194
- * Probes .well-known/oauth-authorization-server endpoint
195
- *
196
- * @param authServerUrl - URL of the authorization server (typically from RFC 9728 discovery)
197
- * @returns AuthorizationServerMetadata if discovered, null otherwise
184
+ * Discovers RFC 8414 Authorization Server Metadata at
185
+ * `.well-known/oauth-authorization-server`, path-local variant first.
198
186
  *
199
- * @example
200
- * const metadata = await discoverAuthorizationServerMetadata('https://todoist.com');
201
- * // Returns: { issuer: "https://todoist.com", authorization_endpoint: "...", ... }
202
- */ export async function discoverAuthorizationServerMetadata(authServerUrl) {
187
+ * @param authServerUrl - URL of the authorization server (typically from RFC 9728 discovery).
188
+ * @param options.allowLoopback - Loopback trust grant computed from the server the caller is actually talking to, never from `authServerUrl` itself. Defaults to `false`.
189
+ * @returns Discovered metadata, or `null` if none is found.
190
+ */ export async function discoverAuthorizationServerMetadata(authServerUrl, options = {}) {
191
+ const { allowLoopback = false } = options;
203
192
  try {
204
193
  const normalizedAuthServerUrl = normalizeUrl(authServerUrl);
205
194
  const localWellKnownUrl = joinWellKnown(normalizedAuthServerUrl, '/.well-known/oauth-authorization-server');
206
- const localResponse = await fetch(localWellKnownUrl, {
195
+ const localResponse = await discoveryFetch(localWellKnownUrl, {
207
196
  method: 'GET',
208
197
  headers: {
209
198
  Accept: 'application/json',
210
199
  Connection: 'close'
211
200
  }
201
+ }, 'authorization server metadata (path-local)', {
202
+ allowLoopback
212
203
  });
213
204
  if (localResponse.ok) {
214
- return await localResponse.json();
205
+ return await readDiscoveryJson(localResponse, 'authorization server metadata (path-local)');
215
206
  }
216
207
  const origin = getOrigin(normalizedAuthServerUrl);
217
208
  const wellKnownUrl = `${origin}/.well-known/oauth-authorization-server`;
218
- const response = await fetch(wellKnownUrl, {
209
+ const response = await discoveryFetch(wellKnownUrl, {
219
210
  method: 'GET',
220
211
  headers: {
221
212
  Accept: 'application/json',
222
213
  Connection: 'close'
223
214
  }
215
+ }, 'authorization server metadata', {
216
+ allowLoopback
224
217
  });
225
218
  if (!response.ok) {
226
219
  return null;
227
220
  }
228
- return await response.json();
221
+ return await readDiscoveryJson(response, 'authorization server metadata');
229
222
  } catch (_error) {
230
223
  return null;
231
224
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/mcp-z/client/src/auth/rfc9728-discovery.ts"],"sourcesContent":["/**\n * RFC 9728 Protected Resource Metadata Discovery\n * Probes .well-known/oauth-protected-resource endpoint\n */\n\nimport { joinWellKnown, normalizeUrl } from '../lib/url-utils.ts';\nimport type { AuthorizationServerMetadata, ProtectedResourceMetadata } 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 * Extract path from a URL (without origin)\n * @param url - Full URL\n * @returns Path component (e.g., \"/mcp\", \"/api/v1/mcp\") or empty string if no path\n */\nfunction getPath(url: string): string {\n try {\n const parsed = new URL(url);\n // pathname includes leading slash, e.g., \"/mcp\"\n return parsed.pathname === '/' ? '' : parsed.pathname;\n } catch {\n return '';\n }\n}\n\n/**\n * Normalize a resource URL by stripping query/hash and trailing slashes.\n */\n/**\n * Discover OAuth 2.0 Protected Resource Metadata (RFC 9728)\n * Probes .well-known/oauth-protected-resource endpoint\n *\n * Discovery Strategy:\n * 1. Try origin root: {origin}/.well-known/oauth-protected-resource\n * 2. If 404, try sub-path: {origin}/.well-known/oauth-protected-resource{path}\n *\n * @param resourceUrl - URL of the protected resource (e.g., https://ai.todoist.net/mcp)\n * @returns ProtectedResourceMetadata if discovered, null otherwise\n *\n * @example\n * // Todoist case: MCP at ai.todoist.net/mcp, OAuth at todoist.com\n * const metadata = await discoverProtectedResourceMetadata('https://ai.todoist.net/mcp');\n * // Returns: { resource: \"https://ai.todoist.net/mcp\", authorization_servers: [\"https://todoist.com\"] }\n */\nexport async function discoverProtectedResourceMetadata(resourceUrl: string): Promise<ProtectedResourceMetadata | null> {\n try {\n const normalizedResourceUrl = normalizeUrl(resourceUrl);\n const headerMetadata = await discoverProtectedResourceMetadataFromHeader(normalizedResourceUrl);\n if (headerMetadata) return headerMetadata;\n\n // Strategy 0: Try path-local well-known (supports path-prefixed deployments like /outlook)\n const localWellKnownUrl = joinWellKnown(normalizedResourceUrl, '/.well-known/oauth-protected-resource');\n try {\n const response = await fetch(localWellKnownUrl, {\n method: 'GET',\n headers: { Accept: 'application/json', Connection: 'close' },\n });\n if (response.ok) {\n return (await response.json()) as ProtectedResourceMetadata;\n }\n } catch {\n // Continue to origin-based discovery\n }\n\n const origin = getOrigin(normalizedResourceUrl);\n const path = getPath(normalizedResourceUrl);\n\n // Strategy 1: Try root location (REQUIRED by RFC 9728)\n const rootUrl = `${origin}/.well-known/oauth-protected-resource`;\n\n try {\n const response = await fetch(rootUrl, {\n method: 'GET',\n headers: { Accept: 'application/json', Connection: 'close' },\n });\n\n if (response.ok) {\n const metadata = (await response.json()) as ProtectedResourceMetadata;\n // Check if the discovered resource matches what we're looking for\n if (metadata.resource === normalizedResourceUrl) {\n return metadata;\n }\n // If there's no path component, return root metadata\n // (e.g., looking for http://example.com and found it)\n if (!path) {\n return metadata;\n }\n // If requested URL starts with metadata.resource, the root metadata applies to sub-paths\n // (e.g., looking for http://example.com/api/v1/mcp, found http://example.com)\n if (normalizedResourceUrl.startsWith(metadata.resource)) {\n // Still try sub-path location to see if there's more specific metadata\n // But save root metadata as fallback\n const rootMetadata = metadata;\n\n // Try sub-path location for more specific metadata\n const subPathUrl = `${origin}/.well-known/oauth-protected-resource${path}`;\n try {\n const subPathResponse = await fetch(subPathUrl, {\n method: 'GET',\n headers: { Accept: 'application/json', Connection: 'close' },\n });\n if (subPathResponse.ok) {\n return (await subPathResponse.json()) as ProtectedResourceMetadata;\n }\n } catch {\n // Sub-path failed, use root metadata\n }\n\n // Return root metadata as it applies to this resource\n return rootMetadata;\n }\n // Otherwise, try sub-path location before giving up\n }\n } catch {\n // Continue to sub-path location\n }\n\n // Strategy 2: Try sub-path location (MCP spec extension)\n // Only try if there's a path component\n if (path) {\n const subPathUrl = `${origin}/.well-known/oauth-protected-resource${path}`;\n\n try {\n const response = await fetch(subPathUrl, {\n method: 'GET',\n headers: { Accept: 'application/json', Connection: 'close' },\n });\n\n if (response.ok) {\n return (await response.json()) as ProtectedResourceMetadata;\n }\n } catch {\n // Fall through to return null\n }\n }\n\n // Neither location found or resource didn't match\n return null;\n } catch (_error) {\n // Network error, invalid URL, or other failure\n return null;\n }\n}\n\nasync function discoverProtectedResourceMetadataFromHeader(resourceUrl: string): Promise<ProtectedResourceMetadata | null> {\n try {\n const response = await fetch(resourceUrl, {\n method: 'GET',\n headers: { Accept: 'application/json', Connection: 'close' },\n });\n\n let header = response.headers.get('www-authenticate');\n if (!header) {\n const postResponse = await fetch(resourceUrl, {\n method: 'POST',\n headers: { Accept: 'application/json', Connection: 'close', 'Content-Type': 'application/json' },\n body: '{}',\n });\n header = postResponse.headers.get('www-authenticate');\n }\n\n if (!header) return null;\n\n const match = header.match(/resource_metadata=\"([^\"]+)\"/i);\n if (!match || !match[1]) return null;\n\n const metadataUrl = match[1];\n const metadataResponse = await fetch(metadataUrl, {\n method: 'GET',\n headers: { Accept: 'application/json', Connection: 'close' },\n });\n\n if (!metadataResponse.ok) {\n return null;\n }\n\n return (await metadataResponse.json()) as ProtectedResourceMetadata;\n } catch (_error) {\n return null;\n }\n}\n\n/**\n * Discover OAuth 2.0 Authorization Server Metadata (RFC 8414)\n * Probes .well-known/oauth-authorization-server endpoint\n *\n * @param authServerUrl - URL of the authorization server (typically from RFC 9728 discovery)\n * @returns AuthorizationServerMetadata if discovered, null otherwise\n *\n * @example\n * const metadata = await discoverAuthorizationServerMetadata('https://todoist.com');\n * // Returns: { issuer: \"https://todoist.com\", authorization_endpoint: \"...\", ... }\n */\nexport async function discoverAuthorizationServerMetadata(authServerUrl: string): Promise<AuthorizationServerMetadata | null> {\n try {\n const normalizedAuthServerUrl = normalizeUrl(authServerUrl);\n const localWellKnownUrl = joinWellKnown(normalizedAuthServerUrl, '/.well-known/oauth-authorization-server');\n const localResponse = await fetch(localWellKnownUrl, {\n method: 'GET',\n headers: { Accept: 'application/json', Connection: 'close' },\n });\n\n if (localResponse.ok) {\n return (await localResponse.json()) as AuthorizationServerMetadata;\n }\n\n const origin = getOrigin(normalizedAuthServerUrl);\n const wellKnownUrl = `${origin}/.well-known/oauth-authorization-server`;\n\n const response = await fetch(wellKnownUrl, {\n method: 'GET',\n headers: { Accept: 'application/json', Connection: 'close' },\n });\n\n if (!response.ok) {\n return null;\n }\n\n return (await response.json()) as AuthorizationServerMetadata;\n } catch (_error) {\n return null;\n }\n}\n\n/**\n * Discover OAuth Authorization Server Issuer from resource response (RFC 9207)\n *\n * @param resourceUrl - URL of the protected resource\n * @returns Issuer URL if present in WWW-Authenticate header, null otherwise\n */\nexport async function discoverAuthorizationServerIssuer(resourceUrl: string): Promise<string | null> {\n try {\n const response = await fetch(resourceUrl, {\n method: 'GET',\n headers: { Accept: 'application/json', Connection: 'close' },\n });\n\n const header = response.headers.get('www-authenticate');\n if (!header) return null;\n\n const match = header.match(/(?:authorization_server|issuer)=\"([^\"]+)\"/i);\n if (!match) return null;\n\n return match[1] ?? null;\n } catch (_error) {\n return null;\n }\n}\n"],"names":["joinWellKnown","normalizeUrl","getOrigin","url","URL","origin","getPath","parsed","pathname","discoverProtectedResourceMetadata","resourceUrl","normalizedResourceUrl","headerMetadata","discoverProtectedResourceMetadataFromHeader","localWellKnownUrl","response","fetch","method","headers","Accept","Connection","ok","json","path","rootUrl","metadata","resource","startsWith","rootMetadata","subPathUrl","subPathResponse","_error","header","get","postResponse","body","match","metadataUrl","metadataResponse","discoverAuthorizationServerMetadata","authServerUrl","normalizedAuthServerUrl","localResponse","wellKnownUrl","discoverAuthorizationServerIssuer"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/mcp-z/client/src/auth/rfc9728-discovery.ts"],"sourcesContent":["/**\n * RFC 9728 Protected Resource Metadata Discovery\n * Probes .well-known/oauth-protected-resource endpoint\n */\n\nimport { joinWellKnown, normalizeUrl } from '../lib/url-utils.ts';\nimport { discoveryFetch, isLoopbackUrl, readDiscoveryJson } from './discovery-fetch.ts';\nimport type { AuthorizationServerMetadata, ProtectedResourceMetadata } from './types.ts';\n\n/** Returns `url`'s origin (protocol + host), or the original string if it doesn't parse. */\nfunction getOrigin(url: string): string {\n try {\n return new URL(url).origin;\n } catch {\n return url;\n }\n}\n\n/** Returns `url`'s pathname, or `''` for the root path or an unparseable URL. */\nfunction getPath(url: string): string {\n try {\n const parsed = new URL(url);\n return parsed.pathname === '/' ? '' : parsed.pathname;\n } catch {\n return '';\n }\n}\n\n/**\n * Discovers RFC 9728 Protected Resource Metadata: the `WWW-Authenticate`\n * header first, then `.well-known/oauth-protected-resource` at the origin root and, for a path-prefixed resource, its sub-path variant.\n *\n * @param resourceUrl - URL of the protected resource (e.g. `https://ai.todoist.net/mcp`).\n * @returns Discovered metadata, or `null` if none is found.\n */\nexport async function discoverProtectedResourceMetadata(resourceUrl: string): Promise<ProtectedResourceMetadata | null> {\n try {\n const normalizedResourceUrl = normalizeUrl(resourceUrl);\n // resourceUrl is the server the caller configured (never remote-supplied);\n // its loopback-ness is the trust signal every fetch below relies on.\n const allowLoopback = isLoopbackUrl(normalizedResourceUrl);\n const headerMetadata = await discoverProtectedResourceMetadataFromHeader(normalizedResourceUrl, allowLoopback);\n if (headerMetadata) return headerMetadata;\n\n // Strategy 0: Try path-local well-known (supports path-prefixed deployments like /outlook)\n const localWellKnownUrl = joinWellKnown(normalizedResourceUrl, '/.well-known/oauth-protected-resource');\n try {\n const response = await discoveryFetch(\n localWellKnownUrl,\n {\n method: 'GET',\n headers: { Accept: 'application/json', Connection: 'close' },\n },\n 'protected resource metadata (path-local)',\n { allowLoopback }\n );\n if (response.ok) {\n return await readDiscoveryJson<ProtectedResourceMetadata>(response, 'protected resource metadata (path-local)');\n }\n } catch {\n // Continue to origin-based discovery\n }\n\n const origin = getOrigin(normalizedResourceUrl);\n const path = getPath(normalizedResourceUrl);\n\n // Strategy 1: Try root location (REQUIRED by RFC 9728)\n const rootUrl = `${origin}/.well-known/oauth-protected-resource`;\n\n try {\n const response = await discoveryFetch(\n rootUrl,\n {\n method: 'GET',\n headers: { Accept: 'application/json', Connection: 'close' },\n },\n 'protected resource metadata (root)',\n { allowLoopback }\n );\n\n if (response.ok) {\n const metadata = await readDiscoveryJson<ProtectedResourceMetadata>(response, 'protected resource metadata (root)');\n // Check if the discovered resource matches what we're looking for\n if (metadata.resource === normalizedResourceUrl) {\n return metadata;\n }\n // If there's no path component, return root metadata\n // (e.g., looking for http://example.com and found it)\n if (!path) {\n return metadata;\n }\n // If requested URL starts with metadata.resource, the root metadata applies to sub-paths\n // (e.g., looking for http://example.com/api/v1/mcp, found http://example.com)\n if (normalizedResourceUrl.startsWith(metadata.resource)) {\n // Still try sub-path location to see if there's more specific metadata\n // But save root metadata as fallback\n const rootMetadata = metadata;\n\n // Try sub-path location for more specific metadata\n const subPathUrl = `${origin}/.well-known/oauth-protected-resource${path}`;\n try {\n const subPathResponse = await discoveryFetch(\n subPathUrl,\n {\n method: 'GET',\n headers: { Accept: 'application/json', Connection: 'close' },\n },\n 'protected resource metadata (sub-path)',\n { allowLoopback }\n );\n if (subPathResponse.ok) {\n return await readDiscoveryJson<ProtectedResourceMetadata>(subPathResponse, 'protected resource metadata (sub-path)');\n }\n } catch {\n // Sub-path failed, use root metadata\n }\n\n // Return root metadata as it applies to this resource\n return rootMetadata;\n }\n // Otherwise, try sub-path location before giving up\n }\n } catch {\n // Continue to sub-path location\n }\n\n // Strategy 2: Try sub-path location (MCP spec extension)\n // Only try if there's a path component\n if (path) {\n const subPathUrl = `${origin}/.well-known/oauth-protected-resource${path}`;\n\n try {\n const response = await discoveryFetch(\n subPathUrl,\n {\n method: 'GET',\n headers: { Accept: 'application/json', Connection: 'close' },\n },\n 'protected resource metadata (sub-path)',\n { allowLoopback }\n );\n\n if (response.ok) {\n return await readDiscoveryJson<ProtectedResourceMetadata>(response, 'protected resource metadata (sub-path)');\n }\n } catch {\n // Fall through to return null\n }\n }\n\n // Neither location found or resource didn't match\n return null;\n } catch (_error) {\n // Network error, invalid URL, or other failure\n return null;\n }\n}\n\nasync function discoverProtectedResourceMetadataFromHeader(resourceUrl: string, allowLoopback: boolean): Promise<ProtectedResourceMetadata | null> {\n try {\n const response = await fetch(resourceUrl, {\n method: 'GET',\n headers: { Accept: 'application/json', Connection: 'close' },\n });\n\n let header = response.headers.get('www-authenticate');\n if (!header) {\n const postResponse = await fetch(resourceUrl, {\n method: 'POST',\n headers: { Accept: 'application/json', Connection: 'close', 'Content-Type': 'application/json' },\n body: '{}',\n });\n header = postResponse.headers.get('www-authenticate');\n }\n\n if (!header) return null;\n\n const match = header.match(/resource_metadata=\"([^\"]+)\"/i);\n if (!match || !match[1]) return null;\n\n // metadataUrl is remote-server-chosen (the primary SSRF vector here);\n // allowLoopback reflects trust in resourceUrl, not in metadataUrl.\n const metadataUrl = match[1];\n const metadataResponse = await discoveryFetch(\n metadataUrl,\n {\n method: 'GET',\n headers: { Accept: 'application/json', Connection: 'close' },\n },\n 'resource_metadata URL',\n { allowLoopback }\n );\n\n if (!metadataResponse.ok) {\n return null;\n }\n\n return await readDiscoveryJson<ProtectedResourceMetadata>(metadataResponse, 'resource_metadata URL');\n } catch (_error) {\n return null;\n }\n}\n\n/**\n * Discovers RFC 8414 Authorization Server Metadata at\n * `.well-known/oauth-authorization-server`, path-local variant first.\n *\n * @param authServerUrl - URL of the authorization server (typically from RFC 9728 discovery).\n * @param options.allowLoopback - Loopback trust grant computed from the server the caller is actually talking to, never from `authServerUrl` itself. Defaults to `false`.\n * @returns Discovered metadata, or `null` if none is found.\n */\nexport async function discoverAuthorizationServerMetadata(authServerUrl: string, options: { allowLoopback?: boolean } = {}): Promise<AuthorizationServerMetadata | null> {\n const { allowLoopback = false } = options;\n try {\n const normalizedAuthServerUrl = normalizeUrl(authServerUrl);\n const localWellKnownUrl = joinWellKnown(normalizedAuthServerUrl, '/.well-known/oauth-authorization-server');\n const localResponse = await discoveryFetch(\n localWellKnownUrl,\n {\n method: 'GET',\n headers: { Accept: 'application/json', Connection: 'close' },\n },\n 'authorization server metadata (path-local)',\n { allowLoopback }\n );\n\n if (localResponse.ok) {\n return await readDiscoveryJson<AuthorizationServerMetadata>(localResponse, 'authorization server metadata (path-local)');\n }\n\n const origin = getOrigin(normalizedAuthServerUrl);\n const wellKnownUrl = `${origin}/.well-known/oauth-authorization-server`;\n\n const response = await discoveryFetch(\n wellKnownUrl,\n {\n method: 'GET',\n headers: { Accept: 'application/json', Connection: 'close' },\n },\n 'authorization server metadata',\n { allowLoopback }\n );\n\n if (!response.ok) {\n return null;\n }\n\n return await readDiscoveryJson<AuthorizationServerMetadata>(response, 'authorization server metadata');\n } catch (_error) {\n return null;\n }\n}\n\n/**\n * Discover OAuth Authorization Server Issuer from resource response (RFC 9207)\n *\n * @param resourceUrl - URL of the protected resource\n * @returns Issuer URL if present in WWW-Authenticate header, null otherwise\n */\nexport async function discoverAuthorizationServerIssuer(resourceUrl: string): Promise<string | null> {\n try {\n const response = await fetch(resourceUrl, {\n method: 'GET',\n headers: { Accept: 'application/json', Connection: 'close' },\n });\n\n const header = response.headers.get('www-authenticate');\n if (!header) return null;\n\n const match = header.match(/(?:authorization_server|issuer)=\"([^\"]+)\"/i);\n if (!match) return null;\n\n return match[1] ?? null;\n } catch (_error) {\n return null;\n }\n}\n"],"names":["joinWellKnown","normalizeUrl","discoveryFetch","isLoopbackUrl","readDiscoveryJson","getOrigin","url","URL","origin","getPath","parsed","pathname","discoverProtectedResourceMetadata","resourceUrl","normalizedResourceUrl","allowLoopback","headerMetadata","discoverProtectedResourceMetadataFromHeader","localWellKnownUrl","response","method","headers","Accept","Connection","ok","path","rootUrl","metadata","resource","startsWith","rootMetadata","subPathUrl","subPathResponse","_error","fetch","header","get","postResponse","body","match","metadataUrl","metadataResponse","discoverAuthorizationServerMetadata","authServerUrl","options","normalizedAuthServerUrl","localResponse","wellKnownUrl","discoverAuthorizationServerIssuer"],"mappings":"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"}
@@ -9,6 +9,8 @@ export interface CallbackResult {
9
9
  code: string;
10
10
  /** State parameter for CSRF protection */
11
11
  state?: string;
12
+ /** Issuer identifier of the authorization server that minted the response (RFC 9207) */
13
+ iss?: string;
12
14
  }
13
15
  /**
14
16
  * PKCE (Proof Key for Code Exchange) parameters (RFC 7636)
@@ -38,6 +40,8 @@ export interface TokenSet {
38
40
  clientId?: string;
39
41
  /** Client secret used for DCR registration (stored for future use) */
40
42
  clientSecret?: string;
43
+ /** Issuer identifier of the authorization server these credentials belong to (SEP-2352) */
44
+ issuer?: string;
41
45
  }
42
46
  /**
43
47
  * OAuth 2.0 Protected Resource Metadata (RFC 9728)
@@ -76,6 +80,8 @@ export interface AuthorizationServerMetadata {
76
80
  grant_types_supported?: string[];
77
81
  /** Token endpoint authentication methods supported */
78
82
  token_endpoint_auth_methods_supported?: string[];
83
+ /** Whether the authorization response carries an `iss` parameter (RFC 9207) */
84
+ authorization_response_iss_parameter_supported?: boolean;
79
85
  }
80
86
  /**
81
87
  * OAuth server capabilities discovered from .well-known endpoint
@@ -83,6 +89,10 @@ export interface AuthorizationServerMetadata {
83
89
  export interface AuthCapabilities {
84
90
  /** Whether the server supports Dynamic Client Registration (RFC 7591) */
85
91
  supportsDcr: boolean;
92
+ /** Issuer identifier from the authorization server metadata (RFC 8414) */
93
+ issuer?: string;
94
+ /** Whether the authorization response carries an `iss` parameter (RFC 9207) */
95
+ authorizationResponseIssSupported?: boolean;
86
96
  /** DCR client registration endpoint */
87
97
  registrationEndpoint?: string;
88
98
  /** OAuth authorization endpoint */
@@ -109,10 +119,18 @@ export interface ClientCredentials {
109
119
  * Options for DCR client registration
110
120
  */
111
121
  export interface DcrRegistrationOptions {
122
+ /** Redirect URI for OAuth callback, from the loopback listener the caller has already bound (RFC 8252) */
123
+ redirectUri: string;
112
124
  /** Client name to register */
113
125
  clientName?: string;
114
- /** Redirect URI for OAuth callback */
115
- redirectUri?: string;
126
+ /**
127
+ * Loopback trust grant for the registration_endpoint fetch (SSRF
128
+ * mitigation - see `src/auth/discovery-fetch.ts`). Compute this from the
129
+ * MCP server the caller is actually talking to, never from
130
+ * `registrationEndpoint` itself (which is typically sourced from
131
+ * remote-controlled AS metadata). Defaults to `false`.
132
+ */
133
+ allowLoopback?: boolean;
116
134
  }
117
135
  /**
118
136
  * Options for OAuth authorization flow
@@ -120,12 +138,16 @@ export interface DcrRegistrationOptions {
120
138
  export interface OAuthFlowOptions {
121
139
  /** Port for OAuth callback listener (required - use get-port to find available port) */
122
140
  port: number;
141
+ /** Issuer identifier discovered before the flow starts; the `iss` in the authorization response must match it (RFC 9207) */
142
+ issuer: string;
143
+ /** Canonical resource server URI, sent as `resource` on the authorization, token, and refresh requests (RFC 8707) */
144
+ resource: string;
123
145
  /** Redirect URI for OAuth callback (optional - will be built from port if not provided) */
124
146
  redirectUri?: string;
125
147
  /** OAuth scopes to request */
126
148
  scopes?: string[];
127
- /** Resource parameter (RFC 8707) - target resource server identifier */
128
- resource?: string;
149
+ /** Whether the authorization server advertises `authorization_response_iss_parameter_supported` (RFC 9207) */
150
+ authorizationResponseIssSupported?: boolean;
129
151
  /** Enable PKCE (RFC 7636) - recommended for all clients, required for public clients */
130
152
  pkce?: boolean;
131
153
  /** Headless mode (don't open browser) */
@@ -134,4 +156,12 @@ export interface OAuthFlowOptions {
134
156
  timeout?: number;
135
157
  /** Optional logger for debug output (defaults to singleton logger) */
136
158
  logger?: import('../utils/logger.js').Logger;
159
+ /**
160
+ * Loopback trust grant for the token endpoint fetch (SSRF mitigation - see
161
+ * `src/auth/discovery-fetch.ts`). Compute this from the MCP server the
162
+ * caller is actually talking to, never from `tokenEndpoint` itself (which
163
+ * is typically sourced from remote-controlled AS metadata). Defaults to
164
+ * `false`.
165
+ */
166
+ allowLoopback?: boolean;
137
167
  }
@@ -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}\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}\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}\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 /** 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 /** Client name to register */\n clientName?: string;\n /** Redirect URI for OAuth callback */\n redirectUri?: string;\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 /** 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 /** Resource parameter (RFC 8707) - target resource server identifier */\n resource?: string;\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"],"names":[],"mappings":"AAAA;;CAEC,GAED;;CAEC,GAuHD;;CAEC,GACD,WAiBC"}
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"}
@@ -58,16 +58,20 @@ export declare class DcrAuthenticator {
58
58
  * Self-hosted servers manage their own token storage via /oauth/verify
59
59
  */
60
60
  private ensureAuthenticatedSelfHosted;
61
- /**
62
- * Handle authentication for external OAuth providers (original implementation)
63
- */
61
+ /** Handles authentication for external (non-self-hosted) OAuth providers. */
64
62
  private ensureAuthenticatedExternal;
65
63
  /**
66
- * Refresh access token using refresh token
64
+ * Refreshes an access token using a refresh token.
65
+ * @param resource - Canonical resource server URI, audience-binding the token (RFC 8707).
66
+ * @param allowLoopback - Loopback trust grant computed from the server actually being talked to (SSRF mitigation, see discovery-fetch.ts). Defaults to `false`.
67
67
  */
68
68
  private refreshTokens;
69
69
  /**
70
- * Delete stored tokens for a server
70
+ * Deletes both stored token families for a server, across every issuer they were bound to.
71
+ * @throws CredentialBindingError if the configured store cannot enumerate keys.
71
72
  */
72
73
  deleteTokens(baseUrl: string): Promise<void>;
74
+ /** Discards a stored credential that is not bound to `issuer` (SEP-2352), including one stored before issuer binding existed. */
75
+ private loadTokens;
76
+ private buildFlowOptions;
73
77
  }