@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
package/dist/index.mjs CHANGED
@@ -1670,12 +1670,12 @@ async function createStorage() {
1670
1670
  }
1671
1671
  if (type === "supabase") {
1672
1672
  const url = process.env.SUPABASE_URL;
1673
- const key = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SUPABASE_ANON_KEY;
1673
+ const key = process.env.SUPABASE_SECRET_KEY || process.env.SUPABASE_ANON_KEY;
1674
1674
  if (!url || !key) {
1675
- console.warn('[mcp-ts][Storage] Explicit selection "supabase" requires SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY.');
1675
+ console.warn('[mcp-ts][Storage] Explicit selection "supabase" requires SUPABASE_URL and SUPABASE_SECRET_KEY.');
1676
1676
  } else {
1677
- if (!process.env.SUPABASE_SERVICE_ROLE_KEY) {
1678
- 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.');
1677
+ if (!process.env.SUPABASE_SECRET_KEY) {
1678
+ 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.');
1679
1679
  }
1680
1680
  try {
1681
1681
  const { createClient } = await import('@supabase/supabase-js');
@@ -1730,13 +1730,13 @@ async function createStorage() {
1730
1730
  console.log(`[mcp-ts][Storage] Auto-detection: "sqlite" (${process.env.MCP_TS_STORAGE_SQLITE_PATH})`);
1731
1731
  return await initializeStorage(new SqliteStorage({ path: process.env.MCP_TS_STORAGE_SQLITE_PATH }));
1732
1732
  }
1733
- if (process.env.SUPABASE_URL && (process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SUPABASE_ANON_KEY)) {
1733
+ if (process.env.SUPABASE_URL && (process.env.SUPABASE_SECRET_KEY || process.env.SUPABASE_ANON_KEY)) {
1734
1734
  try {
1735
1735
  const { createClient } = await import('@supabase/supabase-js');
1736
1736
  const url = process.env.SUPABASE_URL;
1737
- const key = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SUPABASE_ANON_KEY;
1738
- if (!process.env.SUPABASE_SERVICE_ROLE_KEY) {
1739
- 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.');
1737
+ const key = process.env.SUPABASE_SECRET_KEY || process.env.SUPABASE_ANON_KEY;
1738
+ if (!process.env.SUPABASE_SECRET_KEY) {
1739
+ 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.');
1740
1740
  }
1741
1741
  const client = createClient(url, key);
1742
1742
  console.log('[mcp-ts][Storage] Auto-detection: "supabase" (via SUPABASE_URL)');
@@ -2302,7 +2302,6 @@ var MCPClient = class {
2302
2302
  const response = await fetch(url, { ...init, signal });
2303
2303
  const hasSessionHeader = init?.headers && new Headers(init.headers).has("mcp-session-id");
2304
2304
  if (response.status === 404 && hasSessionHeader) {
2305
- this.client = null;
2306
2305
  throw new Error("MCP_SESSION_EXPIRED: Downstream session was not found on the server.");
2307
2306
  }
2308
2307
  return response;
@@ -2683,7 +2682,9 @@ var MCPClient = class {
2683
2682
  method: "tools/list",
2684
2683
  params: {}
2685
2684
  };
2686
- const result = await this.client.request(request, ListToolsResultSchema);
2685
+ const result = await this.withRetry(
2686
+ () => this.client.request(request, ListToolsResultSchema)
2687
+ );
2687
2688
  if (this.serverId) {
2688
2689
  this._onConnectionEvent.fire({
2689
2690
  type: "tools_discovered",
@@ -2723,7 +2724,9 @@ var MCPClient = class {
2723
2724
  }
2724
2725
  };
2725
2726
  try {
2726
- const result = await this.client.request(request, CallToolResultSchema);
2727
+ const result = await this.withRetry(
2728
+ () => this.client.request(request, CallToolResultSchema)
2729
+ );
2727
2730
  this._onObservabilityEvent.fire({
2728
2731
  type: "mcp:client:tool_call",
2729
2732
  level: "info",
@@ -2775,7 +2778,9 @@ var MCPClient = class {
2775
2778
  method: "prompts/list",
2776
2779
  params: {}
2777
2780
  };
2778
- const result = await this.client.request(request, ListPromptsResultSchema);
2781
+ const result = await this.withRetry(
2782
+ () => this.client.request(request, ListPromptsResultSchema)
2783
+ );
2779
2784
  this.emitStateChange("READY");
2780
2785
  this.emitProgress(`Discovered ${result.prompts.length} prompts`);
2781
2786
  return result;
@@ -2804,7 +2809,9 @@ var MCPClient = class {
2804
2809
  arguments: args
2805
2810
  }
2806
2811
  };
2807
- return await this.client.request(request, GetPromptResultSchema);
2812
+ return await this.withRetry(
2813
+ () => this.client.request(request, GetPromptResultSchema)
2814
+ );
2808
2815
  }
2809
2816
  /**
2810
2817
  * Lists all available resources from the connected MCP server
@@ -2821,7 +2828,9 @@ var MCPClient = class {
2821
2828
  method: "resources/list",
2822
2829
  params: {}
2823
2830
  };
2824
- const result = await this.client.request(request, ListResourcesResultSchema);
2831
+ const result = await this.withRetry(
2832
+ () => this.client.request(request, ListResourcesResultSchema)
2833
+ );
2825
2834
  this.emitStateChange("READY");
2826
2835
  this.emitProgress(`Discovered ${result.resources.length} resources`);
2827
2836
  return result;
@@ -2848,7 +2857,36 @@ var MCPClient = class {
2848
2857
  uri
2849
2858
  }
2850
2859
  };
2851
- return await this.client.request(request, ReadResourceResultSchema);
2860
+ return await this.withRetry(
2861
+ () => this.client.request(request, ReadResourceResultSchema)
2862
+ );
2863
+ }
2864
+ /**
2865
+ * Wraps an MCP request with automatic transport-session recovery.
2866
+ *
2867
+ * When the downstream MCP server rejects the request with a 404 indicating
2868
+ * the transport session has expired, this method tears down the stale SDK
2869
+ * client and transport, calls {@link reconnect} to negotiate a fresh session,
2870
+ * and retries the request once.
2871
+ *
2872
+ * Non-transient errors (network failures, auth errors, etc.) propagate as-is.
2873
+ */
2874
+ async withRetry(fn) {
2875
+ try {
2876
+ return await fn();
2877
+ } catch (error) {
2878
+ if (!(error instanceof Error && error.message.includes("MCP_SESSION_EXPIRED"))) throw error;
2879
+ if (this.client) {
2880
+ try {
2881
+ await this.client.close();
2882
+ } catch {
2883
+ }
2884
+ this.transport = null;
2885
+ this.client = null;
2886
+ }
2887
+ await this.reconnect();
2888
+ return await fn();
2889
+ }
2852
2890
  }
2853
2891
  /**
2854
2892
  * Reconnects to MCP server using existing OAuth provider from Redis
@@ -2861,6 +2899,12 @@ var MCPClient = class {
2861
2899
  if (!this.oauthProvider) {
2862
2900
  throw new Error("OAuth provider not initialized");
2863
2901
  }
2902
+ if (this.client) {
2903
+ try {
2904
+ await this.client.close();
2905
+ } catch {
2906
+ }
2907
+ }
2864
2908
  this.client = new Client(
2865
2909
  {
2866
2910
  name: MCP_CLIENT_NAME,
@@ -2996,17 +3040,14 @@ var DEFAULT_RETRY_DELAY_MS = 1e3;
2996
3040
  var CONNECTION_BATCH_SIZE = 5;
2997
3041
  var MultiSessionClient = class {
2998
3042
  /**
2999
- * Creates a new MultiSessionClient for the given user userId.
3000
- *
3001
- * @param userId - A unique string identifying the user (e.g. user ID or email).
3002
- * @param options - Optional tuning for connection timeout, retry count, and retry delay.
3003
- * Falls back to sensible defaults if not provided.
3043
+ * @param userId - Unique identifier for the user (e.g. user ID or email).
3044
+ * @param options - Optional tuning and lifecycle hooks.
3004
3045
  */
3005
3046
  constructor(userId, options = {}) {
3006
3047
  __publicField(this, "clients", []);
3048
+ __publicField(this, "connectionPromises", /* @__PURE__ */ new Map());
3007
3049
  __publicField(this, "userId");
3008
3050
  __publicField(this, "options");
3009
- __publicField(this, "connectionPromises", /* @__PURE__ */ new Map());
3010
3051
  this.userId = userId;
3011
3052
  this.options = {
3012
3053
  timeout: DEFAULT_TIMEOUT_MS,
@@ -3015,28 +3056,97 @@ var MultiSessionClient = class {
3015
3056
  ...options
3016
3057
  };
3017
3058
  }
3059
+ // -----------------------------------------------------------------------
3060
+ // Public API
3061
+ // -----------------------------------------------------------------------
3018
3062
  /**
3019
- * Fetches all sessions for this userId from storage and returns only the
3020
- * ones that are ready to connect.
3063
+ * Fetches active sessions and establishes connections to all of them.
3021
3064
  *
3022
- * A session is considered connectable when:
3023
- * - It has a `serverId`, `serverUrl`, and `callbackUrl` (i.e. it was fully initialized)
3024
- * - Its status is `active`. Pending sessions are skipped here
3025
- * and let the OAuth flow complete separately before we try to reconnect them.
3065
+ * Call this once after creating the client. On long-running servers you
3066
+ * can cache the `MultiSessionClient` instance and call `connect()` on
3067
+ * each request already-connected sessions are skipped internally.
3026
3068
  */
3027
- async getActiveSessions() {
3028
- const sessionList = await sessions.list(this.userId);
3029
- const valid = sessionList.filter(
3069
+ async connect() {
3070
+ const sessions2 = await this.fetchActiveSessions();
3071
+ const activeSessionIds = new Set(sessions2.map((s) => s.sessionId));
3072
+ for (const client of this.clients) {
3073
+ if (!activeSessionIds.has(client.getSessionId())) {
3074
+ this.options.onSessionEvicted?.(client.getSessionId());
3075
+ }
3076
+ }
3077
+ this.clients = this.clients.filter((c) => activeSessionIds.has(c.getSessionId()));
3078
+ await this.connectInBatches(sessions2);
3079
+ }
3080
+ /**
3081
+ * Drops all cached `MCPClient` instances and reconnects fresh from storage.
3082
+ *
3083
+ * Call this when downstream MCP servers have expired their transport sessions
3084
+ * (e.g. after a remote server restart) and subsequent tool calls return
3085
+ * "Session not found. Reconnect without session header." errors.
3086
+ *
3087
+ * OAuth tokens are preserved in the storage backend — no re-authentication
3088
+ * is required. Only the in-memory transport sessions are cleared.
3089
+ */
3090
+ async reconnect() {
3091
+ await this.disconnect();
3092
+ await this.connect();
3093
+ }
3094
+ /**
3095
+ * Returns all currently connected `MCPClient` instances.
3096
+ *
3097
+ * Use this to enumerate available tools across all connected servers,
3098
+ * or to route a tool call to the right client by `serverId`.
3099
+ */
3100
+ getClients() {
3101
+ return this.clients;
3102
+ }
3103
+ /**
3104
+ * Removes and disconnects a single session by ID.
3105
+ *
3106
+ * @returns `true` if the session was found and removed, `false` if not found.
3107
+ */
3108
+ async removeSession(sessionId) {
3109
+ const idx = this.clients.findIndex((c) => c.getSessionId() === sessionId);
3110
+ if (idx === -1) return false;
3111
+ const [client] = this.clients.splice(idx, 1);
3112
+ await client.disconnect();
3113
+ return true;
3114
+ }
3115
+ /**
3116
+ * Gracefully disconnects all active MCP clients and clears the internal list.
3117
+ *
3118
+ * For Streamable HTTP sessions, each client sends an HTTP DELETE to its MCP
3119
+ * endpoint per the spec before closing locally. All disconnects run in
3120
+ * parallel so shutdown is not serialised across many sessions.
3121
+ *
3122
+ * Call this during server shutdown or when a user logs out to free up
3123
+ * underlying transport resources (SSE streams, HTTP connections, etc.).
3124
+ */
3125
+ async disconnect() {
3126
+ await Promise.all(this.clients.map((client) => client.disconnect()));
3127
+ this.clients = [];
3128
+ }
3129
+ // -----------------------------------------------------------------------
3130
+ // Internals
3131
+ // -----------------------------------------------------------------------
3132
+ /**
3133
+ * Resolves the list of sessions to connect.
3134
+ *
3135
+ * Uses the custom `sessionProvider` when provided, otherwise falls back
3136
+ * to querying the storage backend via `sessions.list(userId)`.
3137
+ */
3138
+ async fetchActiveSessions() {
3139
+ const sessionList = this.options.sessionProvider ? await this.options.sessionProvider() : await sessions.list(this.userId);
3140
+ return sessionList.filter(
3030
3141
  (s) => s.serverId && s.serverUrl && s.callbackUrl && s.status === "active"
3031
3142
  );
3032
- return valid;
3033
3143
  }
3034
3144
  /**
3035
- * Connects to a list of sessions in controlled batches of `CONNECTION_BATCH_SIZE`.
3145
+ * Connects a list of sessions in controlled batches.
3036
3146
  *
3037
- * Batching prevents overwhelming the event loop or external servers when a user
3038
- * has many active MCP sessions (e.g. 20+ servers). Within each batch, sessions
3039
- * are connected concurrently using `Promise.all` for speed.
3147
+ * Batching prevents overwhelming the event loop or external servers when
3148
+ * a user has many active MCP sessions. Within each batch, sessions are
3149
+ * connected concurrently using `Promise.all`.
3040
3150
  */
3041
3151
  async connectInBatches(sessions2) {
3042
3152
  for (let i = 0; i < sessions2.length; i += CONNECTION_BATCH_SIZE) {
@@ -3045,17 +3155,16 @@ var MultiSessionClient = class {
3045
3155
  }
3046
3156
  }
3047
3157
  /**
3048
- * Connects a single session, with built-in deduplication to prevent race conditions.
3158
+ * Connects a single session, with deduplication to prevent race conditions.
3049
3159
  *
3050
- * - If a client for this session already exists and is connected, returns immediately.
3051
- * - If the existing client entry is no longer connected (e.g. it was explicitly
3052
- * disconnected), it is evicted so that `establishConnectionWithRetries` creates a
3053
- * fresh transport preventing "Client already connected" errors from the SDK.
3054
- * - If a connection attempt for this session is already in-flight (e.g. from a
3055
- * concurrent call), it joins the existing promise instead of starting a new one.
3056
- * This is the key concurrency lock the `connectionPromises` map acts as a
3057
- * per-session mutex so we never spin up two physical connections for the same session.
3058
- * - On completion (success or failure), the promise is cleaned up from the map.
3160
+ * - If a client for this session already exists and is connected, returns
3161
+ * immediately.
3162
+ * - If the existing client entry is no longer connected (e.g. explicit
3163
+ * disconnect), it is evicted so a fresh transport is created.
3164
+ * - If a connection attempt for this session is already in-flight, the
3165
+ * existing promise is reused as a per-session mutex.
3166
+ * - On completion (success or failure), the promise is cleaned up from
3167
+ * the connectionPromises map.
3059
3168
  */
3060
3169
  async connectSession(session) {
3061
3170
  const existing = this.clients.find((c) => c.getSessionId() === session.sessionId);
@@ -3063,6 +3172,7 @@ var MultiSessionClient = class {
3063
3172
  if (existing.isConnected()) {
3064
3173
  return;
3065
3174
  }
3175
+ this.options.onSessionEvicted?.(existing.getSessionId());
3066
3176
  this.clients = this.clients.filter((c) => c !== existing);
3067
3177
  }
3068
3178
  if (this.connectionPromises.has(session.sessionId)) {
@@ -3077,22 +3187,19 @@ var MultiSessionClient = class {
3077
3187
  }
3078
3188
  }
3079
3189
  /**
3080
- * The core connection loop for a single session.
3190
+ * Core connection loop for a single session with retry logic.
3081
3191
  *
3082
- * Attempts to establish a physical MCP connection, retrying up to `maxRetries` times
3083
- * if the connection fails. Each attempt:
3084
- * 1. Creates a fresh `MCPClient` instance from the session data.
3085
- * 2. Races the connect call against a timeout promise — if the server doesn't respond
3086
- * within `timeoutMs`, the attempt is aborted and counted as a failure.
3087
- * 3. On success, replaces any stale client entry for this session in the `clients` array.
3192
+ * 1. Creates a fresh `MCPClient` from the session data.
3193
+ * 2. Races `client.connect()` against a timeout.
3194
+ * 3. On success, replaces any stale entry and fires `onSessionConnected`.
3088
3195
  * 4. On failure, waits `retryDelay` ms before the next attempt.
3089
3196
  *
3090
- * If all attempts are exhausted, logs an error and returns silently (does not throw),
3091
- * so a single bad server doesn't block the rest of the batch from connecting.
3197
+ * If all attempts are exhausted, logs an error and returns silently so
3198
+ * a single bad server doesn't block the rest of the batch.
3092
3199
  */
3093
3200
  async establishConnectionWithRetries(session) {
3094
- const maxRetries = this.options.maxRetries ?? DEFAULT_MAX_RETRIES;
3095
- const retryDelay = this.options.retryDelay ?? DEFAULT_RETRY_DELAY_MS;
3201
+ const maxRetries = this.options.maxRetries;
3202
+ const retryDelay = this.options.retryDelay;
3096
3203
  let lastError;
3097
3204
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
3098
3205
  try {
@@ -3106,10 +3213,13 @@ var MultiSessionClient = class {
3106
3213
  transportType: session.transportType,
3107
3214
  headers: session.headers
3108
3215
  });
3109
- const timeoutMs = this.options.timeout ?? DEFAULT_TIMEOUT_MS;
3216
+ const timeoutMs = this.options.timeout;
3110
3217
  let timeoutTimer;
3111
3218
  const timeoutPromise = new Promise((_, reject) => {
3112
- timeoutTimer = setTimeout(() => reject(new Error(`Connection timed out after ${timeoutMs}ms`)), timeoutMs);
3219
+ timeoutTimer = setTimeout(
3220
+ () => reject(new Error(`Connection timed out after ${timeoutMs}ms`)),
3221
+ timeoutMs
3222
+ );
3113
3223
  });
3114
3224
  try {
3115
3225
  await Promise.race([client.connect(), timeoutPromise]);
@@ -3118,6 +3228,7 @@ var MultiSessionClient = class {
3118
3228
  }
3119
3229
  this.clients = this.clients.filter((c) => c.getSessionId() !== session.sessionId);
3120
3230
  this.clients.push(client);
3231
+ this.options.onSessionConnected?.(session.sessionId, client);
3121
3232
  return;
3122
3233
  } catch (error) {
3123
3234
  lastError = error;
@@ -3126,58 +3237,11 @@ var MultiSessionClient = class {
3126
3237
  }
3127
3238
  }
3128
3239
  }
3129
- console.error(`[MultiSessionClient] Failed to connect to session ${session.sessionId} after ${maxRetries + 1} attempts:`, lastError);
3130
- }
3131
- /**
3132
- * The main entry point. Fetches all active sessions for this userId from
3133
- * storage and establishes connections to all of them in batches.
3134
- *
3135
- * Call this once after creating the client. On traditional servers, you can
3136
- * cache the `MultiSessionClient` instance after calling `connect()` to avoid
3137
- * re-fetching and re-connecting on every request.
3138
- */
3139
- async connect() {
3140
- const sessions2 = await this.getActiveSessions();
3141
- const activeSessionIds = new Set(sessions2.map((s) => s.sessionId));
3142
- this.clients = this.clients.filter((c) => activeSessionIds.has(c.getSessionId()));
3143
- await this.connectInBatches(sessions2);
3144
- }
3145
- /**
3146
- * Drops all cached `MCPClient` instances and reconnects fresh from storage.
3147
- *
3148
- * Call this when downstream MCP servers have expired their transport sessions
3149
- * (e.g. after a remote server restart) and subsequent tool calls return
3150
- * "Session not found. Reconnect without session header." errors.
3151
- *
3152
- * OAuth tokens are preserved in the storage backend — no re-authentication
3153
- * is required. Only the in-memory transport sessions are cleared.
3154
- */
3155
- async reconnect() {
3156
- await this.disconnect();
3157
- await this.connect();
3158
- }
3159
- /**
3160
- * Returns all currently connected `MCPClient` instances.
3161
- *
3162
- * Use this to enumerate available tools across all connected servers,
3163
- * or to route a tool call to the right client by `serverId`.
3164
- */
3165
- getClients() {
3166
- return this.clients;
3167
- }
3168
- /**
3169
- * Gracefully disconnects all active MCP clients and clears the internal client list.
3170
- *
3171
- * For Streamable HTTP sessions, each client sends an HTTP DELETE to its MCP
3172
- * endpoint per the spec before closing locally. All disconnects run in
3173
- * parallel so shutdown is not serialised across many sessions.
3174
- *
3175
- * Call this during server shutdown or when a user logs out to free up
3176
- * underlying transport resources (SSE streams, HTTP connections, etc.).
3177
- */
3178
- async disconnect() {
3179
- await Promise.all(this.clients.map((client) => client.disconnect()));
3180
- this.clients = [];
3240
+ this.options.onSessionFailed?.(session.sessionId, lastError);
3241
+ console.error(
3242
+ `[MultiSessionClient] Failed to connect to session ${session.sessionId} after ${maxRetries + 1} attempts:`,
3243
+ lastError
3244
+ );
3181
3245
  }
3182
3246
  };
3183
3247