@lovable.dev/mcp-js 0.20.0 → 0.20.1-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.
@@ -1,4 +1,4 @@
1
- type InvocationOutcome = "ok" | "tool_error" | "handler_error" | "transport_error" | "auth_config_error";
1
+ type InvocationOutcome = "ok" | "tool_error" | "handler_error" | "transport_error" | "auth_config_error" | "auth_upstream_unreachable";
2
2
  /** One recorded MCP call. Carries no arguments or response payloads — only the
3
3
  * tool name, the JSON-RPC method, the result class, timing, and byte sizes. */
4
4
  interface InvocationRecord {
@@ -7,7 +7,7 @@ import {
7
7
  oauthConfigurationErrorResponse,
8
8
  resolveProtectedResource,
9
9
  withCors
10
- } from "./chunk-EPEEEI3C.js";
10
+ } from "./chunk-E7CPOJN6.js";
11
11
  import {
12
12
  describeError,
13
13
  log
@@ -13,7 +13,7 @@ import {
13
13
  } from "./chunk-6DXGZZA4.js";
14
14
  import {
15
15
  version
16
- } from "./chunk-FTTVLHNN.js";
16
+ } from "./chunk-OMEL6F6I.js";
17
17
 
18
18
  // src/core/http.ts
19
19
  var JSON_HEADERS = { "Content-Type": "application/json" };
@@ -289,6 +289,18 @@ var OAuthConfigurationError = class extends Error {
289
289
  this.name = "OAuthConfigurationError";
290
290
  }
291
291
  };
292
+ var OAuthUpstreamUnreachableError = class extends Error {
293
+ constructor(message) {
294
+ super(message);
295
+ this.name = "OAuthUpstreamUnreachableError";
296
+ }
297
+ };
298
+ var MetadataUnreachableError = class extends Error {
299
+ constructor(message) {
300
+ super(message);
301
+ this.name = "MetadataUnreachableError";
302
+ }
303
+ };
292
304
  var METADATA_FETCH_TIMEOUT_MS = 5e3;
293
305
  function issuerPath(url) {
294
306
  const path = trimTrailingSlash(url.pathname);
@@ -313,21 +325,37 @@ function oauthMetadataUrlsForIssuer(issuer) {
313
325
  return [...pathInsertedOAuthMetadataUrls(url, path), `${normalizedIssuer}/.well-known/openid-configuration`];
314
326
  }
315
327
  async function fetchFirstValidOAuthServerMetadata(metadataUrls, expectedIssuer) {
316
- const errors = [];
317
- for (const url of metadataUrls) {
318
- try {
319
- return await fetchOAuthServerMetadata(url, expectedIssuer);
320
- } catch (err) {
328
+ const controller = new AbortController();
329
+ const attempts = metadataUrls.map(
330
+ (url) => fetchOAuthServerMetadata(url, expectedIssuer, controller.signal).catch((err) => {
321
331
  log.debug("oauth.discovery.attempt_failed", { url, ...describeError(err) });
322
- errors.push(`${url}: ${err instanceof Error ? err.message : String(err)}`);
323
- }
332
+ throw err instanceof Error ? err : new Error(String(err));
333
+ })
334
+ );
335
+ try {
336
+ const metadata = await Promise.any(attempts);
337
+ controller.abort();
338
+ return metadata;
339
+ } catch (aggregate) {
340
+ const causes = aggregate instanceof AggregateError ? aggregate.errors : [aggregate];
341
+ const unreachable = causes.some((c) => c instanceof MetadataUnreachableError);
342
+ const detail = causes.map((c) => c instanceof Error ? c.message : String(c)).join("; ");
343
+ log.error("oauth.discovery.exhausted", { expectedIssuer, urlsTried: metadataUrls, errors: detail, unreachable });
344
+ const message = `failed to discover OAuth server metadata (${detail})`;
345
+ throw unreachable ? new MetadataUnreachableError(message) : new Error(message);
324
346
  }
325
- log.error("oauth.discovery.exhausted", { expectedIssuer, urlsTried: metadataUrls, errors });
326
- throw new Error(`failed to discover OAuth server metadata (${errors.join("; ")})`);
327
347
  }
328
- async function fetchOAuthServerMetadata(url, expectedIssuer) {
348
+ async function fetchOAuthServerMetadata(url, expectedIssuer, signal) {
329
349
  log.debug("oauth.discovery.fetch", { url });
330
- const response = await fetch(url, { signal: AbortSignal.timeout(METADATA_FETCH_TIMEOUT_MS), redirect: "manual" });
350
+ let response;
351
+ try {
352
+ response = await fetch(url, {
353
+ signal: AbortSignal.any([signal, AbortSignal.timeout(METADATA_FETCH_TIMEOUT_MS)]),
354
+ redirect: "manual"
355
+ });
356
+ } catch (err) {
357
+ throw new MetadataUnreachableError(err instanceof Error ? err.message : String(err));
358
+ }
331
359
  if (!response.ok) {
332
360
  throw new Error(String(response.status));
333
361
  }
@@ -350,14 +378,21 @@ async function fetchIssuerOAuthServerMetadata(issuer) {
350
378
  try {
351
379
  return await fetchFirstValidOAuthServerMetadata(oauthMetadataUrlsForIssuer(issuer), issuer);
352
380
  } catch (err) {
381
+ const message = `OAuth issuer discovery failed: ${err instanceof Error ? err.message : String(err)}`;
382
+ if (err instanceof MetadataUnreachableError) {
383
+ log.error("oauth.discovery.unreachable", {
384
+ issuer,
385
+ ...describeError(err),
386
+ outcome: "503 authorization server unreachable"
387
+ });
388
+ throw new OAuthUpstreamUnreachableError(message);
389
+ }
353
390
  log.error("oauth.discovery.config_error", {
354
391
  issuer,
355
392
  ...describeError(err),
356
393
  outcome: "500 oauth configuration error"
357
394
  });
358
- throw new OAuthConfigurationError(
359
- `OAuth issuer discovery failed: ${err instanceof Error ? err.message : String(err)}`
360
- );
395
+ throw new OAuthConfigurationError(message);
361
396
  }
362
397
  }
363
398
  function createOAuthDiscoveryResolver(auth) {
@@ -423,8 +458,14 @@ function tokenHeaderFields(token) {
423
458
  }
424
459
  }
425
460
  async function fetchVerificationKeySet(jwksUri) {
461
+ let response;
462
+ try {
463
+ response = await fetch(jwksUri, { signal: AbortSignal.timeout(JWKS_FETCH_TIMEOUT_MS), redirect: "manual" });
464
+ } catch (err) {
465
+ log.error("oauth.jwks.unreachable", { ...describeError(err), outcome: "503 authorization server unreachable" });
466
+ throw new OAuthUpstreamUnreachableError(`JWKS fetch failed: ${err instanceof Error ? err.message : String(err)}`);
467
+ }
426
468
  try {
427
- const response = await fetch(jwksUri, { signal: AbortSignal.timeout(JWKS_FETCH_TIMEOUT_MS), redirect: "manual" });
428
469
  if (!response.ok)
429
470
  throw new Error(`JWKS endpoint returned ${response.status}`);
430
471
  const json = await response.json();
@@ -565,6 +606,12 @@ function oauthConfigurationErrorResponse() {
565
606
  headers: { ...JSON_HEADERS, "Cache-Control": "no-store" }
566
607
  });
567
608
  }
609
+ function oauthUpstreamUnreachableResponse() {
610
+ return new Response(JSON.stringify({ error: "authorization server unreachable" }), {
611
+ status: 503,
612
+ headers: { ...JSON_HEADERS, "Cache-Control": "no-store", "Retry-After": "30" }
613
+ });
614
+ }
568
615
  function parseBearerToken(request) {
569
616
  const header = request.headers.get("Authorization");
570
617
  if (!header)
@@ -637,6 +684,16 @@ function createRequestAuthorizer(mcp, options = {}) {
637
684
  assertRequiredScopes(runtime.auth, auth);
638
685
  return { ok: true, auth };
639
686
  } catch (err) {
687
+ if (err instanceof OAuthUpstreamUnreachableError) {
688
+ log.error("auth.upstream_unreachable", { ...describeError(err), outcome: "503" });
689
+ await recorder?.emit({
690
+ tool: null,
691
+ method: "authorize",
692
+ outcome: "auth_upstream_unreachable",
693
+ durationMs: nowMs() - startedAt
694
+ });
695
+ return { ok: false, response: oauthUpstreamUnreachableResponse() };
696
+ }
640
697
  if (err instanceof OAuthConfigurationError) {
641
698
  log.error("auth.config_error", { ...describeError(err), outcome: "500" });
642
699
  await recorder?.emit({
@@ -7,7 +7,7 @@ import {
7
7
  createRequestAuthorizer,
8
8
  nowMs,
9
9
  withCors
10
- } from "./chunk-EPEEEI3C.js";
10
+ } from "./chunk-E7CPOJN6.js";
11
11
  import {
12
12
  describeError,
13
13
  log
@@ -1,5 +1,5 @@
1
1
  // package.json
2
- var version = "0.20.0";
2
+ var version = "0.20.1-rc.0";
3
3
 
4
4
  export {
5
5
  version
@@ -10,7 +10,7 @@ import {
10
10
  methodNotAllowed,
11
11
  nowMs,
12
12
  withCors
13
- } from "./chunk-EPEEEI3C.js";
13
+ } from "./chunk-E7CPOJN6.js";
14
14
 
15
15
  // src/protocols/rest/invoke-tool.ts
16
16
  import { getParseErrorMessage, objectFromShape, safeParseAsync } from "@modelcontextprotocol/sdk/server/zod-compat.js";
@@ -6,7 +6,7 @@ import {
6
6
  headResponse,
7
7
  methodNotAllowed,
8
8
  withCors
9
- } from "./chunk-EPEEEI3C.js";
9
+ } from "./chunk-E7CPOJN6.js";
10
10
 
11
11
  // src/protocols/rest/list-tools.ts
12
12
  import { objectFromShape } from "@modelcontextprotocol/sdk/server/zod-compat.js";
@@ -13,7 +13,7 @@ function isFileMissing(err) {
13
13
  }
14
14
 
15
15
  // package.json
16
- var version = "0.20.0";
16
+ var version = "0.20.1-rc.0";
17
17
 
18
18
  // src/protocols/rest/list-tools.ts
19
19
  var import_zod_compat = require("@modelcontextprotocol/sdk/server/zod-compat.js");
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  buildMcpListing
4
- } from "../chunk-7JJEPFSO.js";
5
- import "../chunk-EPEEEI3C.js";
4
+ } from "../chunk-YAXKWTJK.js";
5
+ import "../chunk-E7CPOJN6.js";
6
6
  import "../chunk-H37EB22A.js";
7
7
  import "../chunk-6DXGZZA4.js";
8
8
  import {
@@ -10,7 +10,7 @@ import {
10
10
  } from "../chunk-Y3ZFPEQH.js";
11
11
  import {
12
12
  version
13
- } from "../chunk-FTTVLHNN.js";
13
+ } from "../chunk-OMEL6F6I.js";
14
14
 
15
15
  // src/manifest/io.ts
16
16
  import { randomUUID } from "crypto";
@@ -153,6 +153,18 @@ var OAuthConfigurationError = class extends Error {
153
153
  this.name = "OAuthConfigurationError";
154
154
  }
155
155
  };
156
+ var OAuthUpstreamUnreachableError = class extends Error {
157
+ constructor(message) {
158
+ super(message);
159
+ this.name = "OAuthUpstreamUnreachableError";
160
+ }
161
+ };
162
+ var MetadataUnreachableError = class extends Error {
163
+ constructor(message) {
164
+ super(message);
165
+ this.name = "MetadataUnreachableError";
166
+ }
167
+ };
156
168
  var METADATA_FETCH_TIMEOUT_MS = 5e3;
157
169
  function issuerPath(url) {
158
170
  const path = trimTrailingSlash(url.pathname);
@@ -177,21 +189,37 @@ function oauthMetadataUrlsForIssuer(issuer) {
177
189
  return [...pathInsertedOAuthMetadataUrls(url, path), `${normalizedIssuer}/.well-known/openid-configuration`];
178
190
  }
179
191
  async function fetchFirstValidOAuthServerMetadata(metadataUrls, expectedIssuer) {
180
- const errors = [];
181
- for (const url of metadataUrls) {
182
- try {
183
- return await fetchOAuthServerMetadata(url, expectedIssuer);
184
- } catch (err) {
192
+ const controller = new AbortController();
193
+ const attempts = metadataUrls.map(
194
+ (url) => fetchOAuthServerMetadata(url, expectedIssuer, controller.signal).catch((err) => {
185
195
  log.debug("oauth.discovery.attempt_failed", { url, ...describeError(err) });
186
- errors.push(`${url}: ${err instanceof Error ? err.message : String(err)}`);
187
- }
188
- }
189
- log.error("oauth.discovery.exhausted", { expectedIssuer, urlsTried: metadataUrls, errors });
190
- throw new Error(`failed to discover OAuth server metadata (${errors.join("; ")})`);
191
- }
192
- async function fetchOAuthServerMetadata(url, expectedIssuer) {
196
+ throw err instanceof Error ? err : new Error(String(err));
197
+ })
198
+ );
199
+ try {
200
+ const metadata = await Promise.any(attempts);
201
+ controller.abort();
202
+ return metadata;
203
+ } catch (aggregate) {
204
+ const causes = aggregate instanceof AggregateError ? aggregate.errors : [aggregate];
205
+ const unreachable = causes.some((c) => c instanceof MetadataUnreachableError);
206
+ const detail = causes.map((c) => c instanceof Error ? c.message : String(c)).join("; ");
207
+ log.error("oauth.discovery.exhausted", { expectedIssuer, urlsTried: metadataUrls, errors: detail, unreachable });
208
+ const message = `failed to discover OAuth server metadata (${detail})`;
209
+ throw unreachable ? new MetadataUnreachableError(message) : new Error(message);
210
+ }
211
+ }
212
+ async function fetchOAuthServerMetadata(url, expectedIssuer, signal) {
193
213
  log.debug("oauth.discovery.fetch", { url });
194
- const response = await fetch(url, { signal: AbortSignal.timeout(METADATA_FETCH_TIMEOUT_MS), redirect: "manual" });
214
+ let response;
215
+ try {
216
+ response = await fetch(url, {
217
+ signal: AbortSignal.any([signal, AbortSignal.timeout(METADATA_FETCH_TIMEOUT_MS)]),
218
+ redirect: "manual"
219
+ });
220
+ } catch (err) {
221
+ throw new MetadataUnreachableError(err instanceof Error ? err.message : String(err));
222
+ }
195
223
  if (!response.ok) {
196
224
  throw new Error(String(response.status));
197
225
  }
@@ -214,14 +242,21 @@ async function fetchIssuerOAuthServerMetadata(issuer) {
214
242
  try {
215
243
  return await fetchFirstValidOAuthServerMetadata(oauthMetadataUrlsForIssuer(issuer), issuer);
216
244
  } catch (err) {
245
+ const message = `OAuth issuer discovery failed: ${err instanceof Error ? err.message : String(err)}`;
246
+ if (err instanceof MetadataUnreachableError) {
247
+ log.error("oauth.discovery.unreachable", {
248
+ issuer,
249
+ ...describeError(err),
250
+ outcome: "503 authorization server unreachable"
251
+ });
252
+ throw new OAuthUpstreamUnreachableError(message);
253
+ }
217
254
  log.error("oauth.discovery.config_error", {
218
255
  issuer,
219
256
  ...describeError(err),
220
257
  outcome: "500 oauth configuration error"
221
258
  });
222
- throw new OAuthConfigurationError(
223
- `OAuth issuer discovery failed: ${err instanceof Error ? err.message : String(err)}`
224
- );
259
+ throw new OAuthConfigurationError(message);
225
260
  }
226
261
  }
227
262
  function createOAuthDiscoveryResolver(auth) {
@@ -314,8 +349,14 @@ function tokenHeaderFields(token) {
314
349
  }
315
350
  }
316
351
  async function fetchVerificationKeySet(jwksUri) {
352
+ let response;
353
+ try {
354
+ response = await fetch(jwksUri, { signal: AbortSignal.timeout(JWKS_FETCH_TIMEOUT_MS), redirect: "manual" });
355
+ } catch (err) {
356
+ log.error("oauth.jwks.unreachable", { ...describeError(err), outcome: "503 authorization server unreachable" });
357
+ throw new OAuthUpstreamUnreachableError(`JWKS fetch failed: ${err instanceof Error ? err.message : String(err)}`);
358
+ }
317
359
  try {
318
- const response = await fetch(jwksUri, { signal: AbortSignal.timeout(JWKS_FETCH_TIMEOUT_MS), redirect: "manual" });
319
360
  if (!response.ok)
320
361
  throw new Error(`JWKS endpoint returned ${response.status}`);
321
362
  const json = await response.json();
@@ -456,6 +497,12 @@ function oauthConfigurationErrorResponse() {
456
497
  headers: { ...JSON_HEADERS, "Cache-Control": "no-store" }
457
498
  });
458
499
  }
500
+ function oauthUpstreamUnreachableResponse() {
501
+ return new Response(JSON.stringify({ error: "authorization server unreachable" }), {
502
+ status: 503,
503
+ headers: { ...JSON_HEADERS, "Cache-Control": "no-store", "Retry-After": "30" }
504
+ });
505
+ }
459
506
  function parseBearerToken(request) {
460
507
  const header = request.headers.get("Authorization");
461
508
  if (!header)
@@ -520,6 +567,16 @@ function createRequestAuthorizer(mcp, options = {}) {
520
567
  assertRequiredScopes(runtime.auth, auth);
521
568
  return { ok: true, auth };
522
569
  } catch (err) {
570
+ if (err instanceof OAuthUpstreamUnreachableError) {
571
+ log.error("auth.upstream_unreachable", { ...describeError(err), outcome: "503" });
572
+ await recorder?.emit({
573
+ tool: null,
574
+ method: "authorize",
575
+ outcome: "auth_upstream_unreachable",
576
+ durationMs: nowMs() - startedAt
577
+ });
578
+ return { ok: false, response: oauthUpstreamUnreachableResponse() };
579
+ }
523
580
  if (err instanceof OAuthConfigurationError) {
524
581
  log.error("auth.config_error", { ...describeError(err), outcome: "500" });
525
582
  await recorder?.emit({
@@ -1,6 +1,6 @@
1
1
  import { M as McpRuntimeOptions } from '../../authorize-BMeXd_Sh.js';
2
2
  import { d as McpDefinition } from '../../types-CPkhCRxc.js';
3
- import { M as MetricsRecorder } from '../../base-C9rhAHZ0.js';
3
+ import { M as MetricsRecorder } from '../../base-RSqYamjy.js';
4
4
  import 'zod';
5
5
 
6
6
  type McpProtocolHandler = (request: Request, recorder?: MetricsRecorder) => Promise<Response>;
@@ -1,6 +1,6 @@
1
1
  import { M as McpRuntimeOptions } from '../../authorize-BMeXd_Sh.js';
2
2
  import { d as McpDefinition } from '../../types-CPkhCRxc.js';
3
- import { M as MetricsRecorder } from '../../base-C9rhAHZ0.js';
3
+ import { M as MetricsRecorder } from '../../base-RSqYamjy.js';
4
4
  import 'zod';
5
5
 
6
6
  type McpProtocolHandler = (request: Request, recorder?: MetricsRecorder) => Promise<Response>;
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  createMcpProtocolHandler
3
- } from "../../chunk-WW7NRRCJ.js";
3
+ } from "../../chunk-ID3YWZBR.js";
4
4
  import "../../chunk-MA5H6PSF.js";
5
- import "../../chunk-EPEEEI3C.js";
5
+ import "../../chunk-E7CPOJN6.js";
6
6
  import "../../chunk-H37EB22A.js";
7
7
  import "../../chunk-6DXGZZA4.js";
8
- import "../../chunk-FTTVLHNN.js";
8
+ import "../../chunk-OMEL6F6I.js";
9
9
  export {
10
10
  createMcpProtocolHandler
11
11
  };
@@ -148,6 +148,18 @@ var OAuthConfigurationError = class extends Error {
148
148
  this.name = "OAuthConfigurationError";
149
149
  }
150
150
  };
151
+ var OAuthUpstreamUnreachableError = class extends Error {
152
+ constructor(message) {
153
+ super(message);
154
+ this.name = "OAuthUpstreamUnreachableError";
155
+ }
156
+ };
157
+ var MetadataUnreachableError = class extends Error {
158
+ constructor(message) {
159
+ super(message);
160
+ this.name = "MetadataUnreachableError";
161
+ }
162
+ };
151
163
  var METADATA_FETCH_TIMEOUT_MS = 5e3;
152
164
  function issuerPath(url) {
153
165
  const path = trimTrailingSlash(url.pathname);
@@ -172,21 +184,37 @@ function oauthMetadataUrlsForIssuer(issuer) {
172
184
  return [...pathInsertedOAuthMetadataUrls(url, path), `${normalizedIssuer}/.well-known/openid-configuration`];
173
185
  }
174
186
  async function fetchFirstValidOAuthServerMetadata(metadataUrls, expectedIssuer) {
175
- const errors = [];
176
- for (const url of metadataUrls) {
177
- try {
178
- return await fetchOAuthServerMetadata(url, expectedIssuer);
179
- } catch (err) {
187
+ const controller = new AbortController();
188
+ const attempts = metadataUrls.map(
189
+ (url) => fetchOAuthServerMetadata(url, expectedIssuer, controller.signal).catch((err) => {
180
190
  log.debug("oauth.discovery.attempt_failed", { url, ...describeError(err) });
181
- errors.push(`${url}: ${err instanceof Error ? err.message : String(err)}`);
182
- }
191
+ throw err instanceof Error ? err : new Error(String(err));
192
+ })
193
+ );
194
+ try {
195
+ const metadata = await Promise.any(attempts);
196
+ controller.abort();
197
+ return metadata;
198
+ } catch (aggregate) {
199
+ const causes = aggregate instanceof AggregateError ? aggregate.errors : [aggregate];
200
+ const unreachable = causes.some((c) => c instanceof MetadataUnreachableError);
201
+ const detail = causes.map((c) => c instanceof Error ? c.message : String(c)).join("; ");
202
+ log.error("oauth.discovery.exhausted", { expectedIssuer, urlsTried: metadataUrls, errors: detail, unreachable });
203
+ const message = `failed to discover OAuth server metadata (${detail})`;
204
+ throw unreachable ? new MetadataUnreachableError(message) : new Error(message);
183
205
  }
184
- log.error("oauth.discovery.exhausted", { expectedIssuer, urlsTried: metadataUrls, errors });
185
- throw new Error(`failed to discover OAuth server metadata (${errors.join("; ")})`);
186
206
  }
187
- async function fetchOAuthServerMetadata(url, expectedIssuer) {
207
+ async function fetchOAuthServerMetadata(url, expectedIssuer, signal) {
188
208
  log.debug("oauth.discovery.fetch", { url });
189
- const response = await fetch(url, { signal: AbortSignal.timeout(METADATA_FETCH_TIMEOUT_MS), redirect: "manual" });
209
+ let response;
210
+ try {
211
+ response = await fetch(url, {
212
+ signal: AbortSignal.any([signal, AbortSignal.timeout(METADATA_FETCH_TIMEOUT_MS)]),
213
+ redirect: "manual"
214
+ });
215
+ } catch (err) {
216
+ throw new MetadataUnreachableError(err instanceof Error ? err.message : String(err));
217
+ }
190
218
  if (!response.ok) {
191
219
  throw new Error(String(response.status));
192
220
  }
@@ -209,14 +237,21 @@ async function fetchIssuerOAuthServerMetadata(issuer) {
209
237
  try {
210
238
  return await fetchFirstValidOAuthServerMetadata(oauthMetadataUrlsForIssuer(issuer), issuer);
211
239
  } catch (err) {
240
+ const message = `OAuth issuer discovery failed: ${err instanceof Error ? err.message : String(err)}`;
241
+ if (err instanceof MetadataUnreachableError) {
242
+ log.error("oauth.discovery.unreachable", {
243
+ issuer,
244
+ ...describeError(err),
245
+ outcome: "503 authorization server unreachable"
246
+ });
247
+ throw new OAuthUpstreamUnreachableError(message);
248
+ }
212
249
  log.error("oauth.discovery.config_error", {
213
250
  issuer,
214
251
  ...describeError(err),
215
252
  outcome: "500 oauth configuration error"
216
253
  });
217
- throw new OAuthConfigurationError(
218
- `OAuth issuer discovery failed: ${err instanceof Error ? err.message : String(err)}`
219
- );
254
+ throw new OAuthConfigurationError(message);
220
255
  }
221
256
  }
222
257
  function createOAuthDiscoveryResolver(auth) {
@@ -306,8 +341,14 @@ function tokenHeaderFields(token) {
306
341
  }
307
342
  }
308
343
  async function fetchVerificationKeySet(jwksUri) {
344
+ let response;
345
+ try {
346
+ response = await fetch(jwksUri, { signal: AbortSignal.timeout(JWKS_FETCH_TIMEOUT_MS), redirect: "manual" });
347
+ } catch (err) {
348
+ log.error("oauth.jwks.unreachable", { ...describeError(err), outcome: "503 authorization server unreachable" });
349
+ throw new OAuthUpstreamUnreachableError(`JWKS fetch failed: ${err instanceof Error ? err.message : String(err)}`);
350
+ }
309
351
  try {
310
- const response = await fetch(jwksUri, { signal: AbortSignal.timeout(JWKS_FETCH_TIMEOUT_MS), redirect: "manual" });
311
352
  if (!response.ok)
312
353
  throw new Error(`JWKS endpoint returned ${response.status}`);
313
354
  const json = await response.json();
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  createOAuthProtectedResourceMetadataHandler
3
- } from "../chunk-DUNDNXK5.js";
4
- import "../chunk-EPEEEI3C.js";
3
+ } from "../chunk-A2GUTEAK.js";
4
+ import "../chunk-E7CPOJN6.js";
5
5
  import "../chunk-H37EB22A.js";
6
6
  import "../chunk-6DXGZZA4.js";
7
- import "../chunk-FTTVLHNN.js";
7
+ import "../chunk-OMEL6F6I.js";
8
8
  export {
9
9
  createOAuthProtectedResourceMetadataHandler
10
10
  };
@@ -163,6 +163,18 @@ var OAuthConfigurationError = class extends Error {
163
163
  this.name = "OAuthConfigurationError";
164
164
  }
165
165
  };
166
+ var OAuthUpstreamUnreachableError = class extends Error {
167
+ constructor(message) {
168
+ super(message);
169
+ this.name = "OAuthUpstreamUnreachableError";
170
+ }
171
+ };
172
+ var MetadataUnreachableError = class extends Error {
173
+ constructor(message) {
174
+ super(message);
175
+ this.name = "MetadataUnreachableError";
176
+ }
177
+ };
166
178
  var METADATA_FETCH_TIMEOUT_MS = 5e3;
167
179
  function issuerPath(url) {
168
180
  const path = trimTrailingSlash(url.pathname);
@@ -187,21 +199,37 @@ function oauthMetadataUrlsForIssuer(issuer) {
187
199
  return [...pathInsertedOAuthMetadataUrls(url, path), `${normalizedIssuer}/.well-known/openid-configuration`];
188
200
  }
189
201
  async function fetchFirstValidOAuthServerMetadata(metadataUrls, expectedIssuer) {
190
- const errors = [];
191
- for (const url of metadataUrls) {
192
- try {
193
- return await fetchOAuthServerMetadata(url, expectedIssuer);
194
- } catch (err) {
202
+ const controller = new AbortController();
203
+ const attempts = metadataUrls.map(
204
+ (url) => fetchOAuthServerMetadata(url, expectedIssuer, controller.signal).catch((err) => {
195
205
  log.debug("oauth.discovery.attempt_failed", { url, ...describeError(err) });
196
- errors.push(`${url}: ${err instanceof Error ? err.message : String(err)}`);
197
- }
198
- }
199
- log.error("oauth.discovery.exhausted", { expectedIssuer, urlsTried: metadataUrls, errors });
200
- throw new Error(`failed to discover OAuth server metadata (${errors.join("; ")})`);
201
- }
202
- async function fetchOAuthServerMetadata(url, expectedIssuer) {
206
+ throw err instanceof Error ? err : new Error(String(err));
207
+ })
208
+ );
209
+ try {
210
+ const metadata = await Promise.any(attempts);
211
+ controller.abort();
212
+ return metadata;
213
+ } catch (aggregate) {
214
+ const causes = aggregate instanceof AggregateError ? aggregate.errors : [aggregate];
215
+ const unreachable = causes.some((c) => c instanceof MetadataUnreachableError);
216
+ const detail = causes.map((c) => c instanceof Error ? c.message : String(c)).join("; ");
217
+ log.error("oauth.discovery.exhausted", { expectedIssuer, urlsTried: metadataUrls, errors: detail, unreachable });
218
+ const message = `failed to discover OAuth server metadata (${detail})`;
219
+ throw unreachable ? new MetadataUnreachableError(message) : new Error(message);
220
+ }
221
+ }
222
+ async function fetchOAuthServerMetadata(url, expectedIssuer, signal) {
203
223
  log.debug("oauth.discovery.fetch", { url });
204
- const response = await fetch(url, { signal: AbortSignal.timeout(METADATA_FETCH_TIMEOUT_MS), redirect: "manual" });
224
+ let response;
225
+ try {
226
+ response = await fetch(url, {
227
+ signal: AbortSignal.any([signal, AbortSignal.timeout(METADATA_FETCH_TIMEOUT_MS)]),
228
+ redirect: "manual"
229
+ });
230
+ } catch (err) {
231
+ throw new MetadataUnreachableError(err instanceof Error ? err.message : String(err));
232
+ }
205
233
  if (!response.ok) {
206
234
  throw new Error(String(response.status));
207
235
  }
@@ -224,14 +252,21 @@ async function fetchIssuerOAuthServerMetadata(issuer) {
224
252
  try {
225
253
  return await fetchFirstValidOAuthServerMetadata(oauthMetadataUrlsForIssuer(issuer), issuer);
226
254
  } catch (err) {
255
+ const message = `OAuth issuer discovery failed: ${err instanceof Error ? err.message : String(err)}`;
256
+ if (err instanceof MetadataUnreachableError) {
257
+ log.error("oauth.discovery.unreachable", {
258
+ issuer,
259
+ ...describeError(err),
260
+ outcome: "503 authorization server unreachable"
261
+ });
262
+ throw new OAuthUpstreamUnreachableError(message);
263
+ }
227
264
  log.error("oauth.discovery.config_error", {
228
265
  issuer,
229
266
  ...describeError(err),
230
267
  outcome: "500 oauth configuration error"
231
268
  });
232
- throw new OAuthConfigurationError(
233
- `OAuth issuer discovery failed: ${err instanceof Error ? err.message : String(err)}`
234
- );
269
+ throw new OAuthConfigurationError(message);
235
270
  }
236
271
  }
237
272
  function createOAuthDiscoveryResolver(auth) {
@@ -324,8 +359,14 @@ function tokenHeaderFields(token) {
324
359
  }
325
360
  }
326
361
  async function fetchVerificationKeySet(jwksUri) {
362
+ let response;
363
+ try {
364
+ response = await fetch(jwksUri, { signal: AbortSignal.timeout(JWKS_FETCH_TIMEOUT_MS), redirect: "manual" });
365
+ } catch (err) {
366
+ log.error("oauth.jwks.unreachable", { ...describeError(err), outcome: "503 authorization server unreachable" });
367
+ throw new OAuthUpstreamUnreachableError(`JWKS fetch failed: ${err instanceof Error ? err.message : String(err)}`);
368
+ }
327
369
  try {
328
- const response = await fetch(jwksUri, { signal: AbortSignal.timeout(JWKS_FETCH_TIMEOUT_MS), redirect: "manual" });
329
370
  if (!response.ok)
330
371
  throw new Error(`JWKS endpoint returned ${response.status}`);
331
372
  const json = await response.json();
@@ -466,6 +507,12 @@ function oauthConfigurationErrorResponse() {
466
507
  headers: { ...JSON_HEADERS, "Cache-Control": "no-store" }
467
508
  });
468
509
  }
510
+ function oauthUpstreamUnreachableResponse() {
511
+ return new Response(JSON.stringify({ error: "authorization server unreachable" }), {
512
+ status: 503,
513
+ headers: { ...JSON_HEADERS, "Cache-Control": "no-store", "Retry-After": "30" }
514
+ });
515
+ }
469
516
  function parseBearerToken(request) {
470
517
  const header = request.headers.get("Authorization");
471
518
  if (!header)
@@ -538,6 +585,16 @@ function createRequestAuthorizer(mcp, options = {}) {
538
585
  assertRequiredScopes(runtime.auth, auth);
539
586
  return { ok: true, auth };
540
587
  } catch (err) {
588
+ if (err instanceof OAuthUpstreamUnreachableError) {
589
+ log.error("auth.upstream_unreachable", { ...describeError(err), outcome: "503" });
590
+ await recorder?.emit({
591
+ tool: null,
592
+ method: "authorize",
593
+ outcome: "auth_upstream_unreachable",
594
+ durationMs: nowMs() - startedAt
595
+ });
596
+ return { ok: false, response: oauthUpstreamUnreachableResponse() };
597
+ }
541
598
  if (err instanceof OAuthConfigurationError) {
542
599
  log.error("auth.config_error", { ...describeError(err), outcome: "500" });
543
600
  await recorder?.emit({
@@ -1,6 +1,6 @@
1
1
  import { M as McpRuntimeOptions } from '../../authorize-BMeXd_Sh.js';
2
2
  import { d as McpDefinition } from '../../types-CPkhCRxc.js';
3
- import { M as MetricsRecorder } from '../../base-C9rhAHZ0.js';
3
+ import { M as MetricsRecorder } from '../../base-RSqYamjy.js';
4
4
  import 'zod';
5
5
 
6
6
  type RestListToolsHandler = (request: Request, recorder?: MetricsRecorder) => Promise<Response>;
@@ -1,6 +1,6 @@
1
1
  import { M as McpRuntimeOptions } from '../../authorize-BMeXd_Sh.js';
2
2
  import { d as McpDefinition } from '../../types-CPkhCRxc.js';
3
- import { M as MetricsRecorder } from '../../base-C9rhAHZ0.js';
3
+ import { M as MetricsRecorder } from '../../base-RSqYamjy.js';
4
4
  import 'zod';
5
5
 
6
6
  type RestListToolsHandler = (request: Request, recorder?: MetricsRecorder) => Promise<Response>;
@@ -1,14 +1,14 @@
1
1
  import {
2
2
  createInvokeToolHandler
3
- } from "../../chunk-WKIXRW46.js";
3
+ } from "../../chunk-RT2I26ZN.js";
4
4
  import {
5
5
  createListToolsHandler
6
- } from "../../chunk-7JJEPFSO.js";
6
+ } from "../../chunk-YAXKWTJK.js";
7
7
  import "../../chunk-MA5H6PSF.js";
8
- import "../../chunk-EPEEEI3C.js";
8
+ import "../../chunk-E7CPOJN6.js";
9
9
  import "../../chunk-H37EB22A.js";
10
10
  import "../../chunk-6DXGZZA4.js";
11
- import "../../chunk-FTTVLHNN.js";
11
+ import "../../chunk-OMEL6F6I.js";
12
12
  export {
13
13
  createInvokeToolHandler,
14
14
  createListToolsHandler
@@ -174,7 +174,7 @@ function describeError(err) {
174
174
  }
175
175
 
176
176
  // package.json
177
- var version = "0.20.0";
177
+ var version = "0.20.1-rc.0";
178
178
 
179
179
  // src/metrics/otlp.ts
180
180
  var SCOPE_NAME = "@lovable.dev/mcp-js";
@@ -445,6 +445,18 @@ var OAuthConfigurationError = class extends Error {
445
445
  this.name = "OAuthConfigurationError";
446
446
  }
447
447
  };
448
+ var OAuthUpstreamUnreachableError = class extends Error {
449
+ constructor(message) {
450
+ super(message);
451
+ this.name = "OAuthUpstreamUnreachableError";
452
+ }
453
+ };
454
+ var MetadataUnreachableError = class extends Error {
455
+ constructor(message) {
456
+ super(message);
457
+ this.name = "MetadataUnreachableError";
458
+ }
459
+ };
448
460
  var METADATA_FETCH_TIMEOUT_MS = 5e3;
449
461
  function issuerPath(url) {
450
462
  const path = trimTrailingSlash(url.pathname);
@@ -469,21 +481,37 @@ function oauthMetadataUrlsForIssuer(issuer) {
469
481
  return [...pathInsertedOAuthMetadataUrls(url, path), `${normalizedIssuer}/.well-known/openid-configuration`];
470
482
  }
471
483
  async function fetchFirstValidOAuthServerMetadata(metadataUrls, expectedIssuer) {
472
- const errors = [];
473
- for (const url of metadataUrls) {
474
- try {
475
- return await fetchOAuthServerMetadata(url, expectedIssuer);
476
- } catch (err) {
484
+ const controller = new AbortController();
485
+ const attempts = metadataUrls.map(
486
+ (url) => fetchOAuthServerMetadata(url, expectedIssuer, controller.signal).catch((err) => {
477
487
  log.debug("oauth.discovery.attempt_failed", { url, ...describeError(err) });
478
- errors.push(`${url}: ${err instanceof Error ? err.message : String(err)}`);
479
- }
488
+ throw err instanceof Error ? err : new Error(String(err));
489
+ })
490
+ );
491
+ try {
492
+ const metadata = await Promise.any(attempts);
493
+ controller.abort();
494
+ return metadata;
495
+ } catch (aggregate) {
496
+ const causes = aggregate instanceof AggregateError ? aggregate.errors : [aggregate];
497
+ const unreachable = causes.some((c) => c instanceof MetadataUnreachableError);
498
+ const detail = causes.map((c) => c instanceof Error ? c.message : String(c)).join("; ");
499
+ log.error("oauth.discovery.exhausted", { expectedIssuer, urlsTried: metadataUrls, errors: detail, unreachable });
500
+ const message = `failed to discover OAuth server metadata (${detail})`;
501
+ throw unreachable ? new MetadataUnreachableError(message) : new Error(message);
480
502
  }
481
- log.error("oauth.discovery.exhausted", { expectedIssuer, urlsTried: metadataUrls, errors });
482
- throw new Error(`failed to discover OAuth server metadata (${errors.join("; ")})`);
483
503
  }
484
- async function fetchOAuthServerMetadata(url, expectedIssuer) {
504
+ async function fetchOAuthServerMetadata(url, expectedIssuer, signal) {
485
505
  log.debug("oauth.discovery.fetch", { url });
486
- const response = await fetch(url, { signal: AbortSignal.timeout(METADATA_FETCH_TIMEOUT_MS), redirect: "manual" });
506
+ let response;
507
+ try {
508
+ response = await fetch(url, {
509
+ signal: AbortSignal.any([signal, AbortSignal.timeout(METADATA_FETCH_TIMEOUT_MS)]),
510
+ redirect: "manual"
511
+ });
512
+ } catch (err) {
513
+ throw new MetadataUnreachableError(err instanceof Error ? err.message : String(err));
514
+ }
487
515
  if (!response.ok) {
488
516
  throw new Error(String(response.status));
489
517
  }
@@ -506,14 +534,21 @@ async function fetchIssuerOAuthServerMetadata(issuer) {
506
534
  try {
507
535
  return await fetchFirstValidOAuthServerMetadata(oauthMetadataUrlsForIssuer(issuer), issuer);
508
536
  } catch (err) {
537
+ const message = `OAuth issuer discovery failed: ${err instanceof Error ? err.message : String(err)}`;
538
+ if (err instanceof MetadataUnreachableError) {
539
+ log.error("oauth.discovery.unreachable", {
540
+ issuer,
541
+ ...describeError(err),
542
+ outcome: "503 authorization server unreachable"
543
+ });
544
+ throw new OAuthUpstreamUnreachableError(message);
545
+ }
509
546
  log.error("oauth.discovery.config_error", {
510
547
  issuer,
511
548
  ...describeError(err),
512
549
  outcome: "500 oauth configuration error"
513
550
  });
514
- throw new OAuthConfigurationError(
515
- `OAuth issuer discovery failed: ${err instanceof Error ? err.message : String(err)}`
516
- );
551
+ throw new OAuthConfigurationError(message);
517
552
  }
518
553
  }
519
554
  function createOAuthDiscoveryResolver(auth) {
@@ -579,8 +614,14 @@ function tokenHeaderFields(token) {
579
614
  }
580
615
  }
581
616
  async function fetchVerificationKeySet(jwksUri) {
617
+ let response;
618
+ try {
619
+ response = await fetch(jwksUri, { signal: AbortSignal.timeout(JWKS_FETCH_TIMEOUT_MS), redirect: "manual" });
620
+ } catch (err) {
621
+ log.error("oauth.jwks.unreachable", { ...describeError(err), outcome: "503 authorization server unreachable" });
622
+ throw new OAuthUpstreamUnreachableError(`JWKS fetch failed: ${err instanceof Error ? err.message : String(err)}`);
623
+ }
582
624
  try {
583
- const response = await fetch(jwksUri, { signal: AbortSignal.timeout(JWKS_FETCH_TIMEOUT_MS), redirect: "manual" });
584
625
  if (!response.ok)
585
626
  throw new Error(`JWKS endpoint returned ${response.status}`);
586
627
  const json = await response.json();
@@ -721,6 +762,12 @@ function oauthConfigurationErrorResponse() {
721
762
  headers: { ...JSON_HEADERS, "Cache-Control": "no-store" }
722
763
  });
723
764
  }
765
+ function oauthUpstreamUnreachableResponse() {
766
+ return new Response(JSON.stringify({ error: "authorization server unreachable" }), {
767
+ status: 503,
768
+ headers: { ...JSON_HEADERS, "Cache-Control": "no-store", "Retry-After": "30" }
769
+ });
770
+ }
724
771
  function parseBearerToken(request) {
725
772
  const header = request.headers.get("Authorization");
726
773
  if (!header)
@@ -793,6 +840,16 @@ function createRequestAuthorizer(mcp, options = {}) {
793
840
  assertRequiredScopes(runtime.auth, auth);
794
841
  return { ok: true, auth };
795
842
  } catch (err) {
843
+ if (err instanceof OAuthUpstreamUnreachableError) {
844
+ log.error("auth.upstream_unreachable", { ...describeError(err), outcome: "503" });
845
+ await recorder?.emit({
846
+ tool: null,
847
+ method: "authorize",
848
+ outcome: "auth_upstream_unreachable",
849
+ durationMs: nowMs() - startedAt
850
+ });
851
+ return { ok: false, response: oauthUpstreamUnreachableResponse() };
852
+ }
796
853
  if (err instanceof OAuthConfigurationError) {
797
854
  log.error("auth.config_error", { ...describeError(err), outcome: "500" });
798
855
  await recorder?.emit({
@@ -3,21 +3,21 @@ import {
3
3
  } from "../../chunk-UQK5UO6C.js";
4
4
  import {
5
5
  createMcpProtocolHandler
6
- } from "../../chunk-WW7NRRCJ.js";
6
+ } from "../../chunk-ID3YWZBR.js";
7
7
  import {
8
8
  createOAuthProtectedResourceMetadataHandler
9
- } from "../../chunk-DUNDNXK5.js";
9
+ } from "../../chunk-A2GUTEAK.js";
10
10
  import {
11
11
  createInvokeToolHandler
12
- } from "../../chunk-WKIXRW46.js";
12
+ } from "../../chunk-RT2I26ZN.js";
13
13
  import {
14
14
  createListToolsHandler
15
- } from "../../chunk-7JJEPFSO.js";
15
+ } from "../../chunk-YAXKWTJK.js";
16
16
  import "../../chunk-MA5H6PSF.js";
17
17
  import {
18
18
  assertResourcePathShape,
19
19
  createRecorderForRuntime
20
- } from "../../chunk-EPEEEI3C.js";
20
+ } from "../../chunk-E7CPOJN6.js";
21
21
  import {
22
22
  trimTrailingSlash
23
23
  } from "../../chunk-H37EB22A.js";
@@ -28,7 +28,7 @@ import {
28
28
  FUNCTIONS_MOUNT_PREFIX,
29
29
  assertFunctionName
30
30
  } from "../../chunk-XQWJN6DC.js";
31
- import "../../chunk-FTTVLHNN.js";
31
+ import "../../chunk-OMEL6F6I.js";
32
32
 
33
33
  // src/stacks/supabase/handler.ts
34
34
  function deriveResourcePath(options) {
@@ -33,7 +33,7 @@ var import_node_fs = require("fs");
33
33
  var import_node_path = require("path");
34
34
 
35
35
  // package.json
36
- var version = "0.20.0";
36
+ var version = "0.20.1-rc.0";
37
37
 
38
38
  // src/core/fs-errors.ts
39
39
  function isFileMissing(err) {
@@ -7,7 +7,7 @@ import {
7
7
  } from "../../chunk-XQWJN6DC.js";
8
8
  import {
9
9
  version
10
- } from "../../chunk-FTTVLHNN.js";
10
+ } from "../../chunk-OMEL6F6I.js";
11
11
 
12
12
  // src/stacks/supabase/vite.ts
13
13
  import { resolve as resolve2, sep as sep2 } from "path";
@@ -129,7 +129,7 @@ function describeError(err) {
129
129
  }
130
130
 
131
131
  // package.json
132
- var version = "0.20.0";
132
+ var version = "0.20.1-rc.0";
133
133
 
134
134
  // src/metrics/otlp.ts
135
135
  var SCOPE_NAME = "@lovable.dev/mcp-js";
@@ -421,6 +421,18 @@ var OAuthConfigurationError = class extends Error {
421
421
  this.name = "OAuthConfigurationError";
422
422
  }
423
423
  };
424
+ var OAuthUpstreamUnreachableError = class extends Error {
425
+ constructor(message) {
426
+ super(message);
427
+ this.name = "OAuthUpstreamUnreachableError";
428
+ }
429
+ };
430
+ var MetadataUnreachableError = class extends Error {
431
+ constructor(message) {
432
+ super(message);
433
+ this.name = "MetadataUnreachableError";
434
+ }
435
+ };
424
436
  var METADATA_FETCH_TIMEOUT_MS = 5e3;
425
437
  function issuerPath(url) {
426
438
  const path = trimTrailingSlash(url.pathname);
@@ -445,21 +457,37 @@ function oauthMetadataUrlsForIssuer(issuer) {
445
457
  return [...pathInsertedOAuthMetadataUrls(url, path), `${normalizedIssuer}/.well-known/openid-configuration`];
446
458
  }
447
459
  async function fetchFirstValidOAuthServerMetadata(metadataUrls, expectedIssuer) {
448
- const errors = [];
449
- for (const url of metadataUrls) {
450
- try {
451
- return await fetchOAuthServerMetadata(url, expectedIssuer);
452
- } catch (err) {
460
+ const controller = new AbortController();
461
+ const attempts = metadataUrls.map(
462
+ (url) => fetchOAuthServerMetadata(url, expectedIssuer, controller.signal).catch((err) => {
453
463
  log.debug("oauth.discovery.attempt_failed", { url, ...describeError(err) });
454
- errors.push(`${url}: ${err instanceof Error ? err.message : String(err)}`);
455
- }
464
+ throw err instanceof Error ? err : new Error(String(err));
465
+ })
466
+ );
467
+ try {
468
+ const metadata = await Promise.any(attempts);
469
+ controller.abort();
470
+ return metadata;
471
+ } catch (aggregate) {
472
+ const causes = aggregate instanceof AggregateError ? aggregate.errors : [aggregate];
473
+ const unreachable = causes.some((c) => c instanceof MetadataUnreachableError);
474
+ const detail = causes.map((c) => c instanceof Error ? c.message : String(c)).join("; ");
475
+ log.error("oauth.discovery.exhausted", { expectedIssuer, urlsTried: metadataUrls, errors: detail, unreachable });
476
+ const message = `failed to discover OAuth server metadata (${detail})`;
477
+ throw unreachable ? new MetadataUnreachableError(message) : new Error(message);
456
478
  }
457
- log.error("oauth.discovery.exhausted", { expectedIssuer, urlsTried: metadataUrls, errors });
458
- throw new Error(`failed to discover OAuth server metadata (${errors.join("; ")})`);
459
479
  }
460
- async function fetchOAuthServerMetadata(url, expectedIssuer) {
480
+ async function fetchOAuthServerMetadata(url, expectedIssuer, signal) {
461
481
  log.debug("oauth.discovery.fetch", { url });
462
- const response = await fetch(url, { signal: AbortSignal.timeout(METADATA_FETCH_TIMEOUT_MS), redirect: "manual" });
482
+ let response;
483
+ try {
484
+ response = await fetch(url, {
485
+ signal: AbortSignal.any([signal, AbortSignal.timeout(METADATA_FETCH_TIMEOUT_MS)]),
486
+ redirect: "manual"
487
+ });
488
+ } catch (err) {
489
+ throw new MetadataUnreachableError(err instanceof Error ? err.message : String(err));
490
+ }
463
491
  if (!response.ok) {
464
492
  throw new Error(String(response.status));
465
493
  }
@@ -482,14 +510,21 @@ async function fetchIssuerOAuthServerMetadata(issuer) {
482
510
  try {
483
511
  return await fetchFirstValidOAuthServerMetadata(oauthMetadataUrlsForIssuer(issuer), issuer);
484
512
  } catch (err) {
513
+ const message = `OAuth issuer discovery failed: ${err instanceof Error ? err.message : String(err)}`;
514
+ if (err instanceof MetadataUnreachableError) {
515
+ log.error("oauth.discovery.unreachable", {
516
+ issuer,
517
+ ...describeError(err),
518
+ outcome: "503 authorization server unreachable"
519
+ });
520
+ throw new OAuthUpstreamUnreachableError(message);
521
+ }
485
522
  log.error("oauth.discovery.config_error", {
486
523
  issuer,
487
524
  ...describeError(err),
488
525
  outcome: "500 oauth configuration error"
489
526
  });
490
- throw new OAuthConfigurationError(
491
- `OAuth issuer discovery failed: ${err instanceof Error ? err.message : String(err)}`
492
- );
527
+ throw new OAuthConfigurationError(message);
493
528
  }
494
529
  }
495
530
  function createOAuthDiscoveryResolver(auth) {
@@ -582,8 +617,14 @@ function tokenHeaderFields(token) {
582
617
  }
583
618
  }
584
619
  async function fetchVerificationKeySet(jwksUri) {
620
+ let response;
621
+ try {
622
+ response = await fetch(jwksUri, { signal: AbortSignal.timeout(JWKS_FETCH_TIMEOUT_MS), redirect: "manual" });
623
+ } catch (err) {
624
+ log.error("oauth.jwks.unreachable", { ...describeError(err), outcome: "503 authorization server unreachable" });
625
+ throw new OAuthUpstreamUnreachableError(`JWKS fetch failed: ${err instanceof Error ? err.message : String(err)}`);
626
+ }
585
627
  try {
586
- const response = await fetch(jwksUri, { signal: AbortSignal.timeout(JWKS_FETCH_TIMEOUT_MS), redirect: "manual" });
587
628
  if (!response.ok)
588
629
  throw new Error(`JWKS endpoint returned ${response.status}`);
589
630
  const json = await response.json();
@@ -724,6 +765,12 @@ function oauthConfigurationErrorResponse() {
724
765
  headers: { ...JSON_HEADERS, "Cache-Control": "no-store" }
725
766
  });
726
767
  }
768
+ function oauthUpstreamUnreachableResponse() {
769
+ return new Response(JSON.stringify({ error: "authorization server unreachable" }), {
770
+ status: 503,
771
+ headers: { ...JSON_HEADERS, "Cache-Control": "no-store", "Retry-After": "30" }
772
+ });
773
+ }
727
774
  function parseBearerToken(request) {
728
775
  const header = request.headers.get("Authorization");
729
776
  if (!header)
@@ -796,6 +843,16 @@ function createRequestAuthorizer(mcp, options = {}) {
796
843
  assertRequiredScopes(runtime.auth, auth);
797
844
  return { ok: true, auth };
798
845
  } catch (err) {
846
+ if (err instanceof OAuthUpstreamUnreachableError) {
847
+ log.error("auth.upstream_unreachable", { ...describeError(err), outcome: "503" });
848
+ await recorder?.emit({
849
+ tool: null,
850
+ method: "authorize",
851
+ outcome: "auth_upstream_unreachable",
852
+ durationMs: nowMs() - startedAt
853
+ });
854
+ return { ok: false, response: oauthUpstreamUnreachableResponse() };
855
+ }
799
856
  if (err instanceof OAuthConfigurationError) {
800
857
  log.error("auth.config_error", { ...describeError(err), outcome: "500" });
801
858
  await recorder?.emit({
@@ -3,23 +3,23 @@ import {
3
3
  } from "../../chunk-UQK5UO6C.js";
4
4
  import {
5
5
  createMcpProtocolHandler
6
- } from "../../chunk-WW7NRRCJ.js";
6
+ } from "../../chunk-ID3YWZBR.js";
7
7
  import {
8
8
  createOAuthProtectedResourceMetadataHandler
9
- } from "../../chunk-DUNDNXK5.js";
9
+ } from "../../chunk-A2GUTEAK.js";
10
10
  import {
11
11
  createInvokeToolHandler
12
- } from "../../chunk-WKIXRW46.js";
12
+ } from "../../chunk-RT2I26ZN.js";
13
13
  import {
14
14
  createListToolsHandler
15
- } from "../../chunk-7JJEPFSO.js";
15
+ } from "../../chunk-YAXKWTJK.js";
16
16
  import "../../chunk-MA5H6PSF.js";
17
17
  import {
18
18
  createRecorderForRuntime
19
- } from "../../chunk-EPEEEI3C.js";
19
+ } from "../../chunk-E7CPOJN6.js";
20
20
  import "../../chunk-H37EB22A.js";
21
21
  import "../../chunk-6DXGZZA4.js";
22
- import "../../chunk-FTTVLHNN.js";
22
+ import "../../chunk-OMEL6F6I.js";
23
23
 
24
24
  // src/stacks/tanstack/handlers.ts
25
25
  var STACK = "tanstack";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lovable.dev/mcp-js",
3
- "version": "0.20.0",
3
+ "version": "0.20.1-rc.0",
4
4
  "description": "Author MCP servers for Lovable apps. Declare tools with defineTool, register them in defineMcp, and a framework adapter (TanStack or Supabase Edge Functions) emits the route(s) at build time.",
5
5
  "type": "module",
6
6
  "repository": {