@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.
Files changed (39) hide show
  1. package/README.md +2 -1
  2. package/dist/{base-DTtaX5Rf.d.ts → base-CFy5vZ5Z.d.ts} +7 -2
  3. package/dist/chunk-6QFZYYUV.js +11 -0
  4. package/dist/{chunk-5W6JK6OS.js → chunk-CI7JYLV3.js} +6 -2
  5. package/dist/{chunk-SIDWSSWV.js → chunk-E3Q3FRKQ.js} +1 -1
  6. package/dist/{chunk-NENAFXZJ.js → chunk-ENJDNFEB.js} +1 -1
  7. package/dist/{chunk-TVU5FL2T.js → chunk-LFXK3FNF.js} +1 -1
  8. package/dist/{chunk-JIOOUIDX.js → chunk-QPKPYDPB.js} +6 -2
  9. package/dist/{chunk-CIAVINZ5.js → chunk-YLU2JTHL.js} +45 -80
  10. package/dist/cli/extract-manifest.cjs +3 -1
  11. package/dist/cli/extract-manifest.js +5 -3
  12. package/dist/index.cjs +12 -0
  13. package/dist/index.d.cts +3 -2
  14. package/dist/index.d.ts +3 -2
  15. package/dist/index.js +11 -1
  16. package/dist/protocols/mcp/index.cjs +52 -79
  17. package/dist/protocols/mcp/index.d.cts +2 -2
  18. package/dist/protocols/mcp/index.d.ts +2 -2
  19. package/dist/protocols/mcp/index.js +4 -3
  20. package/dist/protocols/oauth-metadata.cjs +40 -60
  21. package/dist/protocols/oauth-metadata.d.cts +1 -1
  22. package/dist/protocols/oauth-metadata.d.ts +1 -1
  23. package/dist/protocols/oauth-metadata.js +3 -3
  24. package/dist/protocols/rest/index.cjs +52 -79
  25. package/dist/protocols/rest/index.d.cts +2 -2
  26. package/dist/protocols/rest/index.d.ts +2 -2
  27. package/dist/protocols/rest/index.js +5 -4
  28. package/dist/stacks/supabase/index.cjs +57 -82
  29. package/dist/stacks/supabase/index.d.cts +1 -1
  30. package/dist/stacks/supabase/index.d.ts +1 -1
  31. package/dist/stacks/supabase/index.js +7 -6
  32. package/dist/stacks/supabase/vite.cjs +1 -1
  33. package/dist/stacks/supabase/vite.js +1 -1
  34. package/dist/stacks/tanstack/index.cjs +57 -82
  35. package/dist/stacks/tanstack/index.d.cts +1 -1
  36. package/dist/stacks/tanstack/index.d.ts +1 -1
  37. package/dist/stacks/tanstack/index.js +7 -6
  38. package/dist/{types-CPkhCRxc.d.ts → types-qqrmJ736.d.ts} +10 -0
  39. package/package.json +1 -1
@@ -129,7 +129,7 @@ function describeError(err) {
129
129
  }
130
130
 
131
131
  // package.json
132
- var version = "0.21.0-rc.3";
132
+ var version = "0.21.0";
133
133
 
134
134
  // src/metrics/otlp.ts
135
135
  var SCOPE_NAME = "@lovable.dev/mcp-js";
@@ -236,7 +236,8 @@ var BaseMetricRecorder = class {
236
236
  method: ev.method,
237
237
  outcome: ev.outcome,
238
238
  durationMs: ev.durationMs,
239
- stack: this.stack
239
+ stack: this.stack,
240
+ ...ev.errorText !== void 0 && { errorText: ev.errorText }
240
241
  });
241
242
  if (!this.config.enabled)
242
243
  return;
@@ -416,23 +417,9 @@ function parseSafeUrl(subject, raw, ErrorClass = Error) {
416
417
 
417
418
  // src/auth/discovery.ts
418
419
  var OAuthConfigurationError = class extends Error {
419
- constructor(message, stage) {
420
- super(message);
421
- this.stage = stage;
422
- this.name = "OAuthConfigurationError";
423
- }
424
- };
425
- var OAuthUpstreamUnreachableError = class extends Error {
426
- constructor(message, stage) {
427
- super(message);
428
- this.stage = stage;
429
- this.name = "OAuthUpstreamUnreachableError";
430
- }
431
- };
432
- var MetadataUnreachableError = class extends Error {
433
420
  constructor(message) {
434
421
  super(message);
435
- this.name = "MetadataUnreachableError";
422
+ this.name = "OAuthConfigurationError";
436
423
  }
437
424
  };
438
425
  var METADATA_FETCH_TIMEOUT_MS = 5e3;
@@ -464,28 +451,16 @@ async function fetchFirstValidOAuthServerMetadata(metadataUrls, expectedIssuer)
464
451
  try {
465
452
  return await fetchOAuthServerMetadata(url, expectedIssuer);
466
453
  } catch (err) {
467
- const error = err instanceof Error ? err : new Error(String(err));
468
- log.debug("oauth.discovery.attempt_failed", { url, ...describeError(error) });
469
- errors.push(error);
454
+ log.debug("oauth.discovery.attempt_failed", { url, ...describeError(err) });
455
+ errors.push(`${url}: ${err instanceof Error ? err.message : String(err)}`);
470
456
  }
471
457
  }
472
- const unreachable = errors.some((e) => e instanceof MetadataUnreachableError);
473
- const detail = errors.map((e) => e.message).join("; ");
474
- log.error("oauth.discovery.exhausted", { expectedIssuer, urlsTried: metadataUrls, errors: detail, unreachable });
475
- const message = `failed to discover OAuth server metadata (${detail})`;
476
- throw unreachable ? new MetadataUnreachableError(message) : new Error(message);
458
+ log.error("oauth.discovery.exhausted", { expectedIssuer, urlsTried: metadataUrls, errors });
459
+ throw new Error(`failed to discover OAuth server metadata (${errors.join("; ")})`);
477
460
  }
478
461
  async function fetchOAuthServerMetadata(url, expectedIssuer) {
479
462
  log.debug("oauth.discovery.fetch", { url });
480
- let response;
481
- try {
482
- response = await fetch(url, {
483
- signal: AbortSignal.timeout(METADATA_FETCH_TIMEOUT_MS),
484
- redirect: "manual"
485
- });
486
- } catch (err) {
487
- throw new MetadataUnreachableError(err instanceof Error ? err.message : String(err));
488
- }
463
+ const response = await fetch(url, { signal: AbortSignal.timeout(METADATA_FETCH_TIMEOUT_MS), redirect: "manual" });
489
464
  if (!response.ok) {
490
465
  throw new Error(String(response.status));
491
466
  }
@@ -508,21 +483,14 @@ async function fetchIssuerOAuthServerMetadata(issuer) {
508
483
  try {
509
484
  return await fetchFirstValidOAuthServerMetadata(oauthMetadataUrlsForIssuer(issuer), issuer);
510
485
  } catch (err) {
511
- const message = `OAuth issuer discovery failed: ${err instanceof Error ? err.message : String(err)}`;
512
- if (err instanceof MetadataUnreachableError) {
513
- log.error("oauth.discovery.unreachable", {
514
- issuer,
515
- ...describeError(err),
516
- outcome: "503 authorization server unreachable"
517
- });
518
- throw new OAuthUpstreamUnreachableError(message, "discovery");
519
- }
520
486
  log.error("oauth.discovery.config_error", {
521
487
  issuer,
522
488
  ...describeError(err),
523
489
  outcome: "500 oauth configuration error"
524
490
  });
525
- throw new OAuthConfigurationError(message, "discovery");
491
+ throw new OAuthConfigurationError(
492
+ `OAuth issuer discovery failed: ${err instanceof Error ? err.message : String(err)}`
493
+ );
526
494
  }
527
495
  }
528
496
  function createOAuthDiscoveryResolver(auth) {
@@ -582,13 +550,18 @@ function readString(value) {
582
550
  function splitScopes(value) {
583
551
  if (typeof value === "string")
584
552
  return value.split(/\s+/).filter(Boolean);
585
- if (Array.isArray(value))
586
- return value.filter((entry) => typeof entry === "string" && entry.length > 0);
587
- return [];
553
+ return stringClaimList(value);
588
554
  }
589
555
  function stringClaim(claims, name) {
590
556
  return readString(claims[name]);
591
557
  }
558
+ function stringClaimList(value) {
559
+ if (typeof value === "string")
560
+ return value === "" ? [] : [value];
561
+ if (Array.isArray(value))
562
+ return value.filter((entry) => typeof entry === "string" && entry !== "");
563
+ return [];
564
+ }
592
565
 
593
566
  // src/auth/verifier.ts
594
567
  var DEFAULT_JWT_ALGORITHMS = ["RS256", "RS384", "RS512", "ES256", "ES384", "ES512", "EdDSA"];
@@ -615,24 +588,15 @@ function tokenHeaderFields(token) {
615
588
  }
616
589
  }
617
590
  async function fetchVerificationKeySet(jwksUri) {
618
- let response;
619
- try {
620
- response = await fetch(jwksUri, { signal: AbortSignal.timeout(JWKS_FETCH_TIMEOUT_MS), redirect: "manual" });
621
- } catch (err) {
622
- log.error("oauth.jwks.unreachable", { ...describeError(err), outcome: "503 authorization server unreachable" });
623
- throw new OAuthUpstreamUnreachableError(
624
- `JWKS fetch failed: ${err instanceof Error ? err.message : String(err)}`,
625
- "jwks"
626
- );
627
- }
628
591
  try {
592
+ const response = await fetch(jwksUri, { signal: AbortSignal.timeout(JWKS_FETCH_TIMEOUT_MS), redirect: "manual" });
629
593
  if (!response.ok)
630
594
  throw new Error(`JWKS endpoint returned ${response.status}`);
631
595
  const json = await response.json();
632
596
  return (0, import_jose.createLocalJWKSet)(json);
633
597
  } catch (err) {
634
598
  log.error("oauth.jwks.fetch_failed", { ...describeError(err), outcome: "500 oauth configuration error" });
635
- throw new OAuthConfigurationError(`JWKS fetch failed: ${err instanceof Error ? err.message : String(err)}`, "jwks");
599
+ throw new OAuthConfigurationError(`JWKS fetch failed: ${err instanceof Error ? err.message : String(err)}`);
636
600
  }
637
601
  }
638
602
  function assertAccessTokenTyp(token, allowed) {
@@ -649,13 +613,12 @@ function assertAccessTokenTyp(token, allowed) {
649
613
  throw new OAuthTokenError(401, "invalid_token", "Access token typ header is not accepted");
650
614
  }
651
615
  }
652
- async function verifyJwtClaims(token, keySet, issuer, audience, auth) {
616
+ async function verifyJwtClaims(token, keySet, issuer, auth) {
653
617
  try {
654
618
  const { payload } = await (0, import_jose.jwtVerify)(token, keySet, {
655
619
  // `issuer` is trimmed of any trailing slash; accept both forms so a token whose
656
620
  // `iss` carries the slash the AS publishes still verifies.
657
621
  issuer: [issuer, `${issuer}/`],
658
- audience: [...audience],
659
622
  algorithms: auth.algorithms ? [...auth.algorithms] : DEFAULT_JWT_ALGORITHMS,
660
623
  requiredClaims: ["sub", "exp"],
661
624
  clockTolerance: auth.clockToleranceSeconds ?? DEFAULT_CLOCK_TOLERANCE_SECONDS
@@ -666,6 +629,22 @@ async function verifyJwtClaims(token, keySet, issuer, audience, auth) {
666
629
  throw err;
667
630
  }
668
631
  }
632
+ function checkAcceptedAudience(claims, accepted, auth) {
633
+ const audPopulated = claims.aud !== void 0 && !(Array.isArray(claims.aud) && claims.aud.length === 0);
634
+ if (audPopulated) {
635
+ if (stringClaimList(claims.aud).some((audience) => accepted.includes(audience)))
636
+ return "aud";
637
+ log.debug("oauth.verify.bad_audience", { tokenAud: claims.aud, accepted, outcome: "401 invalid_token" });
638
+ } else if (auth.acceptResourceClaim !== false) {
639
+ const acceptedTrimmed = accepted.map(trimTrailingSlash);
640
+ if (stringClaimList(claims.resource).some((resource) => acceptedTrimmed.includes(trimTrailingSlash(resource))))
641
+ return "resource";
642
+ log.debug("oauth.verify.bad_resource", { tokenResource: claims.resource, accepted, outcome: "401 invalid_token" });
643
+ } else {
644
+ log.debug("oauth.verify.empty_audience", { accepted, outcome: "401 invalid_token" });
645
+ }
646
+ throw new OAuthTokenError(401, "invalid_token", "token audience is not accepted");
647
+ }
669
648
  function assertNonEmptySubject(claims) {
670
649
  const sub = claims["sub"];
671
650
  if (typeof sub !== "string" || sub.trim() === "") {
@@ -710,14 +689,16 @@ function createOAuthTokenVerifier(auth, discovery) {
710
689
  log.debug("oauth.jwks.fetch", { jwksUri });
711
690
  const keySet = await fetchVerificationKeySet(jwksUri);
712
691
  assertAccessTokenTyp(token, allowedTyps);
713
- const claims = await verifyJwtClaims(token, keySet, issuer, acceptedAudiences, auth);
692
+ const claims = await verifyJwtClaims(token, keySet, issuer, auth);
693
+ const audienceVia = checkAcceptedAudience(claims, acceptedAudiences, auth);
714
694
  assertNonEmptySubject(claims);
715
695
  const context = buildMcpAuthContext({ token, claims, issuer, resource, acceptedAudiences });
716
696
  assertOAuthClientClaim(auth, context.principal.clientId);
717
697
  log.info("oauth.verify.ok", {
718
698
  sub: context.principal.sub,
719
699
  clientId: context.principal.clientId,
720
- scopes: context.principal.scopes
700
+ scopes: context.principal.scopes,
701
+ audienceVia
721
702
  });
722
703
  return context;
723
704
  };
@@ -766,12 +747,6 @@ function oauthConfigurationErrorResponse() {
766
747
  headers: { ...JSON_HEADERS, "Cache-Control": "no-store" }
767
748
  });
768
749
  }
769
- function oauthUpstreamUnreachableResponse() {
770
- return new Response(JSON.stringify({ error: "authorization server unreachable" }), {
771
- status: 503,
772
- headers: { ...JSON_HEADERS, "Cache-Control": "no-store", "Retry-After": "30" }
773
- });
774
- }
775
750
  function parseBearerToken(request) {
776
751
  const header = request.headers.get("Authorization");
777
752
  if (!header)
@@ -844,22 +819,12 @@ function createRequestAuthorizer(mcp, options = {}) {
844
819
  assertRequiredScopes(runtime.auth, auth);
845
820
  return { ok: true, auth };
846
821
  } catch (err) {
847
- if (err instanceof OAuthUpstreamUnreachableError) {
848
- log.error("auth.upstream_unreachable", { ...describeError(err), stage: err.stage, outcome: "503" });
849
- await recorder?.emit({
850
- tool: null,
851
- method: "authorize",
852
- outcome: err.stage === "jwks" ? "auth_jwks_unreachable" : "auth_discovery_unreachable",
853
- durationMs: nowMs() - startedAt
854
- });
855
- return { ok: false, response: oauthUpstreamUnreachableResponse() };
856
- }
857
822
  if (err instanceof OAuthConfigurationError) {
858
- log.error("auth.config_error", { ...describeError(err), stage: err.stage, outcome: "500" });
823
+ log.error("auth.config_error", { ...describeError(err), outcome: "500" });
859
824
  await recorder?.emit({
860
825
  tool: null,
861
826
  method: "authorize",
862
- outcome: err.stage === "jwks" ? "auth_jwks_config_error" : "auth_discovery_config_error",
827
+ outcome: "auth_config_error",
863
828
  durationMs: nowMs() - startedAt
864
829
  });
865
830
  return { ok: false, response: oauthConfigurationErrorResponse() };
@@ -938,6 +903,14 @@ var ToolContext = class {
938
903
  }
939
904
  };
940
905
 
906
+ // src/core/content.ts
907
+ function extractTextContent(content) {
908
+ if (!content)
909
+ return void 0;
910
+ const text = content.filter((block) => block.type === "text").map((block) => block.text).join("\n").trim();
911
+ return text.length > 0 ? text : void 0;
912
+ }
913
+
941
914
  // src/core/cors.ts
942
915
  var EXPOSE_HEADERS = "WWW-Authenticate, Mcp-Session-Id, Mcp-Protocol-Version";
943
916
  var ALLOW_HEADERS = "Authorization, Content-Type, Mcp-Session-Id, Mcp-Protocol-Version, Last-Event-ID";
@@ -988,7 +961,8 @@ function adaptToolToSdkCallback(tool, auth, recorder) {
988
961
  tool: tool.name,
989
962
  method: "tools/call",
990
963
  outcome: result.isError ? "tool_error" : "ok",
991
- durationMs: nowMs() - start
964
+ durationMs: nowMs() - start,
965
+ errorText: result.isError ? extractTextContent(result.content) : void 0
992
966
  });
993
967
  return { content: result.content ?? [], structuredContent: result.structuredContent, isError: result.isError };
994
968
  };
@@ -1244,7 +1218,8 @@ function createInvokeToolHandler(mcp, options = {}) {
1244
1218
  tool: tool.name,
1245
1219
  method: "tools/call",
1246
1220
  outcome: result.isError ? "tool_error" : "ok",
1247
- durationMs: nowMs() - start
1221
+ durationMs: nowMs() - start,
1222
+ errorText: result.isError ? extractTextContent(result.content) : void 0
1248
1223
  });
1249
1224
  return Response.json({
1250
1225
  content: result.content ?? [],
@@ -1,5 +1,5 @@
1
1
  import { M as McpRuntimeOptions } from '../../authorize-BMeXd_Sh.js';
2
- import { d as McpDefinition } from '../../types-CPkhCRxc.js';
2
+ import { d as McpDefinition } from '../../types-qqrmJ736.js';
3
3
  import 'zod';
4
4
 
5
5
  interface TanStackRouteCtx {
@@ -1,5 +1,5 @@
1
1
  import { M as McpRuntimeOptions } from '../../authorize-BMeXd_Sh.js';
2
- import { d as McpDefinition } from '../../types-CPkhCRxc.js';
2
+ import { d as McpDefinition } from '../../types-qqrmJ736.js';
3
3
  import 'zod';
4
4
 
5
5
  interface TanStackRouteCtx {
@@ -3,23 +3,24 @@ import {
3
3
  } from "../../chunk-UQK5UO6C.js";
4
4
  import {
5
5
  createMcpProtocolHandler
6
- } from "../../chunk-5W6JK6OS.js";
6
+ } from "../../chunk-CI7JYLV3.js";
7
7
  import {
8
8
  createOAuthProtectedResourceMetadataHandler
9
- } from "../../chunk-NENAFXZJ.js";
9
+ } from "../../chunk-ENJDNFEB.js";
10
10
  import {
11
11
  createInvokeToolHandler
12
- } from "../../chunk-JIOOUIDX.js";
12
+ } from "../../chunk-QPKPYDPB.js";
13
13
  import {
14
14
  createListToolsHandler
15
- } from "../../chunk-TVU5FL2T.js";
15
+ } from "../../chunk-LFXK3FNF.js";
16
+ import "../../chunk-6QFZYYUV.js";
16
17
  import "../../chunk-MA5H6PSF.js";
17
18
  import {
18
19
  createRecorderForRuntime
19
- } from "../../chunk-CIAVINZ5.js";
20
+ } from "../../chunk-YLU2JTHL.js";
20
21
  import "../../chunk-H37EB22A.js";
21
22
  import "../../chunk-6DXGZZA4.js";
22
- import "../../chunk-SIDWSSWV.js";
23
+ import "../../chunk-E3Q3FRKQ.js";
23
24
 
24
25
  // src/stacks/tanstack/handlers.ts
25
26
  var STACK = "tanstack";
@@ -10,6 +10,8 @@ type JwtClaims = Record<string, unknown> & {
10
10
  scope?: string;
11
11
  client_id?: string;
12
12
  email?: string;
13
+ /** RFC 8707 resource indicator(s) the token was granted for (Lovable OAuth convention). */
14
+ resource?: string | string[];
13
15
  };
14
16
  /**
15
17
  * The safe-to-surface half of the auth context, reachable by tools only through
@@ -60,6 +62,14 @@ interface McpAuthConfig {
60
62
  readonly protectedResourceMetadataUrl?: string;
61
63
  readonly requireOAuthClientClaim?: boolean;
62
64
  readonly acceptedAudiences?: readonly string[];
65
+ /**
66
+ * Accept a token whose RFC 8707 `resource` claim matches an accepted
67
+ * audience (trailing slashes ignored) when its `aud` is an empty array or
68
+ * absent — the shape Lovable's OAuth server mints; applies to any configured
69
+ * issuer. A populated `aud` is always authoritative and is never overridden.
70
+ * Defaults to true. Set false to require a matching `aud` (strict RFC 9068).
71
+ */
72
+ readonly acceptResourceClaim?: boolean;
63
73
  readonly requiredScopes?: readonly string[];
64
74
  readonly jwksUri?: string;
65
75
  readonly algorithms?: readonly string[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lovable.dev/mcp-js",
3
- "version": "0.21.0-rc.3",
3
+ "version": "0.21.0",
4
4
  "description": "Author MCP servers for Lovable apps. Declare tools with defineTool, register them in defineMcp, and a framework adapter (TanStack or Supabase Edge Functions) emits the route(s) at build time.",
5
5
  "type": "module",
6
6
  "repository": {