@lovable.dev/mcp-js 0.6.0 → 0.7.1

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
 
@@ -217,6 +291,7 @@ function stringClaim(claims, name) {
217
291
  // src/auth/verifier.ts
218
292
  var DEFAULT_JWT_ALGORITHMS = ["RS256", "RS384", "RS512", "ES256", "ES384", "ES512", "EdDSA"];
219
293
  var DEFAULT_CLOCK_TOLERANCE_SECONDS = 30;
294
+ var JWKS_FETCH_TIMEOUT_MS = 5e3;
220
295
  var OAuthTokenError = class extends Error {
221
296
  constructor(status, oauthError, message) {
222
297
  super(message);
@@ -228,12 +303,25 @@ var OAuthTokenError = class extends Error {
228
303
  function resolveAcceptedAudiences(auth, resource) {
229
304
  return auth.acceptedAudiences ?? [resource];
230
305
  }
231
- function isJwksFetchFailure(err) {
232
- if (err instanceof import_jose.errors.JWKSTimeout || err instanceof import_jose.errors.JWKSInvalid)
233
- return true;
234
- if (!(err instanceof import_jose.errors.JOSEError))
235
- return err instanceof Error;
236
- return err.code === "ERR_JOSE_GENERIC";
306
+ function tokenHeaderFields(token) {
307
+ try {
308
+ const header = (0, import_jose.decodeProtectedHeader)(token);
309
+ return { jwtAlg: header.alg, jwtKid: header.kid, tokenLength: token.length };
310
+ } catch {
311
+ return { tokenLength: token.length };
312
+ }
313
+ }
314
+ async function fetchVerificationKeySet(jwksUri) {
315
+ try {
316
+ const response = await fetch(jwksUri, { signal: AbortSignal.timeout(JWKS_FETCH_TIMEOUT_MS), redirect: "manual" });
317
+ if (!response.ok)
318
+ throw new Error(`JWKS endpoint returned ${response.status}`);
319
+ const json = await response.json();
320
+ return (0, import_jose.createLocalJWKSet)(json);
321
+ } catch (err) {
322
+ log.error("oauth.jwks.fetch_failed", { ...describeError(err), outcome: "500 oauth configuration error" });
323
+ throw new OAuthConfigurationError(`JWKS fetch failed: ${err instanceof Error ? err.message : String(err)}`);
324
+ }
237
325
  }
238
326
  async function verifyJwtClaims(token, keySet, issuer, audience, auth) {
239
327
  try {
@@ -248,20 +336,20 @@ async function verifyJwtClaims(token, keySet, issuer, audience, auth) {
248
336
  });
249
337
  return payload;
250
338
  } catch (err) {
251
- if (isJwksFetchFailure(err)) {
252
- throw new OAuthConfigurationError(`JWKS fetch failed: ${err instanceof Error ? err.message : String(err)}`);
253
- }
339
+ log.debug("oauth.verify.rejected", { ...describeError(err), outcome: "401 invalid_token" });
254
340
  throw err;
255
341
  }
256
342
  }
257
343
  function assertNonEmptySubject(claims) {
258
344
  const sub = claims["sub"];
259
345
  if (typeof sub !== "string" || sub.trim() === "") {
346
+ log.debug("oauth.verify.bad_subject", { subType: typeof sub });
260
347
  throw new OAuthTokenError(401, "invalid_token", "token subject claim must be a non-empty string");
261
348
  }
262
349
  }
263
350
  function assertOAuthClientClaim(auth, clientId) {
264
351
  if (auth.requireOAuthClientClaim !== false && !clientId) {
352
+ log.debug("oauth.verify.missing_client_claim", { outcome: "401 invalid_token" });
265
353
  throw new OAuthTokenError(401, "invalid_token", "OAuth client claim is required");
266
354
  }
267
355
  }
@@ -286,17 +374,23 @@ function buildMcpAuthContext(args) {
286
374
  };
287
375
  }
288
376
  function createOAuthTokenVerifier(auth, discovery) {
289
- const loadRemoteJwksKeySet = cachedPromise(
290
- () => discovery.resolveJwksUri().then((jwksURI) => (0, import_jose.createRemoteJWKSet)(new URL(jwksURI)))
291
- );
292
377
  return async (token, request, options) => {
293
378
  const resource = resolveProtectedResource(auth, request, options);
294
379
  const issuer = await discovery.resolveIssuer();
295
380
  const acceptedAudiences = resolveAcceptedAudiences(auth, resource);
296
- const claims = await verifyJwtClaims(token, await loadRemoteJwksKeySet(), issuer, acceptedAudiences, auth);
381
+ log.debug("oauth.verify.start", { issuer, acceptedAudiences, resource, ...tokenHeaderFields(token) });
382
+ const jwksUri = await discovery.resolveJwksUri();
383
+ log.debug("oauth.jwks.fetch", { jwksUri });
384
+ const keySet = await fetchVerificationKeySet(jwksUri);
385
+ const claims = await verifyJwtClaims(token, keySet, issuer, acceptedAudiences, auth);
297
386
  assertNonEmptySubject(claims);
298
387
  const context = buildMcpAuthContext({ token, claims, issuer, resource, acceptedAudiences });
299
388
  assertOAuthClientClaim(auth, context.principal.clientId);
389
+ log.info("oauth.verify.ok", {
390
+ sub: context.principal.sub,
391
+ clientId: context.principal.clientId,
392
+ scopes: context.principal.scopes
393
+ });
300
394
  return context;
301
395
  };
302
396
  }
@@ -396,22 +490,27 @@ function createRequestAuthorizer(mcp, options = {}) {
396
490
  if (runtime.kind === "unconfigured")
397
491
  return { ok: true };
398
492
  const token = parseBearerToken(request);
399
- if (!token)
493
+ if (!token) {
494
+ log.info("auth.no_bearer_token", { outcome: "401" });
400
495
  return { ok: false, response: challengeResponse(runtime.auth, request, 401) };
496
+ }
401
497
  try {
402
498
  const auth = await runtime.verify(token, request, runtime.options);
403
499
  assertRequiredScopes(runtime.auth, auth);
404
500
  return { ok: true, auth };
405
501
  } catch (err) {
406
502
  if (err instanceof OAuthConfigurationError) {
503
+ log.error("auth.config_error", { ...describeError(err), outcome: "500" });
407
504
  return { ok: false, response: oauthConfigurationErrorResponse() };
408
505
  }
409
506
  if (err instanceof OAuthTokenError) {
507
+ log.info("auth.token_rejected", { status: err.status, oauthError: err.oauthError });
410
508
  return {
411
509
  ok: false,
412
510
  response: challengeResponse(runtime.auth, request, err.status, err.oauthError, err.message)
413
511
  };
414
512
  }
513
+ log.error("auth.unexpected_error", { ...describeError(err), outcome: "401" });
415
514
  return {
416
515
  ok: false,
417
516
  response: challengeResponse(runtime.auth, request, 401, "invalid_token", "Invalid access token")
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  createInvokeToolHandler,
3
3
  createListToolsHandler
4
- } from "../../chunk-SYI32IRK.js";
4
+ } from "../../chunk-LMT6RXUF.js";
5
5
  import "../../chunk-MA5H6PSF.js";
6
- import "../../chunk-ZKKLOL2C.js";
7
- import "../../chunk-QA3FWDUV.js";
6
+ import "../../chunk-HRMLGCXV.js";
7
+ import "../../chunk-QC3DXQTH.js";
8
8
  import "../../chunk-6DXGZZA4.js";
9
9
  export {
10
10
  createInvokeToolHandler,
@@ -43,17 +43,71 @@ function methodNotAllowed(allow) {
43
43
  });
44
44
  }
45
45
 
46
+ // src/core/logger.ts
47
+ var LEVEL_RANK = { silent: 0, error: 1, warn: 2, info: 3, debug: 4 };
48
+ function isLogLevel(value) {
49
+ return typeof value === "string" && value in LEVEL_RANK;
50
+ }
51
+ function readEnvLevel() {
52
+ try {
53
+ const raw = typeof process !== "undefined" ? process.env?.["LOVABLE_MCP_LOG_LEVEL"] : void 0;
54
+ const normalized = raw?.trim().toLowerCase();
55
+ return isLogLevel(normalized) ? normalized : void 0;
56
+ } catch {
57
+ return void 0;
58
+ }
59
+ }
60
+ var currentLevel = readEnvLevel() ?? "silent";
61
+ function enabled(level) {
62
+ return LEVEL_RANK[level] <= LEVEL_RANK[currentLevel];
63
+ }
64
+ function emit(level, method, event, fields) {
65
+ if (!enabled(level))
66
+ return;
67
+ const message = `[mcp-js] ${event}`;
68
+ if (fields)
69
+ console[method](message, fields);
70
+ else
71
+ console[method](message);
72
+ }
73
+ var log = {
74
+ error: (event, fields) => emit("error", "error", event, fields),
75
+ warn: (event, fields) => emit("warn", "warn", event, fields),
76
+ info: (event, fields) => emit("info", "info", event, fields),
77
+ debug: (event, fields) => emit("debug", "debug", event, fields)
78
+ };
79
+ function describeError(err) {
80
+ if (err instanceof Error) {
81
+ const code = err.code;
82
+ return { name: err.name, message: err.message, ...typeof code === "string" ? { code } : {} };
83
+ }
84
+ return { value: String(err) };
85
+ }
86
+
46
87
  // src/core/promise.ts
47
- function cachedPromise(load) {
88
+ function cachedPromise(load, label) {
48
89
  let settled = false;
49
90
  let value;
50
91
  return async () => {
51
- if (settled)
92
+ if (settled) {
93
+ if (label)
94
+ log.debug(`${label}.cache_hit`);
52
95
  return value;
53
- const loaded = await load();
54
- settled = true;
55
- value = loaded;
56
- return loaded;
96
+ }
97
+ if (label)
98
+ log.debug(`${label}.load_start`);
99
+ try {
100
+ const loaded = await load();
101
+ settled = true;
102
+ value = loaded;
103
+ if (label)
104
+ log.debug(`${label}.settled`);
105
+ return loaded;
106
+ } catch (err) {
107
+ if (label)
108
+ log.debug(`${label}.load_failed`, describeError(err));
109
+ throw err;
110
+ }
57
111
  };
58
112
  }
59
113
 
@@ -129,13 +183,16 @@ async function fetchFirstValidOAuthServerMetadata(metadataUrls, expectedIssuer)
129
183
  try {
130
184
  return await fetchOAuthServerMetadata(url, expectedIssuer);
131
185
  } catch (err) {
186
+ log.debug("oauth.discovery.attempt_failed", { url, ...describeError(err) });
132
187
  errors.push(`${url}: ${err instanceof Error ? err.message : String(err)}`);
133
188
  }
134
189
  }
190
+ log.error("oauth.discovery.exhausted", { expectedIssuer, urlsTried: metadataUrls, errors });
135
191
  throw new Error(`failed to discover OAuth server metadata (${errors.join("; ")})`);
136
192
  }
137
193
  async function fetchOAuthServerMetadata(url, expectedIssuer) {
138
- const response = await fetch(url, { signal: AbortSignal.timeout(METADATA_FETCH_TIMEOUT_MS), redirect: "error" });
194
+ log.debug("oauth.discovery.fetch", { url });
195
+ const response = await fetch(url, { signal: AbortSignal.timeout(METADATA_FETCH_TIMEOUT_MS), redirect: "manual" });
139
196
  if (!response.ok) {
140
197
  throw new Error(String(response.status));
141
198
  }
@@ -145,6 +202,7 @@ async function fetchOAuthServerMetadata(url, expectedIssuer) {
145
202
  }
146
203
  parseSafeUrl("discovered issuer", json.issuer);
147
204
  if (trimTrailingSlash(json.issuer) !== expectedIssuer) {
205
+ log.warn("oauth.discovery.issuer_mismatch", { url, expectedIssuer, published: json.issuer });
148
206
  throw new Error("issuer mismatch");
149
207
  }
150
208
  if (typeof json.jwks_uri !== "string") {
@@ -157,6 +215,11 @@ async function fetchIssuerOAuthServerMetadata(issuer) {
157
215
  try {
158
216
  return await fetchFirstValidOAuthServerMetadata(oauthMetadataUrlsForIssuer(issuer), issuer);
159
217
  } catch (err) {
218
+ log.error("oauth.discovery.config_error", {
219
+ issuer,
220
+ ...describeError(err),
221
+ outcome: "500 oauth configuration error"
222
+ });
160
223
  throw new OAuthConfigurationError(
161
224
  `OAuth issuer discovery failed: ${err instanceof Error ? err.message : String(err)}`
162
225
  );
@@ -164,10 +227,21 @@ async function fetchIssuerOAuthServerMetadata(issuer) {
164
227
  }
165
228
  function createOAuthDiscoveryResolver(auth) {
166
229
  const configuredIssuer = trimTrailingSlash(auth.issuer);
167
- const oauthServerMetadata = cachedPromise(() => fetchIssuerOAuthServerMetadata(configuredIssuer));
230
+ const oauthServerMetadata = cachedPromise(
231
+ () => fetchIssuerOAuthServerMetadata(configuredIssuer),
232
+ "oauth.discovery.metadata"
233
+ );
168
234
  return {
169
235
  resolveIssuer: async () => configuredIssuer,
170
- resolveJwksUri: async () => auth.jwksUri ?? (await oauthServerMetadata()).jwks_uri
236
+ resolveJwksUri: async () => {
237
+ if (auth.jwksUri) {
238
+ log.debug("oauth.jwks.resolved", { jwksUri: auth.jwksUri, source: "configured" });
239
+ return auth.jwksUri;
240
+ }
241
+ const jwksUri = (await oauthServerMetadata()).jwks_uri;
242
+ log.debug("oauth.jwks.resolved", { jwksUri, source: "discovered" });
243
+ return jwksUri;
244
+ }
171
245
  };
172
246
  }
173
247
 
@@ -219,6 +293,7 @@ function stringClaim(claims, name) {
219
293
  // src/auth/verifier.ts
220
294
  var DEFAULT_JWT_ALGORITHMS = ["RS256", "RS384", "RS512", "ES256", "ES384", "ES512", "EdDSA"];
221
295
  var DEFAULT_CLOCK_TOLERANCE_SECONDS = 30;
296
+ var JWKS_FETCH_TIMEOUT_MS = 5e3;
222
297
  var OAuthTokenError = class extends Error {
223
298
  constructor(status, oauthError, message) {
224
299
  super(message);
@@ -230,12 +305,25 @@ var OAuthTokenError = class extends Error {
230
305
  function resolveAcceptedAudiences(auth, resource) {
231
306
  return auth.acceptedAudiences ?? [resource];
232
307
  }
233
- function isJwksFetchFailure(err) {
234
- if (err instanceof import_jose.errors.JWKSTimeout || err instanceof import_jose.errors.JWKSInvalid)
235
- return true;
236
- if (!(err instanceof import_jose.errors.JOSEError))
237
- return err instanceof Error;
238
- return err.code === "ERR_JOSE_GENERIC";
308
+ function tokenHeaderFields(token) {
309
+ try {
310
+ const header = (0, import_jose.decodeProtectedHeader)(token);
311
+ return { jwtAlg: header.alg, jwtKid: header.kid, tokenLength: token.length };
312
+ } catch {
313
+ return { tokenLength: token.length };
314
+ }
315
+ }
316
+ async function fetchVerificationKeySet(jwksUri) {
317
+ try {
318
+ const response = await fetch(jwksUri, { signal: AbortSignal.timeout(JWKS_FETCH_TIMEOUT_MS), redirect: "manual" });
319
+ if (!response.ok)
320
+ throw new Error(`JWKS endpoint returned ${response.status}`);
321
+ const json = await response.json();
322
+ return (0, import_jose.createLocalJWKSet)(json);
323
+ } catch (err) {
324
+ log.error("oauth.jwks.fetch_failed", { ...describeError(err), outcome: "500 oauth configuration error" });
325
+ throw new OAuthConfigurationError(`JWKS fetch failed: ${err instanceof Error ? err.message : String(err)}`);
326
+ }
239
327
  }
240
328
  async function verifyJwtClaims(token, keySet, issuer, audience, auth) {
241
329
  try {
@@ -250,20 +338,20 @@ async function verifyJwtClaims(token, keySet, issuer, audience, auth) {
250
338
  });
251
339
  return payload;
252
340
  } catch (err) {
253
- if (isJwksFetchFailure(err)) {
254
- throw new OAuthConfigurationError(`JWKS fetch failed: ${err instanceof Error ? err.message : String(err)}`);
255
- }
341
+ log.debug("oauth.verify.rejected", { ...describeError(err), outcome: "401 invalid_token" });
256
342
  throw err;
257
343
  }
258
344
  }
259
345
  function assertNonEmptySubject(claims) {
260
346
  const sub = claims["sub"];
261
347
  if (typeof sub !== "string" || sub.trim() === "") {
348
+ log.debug("oauth.verify.bad_subject", { subType: typeof sub });
262
349
  throw new OAuthTokenError(401, "invalid_token", "token subject claim must be a non-empty string");
263
350
  }
264
351
  }
265
352
  function assertOAuthClientClaim(auth, clientId) {
266
353
  if (auth.requireOAuthClientClaim !== false && !clientId) {
354
+ log.debug("oauth.verify.missing_client_claim", { outcome: "401 invalid_token" });
267
355
  throw new OAuthTokenError(401, "invalid_token", "OAuth client claim is required");
268
356
  }
269
357
  }
@@ -288,17 +376,23 @@ function buildMcpAuthContext(args) {
288
376
  };
289
377
  }
290
378
  function createOAuthTokenVerifier(auth, discovery) {
291
- const loadRemoteJwksKeySet = cachedPromise(
292
- () => discovery.resolveJwksUri().then((jwksURI) => (0, import_jose.createRemoteJWKSet)(new URL(jwksURI)))
293
- );
294
379
  return async (token, request, options) => {
295
380
  const resource = resolveProtectedResource(auth, request, options);
296
381
  const issuer = await discovery.resolveIssuer();
297
382
  const acceptedAudiences = resolveAcceptedAudiences(auth, resource);
298
- const claims = await verifyJwtClaims(token, await loadRemoteJwksKeySet(), issuer, acceptedAudiences, auth);
383
+ log.debug("oauth.verify.start", { issuer, acceptedAudiences, resource, ...tokenHeaderFields(token) });
384
+ const jwksUri = await discovery.resolveJwksUri();
385
+ log.debug("oauth.jwks.fetch", { jwksUri });
386
+ const keySet = await fetchVerificationKeySet(jwksUri);
387
+ const claims = await verifyJwtClaims(token, keySet, issuer, acceptedAudiences, auth);
299
388
  assertNonEmptySubject(claims);
300
389
  const context = buildMcpAuthContext({ token, claims, issuer, resource, acceptedAudiences });
301
390
  assertOAuthClientClaim(auth, context.principal.clientId);
391
+ log.info("oauth.verify.ok", {
392
+ sub: context.principal.sub,
393
+ clientId: context.principal.clientId,
394
+ scopes: context.principal.scopes
395
+ });
302
396
  return context;
303
397
  };
304
398
  }
@@ -398,22 +492,27 @@ function createRequestAuthorizer(mcp, options = {}) {
398
492
  if (runtime.kind === "unconfigured")
399
493
  return { ok: true };
400
494
  const token = parseBearerToken(request);
401
- if (!token)
495
+ if (!token) {
496
+ log.info("auth.no_bearer_token", { outcome: "401" });
402
497
  return { ok: false, response: challengeResponse(runtime.auth, request, 401) };
498
+ }
403
499
  try {
404
500
  const auth = await runtime.verify(token, request, runtime.options);
405
501
  assertRequiredScopes(runtime.auth, auth);
406
502
  return { ok: true, auth };
407
503
  } catch (err) {
408
504
  if (err instanceof OAuthConfigurationError) {
505
+ log.error("auth.config_error", { ...describeError(err), outcome: "500" });
409
506
  return { ok: false, response: oauthConfigurationErrorResponse() };
410
507
  }
411
508
  if (err instanceof OAuthTokenError) {
509
+ log.info("auth.token_rejected", { status: err.status, oauthError: err.oauthError });
412
510
  return {
413
511
  ok: false,
414
512
  response: challengeResponse(runtime.auth, request, err.status, err.oauthError, err.message)
415
513
  };
416
514
  }
515
+ log.error("auth.unexpected_error", { ...describeError(err), outcome: "401" });
417
516
  return {
418
517
  ok: false,
419
518
  response: challengeResponse(runtime.auth, request, 401, "invalid_token", "Invalid access token")
@@ -531,7 +630,8 @@ function createMcpProtocolHandler(mcp, options = {}) {
531
630
  });
532
631
  await server.connect(transport);
533
632
  return await transport.handleRequest(request);
534
- } catch {
633
+ } catch (err) {
634
+ log.error("mcp.transport_error", { ...describeError(err), outcome: "500 internal error" });
535
635
  return Response.json(
536
636
  { jsonrpc: "2.0", id: null, error: { code: -32603, message: "internal error" } },
537
637
  { status: 500 }
@@ -598,7 +698,8 @@ function createOAuthProtectedResourceMetadataHandler(mcp, options = {}) {
598
698
  );
599
699
  const response = withCors(Response.json(metadata, { headers }));
600
700
  return request.method === "HEAD" ? headResponse(response) : response;
601
- } catch {
701
+ } catch (err) {
702
+ log.error("oauth.metadata.config_error", { ...describeError(err), outcome: "500 oauth configuration error" });
602
703
  const response = withCors(oauthConfigurationErrorResponse());
603
704
  return request.method === "HEAD" ? headResponse(response) : response;
604
705
  }
@@ -1,16 +1,16 @@
1
1
  import {
2
2
  createMcpProtocolHandler
3
- } from "../../chunk-NJ5WBRYI.js";
3
+ } from "../../chunk-3XPKR4G6.js";
4
4
  import {
5
5
  createOAuthProtectedResourceMetadataHandler
6
- } from "../../chunk-K2T4WTKX.js";
6
+ } from "../../chunk-W7F6JRDB.js";
7
7
  import {
8
8
  createInvokeToolHandler,
9
9
  createListToolsHandler
10
- } from "../../chunk-SYI32IRK.js";
10
+ } from "../../chunk-LMT6RXUF.js";
11
11
  import "../../chunk-MA5H6PSF.js";
12
- import "../../chunk-ZKKLOL2C.js";
13
- import "../../chunk-QA3FWDUV.js";
12
+ import "../../chunk-HRMLGCXV.js";
13
+ import "../../chunk-QC3DXQTH.js";
14
14
  import "../../chunk-6DXGZZA4.js";
15
15
 
16
16
  // src/stacks/tanstack/handlers.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lovable.dev/mcp-js",
3
- "version": "0.6.0",
3
+ "version": "0.7.1",
4
4
  "description": "Author MCP servers for Lovable apps. Declare tools with defineTool, register them in defineMcp, and the framework adapter (TanStack today, Supabase Edge Functions next) emits the route(s) at build time.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -1,40 +0,0 @@
1
- // src/core/url.ts
2
- function trimTrailingSlash(value) {
3
- return value.replace(/\/+$/, "");
4
- }
5
- function isLocalHTTPHost(hostname) {
6
- return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]";
7
- }
8
- function urlSafetyProblem(url) {
9
- const isAllowedHTTP = url.protocol === "http:" && isLocalHTTPHost(url.hostname);
10
- if (url.protocol !== "https:" && !isAllowedHTTP) {
11
- return "must use https://, except localhost development URLs";
12
- }
13
- if (url.username || url.password) {
14
- return "must not include credentials";
15
- }
16
- if (url.search || url.hash) {
17
- return "must not include query or fragment";
18
- }
19
- return void 0;
20
- }
21
-
22
- // src/core/validation.ts
23
- function parseSafeUrl(subject, raw, ErrorClass = Error) {
24
- let url;
25
- try {
26
- url = new URL(raw);
27
- } catch {
28
- throw new ErrorClass(`${subject} must be an absolute URL`);
29
- }
30
- const problem = urlSafetyProblem(url);
31
- if (problem) {
32
- throw new ErrorClass(`${subject} ${problem}`);
33
- }
34
- return url;
35
- }
36
-
37
- export {
38
- trimTrailingSlash,
39
- parseSafeUrl
40
- };