@lovable.dev/mcp-js 0.5.1 → 0.7.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.
@@ -41,17 +41,71 @@ function methodNotAllowed(allow) {
41
41
  });
42
42
  }
43
43
 
44
+ // src/core/logger.ts
45
+ var LEVEL_RANK = { silent: 0, error: 1, warn: 2, info: 3, debug: 4 };
46
+ function isLogLevel(value) {
47
+ return typeof value === "string" && value in LEVEL_RANK;
48
+ }
49
+ function readEnvLevel() {
50
+ try {
51
+ const raw = typeof process !== "undefined" ? process.env?.["LOVABLE_MCP_LOG_LEVEL"] : void 0;
52
+ const normalized = raw?.trim().toLowerCase();
53
+ return isLogLevel(normalized) ? normalized : void 0;
54
+ } catch {
55
+ return void 0;
56
+ }
57
+ }
58
+ var currentLevel = readEnvLevel() ?? "silent";
59
+ function enabled(level) {
60
+ return LEVEL_RANK[level] <= LEVEL_RANK[currentLevel];
61
+ }
62
+ function emit(level, method, event, fields) {
63
+ if (!enabled(level))
64
+ return;
65
+ const message = `[mcp-js] ${event}`;
66
+ if (fields)
67
+ console[method](message, fields);
68
+ else
69
+ console[method](message);
70
+ }
71
+ var log = {
72
+ error: (event, fields) => emit("error", "error", event, fields),
73
+ warn: (event, fields) => emit("warn", "warn", event, fields),
74
+ info: (event, fields) => emit("info", "info", event, fields),
75
+ debug: (event, fields) => emit("debug", "debug", event, fields)
76
+ };
77
+ function describeError(err) {
78
+ if (err instanceof Error) {
79
+ const code = err.code;
80
+ return { name: err.name, message: err.message, ...typeof code === "string" ? { code } : {} };
81
+ }
82
+ return { value: String(err) };
83
+ }
84
+
44
85
  // src/core/promise.ts
45
- function cachedPromise(load) {
86
+ function cachedPromise(load, label) {
46
87
  let settled = false;
47
88
  let value;
48
89
  return async () => {
49
- if (settled)
90
+ if (settled) {
91
+ if (label)
92
+ log.debug(`${label}.cache_hit`);
50
93
  return value;
51
- const loaded = await load();
52
- settled = true;
53
- value = loaded;
54
- return loaded;
94
+ }
95
+ if (label)
96
+ log.debug(`${label}.load_start`);
97
+ try {
98
+ const loaded = await load();
99
+ settled = true;
100
+ value = loaded;
101
+ if (label)
102
+ log.debug(`${label}.settled`);
103
+ return loaded;
104
+ } catch (err) {
105
+ if (label)
106
+ log.debug(`${label}.load_failed`, describeError(err));
107
+ throw err;
108
+ }
55
109
  };
56
110
  }
57
111
 
@@ -127,13 +181,16 @@ async function fetchFirstValidOAuthServerMetadata(metadataUrls, expectedIssuer)
127
181
  try {
128
182
  return await fetchOAuthServerMetadata(url, expectedIssuer);
129
183
  } catch (err) {
184
+ log.debug("oauth.discovery.attempt_failed", { url, ...describeError(err) });
130
185
  errors.push(`${url}: ${err instanceof Error ? err.message : String(err)}`);
131
186
  }
132
187
  }
188
+ log.error("oauth.discovery.exhausted", { expectedIssuer, urlsTried: metadataUrls, errors });
133
189
  throw new Error(`failed to discover OAuth server metadata (${errors.join("; ")})`);
134
190
  }
135
191
  async function fetchOAuthServerMetadata(url, expectedIssuer) {
136
- const response = await fetch(url, { signal: AbortSignal.timeout(METADATA_FETCH_TIMEOUT_MS), redirect: "error" });
192
+ log.debug("oauth.discovery.fetch", { url });
193
+ const response = await fetch(url, { signal: AbortSignal.timeout(METADATA_FETCH_TIMEOUT_MS), redirect: "manual" });
137
194
  if (!response.ok) {
138
195
  throw new Error(String(response.status));
139
196
  }
@@ -143,6 +200,7 @@ async function fetchOAuthServerMetadata(url, expectedIssuer) {
143
200
  }
144
201
  parseSafeUrl("discovered issuer", json.issuer);
145
202
  if (trimTrailingSlash(json.issuer) !== expectedIssuer) {
203
+ log.warn("oauth.discovery.issuer_mismatch", { url, expectedIssuer, published: json.issuer });
146
204
  throw new Error("issuer mismatch");
147
205
  }
148
206
  if (typeof json.jwks_uri !== "string") {
@@ -155,6 +213,11 @@ async function fetchIssuerOAuthServerMetadata(issuer) {
155
213
  try {
156
214
  return await fetchFirstValidOAuthServerMetadata(oauthMetadataUrlsForIssuer(issuer), issuer);
157
215
  } catch (err) {
216
+ log.error("oauth.discovery.config_error", {
217
+ issuer,
218
+ ...describeError(err),
219
+ outcome: "500 oauth configuration error"
220
+ });
158
221
  throw new OAuthConfigurationError(
159
222
  `OAuth issuer discovery failed: ${err instanceof Error ? err.message : String(err)}`
160
223
  );
@@ -162,10 +225,21 @@ async function fetchIssuerOAuthServerMetadata(issuer) {
162
225
  }
163
226
  function createOAuthDiscoveryResolver(auth) {
164
227
  const configuredIssuer = trimTrailingSlash(auth.issuer);
165
- const oauthServerMetadata = cachedPromise(() => fetchIssuerOAuthServerMetadata(configuredIssuer));
228
+ const oauthServerMetadata = cachedPromise(
229
+ () => fetchIssuerOAuthServerMetadata(configuredIssuer),
230
+ "oauth.discovery.metadata"
231
+ );
166
232
  return {
167
233
  resolveIssuer: async () => configuredIssuer,
168
- resolveJwksUri: async () => auth.jwksUri ?? (await oauthServerMetadata()).jwks_uri
234
+ resolveJwksUri: async () => {
235
+ if (auth.jwksUri) {
236
+ log.debug("oauth.jwks.resolved", { jwksUri: auth.jwksUri, source: "configured" });
237
+ return auth.jwksUri;
238
+ }
239
+ const jwksUri = (await oauthServerMetadata()).jwks_uri;
240
+ log.debug("oauth.jwks.resolved", { jwksUri, source: "discovered" });
241
+ return jwksUri;
242
+ }
169
243
  };
170
244
  }
171
245
 
@@ -228,6 +302,14 @@ var OAuthTokenError = class extends Error {
228
302
  function resolveAcceptedAudiences(auth, resource) {
229
303
  return auth.acceptedAudiences ?? [resource];
230
304
  }
305
+ function tokenHeaderFields(token) {
306
+ try {
307
+ const header = (0, import_jose.decodeProtectedHeader)(token);
308
+ return { jwtAlg: header.alg, jwtKid: header.kid, tokenLength: token.length };
309
+ } catch {
310
+ return { tokenLength: token.length };
311
+ }
312
+ }
231
313
  function isJwksFetchFailure(err) {
232
314
  if (err instanceof import_jose.errors.JWKSTimeout || err instanceof import_jose.errors.JWKSInvalid)
233
315
  return true;
@@ -249,19 +331,23 @@ async function verifyJwtClaims(token, keySet, issuer, audience, auth) {
249
331
  return payload;
250
332
  } catch (err) {
251
333
  if (isJwksFetchFailure(err)) {
334
+ log.error("oauth.jwks.fetch_failed", { ...describeError(err), outcome: "500 oauth configuration error" });
252
335
  throw new OAuthConfigurationError(`JWKS fetch failed: ${err instanceof Error ? err.message : String(err)}`);
253
336
  }
337
+ log.debug("oauth.verify.rejected", { ...describeError(err), outcome: "401 invalid_token" });
254
338
  throw err;
255
339
  }
256
340
  }
257
341
  function assertNonEmptySubject(claims) {
258
342
  const sub = claims["sub"];
259
343
  if (typeof sub !== "string" || sub.trim() === "") {
344
+ log.debug("oauth.verify.bad_subject", { subType: typeof sub });
260
345
  throw new OAuthTokenError(401, "invalid_token", "token subject claim must be a non-empty string");
261
346
  }
262
347
  }
263
348
  function assertOAuthClientClaim(auth, clientId) {
264
349
  if (auth.requireOAuthClientClaim !== false && !clientId) {
350
+ log.debug("oauth.verify.missing_client_claim", { outcome: "401 invalid_token" });
265
351
  throw new OAuthTokenError(401, "invalid_token", "OAuth client claim is required");
266
352
  }
267
353
  }
@@ -286,17 +372,23 @@ function buildMcpAuthContext(args) {
286
372
  };
287
373
  }
288
374
  function createOAuthTokenVerifier(auth, discovery) {
289
- const loadRemoteJwksKeySet = cachedPromise(
290
- () => discovery.resolveJwksUri().then((jwksURI) => (0, import_jose.createRemoteJWKSet)(new URL(jwksURI)))
291
- );
292
375
  return async (token, request, options) => {
293
376
  const resource = resolveProtectedResource(auth, request, options);
294
377
  const issuer = await discovery.resolveIssuer();
295
378
  const acceptedAudiences = resolveAcceptedAudiences(auth, resource);
296
- const claims = await verifyJwtClaims(token, await loadRemoteJwksKeySet(), issuer, acceptedAudiences, auth);
379
+ log.debug("oauth.verify.start", { issuer, acceptedAudiences, resource, ...tokenHeaderFields(token) });
380
+ const jwksUri = await discovery.resolveJwksUri();
381
+ log.debug("oauth.jwks.keyset_created", { jwksUri });
382
+ const keySet = (0, import_jose.createRemoteJWKSet)(new URL(jwksUri));
383
+ const claims = await verifyJwtClaims(token, keySet, issuer, acceptedAudiences, auth);
297
384
  assertNonEmptySubject(claims);
298
385
  const context = buildMcpAuthContext({ token, claims, issuer, resource, acceptedAudiences });
299
386
  assertOAuthClientClaim(auth, context.principal.clientId);
387
+ log.info("oauth.verify.ok", {
388
+ sub: context.principal.sub,
389
+ clientId: context.principal.clientId,
390
+ scopes: context.principal.scopes
391
+ });
300
392
  return context;
301
393
  };
302
394
  }
@@ -396,22 +488,27 @@ function createRequestAuthorizer(mcp, options = {}) {
396
488
  if (runtime.kind === "unconfigured")
397
489
  return { ok: true };
398
490
  const token = parseBearerToken(request);
399
- if (!token)
491
+ if (!token) {
492
+ log.info("auth.no_bearer_token", { outcome: "401" });
400
493
  return { ok: false, response: challengeResponse(runtime.auth, request, 401) };
494
+ }
401
495
  try {
402
496
  const auth = await runtime.verify(token, request, runtime.options);
403
497
  assertRequiredScopes(runtime.auth, auth);
404
498
  return { ok: true, auth };
405
499
  } catch (err) {
406
500
  if (err instanceof OAuthConfigurationError) {
501
+ log.error("auth.config_error", { ...describeError(err), outcome: "500" });
407
502
  return { ok: false, response: oauthConfigurationErrorResponse() };
408
503
  }
409
504
  if (err instanceof OAuthTokenError) {
505
+ log.info("auth.token_rejected", { status: err.status, oauthError: err.oauthError });
410
506
  return {
411
507
  ok: false,
412
508
  response: challengeResponse(runtime.auth, request, err.status, err.oauthError, err.message)
413
509
  };
414
510
  }
511
+ log.error("auth.unexpected_error", { ...describeError(err), outcome: "401" });
415
512
  return {
416
513
  ok: false,
417
514
  response: challengeResponse(runtime.auth, request, 401, "invalid_token", "Invalid access token")
@@ -421,6 +518,26 @@ function createRequestAuthorizer(mcp, options = {}) {
421
518
  };
422
519
  }
423
520
 
521
+ // src/core/cors.ts
522
+ var EXPOSE_HEADERS = "WWW-Authenticate, Mcp-Session-Id, Mcp-Protocol-Version";
523
+ var ALLOW_HEADERS = "Authorization, Content-Type, Mcp-Session-Id, Mcp-Protocol-Version, Last-Event-ID";
524
+ function withCors(response) {
525
+ response.headers.set("Access-Control-Allow-Origin", "*");
526
+ response.headers.set("Access-Control-Expose-Headers", EXPOSE_HEADERS);
527
+ return response;
528
+ }
529
+ function corsPreflightResponse(allowMethods) {
530
+ return new Response(null, {
531
+ status: 204,
532
+ headers: {
533
+ "Access-Control-Allow-Origin": "*",
534
+ "Access-Control-Allow-Methods": allowMethods,
535
+ "Access-Control-Allow-Headers": ALLOW_HEADERS,
536
+ "Access-Control-Max-Age": "86400"
537
+ }
538
+ });
539
+ }
540
+
424
541
  // src/protocols/rest/list-tools.ts
425
542
  function shapeToJsonSchema(shape) {
426
543
  if (!shape)
@@ -434,12 +551,12 @@ function shapeToJsonSchema(shape) {
434
551
  function createListToolsHandler(mcp, options = {}) {
435
552
  assertRestResourceBinding(mcp, options);
436
553
  const authorizer = createRequestAuthorizer(mcp, options);
437
- return async (request) => {
554
+ const handle = async (request) => {
438
555
  const authResult = await authorizer.authorize(request);
439
556
  if (!authResult.ok)
440
557
  return authResult.response;
441
558
  if (request.method !== "GET" && request.method !== "HEAD")
442
- return methodNotAllowed("GET, HEAD");
559
+ return methodNotAllowed("GET, HEAD, OPTIONS");
443
560
  const body = {
444
561
  server: { name: mcp.name, version: mcp.version, title: mcp.title },
445
562
  tools: mcp.tools.map((tool) => ({
@@ -454,6 +571,11 @@ function createListToolsHandler(mcp, options = {}) {
454
571
  const response = Response.json(body);
455
572
  return request.method === "HEAD" ? headResponse(response) : response;
456
573
  };
574
+ return async (request) => {
575
+ if (request.method === "OPTIONS")
576
+ return corsPreflightResponse("GET, HEAD, OPTIONS");
577
+ return withCors(await handle(request));
578
+ };
457
579
  }
458
580
 
459
581
  // src/protocols/rest/invoke-tool.ts
@@ -518,12 +640,12 @@ function isEmptyArgs(value) {
518
640
  function createInvokeToolHandler(mcp, options = {}) {
519
641
  assertRestResourceBinding(mcp, options);
520
642
  const authorizer = createRequestAuthorizer(mcp, options);
521
- return async (request, toolName) => {
643
+ const handle = async (request, toolName) => {
522
644
  const authResult = await authorizer.authorize(request);
523
645
  if (!authResult.ok)
524
646
  return authResult.response;
525
647
  if (request.method !== "POST")
526
- return methodNotAllowed("POST");
648
+ return methodNotAllowed("POST, OPTIONS");
527
649
  const tool = mcp.tools.find((t) => t.name === toolName);
528
650
  if (!tool) {
529
651
  return new Response(JSON.stringify({ error: `unknown tool: ${safeReflectName(toolName)}` }), {
@@ -591,6 +713,11 @@ function createInvokeToolHandler(mcp, options = {}) {
591
713
  isError: result.isError
592
714
  });
593
715
  };
716
+ return async (request, toolName) => {
717
+ if (request.method === "OPTIONS")
718
+ return corsPreflightResponse("POST, OPTIONS");
719
+ return withCors(await handle(request, toolName));
720
+ };
594
721
  }
595
722
  // Annotate the CommonJS export names for ESM import in node:
596
723
  0 && (module.exports = {
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  createInvokeToolHandler,
3
3
  createListToolsHandler
4
- } from "../../chunk-VD6CS7Y6.js";
4
+ } from "../../chunk-53M2X7FU.js";
5
5
  import "../../chunk-MA5H6PSF.js";
6
- import "../../chunk-XEDRJFAR.js";
7
- import "../../chunk-QA3FWDUV.js";
6
+ import "../../chunk-NTXHOUK6.js";
7
+ import "../../chunk-QC3DXQTH.js";
8
8
  import "../../chunk-6DXGZZA4.js";
9
9
  export {
10
10
  createInvokeToolHandler,