@mcp-ts/sdk 1.3.6 → 1.3.7

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 (59) hide show
  1. package/dist/adapters/agui-adapter.d.mts +1 -1
  2. package/dist/adapters/agui-adapter.d.ts +1 -1
  3. package/dist/adapters/agui-adapter.js +2 -2
  4. package/dist/adapters/agui-adapter.js.map +1 -1
  5. package/dist/adapters/agui-adapter.mjs +2 -2
  6. package/dist/adapters/agui-adapter.mjs.map +1 -1
  7. package/dist/adapters/agui-middleware.d.mts +1 -1
  8. package/dist/adapters/agui-middleware.d.ts +1 -1
  9. package/dist/adapters/agui-middleware.js.map +1 -1
  10. package/dist/adapters/agui-middleware.mjs.map +1 -1
  11. package/dist/adapters/ai-adapter.d.mts +1 -1
  12. package/dist/adapters/ai-adapter.d.ts +1 -1
  13. package/dist/adapters/ai-adapter.js +1 -1
  14. package/dist/adapters/ai-adapter.js.map +1 -1
  15. package/dist/adapters/ai-adapter.mjs +1 -1
  16. package/dist/adapters/ai-adapter.mjs.map +1 -1
  17. package/dist/adapters/langchain-adapter.d.mts +1 -1
  18. package/dist/adapters/langchain-adapter.d.ts +1 -1
  19. package/dist/adapters/langchain-adapter.js +1 -1
  20. package/dist/adapters/langchain-adapter.js.map +1 -1
  21. package/dist/adapters/langchain-adapter.mjs +1 -1
  22. package/dist/adapters/langchain-adapter.mjs.map +1 -1
  23. package/dist/adapters/mastra-adapter.d.mts +1 -1
  24. package/dist/adapters/mastra-adapter.d.ts +1 -1
  25. package/dist/adapters/mastra-adapter.js +1 -1
  26. package/dist/adapters/mastra-adapter.js.map +1 -1
  27. package/dist/adapters/mastra-adapter.mjs +1 -1
  28. package/dist/adapters/mastra-adapter.mjs.map +1 -1
  29. package/dist/index.d.mts +1 -1
  30. package/dist/index.d.ts +1 -1
  31. package/dist/index.js +134 -71
  32. package/dist/index.js.map +1 -1
  33. package/dist/index.mjs +134 -71
  34. package/dist/index.mjs.map +1 -1
  35. package/dist/{multi-session-client-BYLarghq.d.ts → multi-session-client-CHE8QpVE.d.ts} +75 -5
  36. package/dist/{multi-session-client-CzhMkE0k.d.mts → multi-session-client-CQsRbxYI.d.mts} +75 -5
  37. package/dist/server/index.d.mts +1 -1
  38. package/dist/server/index.d.ts +1 -1
  39. package/dist/server/index.js +134 -71
  40. package/dist/server/index.js.map +1 -1
  41. package/dist/server/index.mjs +134 -71
  42. package/dist/server/index.mjs.map +1 -1
  43. package/dist/shared/index.js +10 -2
  44. package/dist/shared/index.js.map +1 -1
  45. package/dist/shared/index.mjs +10 -2
  46. package/dist/shared/index.mjs.map +1 -1
  47. package/package.json +2 -2
  48. package/src/adapters/agui-adapter.ts +222 -222
  49. package/src/adapters/ai-adapter.ts +115 -115
  50. package/src/adapters/langchain-adapter.ts +127 -127
  51. package/src/adapters/mastra-adapter.ts +126 -126
  52. package/src/server/mcp/multi-session-client.ts +135 -39
  53. package/src/server/storage/file-backend.ts +3 -16
  54. package/src/server/storage/index.ts +1 -0
  55. package/src/server/storage/memory-backend.ts +3 -16
  56. package/src/server/storage/redis-backend.ts +3 -16
  57. package/src/server/storage/sqlite-backend.ts +2 -6
  58. package/src/server/storage/supabase-backend.ts +2 -1
  59. package/src/shared/utils.ts +22 -0
@@ -123,8 +123,6 @@ var SOFTWARE_ID = "@mcp-ts";
123
123
  var SOFTWARE_VERSION = "1.3.4";
124
124
  var MCP_CLIENT_NAME = "mcp-ts-oauth-client";
125
125
  var MCP_CLIENT_VERSION = "2.0";
126
-
127
- // src/server/storage/redis-backend.ts
128
126
  var firstChar = customAlphabet(
129
127
  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
130
128
  1
@@ -133,6 +131,18 @@ var rest = customAlphabet(
133
131
  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
134
132
  11
135
133
  );
134
+ function sanitizeServerLabel(name) {
135
+ let sanitized = name.replace(/[^a-zA-Z0-9-_]/g, "_").replace(/_{2,}/g, "_").toLowerCase();
136
+ if (!/^[a-zA-Z]/.test(sanitized)) {
137
+ sanitized = "s_" + sanitized;
138
+ }
139
+ return sanitized;
140
+ }
141
+ function generateSessionId() {
142
+ return firstChar() + rest();
143
+ }
144
+
145
+ // src/server/storage/redis-backend.ts
136
146
  var RedisStorageBackend = class {
137
147
  constructor(redis2) {
138
148
  this.redis = redis2;
@@ -191,7 +201,7 @@ var RedisStorageBackend = class {
191
201
  return Array.from(keys);
192
202
  }
193
203
  generateSessionId() {
194
- return firstChar() + rest();
204
+ return generateSessionId();
195
205
  }
196
206
  async createSession(session, ttl) {
197
207
  const { sessionId, identity } = session;
@@ -359,14 +369,8 @@ var RedisStorageBackend = class {
359
369
  }
360
370
  }
361
371
  };
362
- var firstChar2 = customAlphabet(
363
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
364
- 1
365
- );
366
- var rest2 = customAlphabet(
367
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
368
- 11
369
- );
372
+
373
+ // src/server/storage/memory-backend.ts
370
374
  var MemoryStorageBackend = class {
371
375
  constructor() {
372
376
  // Map<identity:sessionId, SessionData>
@@ -381,7 +385,7 @@ var MemoryStorageBackend = class {
381
385
  return `${identity}:${sessionId}`;
382
386
  }
383
387
  generateSessionId() {
384
- return firstChar2() + rest2();
388
+ return generateSessionId();
385
389
  }
386
390
  async createSession(session, ttl) {
387
391
  const { sessionId, identity } = session;
@@ -452,14 +456,6 @@ var MemoryStorageBackend = class {
452
456
  async disconnect() {
453
457
  }
454
458
  };
455
- var firstChar3 = customAlphabet(
456
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
457
- 1
458
- );
459
- var rest3 = customAlphabet(
460
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
461
- 11
462
- );
463
459
  var FileStorageBackend = class {
464
460
  /**
465
461
  * @param options.path Path to the JSON file storage (default: ./sessions.json)
@@ -510,7 +506,7 @@ var FileStorageBackend = class {
510
506
  return `${identity}:${sessionId}`;
511
507
  }
512
508
  generateSessionId() {
513
- return firstChar3() + rest3();
509
+ return generateSessionId();
514
510
  }
515
511
  async createSession(session, ttl) {
516
512
  await this.ensureInitialized();
@@ -617,12 +613,7 @@ var SqliteStorage = class {
617
613
  }
618
614
  }
619
615
  generateSessionId() {
620
- const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
621
- let result = "";
622
- for (let i = 0; i < 32; i++) {
623
- result += chars.charAt(Math.floor(Math.random() * chars.length));
624
- }
625
- return result;
616
+ return generateSessionId();
626
617
  }
627
618
  async createSession(session, ttl) {
628
619
  this.ensureInitialized();
@@ -736,7 +727,7 @@ var SupabaseStorageBackend = class {
736
727
  console.log('[mcp-ts][Storage] Supabase: \u2713 "mcp_sessions" table verified.');
737
728
  }
738
729
  generateSessionId() {
739
- return crypto.randomUUID();
730
+ return generateSessionId();
740
731
  }
741
732
  mapRowToSessionData(row) {
742
733
  return {
@@ -1174,15 +1165,6 @@ var StorageOAuthClientProvider = class {
1174
1165
  }
1175
1166
  };
1176
1167
 
1177
- // src/shared/utils.ts
1178
- function sanitizeServerLabel(name) {
1179
- let sanitized = name.replace(/[^a-zA-Z0-9-_]/g, "_").replace(/_{2,}/g, "_").toLowerCase();
1180
- if (!/^[a-zA-Z]/.test(sanitized)) {
1181
- sanitized = "s_" + sanitized;
1182
- }
1183
- return sanitized;
1184
- }
1185
-
1186
1168
  // src/shared/events.ts
1187
1169
  var Emitter = class {
1188
1170
  constructor() {
@@ -2145,47 +2127,132 @@ var MCPClient = class _MCPClient {
2145
2127
  };
2146
2128
 
2147
2129
  // src/server/mcp/multi-session-client.ts
2130
+ var DEFAULT_TIMEOUT_MS = 15e3;
2131
+ var DEFAULT_MAX_RETRIES = 2;
2132
+ var DEFAULT_RETRY_DELAY_MS = 1e3;
2133
+ var CONNECTION_BATCH_SIZE = 5;
2148
2134
  var MultiSessionClient = class {
2135
+ /**
2136
+ * Creates a new MultiSessionClient for the given user identity.
2137
+ *
2138
+ * @param identity - A unique string identifying the user (e.g. user ID or email).
2139
+ * @param options - Optional tuning for connection timeout, retry count, and retry delay.
2140
+ * Falls back to sensible defaults if not provided.
2141
+ */
2149
2142
  constructor(identity, options = {}) {
2150
2143
  __publicField(this, "clients", []);
2151
2144
  __publicField(this, "identity");
2152
2145
  __publicField(this, "options");
2146
+ __publicField(this, "connectionPromises", /* @__PURE__ */ new Map());
2153
2147
  this.identity = identity;
2154
2148
  this.options = {
2155
- timeout: 15e3,
2156
- maxRetries: 2,
2157
- retryDelay: 1e3,
2149
+ timeout: DEFAULT_TIMEOUT_MS,
2150
+ maxRetries: DEFAULT_MAX_RETRIES,
2151
+ retryDelay: DEFAULT_RETRY_DELAY_MS,
2158
2152
  ...options
2159
2153
  };
2160
2154
  }
2155
+ /**
2156
+ * Fetches all sessions for this identity from storage and returns only the
2157
+ * ones that are ready to connect.
2158
+ *
2159
+ * A session is considered connectable when:
2160
+ * - It has a `serverId`, `serverUrl`, and `callbackUrl` (i.e. it was fully initialized)
2161
+ * - Its `active` flag is not explicitly `false` — sessions with `active: false` are
2162
+ * either mid-OAuth flow, auth-pending, or previously failed. We skip those here
2163
+ * and let the OAuth flow complete separately before we try to reconnect them.
2164
+ *
2165
+ * Note: Sessions where `active` is `undefined` (legacy records) are included
2166
+ * for backwards compatibility.
2167
+ */
2161
2168
  async getActiveSessions() {
2162
2169
  const sessions = await storage.getIdentitySessionsData(this.identity);
2163
- console.log(
2164
- `[MultiSessionClient] All sessions for ${this.identity}:`,
2165
- sessions.map((s) => ({ sessionId: s.sessionId, serverId: s.serverId }))
2170
+ const valid = sessions.filter(
2171
+ (s) => s.serverId && s.serverUrl && s.callbackUrl && s.active !== false
2172
+ // exclude OAuth-pending / failed sessions
2166
2173
  );
2167
- const valid = sessions.filter((s) => s.serverId && s.serverUrl && s.callbackUrl);
2168
- console.log(`[MultiSessionClient] Filtered valid sessions:`, valid.length);
2169
2174
  return valid;
2170
2175
  }
2176
+ /**
2177
+ * Connects to a list of sessions in controlled batches of `CONNECTION_BATCH_SIZE`.
2178
+ *
2179
+ * Batching prevents overwhelming the event loop or external servers when a user
2180
+ * has many active MCP sessions (e.g. 20+ servers). Within each batch, sessions
2181
+ * are connected concurrently using `Promise.all` for speed.
2182
+ */
2171
2183
  async connectInBatches(sessions) {
2172
- const BATCH_SIZE = 5;
2173
- for (let i = 0; i < sessions.length; i += BATCH_SIZE) {
2174
- const batch = sessions.slice(i, i + BATCH_SIZE);
2184
+ for (let i = 0; i < sessions.length; i += CONNECTION_BATCH_SIZE) {
2185
+ const batch = sessions.slice(i, i + CONNECTION_BATCH_SIZE);
2175
2186
  await Promise.all(batch.map((session) => this.connectSession(session)));
2176
2187
  }
2177
2188
  }
2189
+ /**
2190
+ * Connects a single session, with built-in deduplication to prevent race conditions.
2191
+ *
2192
+ * - If a client for this session already exists and is connected, returns immediately.
2193
+ * - If a connection attempt for this session is already in-flight (e.g. from a
2194
+ * concurrent call), it joins the existing promise instead of starting a new one.
2195
+ * This is the key concurrency lock — the `connectionPromises` map acts as a
2196
+ * per-session mutex so we never spin up two physical connections for the same session.
2197
+ * - On completion (success or failure), the promise is cleaned up from the map.
2198
+ */
2178
2199
  async connectSession(session) {
2179
2200
  const existingClient = this.clients.find((c) => c.getSessionId() === session.sessionId);
2180
2201
  if (existingClient?.isConnected()) {
2181
2202
  return;
2182
2203
  }
2183
- const maxRetries = this.options.maxRetries ?? 2;
2184
- const retryDelay = this.options.retryDelay ?? 1e3;
2204
+ if (this.connectionPromises.has(session.sessionId)) {
2205
+ return this.connectionPromises.get(session.sessionId);
2206
+ }
2207
+ const connectPromise = this.establishConnectionWithRetries(session);
2208
+ this.connectionPromises.set(session.sessionId, connectPromise);
2209
+ try {
2210
+ await connectPromise;
2211
+ } finally {
2212
+ this.connectionPromises.delete(session.sessionId);
2213
+ }
2214
+ }
2215
+ /**
2216
+ * The core connection loop for a single session.
2217
+ *
2218
+ * Attempts to establish a physical MCP connection, retrying up to `maxRetries` times
2219
+ * if the connection fails. Each attempt:
2220
+ * 1. Creates a fresh `MCPClient` instance from the session data.
2221
+ * 2. Races the connect call against a timeout promise — if the server doesn't respond
2222
+ * within `timeoutMs`, the attempt is aborted and counted as a failure.
2223
+ * 3. On success, replaces any stale client entry for this session in the `clients` array.
2224
+ * 4. On failure, waits `retryDelay` ms before the next attempt.
2225
+ *
2226
+ * If all attempts are exhausted, logs an error and returns silently (does not throw),
2227
+ * so a single bad server doesn't block the rest of the batch from connecting.
2228
+ */
2229
+ async establishConnectionWithRetries(session) {
2230
+ const maxRetries = this.options.maxRetries ?? DEFAULT_MAX_RETRIES;
2231
+ const retryDelay = this.options.retryDelay ?? DEFAULT_RETRY_DELAY_MS;
2185
2232
  let lastError;
2186
2233
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
2187
2234
  try {
2188
- const client = await this.createAndConnectClient(session);
2235
+ const client = new MCPClient({
2236
+ identity: this.identity,
2237
+ sessionId: session.sessionId,
2238
+ serverId: session.serverId,
2239
+ serverUrl: session.serverUrl,
2240
+ callbackUrl: session.callbackUrl,
2241
+ serverName: session.serverName,
2242
+ transportType: session.transportType,
2243
+ headers: session.headers
2244
+ });
2245
+ const timeoutMs = this.options.timeout ?? DEFAULT_TIMEOUT_MS;
2246
+ let timeoutTimer;
2247
+ const timeoutPromise = new Promise((_, reject) => {
2248
+ timeoutTimer = setTimeout(() => reject(new Error(`Connection timed out after ${timeoutMs}ms`)), timeoutMs);
2249
+ });
2250
+ try {
2251
+ await Promise.race([client.connect(), timeoutPromise]);
2252
+ } finally {
2253
+ clearTimeout(timeoutTimer);
2254
+ }
2255
+ this.clients = this.clients.filter((c) => c.getSessionId() !== session.sessionId);
2189
2256
  this.clients.push(client);
2190
2257
  return;
2191
2258
  } catch (error) {
@@ -2197,36 +2264,32 @@ var MultiSessionClient = class {
2197
2264
  }
2198
2265
  console.error(`[MultiSessionClient] Failed to connect to session ${session.sessionId} after ${maxRetries + 1} attempts:`, lastError);
2199
2266
  }
2200
- async createAndConnectClient(session) {
2201
- const client = new MCPClient({
2202
- identity: this.identity,
2203
- sessionId: session.sessionId,
2204
- serverId: session.serverId,
2205
- serverUrl: session.serverUrl,
2206
- callbackUrl: session.callbackUrl,
2207
- serverName: session.serverName,
2208
- transportType: session.transportType,
2209
- headers: session.headers
2210
- });
2211
- const timeoutMs = this.options.timeout ?? 15e3;
2212
- const timeoutPromise = new Promise((_, reject) => {
2213
- setTimeout(() => reject(new Error(`Connection timed out after ${timeoutMs}ms`)), timeoutMs);
2214
- });
2215
- await Promise.race([client.connect(), timeoutPromise]);
2216
- return client;
2217
- }
2267
+ /**
2268
+ * The main entry point. Fetches all active sessions for this identity from
2269
+ * storage and establishes connections to all of them in batches.
2270
+ *
2271
+ * Call this once after creating the client. On traditional servers, you can
2272
+ * cache the `MultiSessionClient` instance after calling `connect()` to avoid
2273
+ * re-fetching and re-connecting on every request.
2274
+ */
2218
2275
  async connect() {
2219
2276
  const sessions = await this.getActiveSessions();
2220
2277
  await this.connectInBatches(sessions);
2221
2278
  }
2222
2279
  /**
2223
- * Returns the array of currently connected clients.
2280
+ * Returns all currently connected `MCPClient` instances.
2281
+ *
2282
+ * Use this to enumerate available tools across all connected servers,
2283
+ * or to route a tool call to the right client by `serverId`.
2224
2284
  */
2225
2285
  getClients() {
2226
2286
  return this.clients;
2227
2287
  }
2228
2288
  /**
2229
- * Disconnects all clients.
2289
+ * Gracefully disconnects all active MCP clients and clears the internal client list.
2290
+ *
2291
+ * Call this during server shutdown or when a user logs out to free up
2292
+ * underlying transport resources (SSE streams, HTTP connections, etc.).
2230
2293
  */
2231
2294
  disconnect() {
2232
2295
  this.clients.forEach((client) => client.disconnect());