@mcp-use/client 2.3.1-canary.0 → 2.3.1-canary.2

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.
@@ -645,6 +645,9 @@ var BaseConnector = class {
645
645
  serverInfoCache = null;
646
646
  authorizationCache;
647
647
  connected = false;
648
+ connectPromise = null;
649
+ disconnectPromise = null;
650
+ disconnectGeneration = 0;
648
651
  opts;
649
652
  notificationHandlers = [];
650
653
  rootsCache = [];
@@ -907,6 +910,54 @@ var BaseConnector = class {
907
910
  "setupElicitationHandler: Elicitation handler registered successfully"
908
911
  );
909
912
  }
913
+ /**
914
+ * Establishes the transport connection and creates the SDK client.
915
+ *
916
+ * Coalesces concurrent connection attempts, synchronizes with ongoing
917
+ * disconnections, and guarantees that orphaned processes or transports
918
+ * are not leaked on failure or rapid disconnect.
919
+ *
920
+ * @returns A promise that resolves when the connector is connected.
921
+ */
922
+ async connect() {
923
+ const generation = this.disconnectGeneration;
924
+ while (this.disconnectPromise) {
925
+ await this.disconnectPromise;
926
+ }
927
+ if (generation !== this.disconnectGeneration) {
928
+ throw new Error("Connection cancelled by disconnect");
929
+ }
930
+ if (this.connected) {
931
+ logger.debug("Already connected to MCP implementation");
932
+ return;
933
+ }
934
+ if (this.connectPromise) {
935
+ return this.connectPromise;
936
+ }
937
+ const currentConnect = (async () => {
938
+ try {
939
+ await this.establishTransport();
940
+ if (generation !== this.disconnectGeneration) {
941
+ throw new Error("Connection cancelled by disconnect");
942
+ }
943
+ this.connected = true;
944
+ } catch (err) {
945
+ this.connected = false;
946
+ await this.cleanupResources();
947
+ throw err;
948
+ } finally {
949
+ this.connectPromise = null;
950
+ }
951
+ })();
952
+ this.connectPromise = currentConnect;
953
+ return this.connectPromise;
954
+ }
955
+ /**
956
+ * Subclasses override this method to perform transport-specific connection logic.
957
+ * Default implementation is a no-op for backwards compatibility.
958
+ */
959
+ async establishTransport() {
960
+ }
910
961
  /**
911
962
  * Run one logical MCP operation. HTTP connectors override this host seam to
912
963
  * finish an SDK-started interactive OAuth flow and retry exactly once.
@@ -932,17 +983,42 @@ var BaseConnector = class {
932
983
  /**
933
984
  * Disconnects the SDK client and releases transport resources.
934
985
  *
986
+ * Coalesces concurrent calls and coordinates with in-flight connections
987
+ * to ensure no child processes or transports are orphaned.
988
+ *
935
989
  * @returns A promise that resolves after cleanup completes.
936
990
  */
937
991
  async disconnect() {
938
- if (!this.connected) {
992
+ this.disconnectGeneration++;
993
+ if (this.disconnectPromise) {
994
+ return this.disconnectPromise;
995
+ }
996
+ if (!this.connected && !this.connectPromise) {
939
997
  logger.debug("Not connected to MCP implementation");
940
998
  return;
941
999
  }
942
- logger.debug("Disconnecting from MCP implementation");
943
- await this.cleanupResources();
944
- this.connected = false;
945
- logger.debug("Disconnected from MCP implementation");
1000
+ const currentDisconnect = (async () => {
1001
+ let connectFailed = false;
1002
+ try {
1003
+ if (this.connectPromise) {
1004
+ try {
1005
+ await this.connectPromise;
1006
+ } catch {
1007
+ connectFailed = true;
1008
+ }
1009
+ }
1010
+ if (!connectFailed) {
1011
+ logger.debug("Disconnecting from MCP implementation");
1012
+ await this.cleanupResources();
1013
+ }
1014
+ this.connected = false;
1015
+ logger.debug("Disconnected from MCP implementation");
1016
+ } finally {
1017
+ this.disconnectPromise = null;
1018
+ }
1019
+ })();
1020
+ this.disconnectPromise = currentDisconnect;
1021
+ return this.disconnectPromise;
946
1022
  }
947
1023
  /** Whether an SDK client currently exists for this connector. */
948
1024
  get isClientConnected() {
@@ -1639,11 +1715,7 @@ var HttpConnector = class extends BaseConnector {
1639
1715
  * @returns A promise that resolves after protocol negotiation completes.
1640
1716
  * @throws An error with `code: 401` when authentication is required.
1641
1717
  */
1642
- async connect() {
1643
- if (this.connected) {
1644
- logger.debug("Already connected to MCP implementation");
1645
- return;
1646
- }
1718
+ async establishTransport() {
1647
1719
  const baseUrl = this.baseUrl;
1648
1720
  logger.debug(`Connecting to MCP implementation via HTTP: ${baseUrl}`);
1649
1721
  const oauthProvider = this.oauthProvider;
@@ -1662,7 +1734,6 @@ var HttpConnector = class extends BaseConnector {
1662
1734
  } catch (err) {
1663
1735
  logger.debug("Streamable HTTP connect failed", err);
1664
1736
  const { fallbackReason, is401Error, httpStatusCode } = this.classifyStreamableHttpFailure(err);
1665
- await this.cleanupResources();
1666
1737
  if (is401Error) {
1667
1738
  logger.info("Authentication required");
1668
1739
  const authError = new Error("Authentication required");
@@ -1836,7 +1907,6 @@ var HttpConnector = class extends BaseConnector {
1836
1907
  }
1837
1908
  }
1838
1909
  };
1839
- this.connected = true;
1840
1910
  this.transportType = "streamable-http";
1841
1911
  logger.debug(
1842
1912
  `Successfully connected to MCP implementation via streamable HTTP: ${baseUrl}`
@@ -1905,7 +1975,7 @@ var HttpConnector = class extends BaseConnector {
1905
1975
  };
1906
1976
 
1907
1977
  // src/utils/version.ts
1908
- var VERSION = "2.3.1-canary.0";
1978
+ var VERSION = "2.3.1-canary.2";
1909
1979
  function getPackageVersion() {
1910
1980
  return VERSION;
1911
1981
  }
@@ -4725,6 +4795,26 @@ function assert(condition, message) {
4725
4795
  throw new Error(message);
4726
4796
  }
4727
4797
  }
4798
+ function serializeClientIcons(icons) {
4799
+ if (icons === void 0) return "null";
4800
+ if (icons.length === 0) return "[]";
4801
+ return JSON.stringify(
4802
+ icons.map((icon) => [
4803
+ icon.src,
4804
+ icon.mimeType === void 0 ? null : icon.mimeType,
4805
+ icon.sizes === void 0 ? null : [...icon.sizes].sort()
4806
+ ])
4807
+ );
4808
+ }
4809
+ function serializeProxyHeaders(headers, customHeaders) {
4810
+ if (headers === void 0 && customHeaders === void 0) return "";
4811
+ const serializeEntries = (record) => {
4812
+ if (record === void 0) return "null";
4813
+ const keys = Object.keys(record).sort();
4814
+ return JSON.stringify(keys.map((k) => [k, record[k]]));
4815
+ };
4816
+ return `${serializeEntries(headers)}|${serializeEntries(customHeaders)}`;
4817
+ }
4728
4818
  async function loadServerIcon(params) {
4729
4819
  try {
4730
4820
  const iconUrl = params.serverInfo.icons?.[0]?.src;
@@ -5171,7 +5261,7 @@ function useMcp(options) {
5171
5261
  storageKeyPrefix = "mcp:auth",
5172
5262
  authProvider: providedAuthProvider,
5173
5263
  headers: headersOption,
5174
- proxyConfig,
5264
+ proxyConfig: rawProxyConfig,
5175
5265
  oauthProxyUrl: oauthProxyUrlOption,
5176
5266
  connectionMode,
5177
5267
  autoProxyFallback = false,
@@ -5197,6 +5287,34 @@ function useMcp(options) {
5197
5287
  onElicitation: onElicitationOption,
5198
5288
  oauth: oauthOptions
5199
5289
  } = options;
5290
+ const rawClientInfo = options.clientInfo;
5291
+ const clientInfoName = rawClientInfo?.name;
5292
+ const clientInfoTitle = rawClientInfo?.title;
5293
+ const clientInfoVersion = rawClientInfo?.version;
5294
+ const clientInfoDescription = rawClientInfo?.description;
5295
+ const clientInfoWebsiteUrl = rawClientInfo?.websiteUrl;
5296
+ const serializedClientIcons = serializeClientIcons(rawClientInfo?.icons);
5297
+ const clientInfo = useMemo(() => {
5298
+ if (!rawClientInfo) return void 0;
5299
+ return { ...rawClientInfo };
5300
+ }, [
5301
+ clientInfoName,
5302
+ clientInfoTitle,
5303
+ clientInfoVersion,
5304
+ clientInfoDescription,
5305
+ clientInfoWebsiteUrl,
5306
+ serializedClientIcons
5307
+ ]);
5308
+ const rawProxyAddress = rawProxyConfig?.proxyAddress;
5309
+ const serializedProxyHeaders = serializeProxyHeaders(
5310
+ rawProxyConfig?.headers,
5311
+ rawProxyConfig?.customHeaders
5312
+ );
5313
+ const proxyConfig = useMemo(() => {
5314
+ if (!rawProxyConfig) return void 0;
5315
+ return { ...rawProxyConfig };
5316
+ }, [rawProxyAddress, serializedProxyHeaders]);
5317
+ const headers = headersOption ?? {};
5200
5318
  const transportType = "http";
5201
5319
  const requestedProxyAddress = proxyConfig?.proxyAddress;
5202
5320
  const oauthClientId = oauthOptions?.clientId?.trim() || void 0;
@@ -5214,7 +5332,6 @@ function useMcp(options) {
5214
5332
  }
5215
5333
  return inst;
5216
5334
  }, [url, logLevelOption]);
5217
- const headers = headersOption ?? {};
5218
5335
  const effectiveClientOptions = useMemo(
5219
5336
  () => resolveClientOptions(clientOptions),
5220
5337
  [clientOptions]
@@ -5237,8 +5354,8 @@ function useMcp(options) {
5237
5354
  []
5238
5355
  );
5239
5356
  const mergedClientInfo = useMemo(
5240
- () => options.clientInfo ? { ...defaultClientInfo, ...options.clientInfo } : defaultClientInfo,
5241
- [options.clientInfo, defaultClientInfo]
5357
+ () => clientInfo ? { ...defaultClientInfo, ...clientInfo } : defaultClientInfo,
5358
+ [clientInfo, defaultClientInfo]
5242
5359
  );
5243
5360
  const derivedOAuthClientConfig = useMemo(
5244
5361
  () => deriveOAuthClientConfigFromClientInfo(mergedClientInfo),