tiny-http-mcp-server 0.1.20 → 0.1.22

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.
@@ -4102,47 +4102,137 @@ function getOwnEntry4(record2, key2) {
4102
4102
 
4103
4103
  // ../mcp-oauth/dist/client/loopback-authorization.js
4104
4104
  async function createLoopbackAuthorizationSession(options = {}) {
4105
- const callbackPath = options.callbackPath ?? "/callback";
4105
+ options.signal?.throwIfAborted();
4106
+ const timeoutMs = options.timeoutMs ?? 12e4;
4107
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 2147483647)
4108
+ throw new Error("OAuth authorization timeoutMs must be a positive supported timer interval");
4109
+ const target = loopbackTarget(options);
4106
4110
  const server = options.createServer ? options.createServer() : http.createServer();
4107
- const port = await startServer(server);
4108
- const redirectUri = `http://127.0.0.1:${port}${callbackPath}`;
4111
+ const controller = new AbortController();
4112
+ let closed = false;
4113
+ let used = false;
4114
+ const callerAbort = () => controller.abort(options.signal?.reason);
4115
+ const teardown = () => {
4116
+ if (closed)
4117
+ return;
4118
+ closed = true;
4119
+ clearTimeout(timer);
4120
+ options.signal?.removeEventListener("abort", callerAbort);
4121
+ server.closeAllConnections?.();
4122
+ server.close();
4123
+ };
4124
+ const timer = setTimeout(() => controller.abort(new Error("OAuth authorization timed out")), timeoutMs);
4125
+ timer.unref?.();
4126
+ controller.signal.addEventListener("abort", teardown, { once: true });
4127
+ options.signal?.addEventListener("abort", callerAbort, { once: true });
4128
+ if (options.signal?.aborted)
4129
+ callerAbort();
4130
+ let port;
4131
+ try {
4132
+ port = await startServer(server, target.port, target.host, controller.signal);
4133
+ } catch (error) {
4134
+ controller.abort(error);
4135
+ throw error;
4136
+ }
4137
+ const redirectUri = options.redirectUri ?? `http://127.0.0.1:${port}${target.callbackPath}`;
4109
4138
  return {
4110
4139
  redirectUri,
4111
4140
  async waitForCode(authorizationUrl) {
4112
- return waitForAuthorizationCode(server, authorizationUrl, options, callbackPath);
4141
+ controller.signal.throwIfAborted();
4142
+ if (used)
4143
+ throw new Error("OAuth authorization session has already been used");
4144
+ used = true;
4145
+ try {
4146
+ return await waitForAuthorizationCode(server, authorizationUrl, options, target.callbackPath, controller.signal);
4147
+ } finally {
4148
+ clearTimeout(timer);
4149
+ }
4113
4150
  },
4114
4151
  close() {
4115
- server.closeAllConnections?.();
4116
- server.close();
4152
+ controller.abort(new Error("OAuth authorization session closed"));
4117
4153
  }
4118
4154
  };
4119
4155
  }
4120
- async function startServer(server) {
4156
+ function loopbackTarget(options) {
4157
+ if (options.redirectUri !== void 0) {
4158
+ let url;
4159
+ try {
4160
+ url = new URL(options.redirectUri);
4161
+ } catch (cause) {
4162
+ throw new Error("Invalid OAuth loopback redirect URI", { cause });
4163
+ }
4164
+ const forbiddenQuery = ["code", "state", "error", "error_description", "iss"];
4165
+ if (url.protocol !== "http:" || !["localhost", "127.0.0.1", "[::1]"].includes(url.hostname) || url.username || url.password || url.hash || url.port === "0" || forbiddenQuery.some((name) => url.searchParams.has(name)) || [...options.redirectUri].some((char) => char.codePointAt(0) <= 32) || options.callbackPath !== void 0 && options.callbackPath !== url.pathname)
4166
+ throw new Error("Invalid OAuth loopback redirect URI");
4167
+ return { port: url.port ? Number(url.port) : 80, host: url.hostname === "[::1]" ? "::1" : url.hostname, callbackPath: url.pathname };
4168
+ }
4169
+ const callbackPath = options.callbackPath ?? "/callback";
4170
+ const parsed = new URL(callbackPath, "http://127.0.0.1");
4171
+ if (!callbackPath.startsWith("/") || parsed.origin !== "http://127.0.0.1" || parsed.pathname !== callbackPath || parsed.search || parsed.hash)
4172
+ throw new Error("Invalid OAuth loopback callback path");
4173
+ return { port: 0, host: "127.0.0.1", callbackPath };
4174
+ }
4175
+ async function startServer(server, port, host, signal) {
4176
+ signal.throwIfAborted();
4121
4177
  return new Promise((resolve, reject) => {
4122
- const handleError = (error) => {
4178
+ const cleanup = () => {
4123
4179
  server.off("error", handleError);
4180
+ signal.removeEventListener("abort", aborted);
4181
+ };
4182
+ const handleError = (error) => {
4183
+ cleanup();
4124
4184
  reject(error);
4125
4185
  };
4186
+ const aborted = () => {
4187
+ cleanup();
4188
+ reject(signal.reason);
4189
+ };
4126
4190
  server.once("error", handleError);
4127
- server.listen(0, "127.0.0.1", () => {
4128
- server.off("error", handleError);
4129
- const address = server.address();
4130
- resolve(address.port);
4131
- });
4191
+ signal.addEventListener("abort", aborted, { once: true });
4192
+ try {
4193
+ server.listen(port, host, () => {
4194
+ cleanup();
4195
+ if (signal.aborted) {
4196
+ server.close();
4197
+ reject(signal.reason);
4198
+ return;
4199
+ }
4200
+ const address = server.address();
4201
+ if (address === null || typeof address === "string") {
4202
+ reject(new Error("OAuth listener has no TCP address"));
4203
+ return;
4204
+ }
4205
+ resolve(address.port);
4206
+ });
4207
+ } catch (error) {
4208
+ cleanup();
4209
+ reject(error);
4210
+ }
4132
4211
  });
4133
4212
  }
4134
- function waitForAuthorizationCode(server, authorizationUrl, options, callbackPath) {
4213
+ function waitForAuthorizationCode(server, authorizationUrl, options, callbackPath, signal) {
4214
+ signal.throwIfAborted();
4135
4215
  const expectedAuthorization = readExpectedAuthorizationCallback(authorizationUrl);
4136
4216
  return new Promise((resolve, reject) => {
4137
4217
  let settled = false;
4138
4218
  const settle = (fn) => {
4139
- if (!settled) {
4140
- settled = true;
4141
- fn();
4142
- }
4219
+ if (settled)
4220
+ return;
4221
+ settled = true;
4222
+ server.off("request", request);
4223
+ signal.removeEventListener("abort", aborted);
4224
+ fn();
4143
4225
  };
4144
- server.on("request", (req, res) => {
4145
- const url = new URL(req.url ?? "/", "http://127.0.0.1");
4226
+ const aborted = () => settle(() => reject(signal.reason));
4227
+ const request = (req, res) => {
4228
+ let url;
4229
+ try {
4230
+ url = new URL(req.url ?? "/", "http://127.0.0.1");
4231
+ } catch {
4232
+ res.writeHead(400);
4233
+ res.end("Invalid callback URL");
4234
+ return;
4235
+ }
4146
4236
  if (url.pathname !== callbackPath) {
4147
4237
  res.writeHead(404);
4148
4238
  res.end("Not found");
@@ -4157,21 +4247,8 @@ function waitForAuthorizationCode(server, authorizationUrl, options, callbackPat
4157
4247
  };
4158
4248
  try {
4159
4249
  validateAuthorizationCallbackBinding(callbackParameters, expectedAuthorization);
4160
- } catch (error) {
4161
- res.writeHead(400);
4162
- res.end(error instanceof Error ? error.message : "Invalid OAuth callback");
4163
- settle(() => reject(error instanceof Error ? error : new Error(String(error))));
4164
- return;
4165
- }
4166
- const authorizationError = callbackParameters.error;
4167
- if (authorizationError !== null) {
4168
- const description = callbackParameters.errorDescription ?? authorizationError;
4169
- res.writeHead(400);
4170
- res.end(`Authorization failed: ${description}`);
4171
- settle(() => reject(createAuthorizationError(authorizationError, description)));
4172
- return;
4173
- }
4174
- try {
4250
+ if (callbackParameters.error !== null)
4251
+ throw createAuthorizationError(callbackParameters.error, callbackParameters.errorDescription ?? callbackParameters.error);
4175
4252
  const code = validateAuthorizationCallbackParameters(callbackParameters, expectedAuthorization);
4176
4253
  res.writeHead(200, { "Content-Type": "text/html" });
4177
4254
  res.end(buildSuccessPage(options.landingPage));
@@ -4179,35 +4256,27 @@ function waitForAuthorizationCode(server, authorizationUrl, options, callbackPat
4179
4256
  } catch (error) {
4180
4257
  res.writeHead(400);
4181
4258
  res.end(error instanceof Error ? error.message : "Invalid OAuth callback");
4182
- settle(() => reject(error instanceof Error ? error : new Error(String(error))));
4259
+ settle(() => reject(error));
4183
4260
  }
4184
- });
4261
+ };
4262
+ server.on("request", request);
4263
+ signal.addEventListener("abort", aborted, { once: true });
4185
4264
  if (options.readLine !== void 0) {
4186
- options.readLine().then((input) => {
4187
- const callbackParameters = extractCallbackParametersFromInput(input);
4188
- if (callbackParameters === null) {
4189
- settle(() => reject(new Error("OAuth callback missing authorization code")));
4265
+ void Promise.resolve().then(() => settled ? void 0 : options.readLine()).then((input) => {
4266
+ if (settled)
4190
4267
  return;
4191
- }
4192
- try {
4193
- validateAuthorizationCallbackBinding(callbackParameters, expectedAuthorization);
4194
- if (callbackParameters.error !== null) {
4195
- const description = callbackParameters.errorDescription ?? callbackParameters.error;
4196
- throw createAuthorizationError(callbackParameters.error, description);
4197
- }
4198
- const code = validateAuthorizationCallbackParameters(callbackParameters, expectedAuthorization);
4199
- settle(() => resolve(code));
4200
- } catch (error) {
4201
- settle(() => reject(error instanceof Error ? error : new Error(String(error))));
4202
- }
4203
- }).catch((error) => {
4204
- settle(() => reject(error instanceof Error ? error : new Error(String(error))));
4205
- });
4268
+ const callbackParameters = extractCallbackParametersFromInput(input);
4269
+ if (callbackParameters === null)
4270
+ throw new Error("OAuth callback missing authorization code");
4271
+ validateAuthorizationCallbackBinding(callbackParameters, expectedAuthorization);
4272
+ if (callbackParameters.error !== null)
4273
+ throw createAuthorizationError(callbackParameters.error, callbackParameters.errorDescription ?? callbackParameters.error);
4274
+ const code = validateAuthorizationCallbackParameters(callbackParameters, expectedAuthorization);
4275
+ settle(() => resolve(code));
4276
+ }).catch((error) => settle(() => reject(error)));
4206
4277
  }
4207
4278
  if (options.openBrowser !== void 0) {
4208
- options.openBrowser(authorizationUrl).catch((error) => {
4209
- settle(() => reject(error));
4210
- });
4279
+ void Promise.resolve().then(() => settled ? void 0 : options.openBrowser(authorizationUrl)).catch((error) => settle(() => reject(error)));
4211
4280
  }
4212
4281
  });
4213
4282
  }
@@ -4387,6 +4456,7 @@ async function exchangeAuthorizationCode(input) {
4387
4456
  resource
4388
4457
  },
4389
4458
  fetch: input.fetch,
4459
+ signal: input.signal,
4390
4460
  now: input.now
4391
4461
  });
4392
4462
  }
@@ -4402,6 +4472,7 @@ async function refreshAccessToken(input) {
4402
4472
  resource
4403
4473
  },
4404
4474
  fetch: input.fetch,
4475
+ signal: input.signal,
4405
4476
  now: input.now
4406
4477
  });
4407
4478
  }
@@ -4413,7 +4484,9 @@ async function requestTokens(input) {
4413
4484
  if (input.clientSecret !== void 0) {
4414
4485
  body.set("client_secret", input.clientSecret);
4415
4486
  }
4416
- const signal = AbortSignal.timeout(3e4);
4487
+ input.signal?.throwIfAborted();
4488
+ const deadline = AbortSignal.timeout(3e4);
4489
+ const signal = input.signal === void 0 ? deadline : AbortSignal.any([input.signal, deadline]);
4417
4490
  const response = await fetchMcpResponse(input.fetch, input.tokenEndpoint, {
4418
4491
  method: "POST",
4419
4492
  headers: {
@@ -4525,7 +4598,7 @@ function createDefaultOAuthClientProvider(options) {
4525
4598
  async authorizeRequest(input) {
4526
4599
  assertNoAccessTokenInUrl(input.requestUrl, "Protected resource request URL");
4527
4600
  const requestUrl = canonicalizeResourceIndicator(input.requestUrl);
4528
- const session = await ensureAuthorizedSession(requestUrl, void 0, input.fetch, false);
4601
+ const session = await ensureAuthorizedSession(requestUrl, void 0, input.fetch, false, false, input.signal);
4529
4602
  const accessToken = session?.tokens?.accessToken;
4530
4603
  if (session === null || accessToken === void 0 || session.tokens === void 0 || isExpired(session.tokens, now)) {
4531
4604
  return;
@@ -4543,12 +4616,13 @@ function createDefaultOAuthClientProvider(options) {
4543
4616
  const session = await ensureAuthorizedSession(resource, {
4544
4617
  ...input.discovery,
4545
4618
  resource
4546
- }, input.fetch, true, forceRefresh);
4619
+ }, input.fetch, true, forceRefresh, input.signal);
4547
4620
  if (session?.tokens?.accessToken === void 0) {
4548
4621
  return { action: "fail" };
4549
4622
  }
4550
4623
  return { action: "retry" };
4551
4624
  } catch (error) {
4625
+ input.signal?.throwIfAborted();
4552
4626
  return {
4553
4627
  action: "fail",
4554
4628
  error: error instanceof Error ? error : new Error(String(error))
@@ -4556,9 +4630,11 @@ function createDefaultOAuthClientProvider(options) {
4556
4630
  }
4557
4631
  }
4558
4632
  };
4559
- async function ensureAuthorizedSession(resource, discovery, fetch2, allowInteractive, forceRefresh = false) {
4633
+ async function ensureAuthorizedSession(resource, discovery, fetch2, allowInteractive, forceRefresh = false, signal) {
4634
+ signal?.throwIfAborted();
4560
4635
  const canonicalResource = canonicalizeResourceIndicator(resource);
4561
4636
  let session = await loadSession(canonicalResource);
4637
+ signal?.throwIfAborted();
4562
4638
  if (discovery !== void 0 && getOwnString2(discovery.authorizationServerMetadata, "issuer") !== discovery.authorizationServer) {
4563
4639
  throw new Error("OAuth discovery authorization-server issuer mismatch");
4564
4640
  }
@@ -4571,7 +4647,7 @@ function createDefaultOAuthClientProvider(options) {
4571
4647
  return session;
4572
4648
  }
4573
4649
  if (session?.tokens?.refreshToken !== void 0 && sessionDiscovery !== void 0 && (forceRefresh || isExpired(session.tokens, now))) {
4574
- session = await refreshSession(canonicalResource, session, sessionDiscovery, fetch2);
4650
+ session = await refreshSession(canonicalResource, session, sessionDiscovery, fetch2, signal);
4575
4651
  if (session?.tokens !== void 0 && !isExpired(session.tokens, now)) {
4576
4652
  return session;
4577
4653
  }
@@ -4583,9 +4659,12 @@ function createDefaultOAuthClientProvider(options) {
4583
4659
  if (!allowInteractive || sessionDiscovery === void 0) {
4584
4660
  return session;
4585
4661
  }
4586
- return authorizeSession(canonicalResource, session, sessionDiscovery, fetch2);
4662
+ if (options.allowInteractive === false)
4663
+ throw new Error("OAuth interactive authorization is disabled");
4664
+ return authorizeSession(canonicalResource, session, sessionDiscovery, fetch2, signal);
4587
4665
  }
4588
- async function refreshSession(resource, session, discovery, fetch2) {
4666
+ async function refreshSession(resource, session, discovery, fetch2, signal) {
4667
+ signal?.throwIfAborted();
4589
4668
  assertSecureOAuthFlowEndpoints(discovery.authorizationServerMetadata);
4590
4669
  const inFlight = refreshPromises.get(resource);
4591
4670
  if (inFlight !== void 0) {
@@ -4607,10 +4686,12 @@ function createDefaultOAuthClientProvider(options) {
4607
4686
  refreshToken: session.tokens.refreshToken,
4608
4687
  resource,
4609
4688
  fetch: fetch2,
4689
+ signal,
4610
4690
  now
4611
4691
  });
4612
4692
  break;
4613
4693
  } catch (error) {
4694
+ signal?.throwIfAborted();
4614
4695
  if (error instanceof OAuthError && error.error === "invalid_grant") {
4615
4696
  const clearedSession = clearSessionTokens(session);
4616
4697
  await saveSession(resource, clearedSession);
@@ -4645,7 +4726,8 @@ function createDefaultOAuthClientProvider(options) {
4645
4726
  refreshPromises.set(resource, promise);
4646
4727
  return promise;
4647
4728
  }
4648
- async function authorizeSession(resource, existingSession, discovery, fetch2) {
4729
+ async function authorizeSession(resource, existingSession, discovery, fetch2, signal) {
4730
+ signal?.throwIfAborted();
4649
4731
  const inFlight = authorizationPromises.get(resource);
4650
4732
  if (inFlight !== void 0) {
4651
4733
  return inFlight;
@@ -4661,11 +4743,14 @@ function createDefaultOAuthClientProvider(options) {
4661
4743
  openBrowser: options.browser.openBrowser,
4662
4744
  readLine: options.browser.readLine,
4663
4745
  createServer: options.browser.createServer,
4664
- landingPage: options.browser.landingPage
4746
+ landingPage: options.browser.landingPage,
4747
+ redirectUri: options.browser.redirectUri,
4748
+ signal: options.browser.signal === void 0 ? signal : signal === void 0 ? options.browser.signal : AbortSignal.any([signal, options.browser.signal]),
4749
+ timeoutMs: options.browser.timeoutMs
4665
4750
  });
4666
4751
  let resolvedClient = null;
4667
4752
  try {
4668
- resolvedClient = await resolveClient(currentSession, discovery, loopback.redirectUri, fetch2);
4753
+ resolvedClient = await resolveClient(currentSession, discovery, loopback.redirectUri, fetch2, signal);
4669
4754
  const sessionWithoutTokens = {
4670
4755
  resource,
4671
4756
  authorizationServer: discovery.authorizationServer,
@@ -4693,6 +4778,7 @@ function createDefaultOAuthClientProvider(options) {
4693
4778
  redirectUri: loopback.redirectUri,
4694
4779
  resource,
4695
4780
  fetch: fetch2,
4781
+ signal,
4696
4782
  now
4697
4783
  });
4698
4784
  const session = {
@@ -4702,6 +4788,7 @@ function createDefaultOAuthClientProvider(options) {
4702
4788
  await saveSession(resource, session);
4703
4789
  return session;
4704
4790
  } catch (error) {
4791
+ signal?.throwIfAborted();
4705
4792
  if (shouldReRegisterStoredDynamicClient(error, resolvedClient, reRegistrationAttempted)) {
4706
4793
  reRegistrationAttempted = true;
4707
4794
  await clearRegisteredClient(discovery.authorizationServer);
@@ -4727,7 +4814,8 @@ function createDefaultOAuthClientProvider(options) {
4727
4814
  authorizationPromises.set(resource, finalPromise);
4728
4815
  return finalPromise;
4729
4816
  }
4730
- async function resolveClient(existingSession, discovery, redirectUri, fetch2) {
4817
+ async function resolveClient(existingSession, discovery, redirectUri, fetch2, parentSignal) {
4818
+ parentSignal?.throwIfAborted();
4731
4819
  const configuredClient = normalizeConfiguredClient(options.client);
4732
4820
  if (options.client.mode === "static") {
4733
4821
  if (configuredClient === null) {
@@ -4777,7 +4865,8 @@ function createDefaultOAuthClientProvider(options) {
4777
4865
  }
4778
4866
  }
4779
4867
  const registrationBody = buildClientRegistrationBody(getClientMetadata(options.client), redirectUri);
4780
- const signal = AbortSignal.timeout(3e4);
4868
+ const deadline = AbortSignal.timeout(3e4);
4869
+ const signal = parentSignal === void 0 ? deadline : AbortSignal.any([parentSignal, deadline]);
4781
4870
  const response = await fetchMcpResponse(fetch2, registrationEndpoint, {
4782
4871
  method: "POST",
4783
4872
  headers: {
@@ -5340,8 +5429,10 @@ async function readJsonResponse(response, label, signal) {
5340
5429
  throw new Error(`${label} response must be valid JSON`);
5341
5430
  }
5342
5431
  }
5343
- async function fetchMetadata(fetch2, location, label) {
5344
- const signal = AbortSignal.timeout(1e4);
5432
+ async function fetchMetadata(fetch2, location, label, parentSignal) {
5433
+ parentSignal?.throwIfAborted();
5434
+ const deadline = AbortSignal.timeout(1e4);
5435
+ const signal = parentSignal === void 0 ? deadline : AbortSignal.any([deadline, parentSignal]);
5345
5436
  const response = await fetchMcpResponse(fetch2, location, {
5346
5437
  method: "GET",
5347
5438
  headers: { Accept: "application/json" },
@@ -5443,7 +5534,7 @@ var OAuthMetadataDiscovery = class {
5443
5534
  this.fetchImpl = fetch2;
5444
5535
  this.cache = cache;
5445
5536
  }
5446
- async discoverProtectedResource(resource, resourceMetadataUrl) {
5537
+ async discoverProtectedResource(resource, resourceMetadataUrl, signal) {
5447
5538
  const locations = /* @__PURE__ */ new Set([resolveProtectedResourceMetadataUrl(resource, resourceMetadataUrl)]);
5448
5539
  if (resourceMetadataUrl === void 0) {
5449
5540
  locations.add(new URL("/.well-known/oauth-protected-resource", resource).toString());
@@ -5452,17 +5543,19 @@ var OAuthMetadataDiscovery = class {
5452
5543
  for (const location of locations) {
5453
5544
  try {
5454
5545
  const metadata = validateProtectedResourceMetadata(
5455
- await fetchMetadata(this.fetchImpl, location, "Protected resource metadata"),
5546
+ await fetchMetadata(this.fetchImpl, location, "Protected resource metadata", signal),
5456
5547
  resource
5457
5548
  );
5458
5549
  return { location, metadata };
5459
5550
  } catch (error) {
5551
+ signal?.throwIfAborted();
5460
5552
  lastError = error;
5461
5553
  }
5462
5554
  }
5463
5555
  throw lastError;
5464
5556
  }
5465
- async discover(resourceUrl, { resourceMetadataUrl } = {}) {
5557
+ async discover(resourceUrl, { resourceMetadataUrl, signal } = {}) {
5558
+ signal?.throwIfAborted();
5466
5559
  const cacheKey = canonicalizeResourceIndicator(resourceUrl);
5467
5560
  resolveProtectedResourceMetadataUrl(cacheKey, resourceMetadataUrl);
5468
5561
  const memoryCachedResult = this.memoryCache.get(cacheKey);
@@ -5470,6 +5563,7 @@ var OAuthMetadataDiscovery = class {
5470
5563
  return structuredClone(memoryCachedResult);
5471
5564
  }
5472
5565
  const sharedCachedResult = await this.cache?.get(cacheKey);
5566
+ signal?.throwIfAborted();
5473
5567
  if (sharedCachedResult !== null && sharedCachedResult !== void 0 && resourceMetadataUrl === void 0) {
5474
5568
  try {
5475
5569
  const result = validateCachedDiscovery(sharedCachedResult, cacheKey);
@@ -5479,7 +5573,7 @@ var OAuthMetadataDiscovery = class {
5479
5573
  await this.cache?.delete?.(cacheKey);
5480
5574
  }
5481
5575
  }
5482
- const { location: resourceMetadataLocation, metadata: resourceMetadata } = await this.discoverProtectedResource(cacheKey, resourceMetadataUrl);
5576
+ const { location: resourceMetadataLocation, metadata: resourceMetadata } = await this.discoverProtectedResource(cacheKey, resourceMetadataUrl, signal);
5483
5577
  const authorizationServerErrors = [];
5484
5578
  for (const authorizationServer of resourceMetadata.authorization_servers) {
5485
5579
  const normalizedAuthorizationServer = validateAuthorizationServerIssuer(authorizationServer);
@@ -5487,7 +5581,7 @@ var OAuthMetadataDiscovery = class {
5487
5581
  for (const authorizationServerMetadataUrl of metadataLocations) {
5488
5582
  try {
5489
5583
  const authorizationServerMetadata = validateAuthorizationServerMetadata(
5490
- await fetchMetadata(this.fetchImpl, authorizationServerMetadataUrl, "Authorization server metadata"),
5584
+ await fetchMetadata(this.fetchImpl, authorizationServerMetadataUrl, "Authorization server metadata", signal),
5491
5585
  normalizedAuthorizationServer
5492
5586
  );
5493
5587
  const result = {
@@ -5502,6 +5596,7 @@ var OAuthMetadataDiscovery = class {
5502
5596
  await this.cache?.set(cacheKey, structuredClone(result));
5503
5597
  return result;
5504
5598
  } catch (error) {
5599
+ signal?.throwIfAborted();
5505
5600
  authorizationServerErrors.push(
5506
5601
  `${authorizationServerMetadataUrl}: ${error instanceof Error ? error.message : String(error)}`
5507
5602
  );
@@ -6505,6 +6600,16 @@ async function createTestPair(server, createClient) {
6505
6600
  };
6506
6601
  return { client, cleanup };
6507
6602
  }
6603
+ var HttpTransportError = class extends Error {
6604
+ constructor(message, status, method) {
6605
+ super(message);
6606
+ this.status = status;
6607
+ this.method = method;
6608
+ this.name = "HttpTransportError";
6609
+ }
6610
+ status;
6611
+ method;
6612
+ };
6508
6613
  function defaultStdioSpawn(command, args, options) {
6509
6614
  return spawn2(command, args, options);
6510
6615
  }
@@ -6632,6 +6737,7 @@ var HttpTransport = class {
6632
6737
  oauthProvider;
6633
6738
  oauthMetadataDiscovery;
6634
6739
  inFlightFetchAbortControllers = /* @__PURE__ */ new Set();
6740
+ inFlightOAuthAbortControllers = /* @__PURE__ */ new Set();
6635
6741
  openResponseReaders = /* @__PURE__ */ new Set();
6636
6742
  modernRequests = /* @__PURE__ */ new Map();
6637
6743
  modernMode = false;
@@ -6701,7 +6807,7 @@ var HttpTransport = class {
6701
6807
  this.rejectLegacyEndpoint = void 0;
6702
6808
  this.resolveLegacyEndpoint = void 0;
6703
6809
  this.toolParameterHeaders.clear();
6704
- this.abortInFlightFetches();
6810
+ this.abortInFlightFetches(reason);
6705
6811
  this.cancelOpenResponseReaders();
6706
6812
  if (!this.writeStream.destroyed && !this.writeStream.writableEnded) {
6707
6813
  this.writeStream.end();
@@ -6737,12 +6843,14 @@ var HttpTransport = class {
6737
6843
  this.resolveClosed = void 0;
6738
6844
  resolveClosed?.({ reason: closeReason });
6739
6845
  }
6740
- abortInFlightFetches() {
6741
- for (const controller of this.modernRequests.values()) controller.abort();
6846
+ abortInFlightFetches(reason) {
6847
+ for (const controller of this.modernRequests.values()) controller.abort(reason);
6742
6848
  this.modernRequests.clear();
6743
6849
  for (const abortController of this.inFlightFetchAbortControllers) {
6744
- abortController.abort();
6850
+ abortController.abort(reason);
6745
6851
  }
6852
+ for (const controller of this.inFlightOAuthAbortControllers) controller.abort(reason);
6853
+ this.inFlightOAuthAbortControllers.clear();
6746
6854
  this.inFlightFetchAbortControllers.clear();
6747
6855
  }
6748
6856
  cancelOpenResponseReaders() {
@@ -6808,7 +6916,7 @@ var HttpTransport = class {
6808
6916
  const response = await this.fetchWithOAuthRetry({
6809
6917
  url: postUrl,
6810
6918
  method: "POST",
6811
- createHeaders: () => this.createPostHeaders(message, modern),
6919
+ createHeaders: (signal) => this.createPostHeaders(message, modern, signal),
6812
6920
  body: line,
6813
6921
  controller
6814
6922
  });
@@ -6819,7 +6927,7 @@ var HttpTransport = class {
6819
6927
  if (hasSessionId && response.status === 404) {
6820
6928
  void response.body?.cancel().catch(() => void 0);
6821
6929
  this.sessionId = void 0;
6822
- this.dispose(new Error("HTTP transport session expired (404 response)"));
6930
+ this.dispose(new HttpTransportError("HTTP transport session expired (404 response)", 404, "POST"));
6823
6931
  return;
6824
6932
  }
6825
6933
  if (await this.throwForPostHttpError(
@@ -6848,7 +6956,7 @@ var HttpTransport = class {
6848
6956
  this.modernRequests.delete(id);
6849
6957
  }
6850
6958
  }
6851
- async createPostHeaders(message, modern = false) {
6959
+ async createPostHeaders(message, modern = false, signal) {
6852
6960
  const headers = new Headers(this.headers);
6853
6961
  headers.set("Accept", "application/json, text/event-stream");
6854
6962
  headers.set("Content-Type", "application/json");
@@ -6878,9 +6986,9 @@ var HttpTransport = class {
6878
6986
  headers.set("Mcp-Session-Id", this.sessionId);
6879
6987
  headers.set("MCP-Protocol-Version", MCP_PROTOCOL_VERSION);
6880
6988
  }
6881
- return this.authorizeRequestHeaders(headers);
6989
+ return this.authorizeRequestHeaders(headers, signal);
6882
6990
  }
6883
- async createGetHeaders() {
6991
+ async createGetHeaders(signal) {
6884
6992
  const headers = new Headers(this.headers);
6885
6993
  headers.set("Accept", "text/event-stream");
6886
6994
  if (this.sessionId !== void 0) {
@@ -6890,20 +6998,26 @@ var HttpTransport = class {
6890
6998
  if (this.lastEventId !== void 0) {
6891
6999
  headers.set("Last-Event-ID", this.lastEventId);
6892
7000
  }
6893
- return this.authorizeRequestHeaders(headers);
7001
+ return this.authorizeRequestHeaders(headers, signal);
6894
7002
  }
6895
- async createDeleteHeaders(sessionId) {
7003
+ async createDeleteHeaders(sessionId, signal) {
6896
7004
  const headers = new Headers(this.headers);
6897
7005
  headers.set("Mcp-Session-Id", sessionId);
6898
7006
  headers.set("MCP-Protocol-Version", MCP_PROTOCOL_VERSION);
6899
- return this.authorizeRequestHeaders(headers);
7007
+ return this.authorizeRequestHeaders(headers, signal);
6900
7008
  }
6901
- async authorizeRequestHeaders(headers) {
7009
+ async authorizeRequestHeaders(headers, signal) {
7010
+ signal?.throwIfAborted();
6902
7011
  await this.oauthProvider?.authorizeRequest?.({
6903
7012
  requestUrl: new URL(this.url),
6904
7013
  headers,
6905
- fetch: this.fetchImpl
7014
+ signal,
7015
+ fetch: (url, init) => fetchMcpResponse(this.fetchImpl, url, {
7016
+ ...init,
7017
+ signal: signal === void 0 ? init?.signal : init?.signal == null ? signal : AbortSignal.any([signal, init.signal])
7018
+ })
6906
7019
  });
7020
+ signal?.throwIfAborted();
6907
7021
  return headers;
6908
7022
  }
6909
7023
  captureSessionId(response) {
@@ -6940,7 +7054,7 @@ var HttpTransport = class {
6940
7054
  return this.legacyEndpointReady;
6941
7055
  }
6942
7056
  async sendSessionTerminationRequest(sessionId, signal) {
6943
- const headers = await this.createDeleteHeaders(sessionId);
7057
+ const headers = await this.createDeleteHeaders(sessionId, signal);
6944
7058
  signal.throwIfAborted();
6945
7059
  const response = await fetchMcpResponse(this.fetchImpl, this.url, {
6946
7060
  method: "DELETE",
@@ -6958,12 +7072,12 @@ var HttpTransport = class {
6958
7072
  const responseBody = (await readBoundedResponseText(response, this.maxResponseBytes, this.openResponseReaders, signal)).trim();
6959
7073
  const statusDescriptor = `${response.status} ${response.statusText}`.trim();
6960
7074
  const message = responseBody.length === 0 ? `HTTP transport DELETE failed (${statusDescriptor})` : `HTTP transport DELETE failed (${statusDescriptor}): ${responseBody}`;
6961
- throw new Error(message);
7075
+ throw new HttpTransportError(message, response.status, "DELETE");
6962
7076
  }
6963
7077
  async consumeGetSseStream() {
6964
7078
  const response = await this.fetchWithOAuthRetry({
6965
7079
  method: "GET",
6966
- createHeaders: () => this.createGetHeaders()
7080
+ createHeaders: (signal) => this.createGetHeaders(signal)
6967
7081
  });
6968
7082
  if (this.disposed) {
6969
7083
  void response.body?.cancel().catch(() => void 0);
@@ -6971,18 +7085,19 @@ var HttpTransport = class {
6971
7085
  }
6972
7086
  if (response.status === 405) {
6973
7087
  void response.body?.cancel().catch(() => void 0);
7088
+ if (this.mode === "sse") throw new HttpTransportError("Legacy SSE GET failed (405)", 405, "GET");
6974
7089
  throw new HttpTransportGetSseNotSupportedError();
6975
7090
  }
6976
7091
  if (response.status === 404) {
6977
7092
  void response.body?.cancel().catch(() => void 0);
6978
7093
  this.sessionId = void 0;
6979
- throw new Error("HTTP transport session expired (GET 404 response)");
7094
+ throw new HttpTransportError("HTTP transport session expired (GET 404 response)", 404, "GET");
6980
7095
  }
6981
7096
  if (!response.ok) {
6982
7097
  const responseBody = (await readBoundedResponseText(response, this.maxResponseBytes, this.openResponseReaders)).trim();
6983
7098
  const statusDescriptor = `${response.status} ${response.statusText}`.trim();
6984
7099
  const message = responseBody.length === 0 ? `HTTP transport GET failed (${statusDescriptor})` : `HTTP transport GET failed (${statusDescriptor}): ${responseBody}`;
6985
- throw new Error(message);
7100
+ throw new HttpTransportError(message, response.status, "GET");
6986
7101
  }
6987
7102
  const contentType = response.headers.get("Content-Type");
6988
7103
  if (contentType === null) {
@@ -7043,9 +7158,9 @@ var HttpTransport = class {
7043
7158
  }
7044
7159
  const statusDescriptor = `${response.status} ${response.statusText}`.trim();
7045
7160
  const message = responseBody.length === 0 ? `HTTP transport POST failed (${statusDescriptor})` : `HTTP transport POST failed (${statusDescriptor}): ${responseBody}`;
7046
- throw new Error(message);
7161
+ throw new HttpTransportError(message, response.status, "POST");
7047
7162
  }
7048
- async maybeHandleUnauthorizedResponse(response) {
7163
+ async maybeHandleUnauthorizedResponse(response, signal) {
7049
7164
  if (response.status !== 401 || this.oauthProvider === void 0) {
7050
7165
  return false;
7051
7166
  }
@@ -7056,7 +7171,7 @@ var HttpTransport = class {
7056
7171
  const challenge = parseBearerWwwAuthenticateHeader(response.headers.get("WWW-Authenticate"));
7057
7172
  const resourceMetadataUrl = challenge?.params.resource_metadata;
7058
7173
  try {
7059
- const discovery = await discoveryClient.discover(this.url, { resourceMetadataUrl });
7174
+ const discovery = await discoveryClient.discover(this.url, { resourceMetadataUrl, signal });
7060
7175
  const providerResponse = response.clone();
7061
7176
  let result;
7062
7177
  try {
@@ -7065,8 +7180,13 @@ var HttpTransport = class {
7065
7180
  response: providerResponse,
7066
7181
  challenge,
7067
7182
  discovery,
7068
- fetch: this.fetchImpl
7183
+ signal,
7184
+ fetch: (url, init) => fetchMcpResponse(this.fetchImpl, url, {
7185
+ ...init,
7186
+ signal: init?.signal == null ? signal : AbortSignal.any([signal, init.signal])
7187
+ })
7069
7188
  });
7189
+ signal.throwIfAborted();
7070
7190
  } finally {
7071
7191
  void providerResponse.body?.cancel().catch(() => void 0);
7072
7192
  }
@@ -7196,25 +7316,30 @@ var HttpTransport = class {
7196
7316
  `);
7197
7317
  }
7198
7318
  async fetchWithOAuthRetry(input) {
7199
- const request = async () => this.fetchWithAbort(
7200
- input.url ?? this.url,
7201
- {
7319
+ const controller = input.controller ?? new AbortController();
7320
+ this.inFlightOAuthAbortControllers.add(controller);
7321
+ const request = async () => {
7322
+ controller.signal.throwIfAborted();
7323
+ const headers = await input.createHeaders(controller.signal);
7324
+ controller.signal.throwIfAborted();
7325
+ return this.fetchWithAbort(input.url ?? this.url, {
7202
7326
  method: input.method,
7203
- headers: await input.createHeaders(),
7327
+ headers,
7204
7328
  body: input.body
7205
- },
7206
- input.controller
7207
- );
7208
- let response = await request();
7209
- if (await this.maybeHandleUnauthorizedResponse(response)) {
7210
- response = await request();
7211
- }
7212
- const oauthError = this.oauthProvider === void 0 ? null : this.readOAuthChallengeError(response);
7213
- if (oauthError !== null) {
7214
- void response.body?.cancel().catch(() => void 0);
7215
- throw oauthError;
7329
+ }, controller);
7330
+ };
7331
+ try {
7332
+ let response = await request();
7333
+ if (await this.maybeHandleUnauthorizedResponse(response, controller.signal)) response = await request();
7334
+ const oauthError = this.oauthProvider === void 0 ? null : this.readOAuthChallengeError(response);
7335
+ if (oauthError !== null) {
7336
+ void response.body?.cancel().catch(() => void 0);
7337
+ throw oauthError;
7338
+ }
7339
+ return response;
7340
+ } finally {
7341
+ this.inFlightOAuthAbortControllers.delete(controller);
7216
7342
  }
7217
- return response;
7218
7343
  }
7219
7344
  readOAuthChallengeError(response) {
7220
7345
  if (response.status !== 401 && response.status !== 403) {
@@ -8123,6 +8248,7 @@ export {
8123
8248
  ERROR_METHOD_NOT_FOUND,
8124
8249
  ERROR_PARSE,
8125
8250
  HttpTransport,
8251
+ HttpTransportError,
8126
8252
  JsonRpcMessageLayer,
8127
8253
  McpClient,
8128
8254
  McpError,