@lovable.dev/mcp-js 0.21.0-rc.3 → 0.21.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.
- package/README.md +2 -1
- package/dist/{base-DTtaX5Rf.d.ts → base-CFy5vZ5Z.d.ts} +7 -2
- package/dist/chunk-6QFZYYUV.js +11 -0
- package/dist/{chunk-5W6JK6OS.js → chunk-CI7JYLV3.js} +6 -2
- package/dist/{chunk-SIDWSSWV.js → chunk-E3Q3FRKQ.js} +1 -1
- package/dist/{chunk-NENAFXZJ.js → chunk-ENJDNFEB.js} +1 -1
- package/dist/{chunk-TVU5FL2T.js → chunk-LFXK3FNF.js} +1 -1
- package/dist/{chunk-JIOOUIDX.js → chunk-QPKPYDPB.js} +6 -2
- package/dist/{chunk-CIAVINZ5.js → chunk-YLU2JTHL.js} +45 -80
- package/dist/cli/extract-manifest.cjs +3 -1
- package/dist/cli/extract-manifest.js +5 -3
- package/dist/index.cjs +12 -0
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +11 -1
- package/dist/protocols/mcp/index.cjs +52 -79
- package/dist/protocols/mcp/index.d.cts +2 -2
- package/dist/protocols/mcp/index.d.ts +2 -2
- package/dist/protocols/mcp/index.js +4 -3
- package/dist/protocols/oauth-metadata.cjs +40 -60
- package/dist/protocols/oauth-metadata.d.cts +1 -1
- package/dist/protocols/oauth-metadata.d.ts +1 -1
- package/dist/protocols/oauth-metadata.js +3 -3
- package/dist/protocols/rest/index.cjs +52 -79
- package/dist/protocols/rest/index.d.cts +2 -2
- package/dist/protocols/rest/index.d.ts +2 -2
- package/dist/protocols/rest/index.js +5 -4
- package/dist/stacks/supabase/index.cjs +57 -82
- package/dist/stacks/supabase/index.d.cts +1 -1
- package/dist/stacks/supabase/index.d.ts +1 -1
- package/dist/stacks/supabase/index.js +7 -6
- package/dist/stacks/supabase/vite.cjs +1 -1
- package/dist/stacks/supabase/vite.js +1 -1
- package/dist/stacks/tanstack/index.cjs +57 -82
- package/dist/stacks/tanstack/index.d.cts +1 -1
- package/dist/stacks/tanstack/index.d.ts +1 -1
- package/dist/stacks/tanstack/index.js +7 -6
- package/dist/{types-CPkhCRxc.d.ts → types-qqrmJ736.d.ts} +10 -0
- package/package.json +1 -1
|
@@ -148,23 +148,9 @@ function parseSafeUrl(subject, raw, ErrorClass = Error) {
|
|
|
148
148
|
|
|
149
149
|
// src/auth/discovery.ts
|
|
150
150
|
var OAuthConfigurationError = class extends Error {
|
|
151
|
-
constructor(message, stage) {
|
|
152
|
-
super(message);
|
|
153
|
-
this.stage = stage;
|
|
154
|
-
this.name = "OAuthConfigurationError";
|
|
155
|
-
}
|
|
156
|
-
};
|
|
157
|
-
var OAuthUpstreamUnreachableError = class extends Error {
|
|
158
|
-
constructor(message, stage) {
|
|
159
|
-
super(message);
|
|
160
|
-
this.stage = stage;
|
|
161
|
-
this.name = "OAuthUpstreamUnreachableError";
|
|
162
|
-
}
|
|
163
|
-
};
|
|
164
|
-
var MetadataUnreachableError = class extends Error {
|
|
165
151
|
constructor(message) {
|
|
166
152
|
super(message);
|
|
167
|
-
this.name = "
|
|
153
|
+
this.name = "OAuthConfigurationError";
|
|
168
154
|
}
|
|
169
155
|
};
|
|
170
156
|
var METADATA_FETCH_TIMEOUT_MS = 5e3;
|
|
@@ -196,28 +182,16 @@ async function fetchFirstValidOAuthServerMetadata(metadataUrls, expectedIssuer)
|
|
|
196
182
|
try {
|
|
197
183
|
return await fetchOAuthServerMetadata(url, expectedIssuer);
|
|
198
184
|
} catch (err) {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
errors.push(error);
|
|
185
|
+
log.debug("oauth.discovery.attempt_failed", { url, ...describeError(err) });
|
|
186
|
+
errors.push(`${url}: ${err instanceof Error ? err.message : String(err)}`);
|
|
202
187
|
}
|
|
203
188
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
log.error("oauth.discovery.exhausted", { expectedIssuer, urlsTried: metadataUrls, errors: detail, unreachable });
|
|
207
|
-
const message = `failed to discover OAuth server metadata (${detail})`;
|
|
208
|
-
throw unreachable ? new MetadataUnreachableError(message) : new Error(message);
|
|
189
|
+
log.error("oauth.discovery.exhausted", { expectedIssuer, urlsTried: metadataUrls, errors });
|
|
190
|
+
throw new Error(`failed to discover OAuth server metadata (${errors.join("; ")})`);
|
|
209
191
|
}
|
|
210
192
|
async function fetchOAuthServerMetadata(url, expectedIssuer) {
|
|
211
193
|
log.debug("oauth.discovery.fetch", { url });
|
|
212
|
-
|
|
213
|
-
try {
|
|
214
|
-
response = await fetch(url, {
|
|
215
|
-
signal: AbortSignal.timeout(METADATA_FETCH_TIMEOUT_MS),
|
|
216
|
-
redirect: "manual"
|
|
217
|
-
});
|
|
218
|
-
} catch (err) {
|
|
219
|
-
throw new MetadataUnreachableError(err instanceof Error ? err.message : String(err));
|
|
220
|
-
}
|
|
194
|
+
const response = await fetch(url, { signal: AbortSignal.timeout(METADATA_FETCH_TIMEOUT_MS), redirect: "manual" });
|
|
221
195
|
if (!response.ok) {
|
|
222
196
|
throw new Error(String(response.status));
|
|
223
197
|
}
|
|
@@ -240,21 +214,14 @@ async function fetchIssuerOAuthServerMetadata(issuer) {
|
|
|
240
214
|
try {
|
|
241
215
|
return await fetchFirstValidOAuthServerMetadata(oauthMetadataUrlsForIssuer(issuer), issuer);
|
|
242
216
|
} catch (err) {
|
|
243
|
-
const message = `OAuth issuer discovery failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
244
|
-
if (err instanceof MetadataUnreachableError) {
|
|
245
|
-
log.error("oauth.discovery.unreachable", {
|
|
246
|
-
issuer,
|
|
247
|
-
...describeError(err),
|
|
248
|
-
outcome: "503 authorization server unreachable"
|
|
249
|
-
});
|
|
250
|
-
throw new OAuthUpstreamUnreachableError(message, "discovery");
|
|
251
|
-
}
|
|
252
217
|
log.error("oauth.discovery.config_error", {
|
|
253
218
|
issuer,
|
|
254
219
|
...describeError(err),
|
|
255
220
|
outcome: "500 oauth configuration error"
|
|
256
221
|
});
|
|
257
|
-
throw new OAuthConfigurationError(
|
|
222
|
+
throw new OAuthConfigurationError(
|
|
223
|
+
`OAuth issuer discovery failed: ${err instanceof Error ? err.message : String(err)}`
|
|
224
|
+
);
|
|
258
225
|
}
|
|
259
226
|
}
|
|
260
227
|
function createOAuthDiscoveryResolver(auth) {
|
|
@@ -314,13 +281,18 @@ function readString(value) {
|
|
|
314
281
|
function splitScopes(value) {
|
|
315
282
|
if (typeof value === "string")
|
|
316
283
|
return value.split(/\s+/).filter(Boolean);
|
|
317
|
-
|
|
318
|
-
return value.filter((entry) => typeof entry === "string" && entry.length > 0);
|
|
319
|
-
return [];
|
|
284
|
+
return stringClaimList(value);
|
|
320
285
|
}
|
|
321
286
|
function stringClaim(claims, name) {
|
|
322
287
|
return readString(claims[name]);
|
|
323
288
|
}
|
|
289
|
+
function stringClaimList(value) {
|
|
290
|
+
if (typeof value === "string")
|
|
291
|
+
return value === "" ? [] : [value];
|
|
292
|
+
if (Array.isArray(value))
|
|
293
|
+
return value.filter((entry) => typeof entry === "string" && entry !== "");
|
|
294
|
+
return [];
|
|
295
|
+
}
|
|
324
296
|
|
|
325
297
|
// src/auth/verifier.ts
|
|
326
298
|
var DEFAULT_JWT_ALGORITHMS = ["RS256", "RS384", "RS512", "ES256", "ES384", "ES512", "EdDSA"];
|
|
@@ -347,24 +319,15 @@ function tokenHeaderFields(token) {
|
|
|
347
319
|
}
|
|
348
320
|
}
|
|
349
321
|
async function fetchVerificationKeySet(jwksUri) {
|
|
350
|
-
let response;
|
|
351
|
-
try {
|
|
352
|
-
response = await fetch(jwksUri, { signal: AbortSignal.timeout(JWKS_FETCH_TIMEOUT_MS), redirect: "manual" });
|
|
353
|
-
} catch (err) {
|
|
354
|
-
log.error("oauth.jwks.unreachable", { ...describeError(err), outcome: "503 authorization server unreachable" });
|
|
355
|
-
throw new OAuthUpstreamUnreachableError(
|
|
356
|
-
`JWKS fetch failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
357
|
-
"jwks"
|
|
358
|
-
);
|
|
359
|
-
}
|
|
360
322
|
try {
|
|
323
|
+
const response = await fetch(jwksUri, { signal: AbortSignal.timeout(JWKS_FETCH_TIMEOUT_MS), redirect: "manual" });
|
|
361
324
|
if (!response.ok)
|
|
362
325
|
throw new Error(`JWKS endpoint returned ${response.status}`);
|
|
363
326
|
const json = await response.json();
|
|
364
327
|
return (0, import_jose.createLocalJWKSet)(json);
|
|
365
328
|
} catch (err) {
|
|
366
329
|
log.error("oauth.jwks.fetch_failed", { ...describeError(err), outcome: "500 oauth configuration error" });
|
|
367
|
-
throw new OAuthConfigurationError(`JWKS fetch failed: ${err instanceof Error ? err.message : String(err)}
|
|
330
|
+
throw new OAuthConfigurationError(`JWKS fetch failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
368
331
|
}
|
|
369
332
|
}
|
|
370
333
|
function assertAccessTokenTyp(token, allowed) {
|
|
@@ -381,13 +344,12 @@ function assertAccessTokenTyp(token, allowed) {
|
|
|
381
344
|
throw new OAuthTokenError(401, "invalid_token", "Access token typ header is not accepted");
|
|
382
345
|
}
|
|
383
346
|
}
|
|
384
|
-
async function verifyJwtClaims(token, keySet, issuer,
|
|
347
|
+
async function verifyJwtClaims(token, keySet, issuer, auth) {
|
|
385
348
|
try {
|
|
386
349
|
const { payload } = await (0, import_jose.jwtVerify)(token, keySet, {
|
|
387
350
|
// `issuer` is trimmed of any trailing slash; accept both forms so a token whose
|
|
388
351
|
// `iss` carries the slash the AS publishes still verifies.
|
|
389
352
|
issuer: [issuer, `${issuer}/`],
|
|
390
|
-
audience: [...audience],
|
|
391
353
|
algorithms: auth.algorithms ? [...auth.algorithms] : DEFAULT_JWT_ALGORITHMS,
|
|
392
354
|
requiredClaims: ["sub", "exp"],
|
|
393
355
|
clockTolerance: auth.clockToleranceSeconds ?? DEFAULT_CLOCK_TOLERANCE_SECONDS
|
|
@@ -398,6 +360,22 @@ async function verifyJwtClaims(token, keySet, issuer, audience, auth) {
|
|
|
398
360
|
throw err;
|
|
399
361
|
}
|
|
400
362
|
}
|
|
363
|
+
function checkAcceptedAudience(claims, accepted, auth) {
|
|
364
|
+
const audPopulated = claims.aud !== void 0 && !(Array.isArray(claims.aud) && claims.aud.length === 0);
|
|
365
|
+
if (audPopulated) {
|
|
366
|
+
if (stringClaimList(claims.aud).some((audience) => accepted.includes(audience)))
|
|
367
|
+
return "aud";
|
|
368
|
+
log.debug("oauth.verify.bad_audience", { tokenAud: claims.aud, accepted, outcome: "401 invalid_token" });
|
|
369
|
+
} else if (auth.acceptResourceClaim !== false) {
|
|
370
|
+
const acceptedTrimmed = accepted.map(trimTrailingSlash);
|
|
371
|
+
if (stringClaimList(claims.resource).some((resource) => acceptedTrimmed.includes(trimTrailingSlash(resource))))
|
|
372
|
+
return "resource";
|
|
373
|
+
log.debug("oauth.verify.bad_resource", { tokenResource: claims.resource, accepted, outcome: "401 invalid_token" });
|
|
374
|
+
} else {
|
|
375
|
+
log.debug("oauth.verify.empty_audience", { accepted, outcome: "401 invalid_token" });
|
|
376
|
+
}
|
|
377
|
+
throw new OAuthTokenError(401, "invalid_token", "token audience is not accepted");
|
|
378
|
+
}
|
|
401
379
|
function assertNonEmptySubject(claims) {
|
|
402
380
|
const sub = claims["sub"];
|
|
403
381
|
if (typeof sub !== "string" || sub.trim() === "") {
|
|
@@ -442,14 +420,16 @@ function createOAuthTokenVerifier(auth, discovery) {
|
|
|
442
420
|
log.debug("oauth.jwks.fetch", { jwksUri });
|
|
443
421
|
const keySet = await fetchVerificationKeySet(jwksUri);
|
|
444
422
|
assertAccessTokenTyp(token, allowedTyps);
|
|
445
|
-
const claims = await verifyJwtClaims(token, keySet, issuer,
|
|
423
|
+
const claims = await verifyJwtClaims(token, keySet, issuer, auth);
|
|
424
|
+
const audienceVia = checkAcceptedAudience(claims, acceptedAudiences, auth);
|
|
446
425
|
assertNonEmptySubject(claims);
|
|
447
426
|
const context = buildMcpAuthContext({ token, claims, issuer, resource, acceptedAudiences });
|
|
448
427
|
assertOAuthClientClaim(auth, context.principal.clientId);
|
|
449
428
|
log.info("oauth.verify.ok", {
|
|
450
429
|
sub: context.principal.sub,
|
|
451
430
|
clientId: context.principal.clientId,
|
|
452
|
-
scopes: context.principal.scopes
|
|
431
|
+
scopes: context.principal.scopes,
|
|
432
|
+
audienceVia
|
|
453
433
|
});
|
|
454
434
|
return context;
|
|
455
435
|
};
|
|
@@ -498,12 +478,6 @@ function oauthConfigurationErrorResponse() {
|
|
|
498
478
|
headers: { ...JSON_HEADERS, "Cache-Control": "no-store" }
|
|
499
479
|
});
|
|
500
480
|
}
|
|
501
|
-
function oauthUpstreamUnreachableResponse() {
|
|
502
|
-
return new Response(JSON.stringify({ error: "authorization server unreachable" }), {
|
|
503
|
-
status: 503,
|
|
504
|
-
headers: { ...JSON_HEADERS, "Cache-Control": "no-store", "Retry-After": "30" }
|
|
505
|
-
});
|
|
506
|
-
}
|
|
507
481
|
function parseBearerToken(request) {
|
|
508
482
|
const header = request.headers.get("Authorization");
|
|
509
483
|
if (!header)
|
|
@@ -568,22 +542,12 @@ function createRequestAuthorizer(mcp, options = {}) {
|
|
|
568
542
|
assertRequiredScopes(runtime.auth, auth);
|
|
569
543
|
return { ok: true, auth };
|
|
570
544
|
} catch (err) {
|
|
571
|
-
if (err instanceof OAuthUpstreamUnreachableError) {
|
|
572
|
-
log.error("auth.upstream_unreachable", { ...describeError(err), stage: err.stage, outcome: "503" });
|
|
573
|
-
await recorder?.emit({
|
|
574
|
-
tool: null,
|
|
575
|
-
method: "authorize",
|
|
576
|
-
outcome: err.stage === "jwks" ? "auth_jwks_unreachable" : "auth_discovery_unreachable",
|
|
577
|
-
durationMs: nowMs() - startedAt
|
|
578
|
-
});
|
|
579
|
-
return { ok: false, response: oauthUpstreamUnreachableResponse() };
|
|
580
|
-
}
|
|
581
545
|
if (err instanceof OAuthConfigurationError) {
|
|
582
|
-
log.error("auth.config_error", { ...describeError(err),
|
|
546
|
+
log.error("auth.config_error", { ...describeError(err), outcome: "500" });
|
|
583
547
|
await recorder?.emit({
|
|
584
548
|
tool: null,
|
|
585
549
|
method: "authorize",
|
|
586
|
-
outcome:
|
|
550
|
+
outcome: "auth_config_error",
|
|
587
551
|
durationMs: nowMs() - startedAt
|
|
588
552
|
});
|
|
589
553
|
return { ok: false, response: oauthConfigurationErrorResponse() };
|
|
@@ -662,6 +626,14 @@ var ToolContext = class {
|
|
|
662
626
|
}
|
|
663
627
|
};
|
|
664
628
|
|
|
629
|
+
// src/core/content.ts
|
|
630
|
+
function extractTextContent(content) {
|
|
631
|
+
if (!content)
|
|
632
|
+
return void 0;
|
|
633
|
+
const text = content.filter((block) => block.type === "text").map((block) => block.text).join("\n").trim();
|
|
634
|
+
return text.length > 0 ? text : void 0;
|
|
635
|
+
}
|
|
636
|
+
|
|
665
637
|
// src/core/cors.ts
|
|
666
638
|
var EXPOSE_HEADERS = "WWW-Authenticate, Mcp-Session-Id, Mcp-Protocol-Version";
|
|
667
639
|
var ALLOW_HEADERS = "Authorization, Content-Type, Mcp-Session-Id, Mcp-Protocol-Version, Last-Event-ID";
|
|
@@ -712,7 +684,8 @@ function adaptToolToSdkCallback(tool, auth, recorder) {
|
|
|
712
684
|
tool: tool.name,
|
|
713
685
|
method: "tools/call",
|
|
714
686
|
outcome: result.isError ? "tool_error" : "ok",
|
|
715
|
-
durationMs: nowMs() - start
|
|
687
|
+
durationMs: nowMs() - start,
|
|
688
|
+
errorText: result.isError ? extractTextContent(result.content) : void 0
|
|
716
689
|
});
|
|
717
690
|
return { content: result.content ?? [], structuredContent: result.structuredContent, isError: result.isError };
|
|
718
691
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { M as McpRuntimeOptions } from '../../authorize-BMeXd_Sh.js';
|
|
2
|
-
import { d as McpDefinition } from '../../types-
|
|
3
|
-
import { M as MetricsRecorder } from '../../base-
|
|
2
|
+
import { d as McpDefinition } from '../../types-qqrmJ736.js';
|
|
3
|
+
import { M as MetricsRecorder } from '../../base-CFy5vZ5Z.js';
|
|
4
4
|
import 'zod';
|
|
5
5
|
|
|
6
6
|
type McpProtocolHandler = (request: Request, recorder?: MetricsRecorder) => Promise<Response>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { M as McpRuntimeOptions } from '../../authorize-BMeXd_Sh.js';
|
|
2
|
-
import { d as McpDefinition } from '../../types-
|
|
3
|
-
import { M as MetricsRecorder } from '../../base-
|
|
2
|
+
import { d as McpDefinition } from '../../types-qqrmJ736.js';
|
|
3
|
+
import { M as MetricsRecorder } from '../../base-CFy5vZ5Z.js';
|
|
4
4
|
import 'zod';
|
|
5
5
|
|
|
6
6
|
type McpProtocolHandler = (request: Request, recorder?: MetricsRecorder) => Promise<Response>;
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createMcpProtocolHandler
|
|
3
|
-
} from "../../chunk-
|
|
3
|
+
} from "../../chunk-CI7JYLV3.js";
|
|
4
|
+
import "../../chunk-6QFZYYUV.js";
|
|
4
5
|
import "../../chunk-MA5H6PSF.js";
|
|
5
|
-
import "../../chunk-
|
|
6
|
+
import "../../chunk-YLU2JTHL.js";
|
|
6
7
|
import "../../chunk-H37EB22A.js";
|
|
7
8
|
import "../../chunk-6DXGZZA4.js";
|
|
8
|
-
import "../../chunk-
|
|
9
|
+
import "../../chunk-E3Q3FRKQ.js";
|
|
9
10
|
export {
|
|
10
11
|
createMcpProtocolHandler
|
|
11
12
|
};
|
|
@@ -143,23 +143,9 @@ function parseSafeUrl(subject, raw, ErrorClass = Error) {
|
|
|
143
143
|
|
|
144
144
|
// src/auth/discovery.ts
|
|
145
145
|
var OAuthConfigurationError = class extends Error {
|
|
146
|
-
constructor(message, stage) {
|
|
147
|
-
super(message);
|
|
148
|
-
this.stage = stage;
|
|
149
|
-
this.name = "OAuthConfigurationError";
|
|
150
|
-
}
|
|
151
|
-
};
|
|
152
|
-
var OAuthUpstreamUnreachableError = class extends Error {
|
|
153
|
-
constructor(message, stage) {
|
|
154
|
-
super(message);
|
|
155
|
-
this.stage = stage;
|
|
156
|
-
this.name = "OAuthUpstreamUnreachableError";
|
|
157
|
-
}
|
|
158
|
-
};
|
|
159
|
-
var MetadataUnreachableError = class extends Error {
|
|
160
146
|
constructor(message) {
|
|
161
147
|
super(message);
|
|
162
|
-
this.name = "
|
|
148
|
+
this.name = "OAuthConfigurationError";
|
|
163
149
|
}
|
|
164
150
|
};
|
|
165
151
|
var METADATA_FETCH_TIMEOUT_MS = 5e3;
|
|
@@ -191,28 +177,16 @@ async function fetchFirstValidOAuthServerMetadata(metadataUrls, expectedIssuer)
|
|
|
191
177
|
try {
|
|
192
178
|
return await fetchOAuthServerMetadata(url, expectedIssuer);
|
|
193
179
|
} catch (err) {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
errors.push(error);
|
|
180
|
+
log.debug("oauth.discovery.attempt_failed", { url, ...describeError(err) });
|
|
181
|
+
errors.push(`${url}: ${err instanceof Error ? err.message : String(err)}`);
|
|
197
182
|
}
|
|
198
183
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
log.error("oauth.discovery.exhausted", { expectedIssuer, urlsTried: metadataUrls, errors: detail, unreachable });
|
|
202
|
-
const message = `failed to discover OAuth server metadata (${detail})`;
|
|
203
|
-
throw unreachable ? new MetadataUnreachableError(message) : new Error(message);
|
|
184
|
+
log.error("oauth.discovery.exhausted", { expectedIssuer, urlsTried: metadataUrls, errors });
|
|
185
|
+
throw new Error(`failed to discover OAuth server metadata (${errors.join("; ")})`);
|
|
204
186
|
}
|
|
205
187
|
async function fetchOAuthServerMetadata(url, expectedIssuer) {
|
|
206
188
|
log.debug("oauth.discovery.fetch", { url });
|
|
207
|
-
|
|
208
|
-
try {
|
|
209
|
-
response = await fetch(url, {
|
|
210
|
-
signal: AbortSignal.timeout(METADATA_FETCH_TIMEOUT_MS),
|
|
211
|
-
redirect: "manual"
|
|
212
|
-
});
|
|
213
|
-
} catch (err) {
|
|
214
|
-
throw new MetadataUnreachableError(err instanceof Error ? err.message : String(err));
|
|
215
|
-
}
|
|
189
|
+
const response = await fetch(url, { signal: AbortSignal.timeout(METADATA_FETCH_TIMEOUT_MS), redirect: "manual" });
|
|
216
190
|
if (!response.ok) {
|
|
217
191
|
throw new Error(String(response.status));
|
|
218
192
|
}
|
|
@@ -235,21 +209,14 @@ async function fetchIssuerOAuthServerMetadata(issuer) {
|
|
|
235
209
|
try {
|
|
236
210
|
return await fetchFirstValidOAuthServerMetadata(oauthMetadataUrlsForIssuer(issuer), issuer);
|
|
237
211
|
} catch (err) {
|
|
238
|
-
const message = `OAuth issuer discovery failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
239
|
-
if (err instanceof MetadataUnreachableError) {
|
|
240
|
-
log.error("oauth.discovery.unreachable", {
|
|
241
|
-
issuer,
|
|
242
|
-
...describeError(err),
|
|
243
|
-
outcome: "503 authorization server unreachable"
|
|
244
|
-
});
|
|
245
|
-
throw new OAuthUpstreamUnreachableError(message, "discovery");
|
|
246
|
-
}
|
|
247
212
|
log.error("oauth.discovery.config_error", {
|
|
248
213
|
issuer,
|
|
249
214
|
...describeError(err),
|
|
250
215
|
outcome: "500 oauth configuration error"
|
|
251
216
|
});
|
|
252
|
-
throw new OAuthConfigurationError(
|
|
217
|
+
throw new OAuthConfigurationError(
|
|
218
|
+
`OAuth issuer discovery failed: ${err instanceof Error ? err.message : String(err)}`
|
|
219
|
+
);
|
|
253
220
|
}
|
|
254
221
|
}
|
|
255
222
|
function createOAuthDiscoveryResolver(auth) {
|
|
@@ -306,13 +273,18 @@ function readString(value) {
|
|
|
306
273
|
function splitScopes(value) {
|
|
307
274
|
if (typeof value === "string")
|
|
308
275
|
return value.split(/\s+/).filter(Boolean);
|
|
309
|
-
|
|
310
|
-
return value.filter((entry) => typeof entry === "string" && entry.length > 0);
|
|
311
|
-
return [];
|
|
276
|
+
return stringClaimList(value);
|
|
312
277
|
}
|
|
313
278
|
function stringClaim(claims, name) {
|
|
314
279
|
return readString(claims[name]);
|
|
315
280
|
}
|
|
281
|
+
function stringClaimList(value) {
|
|
282
|
+
if (typeof value === "string")
|
|
283
|
+
return value === "" ? [] : [value];
|
|
284
|
+
if (Array.isArray(value))
|
|
285
|
+
return value.filter((entry) => typeof entry === "string" && entry !== "");
|
|
286
|
+
return [];
|
|
287
|
+
}
|
|
316
288
|
|
|
317
289
|
// src/auth/verifier.ts
|
|
318
290
|
var DEFAULT_JWT_ALGORITHMS = ["RS256", "RS384", "RS512", "ES256", "ES384", "ES512", "EdDSA"];
|
|
@@ -339,24 +311,15 @@ function tokenHeaderFields(token) {
|
|
|
339
311
|
}
|
|
340
312
|
}
|
|
341
313
|
async function fetchVerificationKeySet(jwksUri) {
|
|
342
|
-
let response;
|
|
343
|
-
try {
|
|
344
|
-
response = await fetch(jwksUri, { signal: AbortSignal.timeout(JWKS_FETCH_TIMEOUT_MS), redirect: "manual" });
|
|
345
|
-
} catch (err) {
|
|
346
|
-
log.error("oauth.jwks.unreachable", { ...describeError(err), outcome: "503 authorization server unreachable" });
|
|
347
|
-
throw new OAuthUpstreamUnreachableError(
|
|
348
|
-
`JWKS fetch failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
349
|
-
"jwks"
|
|
350
|
-
);
|
|
351
|
-
}
|
|
352
314
|
try {
|
|
315
|
+
const response = await fetch(jwksUri, { signal: AbortSignal.timeout(JWKS_FETCH_TIMEOUT_MS), redirect: "manual" });
|
|
353
316
|
if (!response.ok)
|
|
354
317
|
throw new Error(`JWKS endpoint returned ${response.status}`);
|
|
355
318
|
const json = await response.json();
|
|
356
319
|
return (0, import_jose.createLocalJWKSet)(json);
|
|
357
320
|
} catch (err) {
|
|
358
321
|
log.error("oauth.jwks.fetch_failed", { ...describeError(err), outcome: "500 oauth configuration error" });
|
|
359
|
-
throw new OAuthConfigurationError(`JWKS fetch failed: ${err instanceof Error ? err.message : String(err)}
|
|
322
|
+
throw new OAuthConfigurationError(`JWKS fetch failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
360
323
|
}
|
|
361
324
|
}
|
|
362
325
|
function assertAccessTokenTyp(token, allowed) {
|
|
@@ -373,13 +336,12 @@ function assertAccessTokenTyp(token, allowed) {
|
|
|
373
336
|
throw new OAuthTokenError(401, "invalid_token", "Access token typ header is not accepted");
|
|
374
337
|
}
|
|
375
338
|
}
|
|
376
|
-
async function verifyJwtClaims(token, keySet, issuer,
|
|
339
|
+
async function verifyJwtClaims(token, keySet, issuer, auth) {
|
|
377
340
|
try {
|
|
378
341
|
const { payload } = await (0, import_jose.jwtVerify)(token, keySet, {
|
|
379
342
|
// `issuer` is trimmed of any trailing slash; accept both forms so a token whose
|
|
380
343
|
// `iss` carries the slash the AS publishes still verifies.
|
|
381
344
|
issuer: [issuer, `${issuer}/`],
|
|
382
|
-
audience: [...audience],
|
|
383
345
|
algorithms: auth.algorithms ? [...auth.algorithms] : DEFAULT_JWT_ALGORITHMS,
|
|
384
346
|
requiredClaims: ["sub", "exp"],
|
|
385
347
|
clockTolerance: auth.clockToleranceSeconds ?? DEFAULT_CLOCK_TOLERANCE_SECONDS
|
|
@@ -390,6 +352,22 @@ async function verifyJwtClaims(token, keySet, issuer, audience, auth) {
|
|
|
390
352
|
throw err;
|
|
391
353
|
}
|
|
392
354
|
}
|
|
355
|
+
function checkAcceptedAudience(claims, accepted, auth) {
|
|
356
|
+
const audPopulated = claims.aud !== void 0 && !(Array.isArray(claims.aud) && claims.aud.length === 0);
|
|
357
|
+
if (audPopulated) {
|
|
358
|
+
if (stringClaimList(claims.aud).some((audience) => accepted.includes(audience)))
|
|
359
|
+
return "aud";
|
|
360
|
+
log.debug("oauth.verify.bad_audience", { tokenAud: claims.aud, accepted, outcome: "401 invalid_token" });
|
|
361
|
+
} else if (auth.acceptResourceClaim !== false) {
|
|
362
|
+
const acceptedTrimmed = accepted.map(trimTrailingSlash);
|
|
363
|
+
if (stringClaimList(claims.resource).some((resource) => acceptedTrimmed.includes(trimTrailingSlash(resource))))
|
|
364
|
+
return "resource";
|
|
365
|
+
log.debug("oauth.verify.bad_resource", { tokenResource: claims.resource, accepted, outcome: "401 invalid_token" });
|
|
366
|
+
} else {
|
|
367
|
+
log.debug("oauth.verify.empty_audience", { accepted, outcome: "401 invalid_token" });
|
|
368
|
+
}
|
|
369
|
+
throw new OAuthTokenError(401, "invalid_token", "token audience is not accepted");
|
|
370
|
+
}
|
|
393
371
|
function assertNonEmptySubject(claims) {
|
|
394
372
|
const sub = claims["sub"];
|
|
395
373
|
if (typeof sub !== "string" || sub.trim() === "") {
|
|
@@ -434,14 +412,16 @@ function createOAuthTokenVerifier(auth, discovery) {
|
|
|
434
412
|
log.debug("oauth.jwks.fetch", { jwksUri });
|
|
435
413
|
const keySet = await fetchVerificationKeySet(jwksUri);
|
|
436
414
|
assertAccessTokenTyp(token, allowedTyps);
|
|
437
|
-
const claims = await verifyJwtClaims(token, keySet, issuer,
|
|
415
|
+
const claims = await verifyJwtClaims(token, keySet, issuer, auth);
|
|
416
|
+
const audienceVia = checkAcceptedAudience(claims, acceptedAudiences, auth);
|
|
438
417
|
assertNonEmptySubject(claims);
|
|
439
418
|
const context = buildMcpAuthContext({ token, claims, issuer, resource, acceptedAudiences });
|
|
440
419
|
assertOAuthClientClaim(auth, context.principal.clientId);
|
|
441
420
|
log.info("oauth.verify.ok", {
|
|
442
421
|
sub: context.principal.sub,
|
|
443
422
|
clientId: context.principal.clientId,
|
|
444
|
-
scopes: context.principal.scopes
|
|
423
|
+
scopes: context.principal.scopes,
|
|
424
|
+
audienceVia
|
|
445
425
|
});
|
|
446
426
|
return context;
|
|
447
427
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { M as McpRuntimeOptions } from '../authorize-BMeXd_Sh.js';
|
|
2
|
-
import { d as McpDefinition } from '../types-
|
|
2
|
+
import { d as McpDefinition } from '../types-qqrmJ736.js';
|
|
3
3
|
import 'zod';
|
|
4
4
|
|
|
5
5
|
type OAuthProtectedResourceMetadataHandler = (request: Request) => Promise<Response>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { M as McpRuntimeOptions } from '../authorize-BMeXd_Sh.js';
|
|
2
|
-
import { d as McpDefinition } from '../types-
|
|
2
|
+
import { d as McpDefinition } from '../types-qqrmJ736.js';
|
|
3
3
|
import 'zod';
|
|
4
4
|
|
|
5
5
|
type OAuthProtectedResourceMetadataHandler = (request: Request) => Promise<Response>;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createOAuthProtectedResourceMetadataHandler
|
|
3
|
-
} from "../chunk-
|
|
4
|
-
import "../chunk-
|
|
3
|
+
} from "../chunk-ENJDNFEB.js";
|
|
4
|
+
import "../chunk-YLU2JTHL.js";
|
|
5
5
|
import "../chunk-H37EB22A.js";
|
|
6
6
|
import "../chunk-6DXGZZA4.js";
|
|
7
|
-
import "../chunk-
|
|
7
|
+
import "../chunk-E3Q3FRKQ.js";
|
|
8
8
|
export {
|
|
9
9
|
createOAuthProtectedResourceMetadataHandler
|
|
10
10
|
};
|