@mcp-z/client 1.0.3 → 1.0.5
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.
- package/dist/cjs/auth/capability-discovery.js +6 -4
- package/dist/cjs/auth/capability-discovery.js.map +1 -1
- package/dist/cjs/auth/rfc9728-discovery.d.cts +3 -0
- package/dist/cjs/auth/rfc9728-discovery.d.ts +3 -0
- package/dist/cjs/auth/rfc9728-discovery.js +140 -63
- package/dist/cjs/auth/rfc9728-discovery.js.map +1 -1
- package/dist/cjs/connection/connect-client.d.cts +9 -0
- package/dist/cjs/connection/connect-client.d.ts +9 -0
- package/dist/cjs/connection/connect-client.js +26 -9
- package/dist/cjs/connection/connect-client.js.map +1 -1
- package/dist/cjs/lib/url-utils.d.cts +2 -0
- package/dist/cjs/lib/url-utils.d.ts +2 -0
- package/dist/cjs/lib/url-utils.js +33 -0
- package/dist/cjs/lib/url-utils.js.map +1 -0
- package/dist/esm/auth/capability-discovery.js +6 -4
- package/dist/esm/auth/capability-discovery.js.map +1 -1
- package/dist/esm/auth/rfc9728-discovery.d.ts +3 -0
- package/dist/esm/auth/rfc9728-discovery.js +39 -7
- package/dist/esm/auth/rfc9728-discovery.js.map +1 -1
- package/dist/esm/connection/connect-client.d.ts +9 -0
- package/dist/esm/connection/connect-client.js +21 -5
- package/dist/esm/connection/connect-client.js.map +1 -1
- package/dist/esm/lib/url-utils.d.ts +2 -0
- package/dist/esm/lib/url-utils.js +14 -0
- package/dist/esm/lib/url-utils.js.map +1 -0
- package/package.json +11 -11
|
@@ -11,6 +11,7 @@ Object.defineProperty(exports, "probeAuthCapabilities", {
|
|
|
11
11
|
return probeAuthCapabilities;
|
|
12
12
|
}
|
|
13
13
|
});
|
|
14
|
+
var _urlutilsts = require("../lib/url-utils.js");
|
|
14
15
|
var _rfc9728discoveryts = require("./rfc9728-discovery.js");
|
|
15
16
|
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
|
|
16
17
|
try {
|
|
@@ -222,7 +223,7 @@ function resolveCapabilitiesFromAuthorizationServer(authServerUrl, scopes) {
|
|
|
222
223
|
}
|
|
223
224
|
function probeAuthCapabilities(baseUrl) {
|
|
224
225
|
return _async_to_generator(function() {
|
|
225
|
-
var resourceMetadata, authServerUrl, capabilities, issuer, issuerCapabilities, issuer1, issuerCapabilities1, origin, originCapabilities, _error;
|
|
226
|
+
var normalizedBaseUrl, resourceMetadata, authServerUrl, capabilities, issuer, issuerCapabilities, issuer1, issuerCapabilities1, origin, originCapabilities, _error;
|
|
226
227
|
return _ts_generator(this, function(_state) {
|
|
227
228
|
switch(_state.label){
|
|
228
229
|
case 0:
|
|
@@ -232,9 +233,10 @@ function probeAuthCapabilities(baseUrl) {
|
|
|
232
233
|
,
|
|
233
234
|
11
|
|
234
235
|
]);
|
|
236
|
+
normalizedBaseUrl = (0, _urlutilsts.normalizeUrl)(baseUrl);
|
|
235
237
|
return [
|
|
236
238
|
4,
|
|
237
|
-
(0, _rfc9728discoveryts.discoverProtectedResourceMetadata)(
|
|
239
|
+
(0, _rfc9728discoveryts.discoverProtectedResourceMetadata)(normalizedBaseUrl)
|
|
238
240
|
];
|
|
239
241
|
case 1:
|
|
240
242
|
resourceMetadata = _state.sent();
|
|
@@ -290,7 +292,7 @@ function probeAuthCapabilities(baseUrl) {
|
|
|
290
292
|
case 5:
|
|
291
293
|
return [
|
|
292
294
|
4,
|
|
293
|
-
(0, _rfc9728discoveryts.discoverAuthorizationServerIssuer)(
|
|
295
|
+
(0, _rfc9728discoveryts.discoverAuthorizationServerIssuer)(normalizedBaseUrl)
|
|
294
296
|
];
|
|
295
297
|
case 6:
|
|
296
298
|
issuer1 = _state.sent();
|
|
@@ -312,7 +314,7 @@ function probeAuthCapabilities(baseUrl) {
|
|
|
312
314
|
case 8:
|
|
313
315
|
// Strategy 2: Fall back to direct RFC 8414 discovery at resource origin
|
|
314
316
|
// This handles same-domain OAuth (traditional setup)
|
|
315
|
-
origin = getOrigin(
|
|
317
|
+
origin = getOrigin(normalizedBaseUrl);
|
|
316
318
|
return [
|
|
317
319
|
4,
|
|
318
320
|
resolveCapabilitiesFromAuthorizationServer(origin)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/Projects/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 { 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 };\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[]): Promise<AuthCapabilities | null> {\n const metadata = await discoverAuthorizationServerMetadata(authServerUrl);\n if (!metadata) return null;\n return buildCapabilities(metadata, scopes);\n}\n\nexport async function probeAuthCapabilities(baseUrl: string): Promise<AuthCapabilities> {\n try {\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(
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/Projects/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 { 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 };\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[]): Promise<AuthCapabilities | null> {\n const metadata = await discoverAuthorizationServerMetadata(authServerUrl);\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 // 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);\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);\n if (issuerCapabilities) return issuerCapabilities;\n }\n }\n\n const issuer = await discoverAuthorizationServerIssuer(normalizedBaseUrl);\n if (issuer) {\n const issuerCapabilities = await resolveCapabilitiesFromAuthorizationServer(issuer);\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);\n if (originCapabilities) return originCapabilities;\n\n // No OAuth metadata found\n return { supportsDcr: false };\n } catch (_error) {\n // Network error, invalid JSON, or other fetch failure\n // Gracefully degrade - assume no DCR support\n return { supportsDcr: false };\n }\n}\n"],"names":["probeAuthCapabilities","getOrigin","url","URL","origin","buildCapabilities","metadata","scopes","supportsDcr","registration_endpoint","capabilities","registrationEndpoint","authorization_endpoint","authorizationEndpoint","token_endpoint","tokenEndpoint","introspection_endpoint","introspectionEndpoint","length","scopes_supported","resolveCapabilitiesFromAuthorizationServer","authServerUrl","discoverAuthorizationServerMetadata","baseUrl","normalizedBaseUrl","resourceMetadata","issuer","issuerCapabilities","originCapabilities","_error","normalizeUrl","discoverProtectedResourceMetadata","authorization_servers","discoverAuthorizationServerIssuer"],"mappings":"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"}
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
* Probes .well-known/oauth-protected-resource endpoint
|
|
4
4
|
*/
|
|
5
5
|
import type { AuthorizationServerMetadata, ProtectedResourceMetadata } from './types.js';
|
|
6
|
+
/**
|
|
7
|
+
* Normalize a resource URL by stripping query/hash and trailing slashes.
|
|
8
|
+
*/
|
|
6
9
|
/**
|
|
7
10
|
* Discover OAuth 2.0 Protected Resource Metadata (RFC 9728)
|
|
8
11
|
* Probes .well-known/oauth-protected-resource endpoint
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
* Probes .well-known/oauth-protected-resource endpoint
|
|
4
4
|
*/
|
|
5
5
|
import type { AuthorizationServerMetadata, ProtectedResourceMetadata } from './types.js';
|
|
6
|
+
/**
|
|
7
|
+
* Normalize a resource URL by stripping query/hash and trailing slashes.
|
|
8
|
+
*/
|
|
6
9
|
/**
|
|
7
10
|
* Discover OAuth 2.0 Protected Resource Metadata (RFC 9728)
|
|
8
11
|
* Probes .well-known/oauth-protected-resource endpoint
|
|
@@ -22,6 +22,7 @@ _export(exports, {
|
|
|
22
22
|
return discoverProtectedResourceMetadata;
|
|
23
23
|
}
|
|
24
24
|
});
|
|
25
|
+
var _urlutilsts = require("../lib/url-utils.js");
|
|
25
26
|
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
|
|
26
27
|
try {
|
|
27
28
|
var info = gen[key](arg);
|
|
@@ -181,19 +182,20 @@ function _ts_generator(thisArg, body) {
|
|
|
181
182
|
}
|
|
182
183
|
function discoverProtectedResourceMetadata(resourceUrl) {
|
|
183
184
|
return _async_to_generator(function() {
|
|
184
|
-
var headerMetadata, origin, path, rootUrl,
|
|
185
|
+
var normalizedResourceUrl, headerMetadata, localWellKnownUrl, response, unused, origin, path, rootUrl, response1, metadata, rootMetadata, subPathUrl, subPathResponse, unused1, unused2, subPathUrl1, response2, unused3, _error;
|
|
185
186
|
return _ts_generator(this, function(_state) {
|
|
186
187
|
switch(_state.label){
|
|
187
188
|
case 0:
|
|
188
189
|
_state.trys.push([
|
|
189
190
|
0,
|
|
190
|
-
|
|
191
|
+
26,
|
|
191
192
|
,
|
|
192
|
-
|
|
193
|
+
27
|
|
193
194
|
]);
|
|
195
|
+
normalizedResourceUrl = (0, _urlutilsts.normalizeUrl)(resourceUrl);
|
|
194
196
|
return [
|
|
195
197
|
4,
|
|
196
|
-
discoverProtectedResourceMetadataFromHeader(
|
|
198
|
+
discoverProtectedResourceMetadataFromHeader(normalizedResourceUrl)
|
|
197
199
|
];
|
|
198
200
|
case 1:
|
|
199
201
|
headerMetadata = _state.sent();
|
|
@@ -201,21 +203,19 @@ function discoverProtectedResourceMetadata(resourceUrl) {
|
|
|
201
203
|
2,
|
|
202
204
|
headerMetadata
|
|
203
205
|
];
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
// Strategy 1: Try root location (REQUIRED by RFC 9728)
|
|
207
|
-
rootUrl = "".concat(origin, "/.well-known/oauth-protected-resource");
|
|
206
|
+
// Strategy 0: Try path-local well-known (supports path-prefixed deployments like /outlook)
|
|
207
|
+
localWellKnownUrl = (0, _urlutilsts.joinWellKnown)(normalizedResourceUrl, '/.well-known/oauth-protected-resource');
|
|
208
208
|
_state.label = 2;
|
|
209
209
|
case 2:
|
|
210
210
|
_state.trys.push([
|
|
211
211
|
2,
|
|
212
|
-
|
|
212
|
+
6,
|
|
213
213
|
,
|
|
214
|
-
|
|
214
|
+
7
|
|
215
215
|
]);
|
|
216
216
|
return [
|
|
217
217
|
4,
|
|
218
|
-
fetch(
|
|
218
|
+
fetch(localWellKnownUrl, {
|
|
219
219
|
method: 'GET',
|
|
220
220
|
headers: {
|
|
221
221
|
Accept: 'application/json',
|
|
@@ -227,16 +227,65 @@ function discoverProtectedResourceMetadata(resourceUrl) {
|
|
|
227
227
|
response = _state.sent();
|
|
228
228
|
if (!response.ok) return [
|
|
229
229
|
3,
|
|
230
|
-
|
|
230
|
+
5
|
|
231
231
|
];
|
|
232
232
|
return [
|
|
233
233
|
4,
|
|
234
234
|
response.json()
|
|
235
235
|
];
|
|
236
236
|
case 4:
|
|
237
|
+
return [
|
|
238
|
+
2,
|
|
239
|
+
_state.sent()
|
|
240
|
+
];
|
|
241
|
+
case 5:
|
|
242
|
+
return [
|
|
243
|
+
3,
|
|
244
|
+
7
|
|
245
|
+
];
|
|
246
|
+
case 6:
|
|
247
|
+
unused = _state.sent();
|
|
248
|
+
return [
|
|
249
|
+
3,
|
|
250
|
+
7
|
|
251
|
+
];
|
|
252
|
+
case 7:
|
|
253
|
+
origin = getOrigin(normalizedResourceUrl);
|
|
254
|
+
path = getPath(normalizedResourceUrl);
|
|
255
|
+
// Strategy 1: Try root location (REQUIRED by RFC 9728)
|
|
256
|
+
rootUrl = "".concat(origin, "/.well-known/oauth-protected-resource");
|
|
257
|
+
_state.label = 8;
|
|
258
|
+
case 8:
|
|
259
|
+
_state.trys.push([
|
|
260
|
+
8,
|
|
261
|
+
18,
|
|
262
|
+
,
|
|
263
|
+
19
|
|
264
|
+
]);
|
|
265
|
+
return [
|
|
266
|
+
4,
|
|
267
|
+
fetch(rootUrl, {
|
|
268
|
+
method: 'GET',
|
|
269
|
+
headers: {
|
|
270
|
+
Accept: 'application/json',
|
|
271
|
+
Connection: 'close'
|
|
272
|
+
}
|
|
273
|
+
})
|
|
274
|
+
];
|
|
275
|
+
case 9:
|
|
276
|
+
response1 = _state.sent();
|
|
277
|
+
if (!response1.ok) return [
|
|
278
|
+
3,
|
|
279
|
+
17
|
|
280
|
+
];
|
|
281
|
+
return [
|
|
282
|
+
4,
|
|
283
|
+
response1.json()
|
|
284
|
+
];
|
|
285
|
+
case 10:
|
|
237
286
|
metadata = _state.sent();
|
|
238
287
|
// Check if the discovered resource matches what we're looking for
|
|
239
|
-
if (metadata.resource ===
|
|
288
|
+
if (metadata.resource === normalizedResourceUrl) {
|
|
240
289
|
return [
|
|
241
290
|
2,
|
|
242
291
|
metadata
|
|
@@ -250,22 +299,22 @@ function discoverProtectedResourceMetadata(resourceUrl) {
|
|
|
250
299
|
metadata
|
|
251
300
|
];
|
|
252
301
|
}
|
|
253
|
-
if (!
|
|
302
|
+
if (!normalizedResourceUrl.startsWith(metadata.resource)) return [
|
|
254
303
|
3,
|
|
255
|
-
|
|
304
|
+
17
|
|
256
305
|
];
|
|
257
306
|
// Still try sub-path location to see if there's more specific metadata
|
|
258
307
|
// But save root metadata as fallback
|
|
259
308
|
rootMetadata = metadata;
|
|
260
309
|
// Try sub-path location for more specific metadata
|
|
261
310
|
subPathUrl = "".concat(origin, "/.well-known/oauth-protected-resource").concat(path);
|
|
262
|
-
_state.label =
|
|
263
|
-
case
|
|
311
|
+
_state.label = 11;
|
|
312
|
+
case 11:
|
|
264
313
|
_state.trys.push([
|
|
265
|
-
|
|
266
|
-
|
|
314
|
+
11,
|
|
315
|
+
15,
|
|
267
316
|
,
|
|
268
|
-
|
|
317
|
+
16
|
|
269
318
|
]);
|
|
270
319
|
return [
|
|
271
320
|
4,
|
|
@@ -277,62 +326,62 @@ function discoverProtectedResourceMetadata(resourceUrl) {
|
|
|
277
326
|
}
|
|
278
327
|
})
|
|
279
328
|
];
|
|
280
|
-
case
|
|
329
|
+
case 12:
|
|
281
330
|
subPathResponse = _state.sent();
|
|
282
331
|
if (!subPathResponse.ok) return [
|
|
283
332
|
3,
|
|
284
|
-
|
|
333
|
+
14
|
|
285
334
|
];
|
|
286
335
|
return [
|
|
287
336
|
4,
|
|
288
337
|
subPathResponse.json()
|
|
289
338
|
];
|
|
290
|
-
case
|
|
339
|
+
case 13:
|
|
291
340
|
return [
|
|
292
341
|
2,
|
|
293
342
|
_state.sent()
|
|
294
343
|
];
|
|
295
|
-
case
|
|
344
|
+
case 14:
|
|
296
345
|
return [
|
|
297
346
|
3,
|
|
298
|
-
|
|
347
|
+
16
|
|
299
348
|
];
|
|
300
|
-
case
|
|
301
|
-
|
|
349
|
+
case 15:
|
|
350
|
+
unused1 = _state.sent();
|
|
302
351
|
return [
|
|
303
352
|
3,
|
|
304
|
-
|
|
353
|
+
16
|
|
305
354
|
];
|
|
306
|
-
case
|
|
355
|
+
case 16:
|
|
307
356
|
// Return root metadata as it applies to this resource
|
|
308
357
|
return [
|
|
309
358
|
2,
|
|
310
359
|
rootMetadata
|
|
311
360
|
];
|
|
312
|
-
case
|
|
361
|
+
case 17:
|
|
313
362
|
return [
|
|
314
363
|
3,
|
|
315
|
-
|
|
364
|
+
19
|
|
316
365
|
];
|
|
317
|
-
case
|
|
318
|
-
|
|
366
|
+
case 18:
|
|
367
|
+
unused2 = _state.sent();
|
|
319
368
|
return [
|
|
320
369
|
3,
|
|
321
|
-
|
|
370
|
+
19
|
|
322
371
|
];
|
|
323
|
-
case
|
|
372
|
+
case 19:
|
|
324
373
|
if (!path) return [
|
|
325
374
|
3,
|
|
326
|
-
|
|
375
|
+
25
|
|
327
376
|
];
|
|
328
377
|
subPathUrl1 = "".concat(origin, "/.well-known/oauth-protected-resource").concat(path);
|
|
329
|
-
_state.label =
|
|
330
|
-
case
|
|
378
|
+
_state.label = 20;
|
|
379
|
+
case 20:
|
|
331
380
|
_state.trys.push([
|
|
332
|
-
|
|
333
|
-
|
|
381
|
+
20,
|
|
382
|
+
24,
|
|
334
383
|
,
|
|
335
|
-
|
|
384
|
+
25
|
|
336
385
|
]);
|
|
337
386
|
return [
|
|
338
387
|
4,
|
|
@@ -344,46 +393,46 @@ function discoverProtectedResourceMetadata(resourceUrl) {
|
|
|
344
393
|
}
|
|
345
394
|
})
|
|
346
395
|
];
|
|
347
|
-
case
|
|
348
|
-
|
|
349
|
-
if (!
|
|
396
|
+
case 21:
|
|
397
|
+
response2 = _state.sent();
|
|
398
|
+
if (!response2.ok) return [
|
|
350
399
|
3,
|
|
351
|
-
|
|
400
|
+
23
|
|
352
401
|
];
|
|
353
402
|
return [
|
|
354
403
|
4,
|
|
355
|
-
|
|
404
|
+
response2.json()
|
|
356
405
|
];
|
|
357
|
-
case
|
|
406
|
+
case 22:
|
|
358
407
|
return [
|
|
359
408
|
2,
|
|
360
409
|
_state.sent()
|
|
361
410
|
];
|
|
362
|
-
case
|
|
411
|
+
case 23:
|
|
363
412
|
return [
|
|
364
413
|
3,
|
|
365
|
-
|
|
414
|
+
25
|
|
366
415
|
];
|
|
367
|
-
case
|
|
368
|
-
|
|
416
|
+
case 24:
|
|
417
|
+
unused3 = _state.sent();
|
|
369
418
|
return [
|
|
370
419
|
3,
|
|
371
|
-
|
|
420
|
+
25
|
|
372
421
|
];
|
|
373
|
-
case
|
|
422
|
+
case 25:
|
|
374
423
|
// Neither location found or resource didn't match
|
|
375
424
|
return [
|
|
376
425
|
2,
|
|
377
426
|
null
|
|
378
427
|
];
|
|
379
|
-
case
|
|
428
|
+
case 26:
|
|
380
429
|
_error = _state.sent();
|
|
381
430
|
// Network error, invalid URL, or other failure
|
|
382
431
|
return [
|
|
383
432
|
2,
|
|
384
433
|
null
|
|
385
434
|
];
|
|
386
|
-
case
|
|
435
|
+
case 27:
|
|
387
436
|
return [
|
|
388
437
|
2
|
|
389
438
|
];
|
|
@@ -490,17 +539,45 @@ function discoverProtectedResourceMetadataFromHeader(resourceUrl) {
|
|
|
490
539
|
}
|
|
491
540
|
function discoverAuthorizationServerMetadata(authServerUrl) {
|
|
492
541
|
return _async_to_generator(function() {
|
|
493
|
-
var origin, wellKnownUrl, response, _error;
|
|
542
|
+
var normalizedAuthServerUrl, localWellKnownUrl, localResponse, origin, wellKnownUrl, response, _error;
|
|
494
543
|
return _ts_generator(this, function(_state) {
|
|
495
544
|
switch(_state.label){
|
|
496
545
|
case 0:
|
|
497
546
|
_state.trys.push([
|
|
498
547
|
0,
|
|
499
|
-
|
|
548
|
+
6,
|
|
500
549
|
,
|
|
501
|
-
|
|
550
|
+
7
|
|
502
551
|
]);
|
|
503
|
-
|
|
552
|
+
normalizedAuthServerUrl = (0, _urlutilsts.normalizeUrl)(authServerUrl);
|
|
553
|
+
localWellKnownUrl = (0, _urlutilsts.joinWellKnown)(normalizedAuthServerUrl, '/.well-known/oauth-authorization-server');
|
|
554
|
+
return [
|
|
555
|
+
4,
|
|
556
|
+
fetch(localWellKnownUrl, {
|
|
557
|
+
method: 'GET',
|
|
558
|
+
headers: {
|
|
559
|
+
Accept: 'application/json',
|
|
560
|
+
Connection: 'close'
|
|
561
|
+
}
|
|
562
|
+
})
|
|
563
|
+
];
|
|
564
|
+
case 1:
|
|
565
|
+
localResponse = _state.sent();
|
|
566
|
+
if (!localResponse.ok) return [
|
|
567
|
+
3,
|
|
568
|
+
3
|
|
569
|
+
];
|
|
570
|
+
return [
|
|
571
|
+
4,
|
|
572
|
+
localResponse.json()
|
|
573
|
+
];
|
|
574
|
+
case 2:
|
|
575
|
+
return [
|
|
576
|
+
2,
|
|
577
|
+
_state.sent()
|
|
578
|
+
];
|
|
579
|
+
case 3:
|
|
580
|
+
origin = getOrigin(normalizedAuthServerUrl);
|
|
504
581
|
wellKnownUrl = "".concat(origin, "/.well-known/oauth-authorization-server");
|
|
505
582
|
return [
|
|
506
583
|
4,
|
|
@@ -512,7 +589,7 @@ function discoverAuthorizationServerMetadata(authServerUrl) {
|
|
|
512
589
|
}
|
|
513
590
|
})
|
|
514
591
|
];
|
|
515
|
-
case
|
|
592
|
+
case 4:
|
|
516
593
|
response = _state.sent();
|
|
517
594
|
if (!response.ok) {
|
|
518
595
|
return [
|
|
@@ -524,18 +601,18 @@ function discoverAuthorizationServerMetadata(authServerUrl) {
|
|
|
524
601
|
4,
|
|
525
602
|
response.json()
|
|
526
603
|
];
|
|
527
|
-
case
|
|
604
|
+
case 5:
|
|
528
605
|
return [
|
|
529
606
|
2,
|
|
530
607
|
_state.sent()
|
|
531
608
|
];
|
|
532
|
-
case
|
|
609
|
+
case 6:
|
|
533
610
|
_error = _state.sent();
|
|
534
611
|
return [
|
|
535
612
|
2,
|
|
536
613
|
null
|
|
537
614
|
];
|
|
538
|
-
case
|
|
615
|
+
case 7:
|
|
539
616
|
return [
|
|
540
617
|
2
|
|
541
618
|
];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/Projects/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 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 * 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 headerMetadata = await discoverProtectedResourceMetadataFromHeader(resourceUrl);\n if (headerMetadata) return headerMetadata;\n\n const origin = getOrigin(resourceUrl);\n const path = getPath(resourceUrl);\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 === resourceUrl) {\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 (resourceUrl.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 origin = getOrigin(authServerUrl);\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":["discoverAuthorizationServerIssuer","discoverAuthorizationServerMetadata","discoverProtectedResourceMetadata","getOrigin","url","URL","origin","getPath","parsed","pathname","resourceUrl","headerMetadata","path","rootUrl","response","metadata","rootMetadata","subPathUrl","subPathResponse","_error","discoverProtectedResourceMetadataFromHeader","fetch","method","headers","Accept","Connection","ok","json","resource","startsWith","header","postResponse","match","metadataUrl","metadataResponse","get","body","authServerUrl","wellKnownUrl"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/Projects/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":["discoverAuthorizationServerIssuer","discoverAuthorizationServerMetadata","discoverProtectedResourceMetadata","getOrigin","url","URL","origin","getPath","parsed","pathname","resourceUrl","normalizedResourceUrl","headerMetadata","localWellKnownUrl","response","path","rootUrl","metadata","rootMetadata","subPathUrl","subPathResponse","_error","normalizeUrl","discoverProtectedResourceMetadataFromHeader","joinWellKnown","fetch","method","headers","Accept","Connection","ok","json","resource","startsWith","header","postResponse","match","metadataUrl","metadataResponse","get","body","authServerUrl","normalizedAuthServerUrl","localResponse","wellKnownUrl"],"mappings":"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"}
|
|
@@ -18,6 +18,15 @@ interface RegistryLike {
|
|
|
18
18
|
servers: Map<string, ServerProcess>;
|
|
19
19
|
}
|
|
20
20
|
import { type Logger } from '../utils/logger.js';
|
|
21
|
+
/**
|
|
22
|
+
* Extract the "server base" by removing a trailing `/mcp` path segment if present.
|
|
23
|
+
* Examples:
|
|
24
|
+
* - https://example.com/mcp -> https://example.com
|
|
25
|
+
* - https://example.com/sheets/mcp -> https://example.com/sheets
|
|
26
|
+
* - https://example.com/sheets/mcp/ -> https://example.com/sheets
|
|
27
|
+
* - https://example.com/sheets -> https://example.com/sheets
|
|
28
|
+
*/
|
|
29
|
+
export declare function extractBaseUrl(mcpUrl: string): string;
|
|
21
30
|
/**
|
|
22
31
|
* Connect MCP SDK client to server with full readiness handling.
|
|
23
32
|
* @internal - Use registry.connect() instead
|
|
@@ -18,6 +18,15 @@ interface RegistryLike {
|
|
|
18
18
|
servers: Map<string, ServerProcess>;
|
|
19
19
|
}
|
|
20
20
|
import { type Logger } from '../utils/logger.js';
|
|
21
|
+
/**
|
|
22
|
+
* Extract the "server base" by removing a trailing `/mcp` path segment if present.
|
|
23
|
+
* Examples:
|
|
24
|
+
* - https://example.com/mcp -> https://example.com
|
|
25
|
+
* - https://example.com/sheets/mcp -> https://example.com/sheets
|
|
26
|
+
* - https://example.com/sheets/mcp/ -> https://example.com/sheets
|
|
27
|
+
* - https://example.com/sheets -> https://example.com/sheets
|
|
28
|
+
*/
|
|
29
|
+
export declare function extractBaseUrl(mcpUrl: string): string;
|
|
21
30
|
/**
|
|
22
31
|
* Connect MCP SDK client to server with full readiness handling.
|
|
23
32
|
* @internal - Use registry.connect() instead
|
|
@@ -7,10 +7,18 @@
|
|
|
7
7
|
Object.defineProperty(exports, "__esModule", {
|
|
8
8
|
value: true
|
|
9
9
|
});
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
function _export(target, all) {
|
|
11
|
+
for(var name in all)Object.defineProperty(target, name, {
|
|
12
|
+
enumerable: true,
|
|
13
|
+
get: Object.getOwnPropertyDescriptor(all, name).get
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
_export(exports, {
|
|
17
|
+
get connectMcpClient () {
|
|
13
18
|
return connectMcpClient;
|
|
19
|
+
},
|
|
20
|
+
get extractBaseUrl () {
|
|
21
|
+
return extractBaseUrl;
|
|
14
22
|
}
|
|
15
23
|
});
|
|
16
24
|
require("../monkey-patches.js");
|
|
@@ -219,13 +227,22 @@ function _ts_generator(thisArg, body) {
|
|
|
219
227
|
});
|
|
220
228
|
})();
|
|
221
229
|
}
|
|
222
|
-
|
|
223
|
-
* Extract base URL from MCP server URL
|
|
224
|
-
* @param mcpUrl - Full MCP endpoint URL (e.g., https://example.com/mcp)
|
|
225
|
-
* @returns Base URL (e.g., https://example.com)
|
|
226
|
-
*/ function extractBaseUrl(mcpUrl) {
|
|
230
|
+
function extractBaseUrl(mcpUrl) {
|
|
227
231
|
var url = new URL(mcpUrl);
|
|
228
|
-
|
|
232
|
+
// Ignore query/hash for base URL purposes
|
|
233
|
+
url.search = '';
|
|
234
|
+
url.hash = '';
|
|
235
|
+
// Normalize path segments (removes empty segments from leading/trailing slashes)
|
|
236
|
+
var segments = url.pathname.split('/').filter(Boolean);
|
|
237
|
+
// If last segment is exactly "mcp", drop it
|
|
238
|
+
if (segments[segments.length - 1] === 'mcp') {
|
|
239
|
+
segments.pop();
|
|
240
|
+
}
|
|
241
|
+
// Rebuild pathname; empty means root
|
|
242
|
+
url.pathname = segments.length ? "/".concat(segments.join('/')) : '';
|
|
243
|
+
// Return without trailing slash (except root origin)
|
|
244
|
+
var out = url.origin + url.pathname;
|
|
245
|
+
return out === url.origin ? out : out.replace(/\/+$/, '');
|
|
229
246
|
}
|
|
230
247
|
/**
|
|
231
248
|
* Infer transport type from server configuration with validation.
|