@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
|
@@ -158,23 +158,9 @@ function parseSafeUrl(subject, raw, ErrorClass = Error) {
|
|
|
158
158
|
|
|
159
159
|
// src/auth/discovery.ts
|
|
160
160
|
var OAuthConfigurationError = class extends Error {
|
|
161
|
-
constructor(message, stage) {
|
|
162
|
-
super(message);
|
|
163
|
-
this.stage = stage;
|
|
164
|
-
this.name = "OAuthConfigurationError";
|
|
165
|
-
}
|
|
166
|
-
};
|
|
167
|
-
var OAuthUpstreamUnreachableError = class extends Error {
|
|
168
|
-
constructor(message, stage) {
|
|
169
|
-
super(message);
|
|
170
|
-
this.stage = stage;
|
|
171
|
-
this.name = "OAuthUpstreamUnreachableError";
|
|
172
|
-
}
|
|
173
|
-
};
|
|
174
|
-
var MetadataUnreachableError = class extends Error {
|
|
175
161
|
constructor(message) {
|
|
176
162
|
super(message);
|
|
177
|
-
this.name = "
|
|
163
|
+
this.name = "OAuthConfigurationError";
|
|
178
164
|
}
|
|
179
165
|
};
|
|
180
166
|
var METADATA_FETCH_TIMEOUT_MS = 5e3;
|
|
@@ -206,28 +192,16 @@ async function fetchFirstValidOAuthServerMetadata(metadataUrls, expectedIssuer)
|
|
|
206
192
|
try {
|
|
207
193
|
return await fetchOAuthServerMetadata(url, expectedIssuer);
|
|
208
194
|
} catch (err) {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
errors.push(error);
|
|
195
|
+
log.debug("oauth.discovery.attempt_failed", { url, ...describeError(err) });
|
|
196
|
+
errors.push(`${url}: ${err instanceof Error ? err.message : String(err)}`);
|
|
212
197
|
}
|
|
213
198
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
log.error("oauth.discovery.exhausted", { expectedIssuer, urlsTried: metadataUrls, errors: detail, unreachable });
|
|
217
|
-
const message = `failed to discover OAuth server metadata (${detail})`;
|
|
218
|
-
throw unreachable ? new MetadataUnreachableError(message) : new Error(message);
|
|
199
|
+
log.error("oauth.discovery.exhausted", { expectedIssuer, urlsTried: metadataUrls, errors });
|
|
200
|
+
throw new Error(`failed to discover OAuth server metadata (${errors.join("; ")})`);
|
|
219
201
|
}
|
|
220
202
|
async function fetchOAuthServerMetadata(url, expectedIssuer) {
|
|
221
203
|
log.debug("oauth.discovery.fetch", { url });
|
|
222
|
-
|
|
223
|
-
try {
|
|
224
|
-
response = await fetch(url, {
|
|
225
|
-
signal: AbortSignal.timeout(METADATA_FETCH_TIMEOUT_MS),
|
|
226
|
-
redirect: "manual"
|
|
227
|
-
});
|
|
228
|
-
} catch (err) {
|
|
229
|
-
throw new MetadataUnreachableError(err instanceof Error ? err.message : String(err));
|
|
230
|
-
}
|
|
204
|
+
const response = await fetch(url, { signal: AbortSignal.timeout(METADATA_FETCH_TIMEOUT_MS), redirect: "manual" });
|
|
231
205
|
if (!response.ok) {
|
|
232
206
|
throw new Error(String(response.status));
|
|
233
207
|
}
|
|
@@ -250,21 +224,14 @@ async function fetchIssuerOAuthServerMetadata(issuer) {
|
|
|
250
224
|
try {
|
|
251
225
|
return await fetchFirstValidOAuthServerMetadata(oauthMetadataUrlsForIssuer(issuer), issuer);
|
|
252
226
|
} catch (err) {
|
|
253
|
-
const message = `OAuth issuer discovery failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
254
|
-
if (err instanceof MetadataUnreachableError) {
|
|
255
|
-
log.error("oauth.discovery.unreachable", {
|
|
256
|
-
issuer,
|
|
257
|
-
...describeError(err),
|
|
258
|
-
outcome: "503 authorization server unreachable"
|
|
259
|
-
});
|
|
260
|
-
throw new OAuthUpstreamUnreachableError(message, "discovery");
|
|
261
|
-
}
|
|
262
227
|
log.error("oauth.discovery.config_error", {
|
|
263
228
|
issuer,
|
|
264
229
|
...describeError(err),
|
|
265
230
|
outcome: "500 oauth configuration error"
|
|
266
231
|
});
|
|
267
|
-
throw new OAuthConfigurationError(
|
|
232
|
+
throw new OAuthConfigurationError(
|
|
233
|
+
`OAuth issuer discovery failed: ${err instanceof Error ? err.message : String(err)}`
|
|
234
|
+
);
|
|
268
235
|
}
|
|
269
236
|
}
|
|
270
237
|
function createOAuthDiscoveryResolver(auth) {
|
|
@@ -324,13 +291,18 @@ function readString(value) {
|
|
|
324
291
|
function splitScopes(value) {
|
|
325
292
|
if (typeof value === "string")
|
|
326
293
|
return value.split(/\s+/).filter(Boolean);
|
|
327
|
-
|
|
328
|
-
return value.filter((entry) => typeof entry === "string" && entry.length > 0);
|
|
329
|
-
return [];
|
|
294
|
+
return stringClaimList(value);
|
|
330
295
|
}
|
|
331
296
|
function stringClaim(claims, name) {
|
|
332
297
|
return readString(claims[name]);
|
|
333
298
|
}
|
|
299
|
+
function stringClaimList(value) {
|
|
300
|
+
if (typeof value === "string")
|
|
301
|
+
return value === "" ? [] : [value];
|
|
302
|
+
if (Array.isArray(value))
|
|
303
|
+
return value.filter((entry) => typeof entry === "string" && entry !== "");
|
|
304
|
+
return [];
|
|
305
|
+
}
|
|
334
306
|
|
|
335
307
|
// src/auth/verifier.ts
|
|
336
308
|
var DEFAULT_JWT_ALGORITHMS = ["RS256", "RS384", "RS512", "ES256", "ES384", "ES512", "EdDSA"];
|
|
@@ -357,24 +329,15 @@ function tokenHeaderFields(token) {
|
|
|
357
329
|
}
|
|
358
330
|
}
|
|
359
331
|
async function fetchVerificationKeySet(jwksUri) {
|
|
360
|
-
let response;
|
|
361
|
-
try {
|
|
362
|
-
response = await fetch(jwksUri, { signal: AbortSignal.timeout(JWKS_FETCH_TIMEOUT_MS), redirect: "manual" });
|
|
363
|
-
} catch (err) {
|
|
364
|
-
log.error("oauth.jwks.unreachable", { ...describeError(err), outcome: "503 authorization server unreachable" });
|
|
365
|
-
throw new OAuthUpstreamUnreachableError(
|
|
366
|
-
`JWKS fetch failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
367
|
-
"jwks"
|
|
368
|
-
);
|
|
369
|
-
}
|
|
370
332
|
try {
|
|
333
|
+
const response = await fetch(jwksUri, { signal: AbortSignal.timeout(JWKS_FETCH_TIMEOUT_MS), redirect: "manual" });
|
|
371
334
|
if (!response.ok)
|
|
372
335
|
throw new Error(`JWKS endpoint returned ${response.status}`);
|
|
373
336
|
const json = await response.json();
|
|
374
337
|
return (0, import_jose.createLocalJWKSet)(json);
|
|
375
338
|
} catch (err) {
|
|
376
339
|
log.error("oauth.jwks.fetch_failed", { ...describeError(err), outcome: "500 oauth configuration error" });
|
|
377
|
-
throw new OAuthConfigurationError(`JWKS fetch failed: ${err instanceof Error ? err.message : String(err)}
|
|
340
|
+
throw new OAuthConfigurationError(`JWKS fetch failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
378
341
|
}
|
|
379
342
|
}
|
|
380
343
|
function assertAccessTokenTyp(token, allowed) {
|
|
@@ -391,13 +354,12 @@ function assertAccessTokenTyp(token, allowed) {
|
|
|
391
354
|
throw new OAuthTokenError(401, "invalid_token", "Access token typ header is not accepted");
|
|
392
355
|
}
|
|
393
356
|
}
|
|
394
|
-
async function verifyJwtClaims(token, keySet, issuer,
|
|
357
|
+
async function verifyJwtClaims(token, keySet, issuer, auth) {
|
|
395
358
|
try {
|
|
396
359
|
const { payload } = await (0, import_jose.jwtVerify)(token, keySet, {
|
|
397
360
|
// `issuer` is trimmed of any trailing slash; accept both forms so a token whose
|
|
398
361
|
// `iss` carries the slash the AS publishes still verifies.
|
|
399
362
|
issuer: [issuer, `${issuer}/`],
|
|
400
|
-
audience: [...audience],
|
|
401
363
|
algorithms: auth.algorithms ? [...auth.algorithms] : DEFAULT_JWT_ALGORITHMS,
|
|
402
364
|
requiredClaims: ["sub", "exp"],
|
|
403
365
|
clockTolerance: auth.clockToleranceSeconds ?? DEFAULT_CLOCK_TOLERANCE_SECONDS
|
|
@@ -408,6 +370,22 @@ async function verifyJwtClaims(token, keySet, issuer, audience, auth) {
|
|
|
408
370
|
throw err;
|
|
409
371
|
}
|
|
410
372
|
}
|
|
373
|
+
function checkAcceptedAudience(claims, accepted, auth) {
|
|
374
|
+
const audPopulated = claims.aud !== void 0 && !(Array.isArray(claims.aud) && claims.aud.length === 0);
|
|
375
|
+
if (audPopulated) {
|
|
376
|
+
if (stringClaimList(claims.aud).some((audience) => accepted.includes(audience)))
|
|
377
|
+
return "aud";
|
|
378
|
+
log.debug("oauth.verify.bad_audience", { tokenAud: claims.aud, accepted, outcome: "401 invalid_token" });
|
|
379
|
+
} else if (auth.acceptResourceClaim !== false) {
|
|
380
|
+
const acceptedTrimmed = accepted.map(trimTrailingSlash);
|
|
381
|
+
if (stringClaimList(claims.resource).some((resource) => acceptedTrimmed.includes(trimTrailingSlash(resource))))
|
|
382
|
+
return "resource";
|
|
383
|
+
log.debug("oauth.verify.bad_resource", { tokenResource: claims.resource, accepted, outcome: "401 invalid_token" });
|
|
384
|
+
} else {
|
|
385
|
+
log.debug("oauth.verify.empty_audience", { accepted, outcome: "401 invalid_token" });
|
|
386
|
+
}
|
|
387
|
+
throw new OAuthTokenError(401, "invalid_token", "token audience is not accepted");
|
|
388
|
+
}
|
|
411
389
|
function assertNonEmptySubject(claims) {
|
|
412
390
|
const sub = claims["sub"];
|
|
413
391
|
if (typeof sub !== "string" || sub.trim() === "") {
|
|
@@ -452,14 +430,16 @@ function createOAuthTokenVerifier(auth, discovery) {
|
|
|
452
430
|
log.debug("oauth.jwks.fetch", { jwksUri });
|
|
453
431
|
const keySet = await fetchVerificationKeySet(jwksUri);
|
|
454
432
|
assertAccessTokenTyp(token, allowedTyps);
|
|
455
|
-
const claims = await verifyJwtClaims(token, keySet, issuer,
|
|
433
|
+
const claims = await verifyJwtClaims(token, keySet, issuer, auth);
|
|
434
|
+
const audienceVia = checkAcceptedAudience(claims, acceptedAudiences, auth);
|
|
456
435
|
assertNonEmptySubject(claims);
|
|
457
436
|
const context = buildMcpAuthContext({ token, claims, issuer, resource, acceptedAudiences });
|
|
458
437
|
assertOAuthClientClaim(auth, context.principal.clientId);
|
|
459
438
|
log.info("oauth.verify.ok", {
|
|
460
439
|
sub: context.principal.sub,
|
|
461
440
|
clientId: context.principal.clientId,
|
|
462
|
-
scopes: context.principal.scopes
|
|
441
|
+
scopes: context.principal.scopes,
|
|
442
|
+
audienceVia
|
|
463
443
|
});
|
|
464
444
|
return context;
|
|
465
445
|
};
|
|
@@ -508,12 +488,6 @@ function oauthConfigurationErrorResponse() {
|
|
|
508
488
|
headers: { ...JSON_HEADERS, "Cache-Control": "no-store" }
|
|
509
489
|
});
|
|
510
490
|
}
|
|
511
|
-
function oauthUpstreamUnreachableResponse() {
|
|
512
|
-
return new Response(JSON.stringify({ error: "authorization server unreachable" }), {
|
|
513
|
-
status: 503,
|
|
514
|
-
headers: { ...JSON_HEADERS, "Cache-Control": "no-store", "Retry-After": "30" }
|
|
515
|
-
});
|
|
516
|
-
}
|
|
517
491
|
function parseBearerToken(request) {
|
|
518
492
|
const header = request.headers.get("Authorization");
|
|
519
493
|
if (!header)
|
|
@@ -586,22 +560,12 @@ function createRequestAuthorizer(mcp, options = {}) {
|
|
|
586
560
|
assertRequiredScopes(runtime.auth, auth);
|
|
587
561
|
return { ok: true, auth };
|
|
588
562
|
} catch (err) {
|
|
589
|
-
if (err instanceof OAuthUpstreamUnreachableError) {
|
|
590
|
-
log.error("auth.upstream_unreachable", { ...describeError(err), stage: err.stage, outcome: "503" });
|
|
591
|
-
await recorder?.emit({
|
|
592
|
-
tool: null,
|
|
593
|
-
method: "authorize",
|
|
594
|
-
outcome: err.stage === "jwks" ? "auth_jwks_unreachable" : "auth_discovery_unreachable",
|
|
595
|
-
durationMs: nowMs() - startedAt
|
|
596
|
-
});
|
|
597
|
-
return { ok: false, response: oauthUpstreamUnreachableResponse() };
|
|
598
|
-
}
|
|
599
563
|
if (err instanceof OAuthConfigurationError) {
|
|
600
|
-
log.error("auth.config_error", { ...describeError(err),
|
|
564
|
+
log.error("auth.config_error", { ...describeError(err), outcome: "500" });
|
|
601
565
|
await recorder?.emit({
|
|
602
566
|
tool: null,
|
|
603
567
|
method: "authorize",
|
|
604
|
-
outcome:
|
|
568
|
+
outcome: "auth_config_error",
|
|
605
569
|
durationMs: nowMs() - startedAt
|
|
606
570
|
});
|
|
607
571
|
return { ok: false, response: oauthConfigurationErrorResponse() };
|
|
@@ -745,6 +709,14 @@ var ToolContext = class {
|
|
|
745
709
|
}
|
|
746
710
|
};
|
|
747
711
|
|
|
712
|
+
// src/core/content.ts
|
|
713
|
+
function extractTextContent(content) {
|
|
714
|
+
if (!content)
|
|
715
|
+
return void 0;
|
|
716
|
+
const text = content.filter((block) => block.type === "text").map((block) => block.text).join("\n").trim();
|
|
717
|
+
return text.length > 0 ? text : void 0;
|
|
718
|
+
}
|
|
719
|
+
|
|
748
720
|
// src/protocols/rest/invoke-tool.ts
|
|
749
721
|
var MAX_REFLECTED_TOOL_NAME = 256;
|
|
750
722
|
function safeReflectName(name) {
|
|
@@ -845,7 +817,8 @@ function createInvokeToolHandler(mcp, options = {}) {
|
|
|
845
817
|
tool: tool.name,
|
|
846
818
|
method: "tools/call",
|
|
847
819
|
outcome: result.isError ? "tool_error" : "ok",
|
|
848
|
-
durationMs: nowMs() - start
|
|
820
|
+
durationMs: nowMs() - start,
|
|
821
|
+
errorText: result.isError ? extractTextContent(result.content) : void 0
|
|
849
822
|
});
|
|
850
823
|
return Response.json({
|
|
851
824
|
content: result.content ?? [],
|
|
@@ -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 RestListToolsHandler = (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 RestListToolsHandler = (request: Request, recorder?: MetricsRecorder) => Promise<Response>;
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createInvokeToolHandler
|
|
3
|
-
} from "../../chunk-
|
|
3
|
+
} from "../../chunk-QPKPYDPB.js";
|
|
4
4
|
import {
|
|
5
5
|
createListToolsHandler
|
|
6
|
-
} from "../../chunk-
|
|
6
|
+
} from "../../chunk-LFXK3FNF.js";
|
|
7
|
+
import "../../chunk-6QFZYYUV.js";
|
|
7
8
|
import "../../chunk-MA5H6PSF.js";
|
|
8
|
-
import "../../chunk-
|
|
9
|
+
import "../../chunk-YLU2JTHL.js";
|
|
9
10
|
import "../../chunk-H37EB22A.js";
|
|
10
11
|
import "../../chunk-6DXGZZA4.js";
|
|
11
|
-
import "../../chunk-
|
|
12
|
+
import "../../chunk-E3Q3FRKQ.js";
|
|
12
13
|
export {
|
|
13
14
|
createInvokeToolHandler,
|
|
14
15
|
createListToolsHandler
|
|
@@ -174,7 +174,7 @@ function describeError(err) {
|
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
// package.json
|
|
177
|
-
var version = "0.21.0
|
|
177
|
+
var version = "0.21.0";
|
|
178
178
|
|
|
179
179
|
// src/metrics/otlp.ts
|
|
180
180
|
var SCOPE_NAME = "@lovable.dev/mcp-js";
|
|
@@ -281,7 +281,8 @@ var BaseMetricRecorder = class {
|
|
|
281
281
|
method: ev.method,
|
|
282
282
|
outcome: ev.outcome,
|
|
283
283
|
durationMs: ev.durationMs,
|
|
284
|
-
stack: this.stack
|
|
284
|
+
stack: this.stack,
|
|
285
|
+
...ev.errorText !== void 0 && { errorText: ev.errorText }
|
|
285
286
|
});
|
|
286
287
|
if (!this.config.enabled)
|
|
287
288
|
return;
|
|
@@ -440,23 +441,9 @@ function parseSafeUrl(subject, raw, ErrorClass = Error) {
|
|
|
440
441
|
|
|
441
442
|
// src/auth/discovery.ts
|
|
442
443
|
var OAuthConfigurationError = class extends Error {
|
|
443
|
-
constructor(message, stage) {
|
|
444
|
-
super(message);
|
|
445
|
-
this.stage = stage;
|
|
446
|
-
this.name = "OAuthConfigurationError";
|
|
447
|
-
}
|
|
448
|
-
};
|
|
449
|
-
var OAuthUpstreamUnreachableError = class extends Error {
|
|
450
|
-
constructor(message, stage) {
|
|
451
|
-
super(message);
|
|
452
|
-
this.stage = stage;
|
|
453
|
-
this.name = "OAuthUpstreamUnreachableError";
|
|
454
|
-
}
|
|
455
|
-
};
|
|
456
|
-
var MetadataUnreachableError = class extends Error {
|
|
457
444
|
constructor(message) {
|
|
458
445
|
super(message);
|
|
459
|
-
this.name = "
|
|
446
|
+
this.name = "OAuthConfigurationError";
|
|
460
447
|
}
|
|
461
448
|
};
|
|
462
449
|
var METADATA_FETCH_TIMEOUT_MS = 5e3;
|
|
@@ -488,28 +475,16 @@ async function fetchFirstValidOAuthServerMetadata(metadataUrls, expectedIssuer)
|
|
|
488
475
|
try {
|
|
489
476
|
return await fetchOAuthServerMetadata(url, expectedIssuer);
|
|
490
477
|
} catch (err) {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
errors.push(error);
|
|
478
|
+
log.debug("oauth.discovery.attempt_failed", { url, ...describeError(err) });
|
|
479
|
+
errors.push(`${url}: ${err instanceof Error ? err.message : String(err)}`);
|
|
494
480
|
}
|
|
495
481
|
}
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
log.error("oauth.discovery.exhausted", { expectedIssuer, urlsTried: metadataUrls, errors: detail, unreachable });
|
|
499
|
-
const message = `failed to discover OAuth server metadata (${detail})`;
|
|
500
|
-
throw unreachable ? new MetadataUnreachableError(message) : new Error(message);
|
|
482
|
+
log.error("oauth.discovery.exhausted", { expectedIssuer, urlsTried: metadataUrls, errors });
|
|
483
|
+
throw new Error(`failed to discover OAuth server metadata (${errors.join("; ")})`);
|
|
501
484
|
}
|
|
502
485
|
async function fetchOAuthServerMetadata(url, expectedIssuer) {
|
|
503
486
|
log.debug("oauth.discovery.fetch", { url });
|
|
504
|
-
|
|
505
|
-
try {
|
|
506
|
-
response = await fetch(url, {
|
|
507
|
-
signal: AbortSignal.timeout(METADATA_FETCH_TIMEOUT_MS),
|
|
508
|
-
redirect: "manual"
|
|
509
|
-
});
|
|
510
|
-
} catch (err) {
|
|
511
|
-
throw new MetadataUnreachableError(err instanceof Error ? err.message : String(err));
|
|
512
|
-
}
|
|
487
|
+
const response = await fetch(url, { signal: AbortSignal.timeout(METADATA_FETCH_TIMEOUT_MS), redirect: "manual" });
|
|
513
488
|
if (!response.ok) {
|
|
514
489
|
throw new Error(String(response.status));
|
|
515
490
|
}
|
|
@@ -532,21 +507,14 @@ async function fetchIssuerOAuthServerMetadata(issuer) {
|
|
|
532
507
|
try {
|
|
533
508
|
return await fetchFirstValidOAuthServerMetadata(oauthMetadataUrlsForIssuer(issuer), issuer);
|
|
534
509
|
} catch (err) {
|
|
535
|
-
const message = `OAuth issuer discovery failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
536
|
-
if (err instanceof MetadataUnreachableError) {
|
|
537
|
-
log.error("oauth.discovery.unreachable", {
|
|
538
|
-
issuer,
|
|
539
|
-
...describeError(err),
|
|
540
|
-
outcome: "503 authorization server unreachable"
|
|
541
|
-
});
|
|
542
|
-
throw new OAuthUpstreamUnreachableError(message, "discovery");
|
|
543
|
-
}
|
|
544
510
|
log.error("oauth.discovery.config_error", {
|
|
545
511
|
issuer,
|
|
546
512
|
...describeError(err),
|
|
547
513
|
outcome: "500 oauth configuration error"
|
|
548
514
|
});
|
|
549
|
-
throw new OAuthConfigurationError(
|
|
515
|
+
throw new OAuthConfigurationError(
|
|
516
|
+
`OAuth issuer discovery failed: ${err instanceof Error ? err.message : String(err)}`
|
|
517
|
+
);
|
|
550
518
|
}
|
|
551
519
|
}
|
|
552
520
|
function createOAuthDiscoveryResolver(auth) {
|
|
@@ -579,13 +547,18 @@ function readString(value) {
|
|
|
579
547
|
function splitScopes(value) {
|
|
580
548
|
if (typeof value === "string")
|
|
581
549
|
return value.split(/\s+/).filter(Boolean);
|
|
582
|
-
|
|
583
|
-
return value.filter((entry) => typeof entry === "string" && entry.length > 0);
|
|
584
|
-
return [];
|
|
550
|
+
return stringClaimList(value);
|
|
585
551
|
}
|
|
586
552
|
function stringClaim(claims, name) {
|
|
587
553
|
return readString(claims[name]);
|
|
588
554
|
}
|
|
555
|
+
function stringClaimList(value) {
|
|
556
|
+
if (typeof value === "string")
|
|
557
|
+
return value === "" ? [] : [value];
|
|
558
|
+
if (Array.isArray(value))
|
|
559
|
+
return value.filter((entry) => typeof entry === "string" && entry !== "");
|
|
560
|
+
return [];
|
|
561
|
+
}
|
|
589
562
|
|
|
590
563
|
// src/auth/verifier.ts
|
|
591
564
|
var DEFAULT_JWT_ALGORITHMS = ["RS256", "RS384", "RS512", "ES256", "ES384", "ES512", "EdDSA"];
|
|
@@ -612,24 +585,15 @@ function tokenHeaderFields(token) {
|
|
|
612
585
|
}
|
|
613
586
|
}
|
|
614
587
|
async function fetchVerificationKeySet(jwksUri) {
|
|
615
|
-
let response;
|
|
616
|
-
try {
|
|
617
|
-
response = await fetch(jwksUri, { signal: AbortSignal.timeout(JWKS_FETCH_TIMEOUT_MS), redirect: "manual" });
|
|
618
|
-
} catch (err) {
|
|
619
|
-
log.error("oauth.jwks.unreachable", { ...describeError(err), outcome: "503 authorization server unreachable" });
|
|
620
|
-
throw new OAuthUpstreamUnreachableError(
|
|
621
|
-
`JWKS fetch failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
622
|
-
"jwks"
|
|
623
|
-
);
|
|
624
|
-
}
|
|
625
588
|
try {
|
|
589
|
+
const response = await fetch(jwksUri, { signal: AbortSignal.timeout(JWKS_FETCH_TIMEOUT_MS), redirect: "manual" });
|
|
626
590
|
if (!response.ok)
|
|
627
591
|
throw new Error(`JWKS endpoint returned ${response.status}`);
|
|
628
592
|
const json = await response.json();
|
|
629
593
|
return (0, import_jose.createLocalJWKSet)(json);
|
|
630
594
|
} catch (err) {
|
|
631
595
|
log.error("oauth.jwks.fetch_failed", { ...describeError(err), outcome: "500 oauth configuration error" });
|
|
632
|
-
throw new OAuthConfigurationError(`JWKS fetch failed: ${err instanceof Error ? err.message : String(err)}
|
|
596
|
+
throw new OAuthConfigurationError(`JWKS fetch failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
633
597
|
}
|
|
634
598
|
}
|
|
635
599
|
function assertAccessTokenTyp(token, allowed) {
|
|
@@ -646,13 +610,12 @@ function assertAccessTokenTyp(token, allowed) {
|
|
|
646
610
|
throw new OAuthTokenError(401, "invalid_token", "Access token typ header is not accepted");
|
|
647
611
|
}
|
|
648
612
|
}
|
|
649
|
-
async function verifyJwtClaims(token, keySet, issuer,
|
|
613
|
+
async function verifyJwtClaims(token, keySet, issuer, auth) {
|
|
650
614
|
try {
|
|
651
615
|
const { payload } = await (0, import_jose.jwtVerify)(token, keySet, {
|
|
652
616
|
// `issuer` is trimmed of any trailing slash; accept both forms so a token whose
|
|
653
617
|
// `iss` carries the slash the AS publishes still verifies.
|
|
654
618
|
issuer: [issuer, `${issuer}/`],
|
|
655
|
-
audience: [...audience],
|
|
656
619
|
algorithms: auth.algorithms ? [...auth.algorithms] : DEFAULT_JWT_ALGORITHMS,
|
|
657
620
|
requiredClaims: ["sub", "exp"],
|
|
658
621
|
clockTolerance: auth.clockToleranceSeconds ?? DEFAULT_CLOCK_TOLERANCE_SECONDS
|
|
@@ -663,6 +626,22 @@ async function verifyJwtClaims(token, keySet, issuer, audience, auth) {
|
|
|
663
626
|
throw err;
|
|
664
627
|
}
|
|
665
628
|
}
|
|
629
|
+
function checkAcceptedAudience(claims, accepted, auth) {
|
|
630
|
+
const audPopulated = claims.aud !== void 0 && !(Array.isArray(claims.aud) && claims.aud.length === 0);
|
|
631
|
+
if (audPopulated) {
|
|
632
|
+
if (stringClaimList(claims.aud).some((audience) => accepted.includes(audience)))
|
|
633
|
+
return "aud";
|
|
634
|
+
log.debug("oauth.verify.bad_audience", { tokenAud: claims.aud, accepted, outcome: "401 invalid_token" });
|
|
635
|
+
} else if (auth.acceptResourceClaim !== false) {
|
|
636
|
+
const acceptedTrimmed = accepted.map(trimTrailingSlash);
|
|
637
|
+
if (stringClaimList(claims.resource).some((resource) => acceptedTrimmed.includes(trimTrailingSlash(resource))))
|
|
638
|
+
return "resource";
|
|
639
|
+
log.debug("oauth.verify.bad_resource", { tokenResource: claims.resource, accepted, outcome: "401 invalid_token" });
|
|
640
|
+
} else {
|
|
641
|
+
log.debug("oauth.verify.empty_audience", { accepted, outcome: "401 invalid_token" });
|
|
642
|
+
}
|
|
643
|
+
throw new OAuthTokenError(401, "invalid_token", "token audience is not accepted");
|
|
644
|
+
}
|
|
666
645
|
function assertNonEmptySubject(claims) {
|
|
667
646
|
const sub = claims["sub"];
|
|
668
647
|
if (typeof sub !== "string" || sub.trim() === "") {
|
|
@@ -707,14 +686,16 @@ function createOAuthTokenVerifier(auth, discovery) {
|
|
|
707
686
|
log.debug("oauth.jwks.fetch", { jwksUri });
|
|
708
687
|
const keySet = await fetchVerificationKeySet(jwksUri);
|
|
709
688
|
assertAccessTokenTyp(token, allowedTyps);
|
|
710
|
-
const claims = await verifyJwtClaims(token, keySet, issuer,
|
|
689
|
+
const claims = await verifyJwtClaims(token, keySet, issuer, auth);
|
|
690
|
+
const audienceVia = checkAcceptedAudience(claims, acceptedAudiences, auth);
|
|
711
691
|
assertNonEmptySubject(claims);
|
|
712
692
|
const context = buildMcpAuthContext({ token, claims, issuer, resource, acceptedAudiences });
|
|
713
693
|
assertOAuthClientClaim(auth, context.principal.clientId);
|
|
714
694
|
log.info("oauth.verify.ok", {
|
|
715
695
|
sub: context.principal.sub,
|
|
716
696
|
clientId: context.principal.clientId,
|
|
717
|
-
scopes: context.principal.scopes
|
|
697
|
+
scopes: context.principal.scopes,
|
|
698
|
+
audienceVia
|
|
718
699
|
});
|
|
719
700
|
return context;
|
|
720
701
|
};
|
|
@@ -763,12 +744,6 @@ function oauthConfigurationErrorResponse() {
|
|
|
763
744
|
headers: { ...JSON_HEADERS, "Cache-Control": "no-store" }
|
|
764
745
|
});
|
|
765
746
|
}
|
|
766
|
-
function oauthUpstreamUnreachableResponse() {
|
|
767
|
-
return new Response(JSON.stringify({ error: "authorization server unreachable" }), {
|
|
768
|
-
status: 503,
|
|
769
|
-
headers: { ...JSON_HEADERS, "Cache-Control": "no-store", "Retry-After": "30" }
|
|
770
|
-
});
|
|
771
|
-
}
|
|
772
747
|
function parseBearerToken(request) {
|
|
773
748
|
const header = request.headers.get("Authorization");
|
|
774
749
|
if (!header)
|
|
@@ -841,22 +816,12 @@ function createRequestAuthorizer(mcp, options = {}) {
|
|
|
841
816
|
assertRequiredScopes(runtime.auth, auth);
|
|
842
817
|
return { ok: true, auth };
|
|
843
818
|
} catch (err) {
|
|
844
|
-
if (err instanceof OAuthUpstreamUnreachableError) {
|
|
845
|
-
log.error("auth.upstream_unreachable", { ...describeError(err), stage: err.stage, outcome: "503" });
|
|
846
|
-
await recorder?.emit({
|
|
847
|
-
tool: null,
|
|
848
|
-
method: "authorize",
|
|
849
|
-
outcome: err.stage === "jwks" ? "auth_jwks_unreachable" : "auth_discovery_unreachable",
|
|
850
|
-
durationMs: nowMs() - startedAt
|
|
851
|
-
});
|
|
852
|
-
return { ok: false, response: oauthUpstreamUnreachableResponse() };
|
|
853
|
-
}
|
|
854
819
|
if (err instanceof OAuthConfigurationError) {
|
|
855
|
-
log.error("auth.config_error", { ...describeError(err),
|
|
820
|
+
log.error("auth.config_error", { ...describeError(err), outcome: "500" });
|
|
856
821
|
await recorder?.emit({
|
|
857
822
|
tool: null,
|
|
858
823
|
method: "authorize",
|
|
859
|
-
outcome:
|
|
824
|
+
outcome: "auth_config_error",
|
|
860
825
|
durationMs: nowMs() - startedAt
|
|
861
826
|
});
|
|
862
827
|
return { ok: false, response: oauthConfigurationErrorResponse() };
|
|
@@ -935,6 +900,14 @@ var ToolContext = class {
|
|
|
935
900
|
}
|
|
936
901
|
};
|
|
937
902
|
|
|
903
|
+
// src/core/content.ts
|
|
904
|
+
function extractTextContent(content) {
|
|
905
|
+
if (!content)
|
|
906
|
+
return void 0;
|
|
907
|
+
const text = content.filter((block) => block.type === "text").map((block) => block.text).join("\n").trim();
|
|
908
|
+
return text.length > 0 ? text : void 0;
|
|
909
|
+
}
|
|
910
|
+
|
|
938
911
|
// src/core/cors.ts
|
|
939
912
|
var EXPOSE_HEADERS = "WWW-Authenticate, Mcp-Session-Id, Mcp-Protocol-Version";
|
|
940
913
|
var ALLOW_HEADERS = "Authorization, Content-Type, Mcp-Session-Id, Mcp-Protocol-Version, Last-Event-ID";
|
|
@@ -985,7 +958,8 @@ function adaptToolToSdkCallback(tool, auth, recorder) {
|
|
|
985
958
|
tool: tool.name,
|
|
986
959
|
method: "tools/call",
|
|
987
960
|
outcome: result.isError ? "tool_error" : "ok",
|
|
988
|
-
durationMs: nowMs() - start
|
|
961
|
+
durationMs: nowMs() - start,
|
|
962
|
+
errorText: result.isError ? extractTextContent(result.content) : void 0
|
|
989
963
|
});
|
|
990
964
|
return { content: result.content ?? [], structuredContent: result.structuredContent, isError: result.isError };
|
|
991
965
|
};
|
|
@@ -1241,7 +1215,8 @@ function createInvokeToolHandler(mcp, options = {}) {
|
|
|
1241
1215
|
tool: tool.name,
|
|
1242
1216
|
method: "tools/call",
|
|
1243
1217
|
outcome: result.isError ? "tool_error" : "ok",
|
|
1244
|
-
durationMs: nowMs() - start
|
|
1218
|
+
durationMs: nowMs() - start,
|
|
1219
|
+
errorText: result.isError ? extractTextContent(result.content) : void 0
|
|
1245
1220
|
});
|
|
1246
1221
|
return Response.json({
|
|
1247
1222
|
content: result.content ?? [],
|
|
@@ -3,21 +3,22 @@ import {
|
|
|
3
3
|
} from "../../chunk-UQK5UO6C.js";
|
|
4
4
|
import {
|
|
5
5
|
createMcpProtocolHandler
|
|
6
|
-
} from "../../chunk-
|
|
6
|
+
} from "../../chunk-CI7JYLV3.js";
|
|
7
7
|
import {
|
|
8
8
|
createOAuthProtectedResourceMetadataHandler
|
|
9
|
-
} from "../../chunk-
|
|
9
|
+
} from "../../chunk-ENJDNFEB.js";
|
|
10
10
|
import {
|
|
11
11
|
createInvokeToolHandler
|
|
12
|
-
} from "../../chunk-
|
|
12
|
+
} from "../../chunk-QPKPYDPB.js";
|
|
13
13
|
import {
|
|
14
14
|
createListToolsHandler
|
|
15
|
-
} from "../../chunk-
|
|
15
|
+
} from "../../chunk-LFXK3FNF.js";
|
|
16
|
+
import "../../chunk-6QFZYYUV.js";
|
|
16
17
|
import "../../chunk-MA5H6PSF.js";
|
|
17
18
|
import {
|
|
18
19
|
assertResourcePathShape,
|
|
19
20
|
createRecorderForRuntime
|
|
20
|
-
} from "../../chunk-
|
|
21
|
+
} from "../../chunk-YLU2JTHL.js";
|
|
21
22
|
import {
|
|
22
23
|
trimTrailingSlash
|
|
23
24
|
} from "../../chunk-H37EB22A.js";
|
|
@@ -28,7 +29,7 @@ import {
|
|
|
28
29
|
FUNCTIONS_MOUNT_PREFIX,
|
|
29
30
|
assertFunctionName
|
|
30
31
|
} from "../../chunk-XQWJN6DC.js";
|
|
31
|
-
import "../../chunk-
|
|
32
|
+
import "../../chunk-E3Q3FRKQ.js";
|
|
32
33
|
|
|
33
34
|
// src/stacks/supabase/handler.ts
|
|
34
35
|
function deriveResourcePath(options) {
|