@lovable.dev/mcp-js 0.27.1 → 0.29.0-rc.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 (71) hide show
  1. package/README.md +13 -7
  2. package/dist/{authorize-Cs1yyLrq.d.ts → authorize-BjNE3LPr.d.ts} +1 -1
  3. package/dist/{authorize-BcNuNPtP.d.cts → authorize-BzZFS0zi.d.cts} +1 -1
  4. package/dist/{base-CReh7d3E.d.cts → base-DSH8NnPj.d.cts} +10 -3
  5. package/dist/{base-CReh7d3E.d.ts → base-DSH8NnPj.d.ts} +10 -3
  6. package/dist/cli/extract-manifest.cjs +2 -2
  7. package/dist/cli/extract-manifest.js +2 -2
  8. package/dist/{authorize-CB35c3Wg.cjs → cors-CmKzDN7k.cjs} +154 -59
  9. package/dist/{authorize-FOlzhxrM.js → cors-D4YbODs-.js} +150 -55
  10. package/dist/{forwarded-BlLF3dfc.js → forwarded-B3EBjHnA.js} +3 -3
  11. package/dist/{forwarded-Bhwcj22u.cjs → forwarded-BC_RHnn5.cjs} +3 -3
  12. package/dist/{gate-B9RSzvoI.cjs → gate-n_HX6eQ5.cjs} +6 -4
  13. package/dist/{gate-BeHmyGxk.js → gate-pGsVlY_4.js} +5 -3
  14. package/dist/index.cjs +38 -1
  15. package/dist/index.d.cts +3 -3
  16. package/dist/index.d.ts +3 -3
  17. package/dist/index.js +38 -1
  18. package/dist/{io-BzTGjG_c.d.cts → io-BEwvgN8s.d.cts} +1 -1
  19. package/dist/{io--MeYrWG7.d.ts → io-CvALf9NE.d.ts} +1 -1
  20. package/dist/list-tools-Dkyyje7j.cjs +48 -0
  21. package/dist/{list-tools-Dt-mhh52.js → list-tools-ePP9tXND.js} +9 -13
  22. package/dist/{list-tools-Bku6XkVc.d.ts → list-tools-qIvVJeWZ.d.ts} +3 -3
  23. package/dist/{list-tools-Cn7tpE0V.d.cts → list-tools-zagoI477.d.cts} +3 -3
  24. package/dist/{logger-BkMMCFTv.cjs → logger-CN1q-KNn.cjs} +6 -6
  25. package/dist/{logger-rGakBvJ1.js → logger-Ctv5xUSD.js} +6 -6
  26. package/dist/mcp-Cs4zfvSi.js +336 -0
  27. package/dist/mcp-DQ1Vdv4v.cjs +341 -0
  28. package/dist/{package-BdaLbzEh.cjs → package-C3YVYgxp.cjs} +1 -1
  29. package/dist/{package-dtiombMt.js → package-DzB_5Hur.js} +1 -1
  30. package/dist/protocols/mcp/index.cjs +1 -1
  31. package/dist/protocols/mcp/index.d.cts +3 -3
  32. package/dist/protocols/mcp/index.d.ts +3 -3
  33. package/dist/protocols/mcp/index.js +1 -1
  34. package/dist/protocols/oauth-metadata.cjs +12 -10
  35. package/dist/protocols/oauth-metadata.d.cts +2 -2
  36. package/dist/protocols/oauth-metadata.d.ts +2 -2
  37. package/dist/protocols/oauth-metadata.js +6 -4
  38. package/dist/protocols/rest/index.cjs +2 -2
  39. package/dist/protocols/rest/index.d.cts +4 -4
  40. package/dist/protocols/rest/index.d.ts +4 -4
  41. package/dist/protocols/rest/index.js +2 -2
  42. package/dist/{rest-Cit2862S.js → rest-DSclWpIt.js} +13 -12
  43. package/dist/{rest-1Pf9sYNU.cjs → rest-P6XZWP42.cjs} +28 -27
  44. package/dist/schema-CW1jKvio.js +65 -0
  45. package/dist/schema-D3vClxb7.cjs +76 -0
  46. package/dist/stacks/supabase/index.cjs +8 -8
  47. package/dist/stacks/supabase/index.d.cts +1 -1
  48. package/dist/stacks/supabase/index.d.ts +1 -1
  49. package/dist/stacks/supabase/index.js +6 -6
  50. package/dist/stacks/supabase/vite.cjs +1 -1
  51. package/dist/stacks/supabase/vite.d.cts +1 -1
  52. package/dist/stacks/supabase/vite.d.ts +1 -1
  53. package/dist/stacks/supabase/vite.js +1 -1
  54. package/dist/stacks/tanstack/index.cjs +8 -8
  55. package/dist/stacks/tanstack/index.d.cts +2 -2
  56. package/dist/stacks/tanstack/index.d.ts +2 -2
  57. package/dist/stacks/tanstack/index.js +5 -5
  58. package/dist/stacks/tanstack/vite.cjs +8 -4
  59. package/dist/stacks/tanstack/vite.d.cts +11 -1
  60. package/dist/stacks/tanstack/vite.d.ts +11 -1
  61. package/dist/stacks/tanstack/vite.js +8 -4
  62. package/dist/tool-result-CK6hXO43.cjs +89 -0
  63. package/dist/tool-result-DfCk445b.js +78 -0
  64. package/dist/{types-C0Wgm9zp.d.cts → types-D1cgsnPu.d.cts} +15 -7
  65. package/dist/{types-C0Wgm9zp.d.ts → types-D1cgsnPu.d.ts} +15 -7
  66. package/package.json +2 -1
  67. package/dist/content-D5LJAHyM.js +0 -12
  68. package/dist/content-NCYIGgOb.cjs +0 -17
  69. package/dist/list-tools-DiMCRunS.cjs +0 -52
  70. package/dist/mcp-DiVQhHIw.js +0 -148
  71. package/dist/mcp-ECo1Ixk8.cjs +0 -153
@@ -1,28 +1,7 @@
1
- import { i as log, n as applyLogLevelFromEnv, o as parseSafeUrl, r as describeError, s as trimTrailingSlash, t as LOG_LEVEL_ENV_VAR, u as resolveMetricsConfig } from "./logger-rGakBvJ1.js";
2
- import { t as version } from "./package-dtiombMt.js";
1
+ import { i as log, n as applyLogLevelFromEnv, o as parseSafeUrl, r as describeError, s as trimTrailingSlash, t as LOG_LEVEL_ENV_VAR, u as resolveMetricsConfig } from "./logger-Ctv5xUSD.js";
2
+ import { t as version } from "./package-DzB_5Hur.js";
3
3
  import "./metadata-path-CAkNcfyY.js";
4
- import { createLocalJWKSet, decodeProtectedHeader, jwtVerify } from "jose";
5
- //#region src/core/cors.ts
6
- const EXPOSE_HEADERS = "WWW-Authenticate, Mcp-Session-Id, Mcp-Protocol-Version";
7
- const ALLOW_HEADERS = "Authorization, Content-Type, Mcp-Session-Id, Mcp-Protocol-Version, Last-Event-ID";
8
- /** Add permissive CORS to a built response, preserving its other headers. */
9
- function withCors(response) {
10
- response.headers.set("Access-Control-Allow-Origin", "*");
11
- response.headers.set("Access-Control-Expose-Headers", EXPOSE_HEADERS);
12
- return response;
13
- }
14
- function corsPreflightResponse(allowMethods) {
15
- return new Response(null, {
16
- status: 204,
17
- headers: {
18
- "Access-Control-Allow-Origin": "*",
19
- "Access-Control-Allow-Methods": allowMethods,
20
- "Access-Control-Allow-Headers": ALLOW_HEADERS,
21
- "Access-Control-Max-Age": "86400"
22
- }
23
- });
24
- }
25
- //#endregion
4
+ import { createLocalJWKSet, decodeProtectedHeader, errors, jwtVerify } from "jose";
26
5
  //#region src/core/http.ts
27
6
  const JSON_HEADERS = { "Content-Type": "application/json" };
28
7
  /** Strip the body from a built response, preserving status and headers, for HEAD. */
@@ -34,7 +13,7 @@ function headResponse(response) {
34
13
  });
35
14
  }
36
15
  function finalizeWireResponse(request, response) {
37
- return withCors(request.method === "HEAD" ? headResponse(response) : response);
16
+ return request.method === "HEAD" ? headResponse(response) : response;
38
17
  }
39
18
  function methodNotAllowed(allow) {
40
19
  return new Response(JSON.stringify({ error: "method not allowed" }), {
@@ -73,6 +52,8 @@ function toLogRecord(rec) {
73
52
  if (rec.tool !== null) attributes.push(strAttr("mcp.tool", rec.tool));
74
53
  if (rec.reqBytes !== void 0) attributes.push(intAttr("mcp.request_size_bytes", rec.reqBytes));
75
54
  if (rec.resBytes !== void 0) attributes.push(intAttr("mcp.response_size_bytes", rec.resBytes));
55
+ if (rec.httpMethod !== void 0) attributes.push(strAttr("http.request.method", rec.httpMethod));
56
+ if (rec.protocol !== void 0) attributes.push(strAttr("mcp.protocol", rec.protocol));
76
57
  attributes.push(strAttr("mcp.stack", rec.stack));
77
58
  if (rec.endUserId !== void 0) attributes.push(strAttr("mcp.end_user_id", rec.endUserId));
78
59
  return {
@@ -120,6 +101,8 @@ function nowUnixNano() {
120
101
  function nowMs() {
121
102
  return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
122
103
  }
104
+ const METRICS_POST_TIMEOUT_MS = 3e3;
105
+ let warnedNoApiKey = false;
123
106
  const NOOP_RECORDER = { async emit() {} };
124
107
  function createNoopRecorder() {
125
108
  return NOOP_RECORDER;
@@ -168,12 +151,13 @@ var BaseMetricRecorder = class {
168
151
  }
169
152
  async emit(ev) {
170
153
  await this.applyLazyLogLevel();
171
- log.info("tool.invoked", {
154
+ if (ev.outcome !== "auth_challenge") log.info("tool.invoked", {
172
155
  tool: ev.tool,
173
156
  method: ev.method,
174
157
  outcome: ev.outcome,
175
158
  durationMs: ev.durationMs,
176
159
  stack: this.stack,
160
+ ...ev.protocol !== void 0 && { protocol: ev.protocol },
177
161
  ...ev.errorText !== void 0 && { errorText: ev.errorText }
178
162
  });
179
163
  if (!this.config.enabled) return;
@@ -181,30 +165,34 @@ var BaseMetricRecorder = class {
181
165
  log.warn("metrics.disabled_no_fetch", { endpoint: this.config.endpoint });
182
166
  return;
183
167
  }
184
- const headers = { ...this.baseHeaders };
185
- if (this.usesLovableEndpoint) {
186
- const key = await this.resolveApiKey();
187
- if (!key) {
188
- log.warn("metrics.disabled_no_api_key", {
189
- envVar: this.config.apiKeyEnvVar,
190
- endpoint: this.config.endpoint
191
- });
192
- return;
193
- }
194
- headers.authorization = `Bearer ${key}`;
195
- }
196
- const { errorText: _localOnly, ...wireEvent } = ev;
197
- const body = buildLogsPayload([{
198
- ...wireEvent,
199
- timeUnixNano: nowUnixNano(),
200
- stack: this.stack
201
- }], this.server);
202
168
  try {
169
+ const headers = { ...this.baseHeaders };
170
+ if (this.usesLovableEndpoint) {
171
+ const key = await this.resolveApiKey();
172
+ if (!key) {
173
+ if (!warnedNoApiKey) {
174
+ warnedNoApiKey = true;
175
+ log.warn("metrics.disabled_no_api_key", {
176
+ envVar: this.config.apiKeyEnvVar,
177
+ endpoint: this.config.endpoint
178
+ });
179
+ }
180
+ return;
181
+ }
182
+ headers.authorization = `Bearer ${key}`;
183
+ }
184
+ const { errorText: _localOnly, ...wireEvent } = ev;
185
+ const body = buildLogsPayload([{
186
+ ...wireEvent,
187
+ timeUnixNano: nowUnixNano(),
188
+ stack: this.stack
189
+ }], this.server);
203
190
  const res = await this.doFetch(this.config.endpoint, {
204
191
  method: "POST",
205
192
  headers,
206
193
  body,
207
- keepalive: true
194
+ keepalive: true,
195
+ signal: AbortSignal.timeout(METRICS_POST_TIMEOUT_MS)
208
196
  });
209
197
  if (!res.ok) log.warn("metrics.rejected", {
210
198
  status: res.status,
@@ -295,8 +283,8 @@ var SupabaseMetricRecorder = class extends BaseMetricRecorder {
295
283
  //#endregion
296
284
  //#region src/metrics/recorder.ts
297
285
  /** Vend the recorder for a stack, in-flight per request. Returns a no-op when
298
- * metrics are disabled (`metrics: false`); otherwise the stack-specific class,
299
- * which still logs `tool.invoked` even if the key or endpoint is unavailable. */
286
+ * metrics are disabled (`metrics: false`); otherwise the stack-specific class, which
287
+ * logs `tool.invoked` for tool invocations even if the key or endpoint is unavailable. */
300
288
  function createRecorderForRuntime(mcp, opts) {
301
289
  const config = resolveMetricsConfig(mcp.metrics);
302
290
  const server = {
@@ -593,6 +581,16 @@ async function fetchVerificationKeySet(jwksUri) {
593
581
  throw new OAuthConfigurationError(`JWKS fetch failed: ${err instanceof Error ? err.message : String(err)}`, "jwks");
594
582
  }
595
583
  }
584
+ function assertCompactJwsShape(token) {
585
+ const parts = token.split(".");
586
+ if (parts.length !== 3 || parts.some((part) => part.length === 0)) {
587
+ log.debug("oauth.verify.bad_shape", {
588
+ tokenLength: token.length,
589
+ outcome: "401 invalid_token"
590
+ });
591
+ throw new OAuthTokenError(401, "invalid_token", "Access token is not a JWT");
592
+ }
593
+ }
596
594
  function assertAccessTokenTyp(token, allowed) {
597
595
  let header;
598
596
  try {
@@ -694,6 +692,8 @@ function buildMcpAuthContext(args) {
694
692
  function createOAuthTokenVerifier(auth, discovery) {
695
693
  const allowedTyps = auth.accessTokenTyp ?? DEFAULT_ACCESS_TOKEN_TYPS;
696
694
  return async (token, request, options) => {
695
+ assertCompactJwsShape(token);
696
+ assertAccessTokenTyp(token, allowedTyps);
697
697
  const resource = resolveProtectedResource(auth, request, options);
698
698
  const issuer = await discovery.resolveIssuer();
699
699
  const acceptedAudiences = resolveAcceptedAudiences(auth, resource);
@@ -705,9 +705,7 @@ function createOAuthTokenVerifier(auth, discovery) {
705
705
  });
706
706
  const jwksUri = await discovery.resolveJwksUri();
707
707
  log.debug("oauth.jwks.fetch", { jwksUri });
708
- const keySet = await fetchVerificationKeySet(jwksUri);
709
- assertAccessTokenTyp(token, allowedTyps);
710
- const claims = await verifyJwtClaims(token, keySet, issuer, auth);
708
+ const claims = await verifyJwtClaims(token, await fetchVerificationKeySet(jwksUri), issuer, auth);
711
709
  const audienceVia = checkAcceptedAudience(claims, acceptedAudiences, auth);
712
710
  assertNonEmptySubject(claims);
713
711
  const context = buildMcpAuthContext({
@@ -719,11 +717,13 @@ function createOAuthTokenVerifier(auth, discovery) {
719
717
  });
720
718
  assertOAuthClientClaim(auth, context.principal.clientId);
721
719
  log.info("oauth.verify.ok", {
722
- sub: context.principal.sub,
723
- clientId: context.principal.clientId,
724
720
  scopes: context.principal.scopes,
725
721
  audienceVia
726
722
  });
723
+ log.debug("oauth.verify.principal", {
724
+ sub: context.principal.sub,
725
+ clientId: context.principal.clientId
726
+ });
727
727
  return context;
728
728
  };
729
729
  }
@@ -776,6 +776,22 @@ function oauthUpstreamUnreachableResponse() {
776
776
  }
777
777
  });
778
778
  }
779
+ const KNOWN_HTTP_METHODS = /* @__PURE__ */ new Set([
780
+ "GET",
781
+ "HEAD",
782
+ "POST",
783
+ "PUT",
784
+ "DELETE",
785
+ "PATCH",
786
+ "OPTIONS",
787
+ "TRACE",
788
+ "CONNECT",
789
+ "QUERY"
790
+ ]);
791
+ function normalizeHttpMethod(method) {
792
+ const upper = method.toUpperCase();
793
+ return KNOWN_HTTP_METHODS.has(upper) ? upper : "_OTHER";
794
+ }
779
795
  function parseBearerToken(request) {
780
796
  const header = request.headers.get("Authorization");
781
797
  if (!header) return void 0;
@@ -838,7 +854,18 @@ function createRequestAuthorizer(mcp, options = {}) {
838
854
  const startedAt = nowMs();
839
855
  const token = parseBearerToken(request);
840
856
  if (!token) {
841
- log.info("auth.no_bearer_token", { outcome: "401" });
857
+ const httpMethod = normalizeHttpMethod(request.method);
858
+ log.info("auth.no_bearer_token", {
859
+ httpMethod,
860
+ outcome: "401"
861
+ });
862
+ await recorder?.emit({
863
+ tool: null,
864
+ method: "authorize",
865
+ outcome: "auth_challenge",
866
+ httpMethod,
867
+ durationMs: nowMs() - startedAt
868
+ });
842
869
  return {
843
870
  ok: false,
844
871
  response: challengeResponse(runtime.auth, request, runtime.options, 401)
@@ -896,7 +923,12 @@ function createRequestAuthorizer(mcp, options = {}) {
896
923
  response: challengeResponse(runtime.auth, request, runtime.options, err.status, err.oauthError, err.message)
897
924
  };
898
925
  }
899
- log.error("auth.unexpected_error", {
926
+ if (err instanceof errors.JOSEError) log.info("auth.token_rejected", {
927
+ ...describeError(err),
928
+ status: 401,
929
+ oauthError: "invalid_token"
930
+ });
931
+ else log.error("auth.unexpected_error", {
900
932
  ...describeError(err),
901
933
  outcome: "401"
902
934
  });
@@ -908,4 +940,67 @@ function createRequestAuthorizer(mcp, options = {}) {
908
940
  } };
909
941
  }
910
942
  //#endregion
911
- export { assertResourcePathShape as a, createNoopRecorder as c, finalizeWireResponse as d, methodNotAllowed as f, oauthConfigurationErrorResponse as i, nowMs as l, createRequestAuthorizer as n, resolveProtectedResource as o, corsPreflightResponse as p, getOAuthRuntime as r, createRecorderForRuntime as s, assertRestResourceBinding as t, JSON_HEADERS as u };
943
+ //#region src/core/cors.ts
944
+ const EXPOSE_HEADERS = "WWW-Authenticate, MCP-Protocol-Version, Mcp-Session-Id";
945
+ const ALLOW_HEADERS = "Authorization, Content-Type, MCP-Protocol-Version, Mcp-Method, Mcp-Name, Mcp-Session-Id, Last-Event-ID";
946
+ const MCP_PARAM_HEADER = /^mcp-param-[!#$%&'*+.^_`|~0-9A-Za-z-]+$/i;
947
+ function appendVary(headers, value) {
948
+ const values = new Set((headers.get("Vary") ?? "").split(",").map((entry) => entry.trim()).filter(Boolean));
949
+ values.add(value);
950
+ headers.set("Vary", [...values].join(", "));
951
+ }
952
+ function addCorsHeaders(response, allowedOrigin, variesByOrigin) {
953
+ if (allowedOrigin !== void 0) {
954
+ response.headers.set("Access-Control-Allow-Origin", allowedOrigin);
955
+ response.headers.set("Access-Control-Expose-Headers", EXPOSE_HEADERS);
956
+ }
957
+ if (variesByOrigin) appendVary(response.headers, "Origin");
958
+ return response;
959
+ }
960
+ function allowedRequestHeaders(request) {
961
+ const allowed = new Map(ALLOW_HEADERS.split(", ").map((header) => [header.toLowerCase(), header]));
962
+ for (const header of (request.headers.get("Access-Control-Request-Headers") ?? "").split(",")) {
963
+ const normalized = header.trim();
964
+ if (MCP_PARAM_HEADER.test(normalized)) allowed.set(normalized.toLowerCase(), normalized);
965
+ }
966
+ return [...allowed.values()].join(", ");
967
+ }
968
+ function evaluateCors(request, allowedOrigins) {
969
+ const origin = request.headers.get("Origin");
970
+ const allowAny = allowedOrigins === "any";
971
+ let allowedOrigin;
972
+ if (allowAny) allowedOrigin = "*";
973
+ else if (origin === null) allowedOrigin = void 0;
974
+ else if (origin === new URL(request.url).origin || allowedOrigins?.includes(origin)) allowedOrigin = origin;
975
+ else allowedOrigin = null;
976
+ if (allowedOrigin === null) {
977
+ const response = Response.json({ error: "origin not allowed" }, {
978
+ status: 403,
979
+ headers: { "Cache-Control": "no-store" }
980
+ });
981
+ appendVary(response.headers, "Origin");
982
+ return {
983
+ ok: false,
984
+ response
985
+ };
986
+ }
987
+ return {
988
+ ok: true,
989
+ withHeaders: (response) => addCorsHeaders(response, allowedOrigin, !allowAny),
990
+ preflight: (allowMethods) => {
991
+ const requestedHeaders = request.headers.get("Access-Control-Request-Headers");
992
+ const response = new Response(null, {
993
+ status: 204,
994
+ headers: {
995
+ "Access-Control-Allow-Methods": allowMethods,
996
+ "Access-Control-Allow-Headers": allowedRequestHeaders(request),
997
+ "Access-Control-Max-Age": "86400"
998
+ }
999
+ });
1000
+ if (requestedHeaders) appendVary(response.headers, "Access-Control-Request-Headers");
1001
+ return addCorsHeaders(response, allowedOrigin, !allowAny);
1002
+ }
1003
+ };
1004
+ }
1005
+ //#endregion
1006
+ export { oauthConfigurationErrorResponse as a, createRecorderForRuntime as c, JSON_HEADERS as d, finalizeWireResponse as f, getOAuthRuntime as i, createNoopRecorder as l, assertRestResourceBinding as n, assertResourcePathShape as o, methodNotAllowed as p, createRequestAuthorizer as r, resolveProtectedResource as s, evaluateCors as t, nowMs as u };
@@ -15,9 +15,9 @@
15
15
  * proxy which *overwrites* the header (Lovable's web-proxy does), never by a
16
16
  * bare SDK consumer.
17
17
  * - `X-Forwarded-Proto` only selects http/https, so it is gated separately:
18
- * Supabase terminates TLS upstream and forwards over http with a
19
- * gateway-stamped proto, so that stack always trusts it; the TanStack proxy
20
- * does not sanitize proto, so its Vite plugin never emits the flag.
18
+ * Supabase's gateway stamps it (that stack always trusts it), and Lovable's
19
+ * preview proxy overwrites it before the TanStack dev server, so the TanStack
20
+ * Vite plugin emits the flag by default.
21
21
  */
22
22
  function applyForwardedOrigin(request, options) {
23
23
  const proto = options.trustForwardedProto ? firstForwardedValue(request, "x-forwarded-proto") : void 0;
@@ -15,9 +15,9 @@
15
15
  * proxy which *overwrites* the header (Lovable's web-proxy does), never by a
16
16
  * bare SDK consumer.
17
17
  * - `X-Forwarded-Proto` only selects http/https, so it is gated separately:
18
- * Supabase terminates TLS upstream and forwards over http with a
19
- * gateway-stamped proto, so that stack always trusts it; the TanStack proxy
20
- * does not sanitize proto, so its Vite plugin never emits the flag.
18
+ * Supabase's gateway stamps it (that stack always trusts it), and Lovable's
19
+ * preview proxy overwrites it before the TanStack dev server, so the TanStack
20
+ * Vite plugin emits the flag by default.
21
21
  */
22
22
  function applyForwardedOrigin(request, options) {
23
23
  const proto = options.trustForwardedProto ? firstForwardedValue(request, "x-forwarded-proto") : void 0;
@@ -1,4 +1,4 @@
1
- const require_authorize = require("./authorize-CB35c3Wg.cjs");
1
+ const require_cors = require("./cors-CmKzDN7k.cjs");
2
2
  //#region src/protocols/gate.ts
3
3
  function createGatedHandler(gate) {
4
4
  const allow = [...gate.methods, "OPTIONS"].join(", ");
@@ -8,9 +8,11 @@ function createGatedHandler(gate) {
8
8
  if (!gate.methods.includes(request.method)) return gate.methodNotAllowed(allow);
9
9
  return gate.handle(request, authResult, recorder, ...extra);
10
10
  };
11
- return async (request, recorder = require_authorize.createNoopRecorder(), ...extra) => {
12
- if (request.method === "OPTIONS") return require_authorize.corsPreflightResponse(allow);
13
- const final = require_authorize.finalizeWireResponse(request, await respond(request, recorder, ...extra));
11
+ return async (request, recorder = require_cors.createNoopRecorder(), ...extra) => {
12
+ const cors = require_cors.evaluateCors(request, gate.allowedOrigins);
13
+ if (!cors.ok) return require_cors.finalizeWireResponse(request, cors.response);
14
+ if (request.method === "OPTIONS") return cors.preflight(allow);
15
+ const final = require_cors.finalizeWireResponse(request, cors.withHeaders(await respond(request, recorder, ...extra)));
14
16
  if (!final.headers.has("Cache-Control")) final.headers.set("Cache-Control", "no-store");
15
17
  return final;
16
18
  };
@@ -1,4 +1,4 @@
1
- import { c as createNoopRecorder, d as finalizeWireResponse, p as corsPreflightResponse } from "./authorize-FOlzhxrM.js";
1
+ import { f as finalizeWireResponse, l as createNoopRecorder, t as evaluateCors } from "./cors-D4YbODs-.js";
2
2
  //#region src/protocols/gate.ts
3
3
  function createGatedHandler(gate) {
4
4
  const allow = [...gate.methods, "OPTIONS"].join(", ");
@@ -9,8 +9,10 @@ function createGatedHandler(gate) {
9
9
  return gate.handle(request, authResult, recorder, ...extra);
10
10
  };
11
11
  return async (request, recorder = createNoopRecorder(), ...extra) => {
12
- if (request.method === "OPTIONS") return corsPreflightResponse(allow);
13
- const final = finalizeWireResponse(request, await respond(request, recorder, ...extra));
12
+ const cors = evaluateCors(request, gate.allowedOrigins);
13
+ if (!cors.ok) return finalizeWireResponse(request, cors.response);
14
+ if (request.method === "OPTIONS") return cors.preflight(allow);
15
+ const final = finalizeWireResponse(request, cors.withHeaders(await respond(request, recorder, ...extra)));
14
16
  if (!final.headers.has("Cache-Control")) final.headers.set("Cache-Control", "no-store");
15
17
  return final;
16
18
  };
package/dist/index.cjs CHANGED
@@ -1,5 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_logger = require("./logger-BkMMCFTv.cjs");
2
+ const require_logger = require("./logger-CN1q-KNn.cjs");
3
+ const require_schema = require("./schema-D3vClxb7.cjs");
3
4
  const require_errors = require("./errors-CN6Da8iz.cjs");
4
5
  //#region src/core/define.ts
5
6
  function assertUniqueNames(mcp) {
@@ -79,6 +80,39 @@ function freezeAuth(auth) {
79
80
  if (auth.accessTokenTyp) Object.freeze(auth.accessTokenTyp);
80
81
  Object.freeze(auth);
81
82
  }
83
+ function assertAllowedOrigins(allowedOrigins) {
84
+ if (allowedOrigins === void 0 || allowedOrigins === "any") return;
85
+ if (!Array.isArray(allowedOrigins)) throw new Error(`@lovable.dev/mcp-js: allowedOrigins must be "any" or an array of exact origins`);
86
+ for (const origin of allowedOrigins) {
87
+ if (typeof origin !== "string") throw new Error(`@lovable.dev/mcp-js: allowedOrigins must contain strings`);
88
+ let parsed;
89
+ try {
90
+ parsed = new URL(origin);
91
+ } catch {
92
+ throw new Error(`@lovable.dev/mcp-js: allowedOrigins must contain valid exact origins`);
93
+ }
94
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:" || parsed.origin !== origin) throw new Error(`@lovable.dev/mcp-js: allowedOrigins entry "${origin}" must be an exact http(s) origin without a path, query, fragment, or trailing slash`);
95
+ }
96
+ }
97
+ function assertToolSchemas(mcp) {
98
+ for (const tool of mcp.tools) for (const [name, definition, strategy] of [[
99
+ "inputSchema",
100
+ tool.inputSchema,
101
+ "input"
102
+ ], [
103
+ "outputSchema",
104
+ tool.outputSchema,
105
+ "output"
106
+ ]]) {
107
+ if (!definition) continue;
108
+ try {
109
+ require_schema.jsonSchemaFromDefinition(definition, strategy);
110
+ } catch (error) {
111
+ const detail = error instanceof Error ? `: ${error.message}` : "";
112
+ throw new Error(`@lovable.dev/mcp-js: tool "${tool.name}" ${name} cannot be represented as JSON Schema${detail}`);
113
+ }
114
+ }
115
+ }
82
116
  /**
83
117
  * Declare a single MCP tool. Import the result into your MCP definition's
84
118
  * `tools` array — `defineMcp({ tools: [echoTool, ...] })`.
@@ -101,8 +135,11 @@ function defineMcp(def) {
101
135
  assertNonEmptyString("version", def.version);
102
136
  assertUniqueNames(def);
103
137
  if (def.auth) assertOAuthConfig(def.auth);
138
+ assertAllowedOrigins(def.allowedOrigins);
139
+ assertToolSchemas(def);
104
140
  require_logger.resolveMetricsConfig(def.metrics);
105
141
  freezeAuth(def.auth);
142
+ if (Array.isArray(def.allowedOrigins)) Object.freeze(def.allowedOrigins);
106
143
  Object.freeze(def.tools);
107
144
  for (const tool of def.tools) Object.freeze(tool);
108
145
  return def;
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { C as McpAuthConfig, S as JwtClaims, _ as ZodRawShape, a as EmbeddedResource, b as MetricsOptions, c as McpDefinition, d as ResourceLinkIcon, f as TextContent, g as ToolHandlerResult, h as ToolDefinition, i as EmbeddedBlobResource, l as McpDefinitionInput, m as ToolContent, n as ContentAnnotations, o as EmbeddedTextResource, p as ToolAnnotations, r as ContentBlock, s as ImageContent, t as AudioContent, u as ResourceLink, v as ZodSchema, x as ToolContext, y as MetricsConfig } from "./types-C0Wgm9zp.cjs";
1
+ import { C as MetricsOptions, E as McpAuthConfig, S as MetricsConfig, T as JwtClaims, _ as ToolDefinition, a as EmbeddedResource, b as ZodRawShape, c as JsonValue, d as McpDefinitionInput, f as ResourceLink, g as ToolContent, h as ToolAnnotations, i as EmbeddedBlobResource, l as McpAllowedOrigins, m as TextContent, n as ContentAnnotations, o as EmbeddedTextResource, p as ResourceLinkIcon, r as ContentBlock, s as ImageContent, t as AudioContent, u as McpDefinition, v as ToolHandlerResult, w as ToolContext, x as ZodSchema, y as ZodOutputSchema } from "./types-D1cgsnPu.cjs";
2
2
  //#region src/core/define.d.ts
3
3
  /**
4
4
  * Declare a single MCP tool. Import the result into your MCP definition's
@@ -8,7 +8,7 @@ import { C as McpAuthConfig, S as JwtClaims, _ as ZodRawShape, a as EmbeddedReso
8
8
  * full `z.object(...)` — the MCP SDK builds the object wrapper internally
9
9
  * and the raw shape is what the JSON-RPC `tools/list` payload exposes.
10
10
  */
11
- declare function defineTool<TInput extends ZodRawShape | undefined = undefined, TOutput extends ZodRawShape | undefined = undefined>(def: ToolDefinition<TInput, TOutput>): ToolDefinition<TInput, TOutput>;
11
+ declare function defineTool<TInput extends ZodRawShape | undefined = undefined, TOutput extends ZodOutputSchema | undefined = undefined>(def: ToolDefinition<TInput, TOutput>): ToolDefinition<TInput, TOutput>;
12
12
  /**
13
13
  * Declare the MCP server. `export default` the result from your MCP
14
14
  * entrypoint (default `lib/mcp/index.ts`); the Vite plugin reads it to
@@ -71,4 +71,4 @@ type LogLevel = "silent" | "error" | "warn" | "info" | "debug";
71
71
  */
72
72
  declare function setLogLevel(level: LogLevel): LogLevel;
73
73
  //#endregion
74
- export { type AudioContent, type ContentAnnotations, type ContentBlock, type EmbeddedBlobResource, type EmbeddedResource, type EmbeddedTextResource, type ImageContent, type IssuerOAuthOptions, type JwtClaims, type LogLevel, type McpDefinitionInput, type MetricsConfig, type MetricsOptions, type ResourceLink, type ResourceLinkIcon, type TextContent, type ToolAnnotations, type ToolContent, ToolContext, type ToolDefinition, ToolError, type ToolHandlerResult, type ZodRawShape, type ZodSchema, auth, defineMcp, defineTool, setLogLevel };
74
+ export { type AudioContent, type ContentAnnotations, type ContentBlock, type EmbeddedBlobResource, type EmbeddedResource, type EmbeddedTextResource, type ImageContent, type IssuerOAuthOptions, type JsonValue, type JwtClaims, type LogLevel, type McpAllowedOrigins, type McpDefinitionInput, type MetricsConfig, type MetricsOptions, type ResourceLink, type ResourceLinkIcon, type TextContent, type ToolAnnotations, type ToolContent, ToolContext, type ToolDefinition, ToolError, type ToolHandlerResult, type ZodOutputSchema, type ZodRawShape, type ZodSchema, auth, defineMcp, defineTool, setLogLevel };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { C as McpAuthConfig, S as JwtClaims, _ as ZodRawShape, a as EmbeddedResource, b as MetricsOptions, c as McpDefinition, d as ResourceLinkIcon, f as TextContent, g as ToolHandlerResult, h as ToolDefinition, i as EmbeddedBlobResource, l as McpDefinitionInput, m as ToolContent, n as ContentAnnotations, o as EmbeddedTextResource, p as ToolAnnotations, r as ContentBlock, s as ImageContent, t as AudioContent, u as ResourceLink, v as ZodSchema, x as ToolContext, y as MetricsConfig } from "./types-C0Wgm9zp.js";
1
+ import { C as MetricsOptions, E as McpAuthConfig, S as MetricsConfig, T as JwtClaims, _ as ToolDefinition, a as EmbeddedResource, b as ZodRawShape, c as JsonValue, d as McpDefinitionInput, f as ResourceLink, g as ToolContent, h as ToolAnnotations, i as EmbeddedBlobResource, l as McpAllowedOrigins, m as TextContent, n as ContentAnnotations, o as EmbeddedTextResource, p as ResourceLinkIcon, r as ContentBlock, s as ImageContent, t as AudioContent, u as McpDefinition, v as ToolHandlerResult, w as ToolContext, x as ZodSchema, y as ZodOutputSchema } from "./types-D1cgsnPu.js";
2
2
  //#region src/core/define.d.ts
3
3
  /**
4
4
  * Declare a single MCP tool. Import the result into your MCP definition's
@@ -8,7 +8,7 @@ import { C as McpAuthConfig, S as JwtClaims, _ as ZodRawShape, a as EmbeddedReso
8
8
  * full `z.object(...)` — the MCP SDK builds the object wrapper internally
9
9
  * and the raw shape is what the JSON-RPC `tools/list` payload exposes.
10
10
  */
11
- declare function defineTool<TInput extends ZodRawShape | undefined = undefined, TOutput extends ZodRawShape | undefined = undefined>(def: ToolDefinition<TInput, TOutput>): ToolDefinition<TInput, TOutput>;
11
+ declare function defineTool<TInput extends ZodRawShape | undefined = undefined, TOutput extends ZodOutputSchema | undefined = undefined>(def: ToolDefinition<TInput, TOutput>): ToolDefinition<TInput, TOutput>;
12
12
  /**
13
13
  * Declare the MCP server. `export default` the result from your MCP
14
14
  * entrypoint (default `lib/mcp/index.ts`); the Vite plugin reads it to
@@ -71,4 +71,4 @@ type LogLevel = "silent" | "error" | "warn" | "info" | "debug";
71
71
  */
72
72
  declare function setLogLevel(level: LogLevel): LogLevel;
73
73
  //#endregion
74
- export { type AudioContent, type ContentAnnotations, type ContentBlock, type EmbeddedBlobResource, type EmbeddedResource, type EmbeddedTextResource, type ImageContent, type IssuerOAuthOptions, type JwtClaims, type LogLevel, type McpDefinitionInput, type MetricsConfig, type MetricsOptions, type ResourceLink, type ResourceLinkIcon, type TextContent, type ToolAnnotations, type ToolContent, ToolContext, type ToolDefinition, ToolError, type ToolHandlerResult, type ZodRawShape, type ZodSchema, auth, defineMcp, defineTool, setLogLevel };
74
+ export { type AudioContent, type ContentAnnotations, type ContentBlock, type EmbeddedBlobResource, type EmbeddedResource, type EmbeddedTextResource, type ImageContent, type IssuerOAuthOptions, type JsonValue, type JwtClaims, type LogLevel, type McpAllowedOrigins, type McpDefinitionInput, type MetricsConfig, type MetricsOptions, type ResourceLink, type ResourceLinkIcon, type TextContent, type ToolAnnotations, type ToolContent, ToolContext, type ToolDefinition, ToolError, type ToolHandlerResult, type ZodOutputSchema, type ZodRawShape, type ZodSchema, auth, defineMcp, defineTool, setLogLevel };
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
- import { a as setLogLevel, o as parseSafeUrl, s as trimTrailingSlash, u as resolveMetricsConfig } from "./logger-rGakBvJ1.js";
1
+ import { a as setLogLevel, o as parseSafeUrl, s as trimTrailingSlash, u as resolveMetricsConfig } from "./logger-Ctv5xUSD.js";
2
+ import { t as jsonSchemaFromDefinition } from "./schema-CW1jKvio.js";
2
3
  import { i as ToolContext, t as ToolError } from "./errors-DSe96Ffd.js";
3
4
  //#region src/core/define.ts
4
5
  function assertUniqueNames(mcp) {
@@ -78,6 +79,39 @@ function freezeAuth(auth) {
78
79
  if (auth.accessTokenTyp) Object.freeze(auth.accessTokenTyp);
79
80
  Object.freeze(auth);
80
81
  }
82
+ function assertAllowedOrigins(allowedOrigins) {
83
+ if (allowedOrigins === void 0 || allowedOrigins === "any") return;
84
+ if (!Array.isArray(allowedOrigins)) throw new Error(`@lovable.dev/mcp-js: allowedOrigins must be "any" or an array of exact origins`);
85
+ for (const origin of allowedOrigins) {
86
+ if (typeof origin !== "string") throw new Error(`@lovable.dev/mcp-js: allowedOrigins must contain strings`);
87
+ let parsed;
88
+ try {
89
+ parsed = new URL(origin);
90
+ } catch {
91
+ throw new Error(`@lovable.dev/mcp-js: allowedOrigins must contain valid exact origins`);
92
+ }
93
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:" || parsed.origin !== origin) throw new Error(`@lovable.dev/mcp-js: allowedOrigins entry "${origin}" must be an exact http(s) origin without a path, query, fragment, or trailing slash`);
94
+ }
95
+ }
96
+ function assertToolSchemas(mcp) {
97
+ for (const tool of mcp.tools) for (const [name, definition, strategy] of [[
98
+ "inputSchema",
99
+ tool.inputSchema,
100
+ "input"
101
+ ], [
102
+ "outputSchema",
103
+ tool.outputSchema,
104
+ "output"
105
+ ]]) {
106
+ if (!definition) continue;
107
+ try {
108
+ jsonSchemaFromDefinition(definition, strategy);
109
+ } catch (error) {
110
+ const detail = error instanceof Error ? `: ${error.message}` : "";
111
+ throw new Error(`@lovable.dev/mcp-js: tool "${tool.name}" ${name} cannot be represented as JSON Schema${detail}`);
112
+ }
113
+ }
114
+ }
81
115
  /**
82
116
  * Declare a single MCP tool. Import the result into your MCP definition's
83
117
  * `tools` array — `defineMcp({ tools: [echoTool, ...] })`.
@@ -100,8 +134,11 @@ function defineMcp(def) {
100
134
  assertNonEmptyString("version", def.version);
101
135
  assertUniqueNames(def);
102
136
  if (def.auth) assertOAuthConfig(def.auth);
137
+ assertAllowedOrigins(def.allowedOrigins);
138
+ assertToolSchemas(def);
103
139
  resolveMetricsConfig(def.metrics);
104
140
  freezeAuth(def.auth);
141
+ if (Array.isArray(def.allowedOrigins)) Object.freeze(def.allowedOrigins);
105
142
  Object.freeze(def.tools);
106
143
  for (const tool of def.tools) Object.freeze(tool);
107
144
  return def;
@@ -1,4 +1,4 @@
1
- import "./list-tools-Cn7tpE0V.cjs";
1
+ import "./list-tools-zagoI477.cjs";
2
2
  //#region src/manifest/io.d.ts
3
3
  /**
4
4
  * Shape every MCP Vite plugin exposes on its `api` so `runExtract` can read
@@ -1,4 +1,4 @@
1
- import "./list-tools-Bku6XkVc.js";
1
+ import "./list-tools-qIvVJeWZ.js";
2
2
  //#region src/manifest/io.d.ts
3
3
  /**
4
4
  * Shape every MCP Vite plugin exposes on its `api` so `runExtract` can read
@@ -0,0 +1,48 @@
1
+ const require_schema = require("./schema-D3vClxb7.cjs");
2
+ const require_cors = require("./cors-CmKzDN7k.cjs");
3
+ const require_gate = require("./gate-n_HX6eQ5.cjs");
4
+ //#region src/protocols/rest/list-tools.ts
5
+ function schemaToJsonSchema(definition, pipeStrategy) {
6
+ if (!definition) return null;
7
+ return require_schema.jsonSchemaFromDefinition(definition, pipeStrategy);
8
+ }
9
+ function buildMcpListing(mcp) {
10
+ return {
11
+ server: {
12
+ name: mcp.name,
13
+ version: mcp.version,
14
+ title: mcp.title
15
+ },
16
+ tools: mcp.tools.map((tool) => ({
17
+ name: tool.name,
18
+ title: tool.title,
19
+ description: tool.description,
20
+ annotations: tool.annotations,
21
+ inputSchema: schemaToJsonSchema(tool.inputSchema, "input"),
22
+ outputSchema: schemaToJsonSchema(tool.outputSchema, "output")
23
+ }))
24
+ };
25
+ }
26
+ function createListToolsHandler(mcp, options = {}) {
27
+ require_cors.assertRestResourceBinding(mcp, options);
28
+ return require_gate.createGatedHandler({
29
+ authorizer: require_cors.createRequestAuthorizer(mcp, options),
30
+ allowedOrigins: mcp.allowedOrigins,
31
+ methods: ["GET", "HEAD"],
32
+ methodNotAllowed: require_cors.methodNotAllowed,
33
+ handle: async () => Response.json(buildMcpListing(mcp))
34
+ });
35
+ }
36
+ //#endregion
37
+ Object.defineProperty(exports, "buildMcpListing", {
38
+ enumerable: true,
39
+ get: function() {
40
+ return buildMcpListing;
41
+ }
42
+ });
43
+ Object.defineProperty(exports, "createListToolsHandler", {
44
+ enumerable: true,
45
+ get: function() {
46
+ return createListToolsHandler;
47
+ }
48
+ });
@@ -1,15 +1,10 @@
1
- import { f as methodNotAllowed, n as createRequestAuthorizer, t as assertRestResourceBinding } from "./authorize-FOlzhxrM.js";
2
- import { t as createGatedHandler } from "./gate-BeHmyGxk.js";
3
- import { objectFromShape } from "@modelcontextprotocol/sdk/server/zod-compat.js";
4
- import { toJsonSchemaCompat } from "@modelcontextprotocol/sdk/server/zod-json-schema-compat.js";
1
+ import { t as jsonSchemaFromDefinition } from "./schema-CW1jKvio.js";
2
+ import { n as assertRestResourceBinding, p as methodNotAllowed, r as createRequestAuthorizer } from "./cors-D4YbODs-.js";
3
+ import { t as createGatedHandler } from "./gate-pGsVlY_4.js";
5
4
  //#region src/protocols/rest/list-tools.ts
6
- function shapeToJsonSchema(shape) {
7
- if (!shape) return null;
8
- try {
9
- return toJsonSchemaCompat(objectFromShape(shape));
10
- } catch {
11
- return null;
12
- }
5
+ function schemaToJsonSchema(definition, pipeStrategy) {
6
+ if (!definition) return null;
7
+ return jsonSchemaFromDefinition(definition, pipeStrategy);
13
8
  }
14
9
  function buildMcpListing(mcp) {
15
10
  return {
@@ -23,8 +18,8 @@ function buildMcpListing(mcp) {
23
18
  title: tool.title,
24
19
  description: tool.description,
25
20
  annotations: tool.annotations,
26
- inputSchema: shapeToJsonSchema(tool.inputSchema),
27
- outputSchema: shapeToJsonSchema(tool.outputSchema)
21
+ inputSchema: schemaToJsonSchema(tool.inputSchema, "input"),
22
+ outputSchema: schemaToJsonSchema(tool.outputSchema, "output")
28
23
  }))
29
24
  };
30
25
  }
@@ -32,6 +27,7 @@ function createListToolsHandler(mcp, options = {}) {
32
27
  assertRestResourceBinding(mcp, options);
33
28
  return createGatedHandler({
34
29
  authorizer: createRequestAuthorizer(mcp, options),
30
+ allowedOrigins: mcp.allowedOrigins,
35
31
  methods: ["GET", "HEAD"],
36
32
  methodNotAllowed,
37
33
  handle: async () => Response.json(buildMcpListing(mcp))