@lovable.dev/mcp-js 0.21.0-rc.3 → 0.21.0-rc.4
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-SIDWSSWV.js → chunk-2PJKJI76.js} +1 -1
- package/dist/chunk-6QFZYYUV.js +11 -0
- package/dist/{chunk-TVU5FL2T.js → chunk-7G743K3G.js} +1 -1
- package/dist/{chunk-JIOOUIDX.js → chunk-FBR7D3XV.js} +6 -2
- package/dist/{chunk-5W6JK6OS.js → chunk-PML3SKRA.js} +6 -2
- package/dist/{chunk-CIAVINZ5.js → chunk-Q22Z6G6N.js} +45 -80
- package/dist/{chunk-NENAFXZJ.js → chunk-XPYU4DI6.js} +1 -1
- 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
package/README.md
CHANGED
|
@@ -179,7 +179,8 @@ Use an OAuth access token from the configured authorization server for MCP calls
|
|
|
179
179
|
| --- | --- |
|
|
180
180
|
| `issuer` | OAuth/OIDC issuer URL. Must be HTTPS except localhost development URLs and must match the token `iss` exactly. Required. |
|
|
181
181
|
| `resource` | Canonical protected-resource URL published in metadata. It also anchors the accepted audience unless `acceptedAudiences` is set. When omitted, the published resource is derived from the request origin plus the stack adapter's `resourcePath` and you must set `acceptedAudiences`; the metadata handler throws at construction if neither `resource` nor `resourcePath` is set. |
|
|
182
|
-
| `acceptedAudiences` | Accepted JWT audience(s). Defaults to `resource` when that is set. One of `resource` or `acceptedAudiences` is required. |
|
|
182
|
+
| `acceptedAudiences` | Accepted JWT audience(s). Defaults to `resource` when that is set. One of `resource` or `acceptedAudiences` is required. A populated token `aud` must match one of these exactly; when `aud` is empty or absent, see `acceptResourceClaim`. |
|
|
183
|
+
| `acceptResourceClaim` | Defaults to `true`: a token whose `aud` is an empty array or absent is accepted when its `resource` claim matches an accepted audience (trailing slashes ignored). The fallback engages for **any** configured issuer minting such tokens, not only Lovable's. The `resource` claim is a Lovable OAuth convention derived from the RFC 8707 `resource` request parameter, not a registered JWT claim; Lovable's OAuth server validates the requested resource at authorization time before stamping it. A populated `aud` is always authoritative — the fallback never rescues a non-matching `aud`. Set `false` to require a matching `aud` (strict RFC 9068). |
|
|
183
184
|
| `jwksUri` | JWKS URL override. If omitted, the runtime discovers `jwks_uri` from OAuth/OIDC metadata; setting it skips discovery entirely. |
|
|
184
185
|
| `requiredScopes` | Required scopes. Advertised in metadata/challenges and enforced against the token `scope`. |
|
|
185
186
|
| `requireOAuthClientClaim` | Defaults to `true`; rejects tokens carrying neither `client_id` nor the OIDC `azp` (authorized party) so copied app-session JWTs do not pass as OAuth client tokens. Supabase OAuth 2.1 server tokens carry `client_id` and issuers like Google/Keycloak/Entra use `azp`, so keep the default; only legacy `signInWithPassword` session tokens lack both and need `false`. |
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
type InvocationOutcome = "ok" | "tool_error" | "handler_error" | "transport_error" | "
|
|
1
|
+
type InvocationOutcome = "ok" | "tool_error" | "handler_error" | "transport_error" | "auth_config_error";
|
|
2
2
|
/** One recorded MCP call. Carries no arguments or response payloads — only the
|
|
3
|
-
* tool name, the JSON-RPC method, the result class, timing, and byte sizes.
|
|
3
|
+
* tool name, the JSON-RPC method, the result class, timing, and byte sizes.
|
|
4
|
+
* `errorText` is the sole exception: it is logged locally for debugging and
|
|
5
|
+
* deliberately never included in the OTLP payload (see `emit` / `buildLogsPayload`). */
|
|
4
6
|
interface InvocationRecord {
|
|
5
7
|
/** Tool name for `tools/call`; `null` for list/initialize, transport faults, and auth-config failures. */
|
|
6
8
|
tool: string | null;
|
|
@@ -10,6 +12,9 @@ interface InvocationRecord {
|
|
|
10
12
|
durationMs: number;
|
|
11
13
|
reqBytes?: number;
|
|
12
14
|
resBytes?: number;
|
|
15
|
+
/** The `tool_error` message (text content of an `isError` result). Local log
|
|
16
|
+
* only — never sent to the collector, so it stays on-box for the operator. */
|
|
17
|
+
errorText?: string;
|
|
13
18
|
}
|
|
14
19
|
/** A per-request telemetry recorder. `emit` logs the invocation and sends it as a
|
|
15
20
|
* single OTLP POST, awaited so it lands before the request ends. */
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// src/core/content.ts
|
|
2
|
+
function extractTextContent(content) {
|
|
3
|
+
if (!content)
|
|
4
|
+
return void 0;
|
|
5
|
+
const text = content.filter((block) => block.type === "text").map((block) => block.text).join("\n").trim();
|
|
6
|
+
return text.length > 0 ? text : void 0;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export {
|
|
10
|
+
extractTextContent
|
|
11
|
+
};
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
extractTextContent
|
|
3
|
+
} from "./chunk-6QFZYYUV.js";
|
|
1
4
|
import {
|
|
2
5
|
ToolContext
|
|
3
6
|
} from "./chunk-MA5H6PSF.js";
|
|
@@ -10,7 +13,7 @@ import {
|
|
|
10
13
|
methodNotAllowed,
|
|
11
14
|
nowMs,
|
|
12
15
|
withCors
|
|
13
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-Q22Z6G6N.js";
|
|
14
17
|
|
|
15
18
|
// src/protocols/rest/invoke-tool.ts
|
|
16
19
|
import { getParseErrorMessage, objectFromShape, safeParseAsync } from "@modelcontextprotocol/sdk/server/zod-compat.js";
|
|
@@ -113,7 +116,8 @@ function createInvokeToolHandler(mcp, options = {}) {
|
|
|
113
116
|
tool: tool.name,
|
|
114
117
|
method: "tools/call",
|
|
115
118
|
outcome: result.isError ? "tool_error" : "ok",
|
|
116
|
-
durationMs: nowMs() - start
|
|
119
|
+
durationMs: nowMs() - start,
|
|
120
|
+
errorText: result.isError ? extractTextContent(result.content) : void 0
|
|
117
121
|
});
|
|
118
122
|
return Response.json({
|
|
119
123
|
content: result.content ?? [],
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
extractTextContent
|
|
3
|
+
} from "./chunk-6QFZYYUV.js";
|
|
1
4
|
import {
|
|
2
5
|
ToolContext
|
|
3
6
|
} from "./chunk-MA5H6PSF.js";
|
|
@@ -7,7 +10,7 @@ import {
|
|
|
7
10
|
createRequestAuthorizer,
|
|
8
11
|
nowMs,
|
|
9
12
|
withCors
|
|
10
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-Q22Z6G6N.js";
|
|
11
14
|
import {
|
|
12
15
|
describeError,
|
|
13
16
|
log
|
|
@@ -45,7 +48,8 @@ function adaptToolToSdkCallback(tool, auth, recorder) {
|
|
|
45
48
|
tool: tool.name,
|
|
46
49
|
method: "tools/call",
|
|
47
50
|
outcome: result.isError ? "tool_error" : "ok",
|
|
48
|
-
durationMs: nowMs() - start
|
|
51
|
+
durationMs: nowMs() - start,
|
|
52
|
+
errorText: result.isError ? extractTextContent(result.content) : void 0
|
|
49
53
|
});
|
|
50
54
|
return { content: result.content ?? [], structuredContent: result.structuredContent, isError: result.isError };
|
|
51
55
|
};
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
} from "./chunk-6DXGZZA4.js";
|
|
14
14
|
import {
|
|
15
15
|
version
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-2PJKJI76.js";
|
|
17
17
|
|
|
18
18
|
// src/core/http.ts
|
|
19
19
|
var JSON_HEADERS = { "Content-Type": "application/json" };
|
|
@@ -156,7 +156,8 @@ var BaseMetricRecorder = class {
|
|
|
156
156
|
method: ev.method,
|
|
157
157
|
outcome: ev.outcome,
|
|
158
158
|
durationMs: ev.durationMs,
|
|
159
|
-
stack: this.stack
|
|
159
|
+
stack: this.stack,
|
|
160
|
+
...ev.errorText !== void 0 && { errorText: ev.errorText }
|
|
160
161
|
});
|
|
161
162
|
if (!this.config.enabled)
|
|
162
163
|
return;
|
|
@@ -284,23 +285,9 @@ function cachedPromise(load, label) {
|
|
|
284
285
|
|
|
285
286
|
// src/auth/discovery.ts
|
|
286
287
|
var OAuthConfigurationError = class extends Error {
|
|
287
|
-
constructor(message, stage) {
|
|
288
|
-
super(message);
|
|
289
|
-
this.stage = stage;
|
|
290
|
-
this.name = "OAuthConfigurationError";
|
|
291
|
-
}
|
|
292
|
-
};
|
|
293
|
-
var OAuthUpstreamUnreachableError = class extends Error {
|
|
294
|
-
constructor(message, stage) {
|
|
295
|
-
super(message);
|
|
296
|
-
this.stage = stage;
|
|
297
|
-
this.name = "OAuthUpstreamUnreachableError";
|
|
298
|
-
}
|
|
299
|
-
};
|
|
300
|
-
var MetadataUnreachableError = class extends Error {
|
|
301
288
|
constructor(message) {
|
|
302
289
|
super(message);
|
|
303
|
-
this.name = "
|
|
290
|
+
this.name = "OAuthConfigurationError";
|
|
304
291
|
}
|
|
305
292
|
};
|
|
306
293
|
var METADATA_FETCH_TIMEOUT_MS = 5e3;
|
|
@@ -332,28 +319,16 @@ async function fetchFirstValidOAuthServerMetadata(metadataUrls, expectedIssuer)
|
|
|
332
319
|
try {
|
|
333
320
|
return await fetchOAuthServerMetadata(url, expectedIssuer);
|
|
334
321
|
} catch (err) {
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
errors.push(error);
|
|
322
|
+
log.debug("oauth.discovery.attempt_failed", { url, ...describeError(err) });
|
|
323
|
+
errors.push(`${url}: ${err instanceof Error ? err.message : String(err)}`);
|
|
338
324
|
}
|
|
339
325
|
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
log.error("oauth.discovery.exhausted", { expectedIssuer, urlsTried: metadataUrls, errors: detail, unreachable });
|
|
343
|
-
const message = `failed to discover OAuth server metadata (${detail})`;
|
|
344
|
-
throw unreachable ? new MetadataUnreachableError(message) : new Error(message);
|
|
326
|
+
log.error("oauth.discovery.exhausted", { expectedIssuer, urlsTried: metadataUrls, errors });
|
|
327
|
+
throw new Error(`failed to discover OAuth server metadata (${errors.join("; ")})`);
|
|
345
328
|
}
|
|
346
329
|
async function fetchOAuthServerMetadata(url, expectedIssuer) {
|
|
347
330
|
log.debug("oauth.discovery.fetch", { url });
|
|
348
|
-
|
|
349
|
-
try {
|
|
350
|
-
response = await fetch(url, {
|
|
351
|
-
signal: AbortSignal.timeout(METADATA_FETCH_TIMEOUT_MS),
|
|
352
|
-
redirect: "manual"
|
|
353
|
-
});
|
|
354
|
-
} catch (err) {
|
|
355
|
-
throw new MetadataUnreachableError(err instanceof Error ? err.message : String(err));
|
|
356
|
-
}
|
|
331
|
+
const response = await fetch(url, { signal: AbortSignal.timeout(METADATA_FETCH_TIMEOUT_MS), redirect: "manual" });
|
|
357
332
|
if (!response.ok) {
|
|
358
333
|
throw new Error(String(response.status));
|
|
359
334
|
}
|
|
@@ -376,21 +351,14 @@ async function fetchIssuerOAuthServerMetadata(issuer) {
|
|
|
376
351
|
try {
|
|
377
352
|
return await fetchFirstValidOAuthServerMetadata(oauthMetadataUrlsForIssuer(issuer), issuer);
|
|
378
353
|
} catch (err) {
|
|
379
|
-
const message = `OAuth issuer discovery failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
380
|
-
if (err instanceof MetadataUnreachableError) {
|
|
381
|
-
log.error("oauth.discovery.unreachable", {
|
|
382
|
-
issuer,
|
|
383
|
-
...describeError(err),
|
|
384
|
-
outcome: "503 authorization server unreachable"
|
|
385
|
-
});
|
|
386
|
-
throw new OAuthUpstreamUnreachableError(message, "discovery");
|
|
387
|
-
}
|
|
388
354
|
log.error("oauth.discovery.config_error", {
|
|
389
355
|
issuer,
|
|
390
356
|
...describeError(err),
|
|
391
357
|
outcome: "500 oauth configuration error"
|
|
392
358
|
});
|
|
393
|
-
throw new OAuthConfigurationError(
|
|
359
|
+
throw new OAuthConfigurationError(
|
|
360
|
+
`OAuth issuer discovery failed: ${err instanceof Error ? err.message : String(err)}`
|
|
361
|
+
);
|
|
394
362
|
}
|
|
395
363
|
}
|
|
396
364
|
function createOAuthDiscoveryResolver(auth) {
|
|
@@ -423,13 +391,18 @@ function readString(value) {
|
|
|
423
391
|
function splitScopes(value) {
|
|
424
392
|
if (typeof value === "string")
|
|
425
393
|
return value.split(/\s+/).filter(Boolean);
|
|
426
|
-
|
|
427
|
-
return value.filter((entry) => typeof entry === "string" && entry.length > 0);
|
|
428
|
-
return [];
|
|
394
|
+
return stringClaimList(value);
|
|
429
395
|
}
|
|
430
396
|
function stringClaim(claims, name) {
|
|
431
397
|
return readString(claims[name]);
|
|
432
398
|
}
|
|
399
|
+
function stringClaimList(value) {
|
|
400
|
+
if (typeof value === "string")
|
|
401
|
+
return value === "" ? [] : [value];
|
|
402
|
+
if (Array.isArray(value))
|
|
403
|
+
return value.filter((entry) => typeof entry === "string" && entry !== "");
|
|
404
|
+
return [];
|
|
405
|
+
}
|
|
433
406
|
|
|
434
407
|
// src/auth/verifier.ts
|
|
435
408
|
var DEFAULT_JWT_ALGORITHMS = ["RS256", "RS384", "RS512", "ES256", "ES384", "ES512", "EdDSA"];
|
|
@@ -456,24 +429,15 @@ function tokenHeaderFields(token) {
|
|
|
456
429
|
}
|
|
457
430
|
}
|
|
458
431
|
async function fetchVerificationKeySet(jwksUri) {
|
|
459
|
-
let response;
|
|
460
|
-
try {
|
|
461
|
-
response = await fetch(jwksUri, { signal: AbortSignal.timeout(JWKS_FETCH_TIMEOUT_MS), redirect: "manual" });
|
|
462
|
-
} catch (err) {
|
|
463
|
-
log.error("oauth.jwks.unreachable", { ...describeError(err), outcome: "503 authorization server unreachable" });
|
|
464
|
-
throw new OAuthUpstreamUnreachableError(
|
|
465
|
-
`JWKS fetch failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
466
|
-
"jwks"
|
|
467
|
-
);
|
|
468
|
-
}
|
|
469
432
|
try {
|
|
433
|
+
const response = await fetch(jwksUri, { signal: AbortSignal.timeout(JWKS_FETCH_TIMEOUT_MS), redirect: "manual" });
|
|
470
434
|
if (!response.ok)
|
|
471
435
|
throw new Error(`JWKS endpoint returned ${response.status}`);
|
|
472
436
|
const json = await response.json();
|
|
473
437
|
return createLocalJWKSet(json);
|
|
474
438
|
} catch (err) {
|
|
475
439
|
log.error("oauth.jwks.fetch_failed", { ...describeError(err), outcome: "500 oauth configuration error" });
|
|
476
|
-
throw new OAuthConfigurationError(`JWKS fetch failed: ${err instanceof Error ? err.message : String(err)}
|
|
440
|
+
throw new OAuthConfigurationError(`JWKS fetch failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
477
441
|
}
|
|
478
442
|
}
|
|
479
443
|
function assertAccessTokenTyp(token, allowed) {
|
|
@@ -490,13 +454,12 @@ function assertAccessTokenTyp(token, allowed) {
|
|
|
490
454
|
throw new OAuthTokenError(401, "invalid_token", "Access token typ header is not accepted");
|
|
491
455
|
}
|
|
492
456
|
}
|
|
493
|
-
async function verifyJwtClaims(token, keySet, issuer,
|
|
457
|
+
async function verifyJwtClaims(token, keySet, issuer, auth) {
|
|
494
458
|
try {
|
|
495
459
|
const { payload } = await jwtVerify(token, keySet, {
|
|
496
460
|
// `issuer` is trimmed of any trailing slash; accept both forms so a token whose
|
|
497
461
|
// `iss` carries the slash the AS publishes still verifies.
|
|
498
462
|
issuer: [issuer, `${issuer}/`],
|
|
499
|
-
audience: [...audience],
|
|
500
463
|
algorithms: auth.algorithms ? [...auth.algorithms] : DEFAULT_JWT_ALGORITHMS,
|
|
501
464
|
requiredClaims: ["sub", "exp"],
|
|
502
465
|
clockTolerance: auth.clockToleranceSeconds ?? DEFAULT_CLOCK_TOLERANCE_SECONDS
|
|
@@ -507,6 +470,22 @@ async function verifyJwtClaims(token, keySet, issuer, audience, auth) {
|
|
|
507
470
|
throw err;
|
|
508
471
|
}
|
|
509
472
|
}
|
|
473
|
+
function checkAcceptedAudience(claims, accepted, auth) {
|
|
474
|
+
const audPopulated = claims.aud !== void 0 && !(Array.isArray(claims.aud) && claims.aud.length === 0);
|
|
475
|
+
if (audPopulated) {
|
|
476
|
+
if (stringClaimList(claims.aud).some((audience) => accepted.includes(audience)))
|
|
477
|
+
return "aud";
|
|
478
|
+
log.debug("oauth.verify.bad_audience", { tokenAud: claims.aud, accepted, outcome: "401 invalid_token" });
|
|
479
|
+
} else if (auth.acceptResourceClaim !== false) {
|
|
480
|
+
const acceptedTrimmed = accepted.map(trimTrailingSlash);
|
|
481
|
+
if (stringClaimList(claims.resource).some((resource) => acceptedTrimmed.includes(trimTrailingSlash(resource))))
|
|
482
|
+
return "resource";
|
|
483
|
+
log.debug("oauth.verify.bad_resource", { tokenResource: claims.resource, accepted, outcome: "401 invalid_token" });
|
|
484
|
+
} else {
|
|
485
|
+
log.debug("oauth.verify.empty_audience", { accepted, outcome: "401 invalid_token" });
|
|
486
|
+
}
|
|
487
|
+
throw new OAuthTokenError(401, "invalid_token", "token audience is not accepted");
|
|
488
|
+
}
|
|
510
489
|
function assertNonEmptySubject(claims) {
|
|
511
490
|
const sub = claims["sub"];
|
|
512
491
|
if (typeof sub !== "string" || sub.trim() === "") {
|
|
@@ -551,14 +530,16 @@ function createOAuthTokenVerifier(auth, discovery) {
|
|
|
551
530
|
log.debug("oauth.jwks.fetch", { jwksUri });
|
|
552
531
|
const keySet = await fetchVerificationKeySet(jwksUri);
|
|
553
532
|
assertAccessTokenTyp(token, allowedTyps);
|
|
554
|
-
const claims = await verifyJwtClaims(token, keySet, issuer,
|
|
533
|
+
const claims = await verifyJwtClaims(token, keySet, issuer, auth);
|
|
534
|
+
const audienceVia = checkAcceptedAudience(claims, acceptedAudiences, auth);
|
|
555
535
|
assertNonEmptySubject(claims);
|
|
556
536
|
const context = buildMcpAuthContext({ token, claims, issuer, resource, acceptedAudiences });
|
|
557
537
|
assertOAuthClientClaim(auth, context.principal.clientId);
|
|
558
538
|
log.info("oauth.verify.ok", {
|
|
559
539
|
sub: context.principal.sub,
|
|
560
540
|
clientId: context.principal.clientId,
|
|
561
|
-
scopes: context.principal.scopes
|
|
541
|
+
scopes: context.principal.scopes,
|
|
542
|
+
audienceVia
|
|
562
543
|
});
|
|
563
544
|
return context;
|
|
564
545
|
};
|
|
@@ -607,12 +588,6 @@ function oauthConfigurationErrorResponse() {
|
|
|
607
588
|
headers: { ...JSON_HEADERS, "Cache-Control": "no-store" }
|
|
608
589
|
});
|
|
609
590
|
}
|
|
610
|
-
function oauthUpstreamUnreachableResponse() {
|
|
611
|
-
return new Response(JSON.stringify({ error: "authorization server unreachable" }), {
|
|
612
|
-
status: 503,
|
|
613
|
-
headers: { ...JSON_HEADERS, "Cache-Control": "no-store", "Retry-After": "30" }
|
|
614
|
-
});
|
|
615
|
-
}
|
|
616
591
|
function parseBearerToken(request) {
|
|
617
592
|
const header = request.headers.get("Authorization");
|
|
618
593
|
if (!header)
|
|
@@ -685,22 +660,12 @@ function createRequestAuthorizer(mcp, options = {}) {
|
|
|
685
660
|
assertRequiredScopes(runtime.auth, auth);
|
|
686
661
|
return { ok: true, auth };
|
|
687
662
|
} catch (err) {
|
|
688
|
-
if (err instanceof OAuthUpstreamUnreachableError) {
|
|
689
|
-
log.error("auth.upstream_unreachable", { ...describeError(err), stage: err.stage, outcome: "503" });
|
|
690
|
-
await recorder?.emit({
|
|
691
|
-
tool: null,
|
|
692
|
-
method: "authorize",
|
|
693
|
-
outcome: err.stage === "jwks" ? "auth_jwks_unreachable" : "auth_discovery_unreachable",
|
|
694
|
-
durationMs: nowMs() - startedAt
|
|
695
|
-
});
|
|
696
|
-
return { ok: false, response: oauthUpstreamUnreachableResponse() };
|
|
697
|
-
}
|
|
698
663
|
if (err instanceof OAuthConfigurationError) {
|
|
699
|
-
log.error("auth.config_error", { ...describeError(err),
|
|
664
|
+
log.error("auth.config_error", { ...describeError(err), outcome: "500" });
|
|
700
665
|
await recorder?.emit({
|
|
701
666
|
tool: null,
|
|
702
667
|
method: "authorize",
|
|
703
|
-
outcome:
|
|
668
|
+
outcome: "auth_config_error",
|
|
704
669
|
durationMs: nowMs() - startedAt
|
|
705
670
|
});
|
|
706
671
|
return { ok: false, response: oauthConfigurationErrorResponse() };
|
|
@@ -13,7 +13,7 @@ function isFileMissing(err) {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
// package.json
|
|
16
|
-
var version = "0.21.0-rc.
|
|
16
|
+
var version = "0.21.0-rc.4";
|
|
17
17
|
|
|
18
18
|
// src/protocols/rest/list-tools.ts
|
|
19
19
|
var import_zod_compat = require("@modelcontextprotocol/sdk/server/zod-compat.js");
|
|
@@ -80,6 +80,8 @@ function manifestAuth(auth) {
|
|
|
80
80
|
oauth.protected_resource_metadata_url = auth.protectedResourceMetadataUrl;
|
|
81
81
|
if (auth.requireOAuthClientClaim !== void 0)
|
|
82
82
|
oauth.require_oauth_client_claim = auth.requireOAuthClientClaim;
|
|
83
|
+
if (auth.acceptResourceClaim !== void 0)
|
|
84
|
+
oauth.accept_resource_claim = auth.acceptResourceClaim;
|
|
83
85
|
if (auth.acceptedAudiences !== void 0)
|
|
84
86
|
oauth.accepted_audiences = auth.acceptedAudiences;
|
|
85
87
|
if (auth.requiredScopes !== void 0)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
buildMcpListing
|
|
4
|
-
} from "../chunk-
|
|
5
|
-
import "../chunk-
|
|
4
|
+
} from "../chunk-7G743K3G.js";
|
|
5
|
+
import "../chunk-Q22Z6G6N.js";
|
|
6
6
|
import "../chunk-H37EB22A.js";
|
|
7
7
|
import "../chunk-6DXGZZA4.js";
|
|
8
8
|
import {
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
} from "../chunk-Y3ZFPEQH.js";
|
|
11
11
|
import {
|
|
12
12
|
version
|
|
13
|
-
} from "../chunk-
|
|
13
|
+
} from "../chunk-2PJKJI76.js";
|
|
14
14
|
|
|
15
15
|
// src/manifest/io.ts
|
|
16
16
|
import { randomUUID } from "crypto";
|
|
@@ -35,6 +35,8 @@ function manifestAuth(auth) {
|
|
|
35
35
|
oauth.protected_resource_metadata_url = auth.protectedResourceMetadataUrl;
|
|
36
36
|
if (auth.requireOAuthClientClaim !== void 0)
|
|
37
37
|
oauth.require_oauth_client_claim = auth.requireOAuthClientClaim;
|
|
38
|
+
if (auth.acceptResourceClaim !== void 0)
|
|
39
|
+
oauth.accept_resource_claim = auth.acceptResourceClaim;
|
|
38
40
|
if (auth.acceptedAudiences !== void 0)
|
|
39
41
|
oauth.accepted_audiences = auth.acceptedAudiences;
|
|
40
42
|
if (auth.requiredScopes !== void 0)
|
package/dist/index.cjs
CHANGED
|
@@ -55,6 +55,9 @@ function resolveMetricsConfig(config = true) {
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
// src/core/url.ts
|
|
58
|
+
function trimTrailingSlash(value) {
|
|
59
|
+
return value.replace(/\/+$/, "");
|
|
60
|
+
}
|
|
58
61
|
function isLocalHTTPHost(hostname) {
|
|
59
62
|
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]";
|
|
60
63
|
}
|
|
@@ -166,8 +169,17 @@ function assertOAuthConfig(auth2) {
|
|
|
166
169
|
throw new Error(`@lovable.dev/mcp-js: auth.acceptedAudiences must contain strings`);
|
|
167
170
|
}
|
|
168
171
|
assertNonEmptyString("auth.acceptedAudiences", audience);
|
|
172
|
+
if (trimTrailingSlash(audience) === "") {
|
|
173
|
+
throw new Error(`@lovable.dev/mcp-js: auth.acceptedAudiences entries must not be only slashes`);
|
|
174
|
+
}
|
|
169
175
|
}
|
|
170
176
|
}
|
|
177
|
+
if (auth2.requireOAuthClientClaim !== void 0 && typeof auth2.requireOAuthClientClaim !== "boolean") {
|
|
178
|
+
throw new Error(`@lovable.dev/mcp-js: auth.requireOAuthClientClaim must be a boolean`);
|
|
179
|
+
}
|
|
180
|
+
if (auth2.acceptResourceClaim !== void 0 && typeof auth2.acceptResourceClaim !== "boolean") {
|
|
181
|
+
throw new Error(`@lovable.dev/mcp-js: auth.acceptResourceClaim must be a boolean`);
|
|
182
|
+
}
|
|
171
183
|
if (auth2.requiredScopes !== void 0) {
|
|
172
184
|
if (!Array.isArray(auth2.requiredScopes)) {
|
|
173
185
|
throw new Error(`@lovable.dev/mcp-js: auth.requiredScopes must be an array`);
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { e as McpDefinitionInput, d as McpDefinition, Z as ZodRawShape, l as ToolDefinition, M as McpAuthConfig } from './types-
|
|
2
|
-
export { A as AudioContent, C as ContentAnnotations, a as ContentBlock, E as EmbeddedBlobResource, b as EmbeddedResource, c as EmbeddedTextResource, I as ImageContent, J as JwtClaims, f as MetricsConfig, g as MetricsOptions, R as ResourceLink, h as ResourceLinkIcon, T as TextContent, i as ToolAnnotations, j as ToolContent, k as ToolContext, m as ToolHandlerResult, n as ZodSchema } from './types-
|
|
1
|
+
import { e as McpDefinitionInput, d as McpDefinition, Z as ZodRawShape, l as ToolDefinition, M as McpAuthConfig } from './types-qqrmJ736.js';
|
|
2
|
+
export { A as AudioContent, C as ContentAnnotations, a as ContentBlock, E as EmbeddedBlobResource, b as EmbeddedResource, c as EmbeddedTextResource, I as ImageContent, J as JwtClaims, f as MetricsConfig, g as MetricsOptions, R as ResourceLink, h as ResourceLinkIcon, T as TextContent, i as ToolAnnotations, j as ToolContent, k as ToolContext, m as ToolHandlerResult, n as ZodSchema } from './types-qqrmJ736.js';
|
|
3
3
|
import 'zod';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -24,6 +24,7 @@ interface IssuerOAuthOptionsBase {
|
|
|
24
24
|
readonly resourceDocumentation?: string;
|
|
25
25
|
readonly protectedResourceMetadataUrl?: string;
|
|
26
26
|
readonly requireOAuthClientClaim?: boolean;
|
|
27
|
+
readonly acceptResourceClaim?: boolean;
|
|
27
28
|
readonly issuer: string;
|
|
28
29
|
readonly requiredScopes?: readonly string[];
|
|
29
30
|
readonly jwksUri?: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { e as McpDefinitionInput, d as McpDefinition, Z as ZodRawShape, l as ToolDefinition, M as McpAuthConfig } from './types-
|
|
2
|
-
export { A as AudioContent, C as ContentAnnotations, a as ContentBlock, E as EmbeddedBlobResource, b as EmbeddedResource, c as EmbeddedTextResource, I as ImageContent, J as JwtClaims, f as MetricsConfig, g as MetricsOptions, R as ResourceLink, h as ResourceLinkIcon, T as TextContent, i as ToolAnnotations, j as ToolContent, k as ToolContext, m as ToolHandlerResult, n as ZodSchema } from './types-
|
|
1
|
+
import { e as McpDefinitionInput, d as McpDefinition, Z as ZodRawShape, l as ToolDefinition, M as McpAuthConfig } from './types-qqrmJ736.js';
|
|
2
|
+
export { A as AudioContent, C as ContentAnnotations, a as ContentBlock, E as EmbeddedBlobResource, b as EmbeddedResource, c as EmbeddedTextResource, I as ImageContent, J as JwtClaims, f as MetricsConfig, g as MetricsOptions, R as ResourceLink, h as ResourceLinkIcon, T as TextContent, i as ToolAnnotations, j as ToolContent, k as ToolContext, m as ToolHandlerResult, n as ZodSchema } from './types-qqrmJ736.js';
|
|
3
3
|
import 'zod';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -24,6 +24,7 @@ interface IssuerOAuthOptionsBase {
|
|
|
24
24
|
readonly resourceDocumentation?: string;
|
|
25
25
|
readonly protectedResourceMetadataUrl?: string;
|
|
26
26
|
readonly requireOAuthClientClaim?: boolean;
|
|
27
|
+
readonly acceptResourceClaim?: boolean;
|
|
27
28
|
readonly issuer: string;
|
|
28
29
|
readonly requiredScopes?: readonly string[];
|
|
29
30
|
readonly jwksUri?: string;
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,8 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
parseSafeUrl,
|
|
6
6
|
resolveMetricsConfig,
|
|
7
|
-
setLogLevel
|
|
7
|
+
setLogLevel,
|
|
8
|
+
trimTrailingSlash
|
|
8
9
|
} from "./chunk-H37EB22A.js";
|
|
9
10
|
|
|
10
11
|
// src/core/define.ts
|
|
@@ -86,8 +87,17 @@ function assertOAuthConfig(auth2) {
|
|
|
86
87
|
throw new Error(`@lovable.dev/mcp-js: auth.acceptedAudiences must contain strings`);
|
|
87
88
|
}
|
|
88
89
|
assertNonEmptyString("auth.acceptedAudiences", audience);
|
|
90
|
+
if (trimTrailingSlash(audience) === "") {
|
|
91
|
+
throw new Error(`@lovable.dev/mcp-js: auth.acceptedAudiences entries must not be only slashes`);
|
|
92
|
+
}
|
|
89
93
|
}
|
|
90
94
|
}
|
|
95
|
+
if (auth2.requireOAuthClientClaim !== void 0 && typeof auth2.requireOAuthClientClaim !== "boolean") {
|
|
96
|
+
throw new Error(`@lovable.dev/mcp-js: auth.requireOAuthClientClaim must be a boolean`);
|
|
97
|
+
}
|
|
98
|
+
if (auth2.acceptResourceClaim !== void 0 && typeof auth2.acceptResourceClaim !== "boolean") {
|
|
99
|
+
throw new Error(`@lovable.dev/mcp-js: auth.acceptResourceClaim must be a boolean`);
|
|
100
|
+
}
|
|
91
101
|
if (auth2.requiredScopes !== void 0) {
|
|
92
102
|
if (!Array.isArray(auth2.requiredScopes)) {
|
|
93
103
|
throw new Error(`@lovable.dev/mcp-js: auth.requiredScopes must be an array`);
|