@lovable.dev/mcp-js 0.6.0 → 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.
@@ -31,17 +31,71 @@ var import_webStandardStreamableHttp = require("@modelcontextprotocol/sdk/server
31
31
  // src/core/http.ts
32
32
  var JSON_HEADERS = { "Content-Type": "application/json" };
33
33
 
34
+ // src/core/logger.ts
35
+ var LEVEL_RANK = { silent: 0, error: 1, warn: 2, info: 3, debug: 4 };
36
+ function isLogLevel(value) {
37
+ return typeof value === "string" && value in LEVEL_RANK;
38
+ }
39
+ function readEnvLevel() {
40
+ try {
41
+ const raw = typeof process !== "undefined" ? process.env?.["LOVABLE_MCP_LOG_LEVEL"] : void 0;
42
+ const normalized = raw?.trim().toLowerCase();
43
+ return isLogLevel(normalized) ? normalized : void 0;
44
+ } catch {
45
+ return void 0;
46
+ }
47
+ }
48
+ var currentLevel = readEnvLevel() ?? "silent";
49
+ function enabled(level) {
50
+ return LEVEL_RANK[level] <= LEVEL_RANK[currentLevel];
51
+ }
52
+ function emit(level, method, event, fields) {
53
+ if (!enabled(level))
54
+ return;
55
+ const message = `[mcp-js] ${event}`;
56
+ if (fields)
57
+ console[method](message, fields);
58
+ else
59
+ console[method](message);
60
+ }
61
+ var log = {
62
+ error: (event, fields) => emit("error", "error", event, fields),
63
+ warn: (event, fields) => emit("warn", "warn", event, fields),
64
+ info: (event, fields) => emit("info", "info", event, fields),
65
+ debug: (event, fields) => emit("debug", "debug", event, fields)
66
+ };
67
+ function describeError(err) {
68
+ if (err instanceof Error) {
69
+ const code = err.code;
70
+ return { name: err.name, message: err.message, ...typeof code === "string" ? { code } : {} };
71
+ }
72
+ return { value: String(err) };
73
+ }
74
+
34
75
  // src/core/promise.ts
35
- function cachedPromise(load) {
76
+ function cachedPromise(load, label) {
36
77
  let settled = false;
37
78
  let value;
38
79
  return async () => {
39
- if (settled)
80
+ if (settled) {
81
+ if (label)
82
+ log.debug(`${label}.cache_hit`);
40
83
  return value;
41
- const loaded = await load();
42
- settled = true;
43
- value = loaded;
44
- return loaded;
84
+ }
85
+ if (label)
86
+ log.debug(`${label}.load_start`);
87
+ try {
88
+ const loaded = await load();
89
+ settled = true;
90
+ value = loaded;
91
+ if (label)
92
+ log.debug(`${label}.settled`);
93
+ return loaded;
94
+ } catch (err) {
95
+ if (label)
96
+ log.debug(`${label}.load_failed`, describeError(err));
97
+ throw err;
98
+ }
45
99
  };
46
100
  }
47
101
 
@@ -117,13 +171,16 @@ async function fetchFirstValidOAuthServerMetadata(metadataUrls, expectedIssuer)
117
171
  try {
118
172
  return await fetchOAuthServerMetadata(url, expectedIssuer);
119
173
  } catch (err) {
174
+ log.debug("oauth.discovery.attempt_failed", { url, ...describeError(err) });
120
175
  errors.push(`${url}: ${err instanceof Error ? err.message : String(err)}`);
121
176
  }
122
177
  }
178
+ log.error("oauth.discovery.exhausted", { expectedIssuer, urlsTried: metadataUrls, errors });
123
179
  throw new Error(`failed to discover OAuth server metadata (${errors.join("; ")})`);
124
180
  }
125
181
  async function fetchOAuthServerMetadata(url, expectedIssuer) {
126
- const response = await fetch(url, { signal: AbortSignal.timeout(METADATA_FETCH_TIMEOUT_MS), redirect: "error" });
182
+ log.debug("oauth.discovery.fetch", { url });
183
+ const response = await fetch(url, { signal: AbortSignal.timeout(METADATA_FETCH_TIMEOUT_MS), redirect: "manual" });
127
184
  if (!response.ok) {
128
185
  throw new Error(String(response.status));
129
186
  }
@@ -133,6 +190,7 @@ async function fetchOAuthServerMetadata(url, expectedIssuer) {
133
190
  }
134
191
  parseSafeUrl("discovered issuer", json.issuer);
135
192
  if (trimTrailingSlash(json.issuer) !== expectedIssuer) {
193
+ log.warn("oauth.discovery.issuer_mismatch", { url, expectedIssuer, published: json.issuer });
136
194
  throw new Error("issuer mismatch");
137
195
  }
138
196
  if (typeof json.jwks_uri !== "string") {
@@ -145,6 +203,11 @@ async function fetchIssuerOAuthServerMetadata(issuer) {
145
203
  try {
146
204
  return await fetchFirstValidOAuthServerMetadata(oauthMetadataUrlsForIssuer(issuer), issuer);
147
205
  } catch (err) {
206
+ log.error("oauth.discovery.config_error", {
207
+ issuer,
208
+ ...describeError(err),
209
+ outcome: "500 oauth configuration error"
210
+ });
148
211
  throw new OAuthConfigurationError(
149
212
  `OAuth issuer discovery failed: ${err instanceof Error ? err.message : String(err)}`
150
213
  );
@@ -152,10 +215,21 @@ async function fetchIssuerOAuthServerMetadata(issuer) {
152
215
  }
153
216
  function createOAuthDiscoveryResolver(auth) {
154
217
  const configuredIssuer = trimTrailingSlash(auth.issuer);
155
- const oauthServerMetadata = cachedPromise(() => fetchIssuerOAuthServerMetadata(configuredIssuer));
218
+ const oauthServerMetadata = cachedPromise(
219
+ () => fetchIssuerOAuthServerMetadata(configuredIssuer),
220
+ "oauth.discovery.metadata"
221
+ );
156
222
  return {
157
223
  resolveIssuer: async () => configuredIssuer,
158
- resolveJwksUri: async () => auth.jwksUri ?? (await oauthServerMetadata()).jwks_uri
224
+ resolveJwksUri: async () => {
225
+ if (auth.jwksUri) {
226
+ log.debug("oauth.jwks.resolved", { jwksUri: auth.jwksUri, source: "configured" });
227
+ return auth.jwksUri;
228
+ }
229
+ const jwksUri = (await oauthServerMetadata()).jwks_uri;
230
+ log.debug("oauth.jwks.resolved", { jwksUri, source: "discovered" });
231
+ return jwksUri;
232
+ }
159
233
  };
160
234
  }
161
235
 
@@ -218,6 +292,14 @@ var OAuthTokenError = class extends Error {
218
292
  function resolveAcceptedAudiences(auth, resource) {
219
293
  return auth.acceptedAudiences ?? [resource];
220
294
  }
295
+ function tokenHeaderFields(token) {
296
+ try {
297
+ const header = (0, import_jose.decodeProtectedHeader)(token);
298
+ return { jwtAlg: header.alg, jwtKid: header.kid, tokenLength: token.length };
299
+ } catch {
300
+ return { tokenLength: token.length };
301
+ }
302
+ }
221
303
  function isJwksFetchFailure(err) {
222
304
  if (err instanceof import_jose.errors.JWKSTimeout || err instanceof import_jose.errors.JWKSInvalid)
223
305
  return true;
@@ -239,19 +321,23 @@ async function verifyJwtClaims(token, keySet, issuer, audience, auth) {
239
321
  return payload;
240
322
  } catch (err) {
241
323
  if (isJwksFetchFailure(err)) {
324
+ log.error("oauth.jwks.fetch_failed", { ...describeError(err), outcome: "500 oauth configuration error" });
242
325
  throw new OAuthConfigurationError(`JWKS fetch failed: ${err instanceof Error ? err.message : String(err)}`);
243
326
  }
327
+ log.debug("oauth.verify.rejected", { ...describeError(err), outcome: "401 invalid_token" });
244
328
  throw err;
245
329
  }
246
330
  }
247
331
  function assertNonEmptySubject(claims) {
248
332
  const sub = claims["sub"];
249
333
  if (typeof sub !== "string" || sub.trim() === "") {
334
+ log.debug("oauth.verify.bad_subject", { subType: typeof sub });
250
335
  throw new OAuthTokenError(401, "invalid_token", "token subject claim must be a non-empty string");
251
336
  }
252
337
  }
253
338
  function assertOAuthClientClaim(auth, clientId) {
254
339
  if (auth.requireOAuthClientClaim !== false && !clientId) {
340
+ log.debug("oauth.verify.missing_client_claim", { outcome: "401 invalid_token" });
255
341
  throw new OAuthTokenError(401, "invalid_token", "OAuth client claim is required");
256
342
  }
257
343
  }
@@ -276,17 +362,23 @@ function buildMcpAuthContext(args) {
276
362
  };
277
363
  }
278
364
  function createOAuthTokenVerifier(auth, discovery) {
279
- const loadRemoteJwksKeySet = cachedPromise(
280
- () => discovery.resolveJwksUri().then((jwksURI) => (0, import_jose.createRemoteJWKSet)(new URL(jwksURI)))
281
- );
282
365
  return async (token, request, options) => {
283
366
  const resource = resolveProtectedResource(auth, request, options);
284
367
  const issuer = await discovery.resolveIssuer();
285
368
  const acceptedAudiences = resolveAcceptedAudiences(auth, resource);
286
- const claims = await verifyJwtClaims(token, await loadRemoteJwksKeySet(), issuer, acceptedAudiences, auth);
369
+ log.debug("oauth.verify.start", { issuer, acceptedAudiences, resource, ...tokenHeaderFields(token) });
370
+ const jwksUri = await discovery.resolveJwksUri();
371
+ log.debug("oauth.jwks.keyset_created", { jwksUri });
372
+ const keySet = (0, import_jose.createRemoteJWKSet)(new URL(jwksUri));
373
+ const claims = await verifyJwtClaims(token, keySet, issuer, acceptedAudiences, auth);
287
374
  assertNonEmptySubject(claims);
288
375
  const context = buildMcpAuthContext({ token, claims, issuer, resource, acceptedAudiences });
289
376
  assertOAuthClientClaim(auth, context.principal.clientId);
377
+ log.info("oauth.verify.ok", {
378
+ sub: context.principal.sub,
379
+ clientId: context.principal.clientId,
380
+ scopes: context.principal.scopes
381
+ });
290
382
  return context;
291
383
  };
292
384
  }
@@ -378,22 +470,27 @@ function createRequestAuthorizer(mcp, options = {}) {
378
470
  if (runtime.kind === "unconfigured")
379
471
  return { ok: true };
380
472
  const token = parseBearerToken(request);
381
- if (!token)
473
+ if (!token) {
474
+ log.info("auth.no_bearer_token", { outcome: "401" });
382
475
  return { ok: false, response: challengeResponse(runtime.auth, request, 401) };
476
+ }
383
477
  try {
384
478
  const auth = await runtime.verify(token, request, runtime.options);
385
479
  assertRequiredScopes(runtime.auth, auth);
386
480
  return { ok: true, auth };
387
481
  } catch (err) {
388
482
  if (err instanceof OAuthConfigurationError) {
483
+ log.error("auth.config_error", { ...describeError(err), outcome: "500" });
389
484
  return { ok: false, response: oauthConfigurationErrorResponse() };
390
485
  }
391
486
  if (err instanceof OAuthTokenError) {
487
+ log.info("auth.token_rejected", { status: err.status, oauthError: err.oauthError });
392
488
  return {
393
489
  ok: false,
394
490
  response: challengeResponse(runtime.auth, request, err.status, err.oauthError, err.message)
395
491
  };
396
492
  }
493
+ log.error("auth.unexpected_error", { ...describeError(err), outcome: "401" });
397
494
  return {
398
495
  ok: false,
399
496
  response: challengeResponse(runtime.auth, request, 401, "invalid_token", "Invalid access token")
@@ -511,7 +608,8 @@ function createMcpProtocolHandler(mcp, options = {}) {
511
608
  });
512
609
  await server.connect(transport);
513
610
  return await transport.handleRequest(request);
514
- } catch {
611
+ } catch (err) {
612
+ log.error("mcp.transport_error", { ...describeError(err), outcome: "500 internal error" });
515
613
  return Response.json(
516
614
  { jsonrpc: "2.0", id: null, error: { code: -32603, message: "internal error" } },
517
615
  { status: 500 }
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  createMcpProtocolHandler
3
- } from "../../chunk-NJ5WBRYI.js";
3
+ } from "../../chunk-G5GWSV5D.js";
4
4
  import "../../chunk-MA5H6PSF.js";
5
- import "../../chunk-ZKKLOL2C.js";
6
- import "../../chunk-QA3FWDUV.js";
5
+ import "../../chunk-NTXHOUK6.js";
6
+ import "../../chunk-QC3DXQTH.js";
7
7
  import "../../chunk-6DXGZZA4.js";
8
8
  export {
9
9
  createMcpProtocolHandler
@@ -36,17 +36,71 @@ function methodNotAllowed(allow) {
36
36
  });
37
37
  }
38
38
 
39
+ // src/core/logger.ts
40
+ var LEVEL_RANK = { silent: 0, error: 1, warn: 2, info: 3, debug: 4 };
41
+ function isLogLevel(value) {
42
+ return typeof value === "string" && value in LEVEL_RANK;
43
+ }
44
+ function readEnvLevel() {
45
+ try {
46
+ const raw = typeof process !== "undefined" ? process.env?.["LOVABLE_MCP_LOG_LEVEL"] : void 0;
47
+ const normalized = raw?.trim().toLowerCase();
48
+ return isLogLevel(normalized) ? normalized : void 0;
49
+ } catch {
50
+ return void 0;
51
+ }
52
+ }
53
+ var currentLevel = readEnvLevel() ?? "silent";
54
+ function enabled(level) {
55
+ return LEVEL_RANK[level] <= LEVEL_RANK[currentLevel];
56
+ }
57
+ function emit(level, method, event, fields) {
58
+ if (!enabled(level))
59
+ return;
60
+ const message = `[mcp-js] ${event}`;
61
+ if (fields)
62
+ console[method](message, fields);
63
+ else
64
+ console[method](message);
65
+ }
66
+ var log = {
67
+ error: (event, fields) => emit("error", "error", event, fields),
68
+ warn: (event, fields) => emit("warn", "warn", event, fields),
69
+ info: (event, fields) => emit("info", "info", event, fields),
70
+ debug: (event, fields) => emit("debug", "debug", event, fields)
71
+ };
72
+ function describeError(err) {
73
+ if (err instanceof Error) {
74
+ const code = err.code;
75
+ return { name: err.name, message: err.message, ...typeof code === "string" ? { code } : {} };
76
+ }
77
+ return { value: String(err) };
78
+ }
79
+
39
80
  // src/core/promise.ts
40
- function cachedPromise(load) {
81
+ function cachedPromise(load, label) {
41
82
  let settled = false;
42
83
  let value;
43
84
  return async () => {
44
- if (settled)
85
+ if (settled) {
86
+ if (label)
87
+ log.debug(`${label}.cache_hit`);
45
88
  return value;
46
- const loaded = await load();
47
- settled = true;
48
- value = loaded;
49
- return loaded;
89
+ }
90
+ if (label)
91
+ log.debug(`${label}.load_start`);
92
+ try {
93
+ const loaded = await load();
94
+ settled = true;
95
+ value = loaded;
96
+ if (label)
97
+ log.debug(`${label}.settled`);
98
+ return loaded;
99
+ } catch (err) {
100
+ if (label)
101
+ log.debug(`${label}.load_failed`, describeError(err));
102
+ throw err;
103
+ }
50
104
  };
51
105
  }
52
106
 
@@ -122,13 +176,16 @@ async function fetchFirstValidOAuthServerMetadata(metadataUrls, expectedIssuer)
122
176
  try {
123
177
  return await fetchOAuthServerMetadata(url, expectedIssuer);
124
178
  } catch (err) {
179
+ log.debug("oauth.discovery.attempt_failed", { url, ...describeError(err) });
125
180
  errors.push(`${url}: ${err instanceof Error ? err.message : String(err)}`);
126
181
  }
127
182
  }
183
+ log.error("oauth.discovery.exhausted", { expectedIssuer, urlsTried: metadataUrls, errors });
128
184
  throw new Error(`failed to discover OAuth server metadata (${errors.join("; ")})`);
129
185
  }
130
186
  async function fetchOAuthServerMetadata(url, expectedIssuer) {
131
- const response = await fetch(url, { signal: AbortSignal.timeout(METADATA_FETCH_TIMEOUT_MS), redirect: "error" });
187
+ log.debug("oauth.discovery.fetch", { url });
188
+ const response = await fetch(url, { signal: AbortSignal.timeout(METADATA_FETCH_TIMEOUT_MS), redirect: "manual" });
132
189
  if (!response.ok) {
133
190
  throw new Error(String(response.status));
134
191
  }
@@ -138,6 +195,7 @@ async function fetchOAuthServerMetadata(url, expectedIssuer) {
138
195
  }
139
196
  parseSafeUrl("discovered issuer", json.issuer);
140
197
  if (trimTrailingSlash(json.issuer) !== expectedIssuer) {
198
+ log.warn("oauth.discovery.issuer_mismatch", { url, expectedIssuer, published: json.issuer });
141
199
  throw new Error("issuer mismatch");
142
200
  }
143
201
  if (typeof json.jwks_uri !== "string") {
@@ -150,6 +208,11 @@ async function fetchIssuerOAuthServerMetadata(issuer) {
150
208
  try {
151
209
  return await fetchFirstValidOAuthServerMetadata(oauthMetadataUrlsForIssuer(issuer), issuer);
152
210
  } catch (err) {
211
+ log.error("oauth.discovery.config_error", {
212
+ issuer,
213
+ ...describeError(err),
214
+ outcome: "500 oauth configuration error"
215
+ });
153
216
  throw new OAuthConfigurationError(
154
217
  `OAuth issuer discovery failed: ${err instanceof Error ? err.message : String(err)}`
155
218
  );
@@ -157,10 +220,21 @@ async function fetchIssuerOAuthServerMetadata(issuer) {
157
220
  }
158
221
  function createOAuthDiscoveryResolver(auth) {
159
222
  const configuredIssuer = trimTrailingSlash(auth.issuer);
160
- const oauthServerMetadata = cachedPromise(() => fetchIssuerOAuthServerMetadata(configuredIssuer));
223
+ const oauthServerMetadata = cachedPromise(
224
+ () => fetchIssuerOAuthServerMetadata(configuredIssuer),
225
+ "oauth.discovery.metadata"
226
+ );
161
227
  return {
162
228
  resolveIssuer: async () => configuredIssuer,
163
- resolveJwksUri: async () => auth.jwksUri ?? (await oauthServerMetadata()).jwks_uri
229
+ resolveJwksUri: async () => {
230
+ if (auth.jwksUri) {
231
+ log.debug("oauth.jwks.resolved", { jwksUri: auth.jwksUri, source: "configured" });
232
+ return auth.jwksUri;
233
+ }
234
+ const jwksUri = (await oauthServerMetadata()).jwks_uri;
235
+ log.debug("oauth.jwks.resolved", { jwksUri, source: "discovered" });
236
+ return jwksUri;
237
+ }
164
238
  };
165
239
  }
166
240
 
@@ -220,6 +294,14 @@ var OAuthTokenError = class extends Error {
220
294
  function resolveAcceptedAudiences(auth, resource) {
221
295
  return auth.acceptedAudiences ?? [resource];
222
296
  }
297
+ function tokenHeaderFields(token) {
298
+ try {
299
+ const header = (0, import_jose.decodeProtectedHeader)(token);
300
+ return { jwtAlg: header.alg, jwtKid: header.kid, tokenLength: token.length };
301
+ } catch {
302
+ return { tokenLength: token.length };
303
+ }
304
+ }
223
305
  function isJwksFetchFailure(err) {
224
306
  if (err instanceof import_jose.errors.JWKSTimeout || err instanceof import_jose.errors.JWKSInvalid)
225
307
  return true;
@@ -241,19 +323,23 @@ async function verifyJwtClaims(token, keySet, issuer, audience, auth) {
241
323
  return payload;
242
324
  } catch (err) {
243
325
  if (isJwksFetchFailure(err)) {
326
+ log.error("oauth.jwks.fetch_failed", { ...describeError(err), outcome: "500 oauth configuration error" });
244
327
  throw new OAuthConfigurationError(`JWKS fetch failed: ${err instanceof Error ? err.message : String(err)}`);
245
328
  }
329
+ log.debug("oauth.verify.rejected", { ...describeError(err), outcome: "401 invalid_token" });
246
330
  throw err;
247
331
  }
248
332
  }
249
333
  function assertNonEmptySubject(claims) {
250
334
  const sub = claims["sub"];
251
335
  if (typeof sub !== "string" || sub.trim() === "") {
336
+ log.debug("oauth.verify.bad_subject", { subType: typeof sub });
252
337
  throw new OAuthTokenError(401, "invalid_token", "token subject claim must be a non-empty string");
253
338
  }
254
339
  }
255
340
  function assertOAuthClientClaim(auth, clientId) {
256
341
  if (auth.requireOAuthClientClaim !== false && !clientId) {
342
+ log.debug("oauth.verify.missing_client_claim", { outcome: "401 invalid_token" });
257
343
  throw new OAuthTokenError(401, "invalid_token", "OAuth client claim is required");
258
344
  }
259
345
  }
@@ -278,17 +364,23 @@ function buildMcpAuthContext(args) {
278
364
  };
279
365
  }
280
366
  function createOAuthTokenVerifier(auth, discovery) {
281
- const loadRemoteJwksKeySet = cachedPromise(
282
- () => discovery.resolveJwksUri().then((jwksURI) => (0, import_jose.createRemoteJWKSet)(new URL(jwksURI)))
283
- );
284
367
  return async (token, request, options) => {
285
368
  const resource = resolveProtectedResource(auth, request, options);
286
369
  const issuer = await discovery.resolveIssuer();
287
370
  const acceptedAudiences = resolveAcceptedAudiences(auth, resource);
288
- const claims = await verifyJwtClaims(token, await loadRemoteJwksKeySet(), issuer, acceptedAudiences, auth);
371
+ log.debug("oauth.verify.start", { issuer, acceptedAudiences, resource, ...tokenHeaderFields(token) });
372
+ const jwksUri = await discovery.resolveJwksUri();
373
+ log.debug("oauth.jwks.keyset_created", { jwksUri });
374
+ const keySet = (0, import_jose.createRemoteJWKSet)(new URL(jwksUri));
375
+ const claims = await verifyJwtClaims(token, keySet, issuer, acceptedAudiences, auth);
289
376
  assertNonEmptySubject(claims);
290
377
  const context = buildMcpAuthContext({ token, claims, issuer, resource, acceptedAudiences });
291
378
  assertOAuthClientClaim(auth, context.principal.clientId);
379
+ log.info("oauth.verify.ok", {
380
+ sub: context.principal.sub,
381
+ clientId: context.principal.clientId,
382
+ scopes: context.principal.scopes
383
+ });
292
384
  return context;
293
385
  };
294
386
  }
@@ -390,7 +482,8 @@ function createOAuthProtectedResourceMetadataHandler(mcp, options = {}) {
390
482
  );
391
483
  const response = withCors(Response.json(metadata, { headers }));
392
484
  return request.method === "HEAD" ? headResponse(response) : response;
393
- } catch {
485
+ } catch (err) {
486
+ log.error("oauth.metadata.config_error", { ...describeError(err), outcome: "500 oauth configuration error" });
394
487
  const response = withCors(oauthConfigurationErrorResponse());
395
488
  return request.method === "HEAD" ? headResponse(response) : response;
396
489
  }
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  createOAuthProtectedResourceMetadataHandler
3
- } from "../chunk-K2T4WTKX.js";
4
- import "../chunk-ZKKLOL2C.js";
5
- import "../chunk-QA3FWDUV.js";
3
+ } from "../chunk-722HKLIU.js";
4
+ import "../chunk-NTXHOUK6.js";
5
+ import "../chunk-QC3DXQTH.js";
6
6
  import "../chunk-6DXGZZA4.js";
7
7
  export {
8
8
  createOAuthProtectedResourceMetadataHandler