@mcp-ts/sdk 2.4.1 → 2.4.3

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.
Files changed (49) hide show
  1. package/dist/adapters/agui-adapter.d.mts +3 -4
  2. package/dist/adapters/agui-adapter.d.ts +3 -4
  3. package/dist/adapters/agui-middleware.d.mts +3 -4
  4. package/dist/adapters/agui-middleware.d.ts +3 -4
  5. package/dist/adapters/ai-adapter.d.mts +3 -4
  6. package/dist/adapters/ai-adapter.d.ts +3 -4
  7. package/dist/adapters/langchain-adapter.d.mts +3 -4
  8. package/dist/adapters/langchain-adapter.d.ts +3 -4
  9. package/dist/adapters/mastra-adapter.d.mts +2 -2
  10. package/dist/adapters/mastra-adapter.d.ts +2 -2
  11. package/dist/bin/mcp-ts.js +5 -5
  12. package/dist/bin/mcp-ts.js.map +1 -1
  13. package/dist/bin/mcp-ts.mjs +5 -5
  14. package/dist/bin/mcp-ts.mjs.map +1 -1
  15. package/dist/client/index.d.mts +2 -3
  16. package/dist/client/index.d.ts +2 -3
  17. package/dist/client/react.d.mts +4 -6
  18. package/dist/client/react.d.ts +4 -6
  19. package/dist/client/vue.d.mts +4 -6
  20. package/dist/client/vue.d.ts +4 -6
  21. package/dist/{index-Ch7ouNSa.d.ts → index-B8kJSrBJ.d.ts} +1 -2
  22. package/dist/{index-L5XoXgsb.d.mts → index-DiJsm_lK.d.mts} +1 -2
  23. package/dist/index.d.mts +5 -6
  24. package/dist/index.d.ts +5 -6
  25. package/dist/index.js +174 -110
  26. package/dist/index.js.map +1 -1
  27. package/dist/index.mjs +174 -110
  28. package/dist/index.mjs.map +1 -1
  29. package/dist/{multi-session-client-k-9RvWvi.d.mts → multi-session-client-B6hsYO8V.d.mts} +218 -71
  30. package/dist/{multi-session-client-Bwm-efqg.d.ts → multi-session-client-DOBvtaQV.d.ts} +218 -71
  31. package/dist/server/index.d.mts +5 -128
  32. package/dist/server/index.d.ts +5 -128
  33. package/dist/server/index.js +174 -110
  34. package/dist/server/index.js.map +1 -1
  35. package/dist/server/index.mjs +174 -110
  36. package/dist/server/index.mjs.map +1 -1
  37. package/dist/shared/index.d.mts +4 -5
  38. package/dist/shared/index.d.ts +4 -5
  39. package/dist/{tool-router-CuApsDiV.d.mts → tool-router-CbG4Tum6.d.mts} +1 -1
  40. package/dist/{tool-router-CZMrOG8J.d.ts → tool-router-ChIhPwgP.d.ts} +1 -1
  41. package/dist/{types-DCk_IF4L.d.mts → types-CjczQwNX.d.mts} +122 -1
  42. package/dist/{types-DCk_IF4L.d.ts → types-CjczQwNX.d.ts} +122 -1
  43. package/package.json +1 -1
  44. package/src/bin/mcp-ts.ts +5 -5
  45. package/src/server/mcp/multi-session-client.ts +190 -132
  46. package/src/server/mcp/oauth-client.ts +49 -7
  47. package/src/server/storage/index.ts +8 -8
  48. package/dist/events-C4m7tK1U.d.mts +0 -122
  49. package/dist/events-C4m7tK1U.d.ts +0 -122
@@ -1666,12 +1666,12 @@ async function createStorage() {
1666
1666
  }
1667
1667
  if (type === "supabase") {
1668
1668
  const url = process.env.SUPABASE_URL;
1669
- const key = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SUPABASE_ANON_KEY;
1669
+ const key = process.env.SUPABASE_SECRET_KEY || process.env.SUPABASE_ANON_KEY;
1670
1670
  if (!url || !key) {
1671
- console.warn('[mcp-ts][Storage] Explicit selection "supabase" requires SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY.');
1671
+ console.warn('[mcp-ts][Storage] Explicit selection "supabase" requires SUPABASE_URL and SUPABASE_SECRET_KEY.');
1672
1672
  } else {
1673
- if (!process.env.SUPABASE_SERVICE_ROLE_KEY) {
1674
- console.warn('[mcp-ts][Storage] \u26A0\uFE0F Warning: Using "SUPABASE_ANON_KEY" for server-side storage. You may encounter RLS policy violations. "SUPABASE_SERVICE_ROLE_KEY" is recommended.');
1673
+ if (!process.env.SUPABASE_SECRET_KEY) {
1674
+ console.warn('[mcp-ts][Storage] \u26A0\uFE0F Warning: Using "SUPABASE_ANON_KEY" for server-side storage. You may encounter RLS policy violations. "SUPABASE_SECRET_KEY" is recommended.');
1675
1675
  }
1676
1676
  try {
1677
1677
  const { createClient } = await import('@supabase/supabase-js');
@@ -1726,13 +1726,13 @@ async function createStorage() {
1726
1726
  console.log(`[mcp-ts][Storage] Auto-detection: "sqlite" (${process.env.MCP_TS_STORAGE_SQLITE_PATH})`);
1727
1727
  return await initializeStorage(new SqliteStorage({ path: process.env.MCP_TS_STORAGE_SQLITE_PATH }));
1728
1728
  }
1729
- if (process.env.SUPABASE_URL && (process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SUPABASE_ANON_KEY)) {
1729
+ if (process.env.SUPABASE_URL && (process.env.SUPABASE_SECRET_KEY || process.env.SUPABASE_ANON_KEY)) {
1730
1730
  try {
1731
1731
  const { createClient } = await import('@supabase/supabase-js');
1732
1732
  const url = process.env.SUPABASE_URL;
1733
- const key = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SUPABASE_ANON_KEY;
1734
- if (!process.env.SUPABASE_SERVICE_ROLE_KEY) {
1735
- console.warn('[mcp-ts][Storage] \u26A0\uFE0F Warning: Using "SUPABASE_ANON_KEY" for server-side storage. You may encounter RLS policy violations. "SUPABASE_SERVICE_ROLE_KEY" is recommended.');
1733
+ const key = process.env.SUPABASE_SECRET_KEY || process.env.SUPABASE_ANON_KEY;
1734
+ if (!process.env.SUPABASE_SECRET_KEY) {
1735
+ console.warn('[mcp-ts][Storage] \u26A0\uFE0F Warning: Using "SUPABASE_ANON_KEY" for server-side storage. You may encounter RLS policy violations. "SUPABASE_SECRET_KEY" is recommended.');
1736
1736
  }
1737
1737
  const client = createClient(url, key);
1738
1738
  console.log('[mcp-ts][Storage] Auto-detection: "supabase" (via SUPABASE_URL)');
@@ -2230,7 +2230,6 @@ var MCPClient = class {
2230
2230
  const response = await fetch(url, { ...init, signal });
2231
2231
  const hasSessionHeader = init?.headers && new Headers(init.headers).has("mcp-session-id");
2232
2232
  if (response.status === 404 && hasSessionHeader) {
2233
- this.client = null;
2234
2233
  throw new Error("MCP_SESSION_EXPIRED: Downstream session was not found on the server.");
2235
2234
  }
2236
2235
  return response;
@@ -2611,7 +2610,9 @@ var MCPClient = class {
2611
2610
  method: "tools/list",
2612
2611
  params: {}
2613
2612
  };
2614
- const result = await this.client.request(request, ListToolsResultSchema);
2613
+ const result = await this.withRetry(
2614
+ () => this.client.request(request, ListToolsResultSchema)
2615
+ );
2615
2616
  if (this.serverId) {
2616
2617
  this._onConnectionEvent.fire({
2617
2618
  type: "tools_discovered",
@@ -2651,7 +2652,9 @@ var MCPClient = class {
2651
2652
  }
2652
2653
  };
2653
2654
  try {
2654
- const result = await this.client.request(request, CallToolResultSchema);
2655
+ const result = await this.withRetry(
2656
+ () => this.client.request(request, CallToolResultSchema)
2657
+ );
2655
2658
  this._onObservabilityEvent.fire({
2656
2659
  type: "mcp:client:tool_call",
2657
2660
  level: "info",
@@ -2703,7 +2706,9 @@ var MCPClient = class {
2703
2706
  method: "prompts/list",
2704
2707
  params: {}
2705
2708
  };
2706
- const result = await this.client.request(request, ListPromptsResultSchema);
2709
+ const result = await this.withRetry(
2710
+ () => this.client.request(request, ListPromptsResultSchema)
2711
+ );
2707
2712
  this.emitStateChange("READY");
2708
2713
  this.emitProgress(`Discovered ${result.prompts.length} prompts`);
2709
2714
  return result;
@@ -2732,7 +2737,9 @@ var MCPClient = class {
2732
2737
  arguments: args
2733
2738
  }
2734
2739
  };
2735
- return await this.client.request(request, GetPromptResultSchema);
2740
+ return await this.withRetry(
2741
+ () => this.client.request(request, GetPromptResultSchema)
2742
+ );
2736
2743
  }
2737
2744
  /**
2738
2745
  * Lists all available resources from the connected MCP server
@@ -2749,7 +2756,9 @@ var MCPClient = class {
2749
2756
  method: "resources/list",
2750
2757
  params: {}
2751
2758
  };
2752
- const result = await this.client.request(request, ListResourcesResultSchema);
2759
+ const result = await this.withRetry(
2760
+ () => this.client.request(request, ListResourcesResultSchema)
2761
+ );
2753
2762
  this.emitStateChange("READY");
2754
2763
  this.emitProgress(`Discovered ${result.resources.length} resources`);
2755
2764
  return result;
@@ -2776,7 +2785,36 @@ var MCPClient = class {
2776
2785
  uri
2777
2786
  }
2778
2787
  };
2779
- return await this.client.request(request, ReadResourceResultSchema);
2788
+ return await this.withRetry(
2789
+ () => this.client.request(request, ReadResourceResultSchema)
2790
+ );
2791
+ }
2792
+ /**
2793
+ * Wraps an MCP request with automatic transport-session recovery.
2794
+ *
2795
+ * When the downstream MCP server rejects the request with a 404 indicating
2796
+ * the transport session has expired, this method tears down the stale SDK
2797
+ * client and transport, calls {@link reconnect} to negotiate a fresh session,
2798
+ * and retries the request once.
2799
+ *
2800
+ * Non-transient errors (network failures, auth errors, etc.) propagate as-is.
2801
+ */
2802
+ async withRetry(fn) {
2803
+ try {
2804
+ return await fn();
2805
+ } catch (error) {
2806
+ if (!(error instanceof Error && error.message.includes("MCP_SESSION_EXPIRED"))) throw error;
2807
+ if (this.client) {
2808
+ try {
2809
+ await this.client.close();
2810
+ } catch {
2811
+ }
2812
+ this.transport = null;
2813
+ this.client = null;
2814
+ }
2815
+ await this.reconnect();
2816
+ return await fn();
2817
+ }
2780
2818
  }
2781
2819
  /**
2782
2820
  * Reconnects to MCP server using existing OAuth provider from Redis
@@ -2789,6 +2827,12 @@ var MCPClient = class {
2789
2827
  if (!this.oauthProvider) {
2790
2828
  throw new Error("OAuth provider not initialized");
2791
2829
  }
2830
+ if (this.client) {
2831
+ try {
2832
+ await this.client.close();
2833
+ } catch {
2834
+ }
2835
+ }
2792
2836
  this.client = new Client(
2793
2837
  {
2794
2838
  name: MCP_CLIENT_NAME,
@@ -2924,17 +2968,14 @@ var DEFAULT_RETRY_DELAY_MS = 1e3;
2924
2968
  var CONNECTION_BATCH_SIZE = 5;
2925
2969
  var MultiSessionClient = class {
2926
2970
  /**
2927
- * Creates a new MultiSessionClient for the given user userId.
2928
- *
2929
- * @param userId - A unique string identifying the user (e.g. user ID or email).
2930
- * @param options - Optional tuning for connection timeout, retry count, and retry delay.
2931
- * Falls back to sensible defaults if not provided.
2971
+ * @param userId - Unique identifier for the user (e.g. user ID or email).
2972
+ * @param options - Optional tuning and lifecycle hooks.
2932
2973
  */
2933
2974
  constructor(userId, options = {}) {
2934
2975
  __publicField(this, "clients", []);
2976
+ __publicField(this, "connectionPromises", /* @__PURE__ */ new Map());
2935
2977
  __publicField(this, "userId");
2936
2978
  __publicField(this, "options");
2937
- __publicField(this, "connectionPromises", /* @__PURE__ */ new Map());
2938
2979
  this.userId = userId;
2939
2980
  this.options = {
2940
2981
  timeout: DEFAULT_TIMEOUT_MS,
@@ -2943,28 +2984,97 @@ var MultiSessionClient = class {
2943
2984
  ...options
2944
2985
  };
2945
2986
  }
2987
+ // -----------------------------------------------------------------------
2988
+ // Public API
2989
+ // -----------------------------------------------------------------------
2946
2990
  /**
2947
- * Fetches all sessions for this userId from storage and returns only the
2948
- * ones that are ready to connect.
2991
+ * Fetches active sessions and establishes connections to all of them.
2949
2992
  *
2950
- * A session is considered connectable when:
2951
- * - It has a `serverId`, `serverUrl`, and `callbackUrl` (i.e. it was fully initialized)
2952
- * - Its status is `active`. Pending sessions are skipped here
2953
- * and let the OAuth flow complete separately before we try to reconnect them.
2993
+ * Call this once after creating the client. On long-running servers you
2994
+ * can cache the `MultiSessionClient` instance and call `connect()` on
2995
+ * each request already-connected sessions are skipped internally.
2954
2996
  */
2955
- async getActiveSessions() {
2956
- const sessionList = await sessions.list(this.userId);
2957
- const valid = sessionList.filter(
2997
+ async connect() {
2998
+ const sessions2 = await this.fetchActiveSessions();
2999
+ const activeSessionIds = new Set(sessions2.map((s) => s.sessionId));
3000
+ for (const client of this.clients) {
3001
+ if (!activeSessionIds.has(client.getSessionId())) {
3002
+ this.options.onSessionEvicted?.(client.getSessionId());
3003
+ }
3004
+ }
3005
+ this.clients = this.clients.filter((c) => activeSessionIds.has(c.getSessionId()));
3006
+ await this.connectInBatches(sessions2);
3007
+ }
3008
+ /**
3009
+ * Drops all cached `MCPClient` instances and reconnects fresh from storage.
3010
+ *
3011
+ * Call this when downstream MCP servers have expired their transport sessions
3012
+ * (e.g. after a remote server restart) and subsequent tool calls return
3013
+ * "Session not found. Reconnect without session header." errors.
3014
+ *
3015
+ * OAuth tokens are preserved in the storage backend — no re-authentication
3016
+ * is required. Only the in-memory transport sessions are cleared.
3017
+ */
3018
+ async reconnect() {
3019
+ await this.disconnect();
3020
+ await this.connect();
3021
+ }
3022
+ /**
3023
+ * Returns all currently connected `MCPClient` instances.
3024
+ *
3025
+ * Use this to enumerate available tools across all connected servers,
3026
+ * or to route a tool call to the right client by `serverId`.
3027
+ */
3028
+ getClients() {
3029
+ return this.clients;
3030
+ }
3031
+ /**
3032
+ * Removes and disconnects a single session by ID.
3033
+ *
3034
+ * @returns `true` if the session was found and removed, `false` if not found.
3035
+ */
3036
+ async removeSession(sessionId) {
3037
+ const idx = this.clients.findIndex((c) => c.getSessionId() === sessionId);
3038
+ if (idx === -1) return false;
3039
+ const [client] = this.clients.splice(idx, 1);
3040
+ await client.disconnect();
3041
+ return true;
3042
+ }
3043
+ /**
3044
+ * Gracefully disconnects all active MCP clients and clears the internal list.
3045
+ *
3046
+ * For Streamable HTTP sessions, each client sends an HTTP DELETE to its MCP
3047
+ * endpoint per the spec before closing locally. All disconnects run in
3048
+ * parallel so shutdown is not serialised across many sessions.
3049
+ *
3050
+ * Call this during server shutdown or when a user logs out to free up
3051
+ * underlying transport resources (SSE streams, HTTP connections, etc.).
3052
+ */
3053
+ async disconnect() {
3054
+ await Promise.all(this.clients.map((client) => client.disconnect()));
3055
+ this.clients = [];
3056
+ }
3057
+ // -----------------------------------------------------------------------
3058
+ // Internals
3059
+ // -----------------------------------------------------------------------
3060
+ /**
3061
+ * Resolves the list of sessions to connect.
3062
+ *
3063
+ * Uses the custom `sessionProvider` when provided, otherwise falls back
3064
+ * to querying the storage backend via `sessions.list(userId)`.
3065
+ */
3066
+ async fetchActiveSessions() {
3067
+ const sessionList = this.options.sessionProvider ? await this.options.sessionProvider() : await sessions.list(this.userId);
3068
+ return sessionList.filter(
2958
3069
  (s) => s.serverId && s.serverUrl && s.callbackUrl && s.status === "active"
2959
3070
  );
2960
- return valid;
2961
3071
  }
2962
3072
  /**
2963
- * Connects to a list of sessions in controlled batches of `CONNECTION_BATCH_SIZE`.
3073
+ * Connects a list of sessions in controlled batches.
2964
3074
  *
2965
- * Batching prevents overwhelming the event loop or external servers when a user
2966
- * has many active MCP sessions (e.g. 20+ servers). Within each batch, sessions
2967
- * are connected concurrently using `Promise.all` for speed.
3075
+ * Batching prevents overwhelming the event loop or external servers when
3076
+ * a user has many active MCP sessions. Within each batch, sessions are
3077
+ * connected concurrently using `Promise.all`.
2968
3078
  */
2969
3079
  async connectInBatches(sessions2) {
2970
3080
  for (let i = 0; i < sessions2.length; i += CONNECTION_BATCH_SIZE) {
@@ -2973,17 +3083,16 @@ var MultiSessionClient = class {
2973
3083
  }
2974
3084
  }
2975
3085
  /**
2976
- * Connects a single session, with built-in deduplication to prevent race conditions.
3086
+ * Connects a single session, with deduplication to prevent race conditions.
2977
3087
  *
2978
- * - If a client for this session already exists and is connected, returns immediately.
2979
- * - If the existing client entry is no longer connected (e.g. it was explicitly
2980
- * disconnected), it is evicted so that `establishConnectionWithRetries` creates a
2981
- * fresh transport preventing "Client already connected" errors from the SDK.
2982
- * - If a connection attempt for this session is already in-flight (e.g. from a
2983
- * concurrent call), it joins the existing promise instead of starting a new one.
2984
- * This is the key concurrency lock the `connectionPromises` map acts as a
2985
- * per-session mutex so we never spin up two physical connections for the same session.
2986
- * - On completion (success or failure), the promise is cleaned up from the map.
3088
+ * - If a client for this session already exists and is connected, returns
3089
+ * immediately.
3090
+ * - If the existing client entry is no longer connected (e.g. explicit
3091
+ * disconnect), it is evicted so a fresh transport is created.
3092
+ * - If a connection attempt for this session is already in-flight, the
3093
+ * existing promise is reused as a per-session mutex.
3094
+ * - On completion (success or failure), the promise is cleaned up from
3095
+ * the connectionPromises map.
2987
3096
  */
2988
3097
  async connectSession(session) {
2989
3098
  const existing = this.clients.find((c) => c.getSessionId() === session.sessionId);
@@ -2991,6 +3100,7 @@ var MultiSessionClient = class {
2991
3100
  if (existing.isConnected()) {
2992
3101
  return;
2993
3102
  }
3103
+ this.options.onSessionEvicted?.(existing.getSessionId());
2994
3104
  this.clients = this.clients.filter((c) => c !== existing);
2995
3105
  }
2996
3106
  if (this.connectionPromises.has(session.sessionId)) {
@@ -3005,22 +3115,19 @@ var MultiSessionClient = class {
3005
3115
  }
3006
3116
  }
3007
3117
  /**
3008
- * The core connection loop for a single session.
3118
+ * Core connection loop for a single session with retry logic.
3009
3119
  *
3010
- * Attempts to establish a physical MCP connection, retrying up to `maxRetries` times
3011
- * if the connection fails. Each attempt:
3012
- * 1. Creates a fresh `MCPClient` instance from the session data.
3013
- * 2. Races the connect call against a timeout promise — if the server doesn't respond
3014
- * within `timeoutMs`, the attempt is aborted and counted as a failure.
3015
- * 3. On success, replaces any stale client entry for this session in the `clients` array.
3120
+ * 1. Creates a fresh `MCPClient` from the session data.
3121
+ * 2. Races `client.connect()` against a timeout.
3122
+ * 3. On success, replaces any stale entry and fires `onSessionConnected`.
3016
3123
  * 4. On failure, waits `retryDelay` ms before the next attempt.
3017
3124
  *
3018
- * If all attempts are exhausted, logs an error and returns silently (does not throw),
3019
- * so a single bad server doesn't block the rest of the batch from connecting.
3125
+ * If all attempts are exhausted, logs an error and returns silently so
3126
+ * a single bad server doesn't block the rest of the batch.
3020
3127
  */
3021
3128
  async establishConnectionWithRetries(session) {
3022
- const maxRetries = this.options.maxRetries ?? DEFAULT_MAX_RETRIES;
3023
- const retryDelay = this.options.retryDelay ?? DEFAULT_RETRY_DELAY_MS;
3129
+ const maxRetries = this.options.maxRetries;
3130
+ const retryDelay = this.options.retryDelay;
3024
3131
  let lastError;
3025
3132
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
3026
3133
  try {
@@ -3034,10 +3141,13 @@ var MultiSessionClient = class {
3034
3141
  transportType: session.transportType,
3035
3142
  headers: session.headers
3036
3143
  });
3037
- const timeoutMs = this.options.timeout ?? DEFAULT_TIMEOUT_MS;
3144
+ const timeoutMs = this.options.timeout;
3038
3145
  let timeoutTimer;
3039
3146
  const timeoutPromise = new Promise((_, reject) => {
3040
- timeoutTimer = setTimeout(() => reject(new Error(`Connection timed out after ${timeoutMs}ms`)), timeoutMs);
3147
+ timeoutTimer = setTimeout(
3148
+ () => reject(new Error(`Connection timed out after ${timeoutMs}ms`)),
3149
+ timeoutMs
3150
+ );
3041
3151
  });
3042
3152
  try {
3043
3153
  await Promise.race([client.connect(), timeoutPromise]);
@@ -3046,6 +3156,7 @@ var MultiSessionClient = class {
3046
3156
  }
3047
3157
  this.clients = this.clients.filter((c) => c.getSessionId() !== session.sessionId);
3048
3158
  this.clients.push(client);
3159
+ this.options.onSessionConnected?.(session.sessionId, client);
3049
3160
  return;
3050
3161
  } catch (error) {
3051
3162
  lastError = error;
@@ -3054,58 +3165,11 @@ var MultiSessionClient = class {
3054
3165
  }
3055
3166
  }
3056
3167
  }
3057
- console.error(`[MultiSessionClient] Failed to connect to session ${session.sessionId} after ${maxRetries + 1} attempts:`, lastError);
3058
- }
3059
- /**
3060
- * The main entry point. Fetches all active sessions for this userId from
3061
- * storage and establishes connections to all of them in batches.
3062
- *
3063
- * Call this once after creating the client. On traditional servers, you can
3064
- * cache the `MultiSessionClient` instance after calling `connect()` to avoid
3065
- * re-fetching and re-connecting on every request.
3066
- */
3067
- async connect() {
3068
- const sessions2 = await this.getActiveSessions();
3069
- const activeSessionIds = new Set(sessions2.map((s) => s.sessionId));
3070
- this.clients = this.clients.filter((c) => activeSessionIds.has(c.getSessionId()));
3071
- await this.connectInBatches(sessions2);
3072
- }
3073
- /**
3074
- * Drops all cached `MCPClient` instances and reconnects fresh from storage.
3075
- *
3076
- * Call this when downstream MCP servers have expired their transport sessions
3077
- * (e.g. after a remote server restart) and subsequent tool calls return
3078
- * "Session not found. Reconnect without session header." errors.
3079
- *
3080
- * OAuth tokens are preserved in the storage backend — no re-authentication
3081
- * is required. Only the in-memory transport sessions are cleared.
3082
- */
3083
- async reconnect() {
3084
- await this.disconnect();
3085
- await this.connect();
3086
- }
3087
- /**
3088
- * Returns all currently connected `MCPClient` instances.
3089
- *
3090
- * Use this to enumerate available tools across all connected servers,
3091
- * or to route a tool call to the right client by `serverId`.
3092
- */
3093
- getClients() {
3094
- return this.clients;
3095
- }
3096
- /**
3097
- * Gracefully disconnects all active MCP clients and clears the internal client list.
3098
- *
3099
- * For Streamable HTTP sessions, each client sends an HTTP DELETE to its MCP
3100
- * endpoint per the spec before closing locally. All disconnects run in
3101
- * parallel so shutdown is not serialised across many sessions.
3102
- *
3103
- * Call this during server shutdown or when a user logs out to free up
3104
- * underlying transport resources (SSE streams, HTTP connections, etc.).
3105
- */
3106
- async disconnect() {
3107
- await Promise.all(this.clients.map((client) => client.disconnect()));
3108
- this.clients = [];
3168
+ this.options.onSessionFailed?.(session.sessionId, lastError);
3169
+ console.error(
3170
+ `[MultiSessionClient] Failed to connect to session ${session.sessionId} after ${maxRetries + 1} attempts:`,
3171
+ lastError
3172
+ );
3109
3173
  }
3110
3174
  };
3111
3175