@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
package/dist/index.js CHANGED
@@ -175,7 +175,8 @@ var SOFTWARE_VERSION = "1.3.4";
175
175
  var MCP_CLIENT_NAME = "mcp-ts-oauth-client";
176
176
  var MCP_CLIENT_VERSION = "2.0";
177
177
 
178
- // src/server/storage/redis-backend.ts
178
+ // src/shared/utils.ts
179
+ init_cjs_shims();
179
180
  var firstChar = nanoid.customAlphabet(
180
181
  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
181
182
  1
@@ -184,6 +185,18 @@ var rest = nanoid.customAlphabet(
184
185
  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
185
186
  11
186
187
  );
188
+ function sanitizeServerLabel(name) {
189
+ let sanitized = name.replace(/[^a-zA-Z0-9-_]/g, "_").replace(/_{2,}/g, "_").toLowerCase();
190
+ if (!/^[a-zA-Z]/.test(sanitized)) {
191
+ sanitized = "s_" + sanitized;
192
+ }
193
+ return sanitized;
194
+ }
195
+ function generateSessionId() {
196
+ return firstChar() + rest();
197
+ }
198
+
199
+ // src/server/storage/redis-backend.ts
187
200
  var RedisStorageBackend = class {
188
201
  constructor(redis2) {
189
202
  this.redis = redis2;
@@ -242,7 +255,7 @@ var RedisStorageBackend = class {
242
255
  return Array.from(keys);
243
256
  }
244
257
  generateSessionId() {
245
- return firstChar() + rest();
258
+ return generateSessionId();
246
259
  }
247
260
  async createSession(session, ttl) {
248
261
  const { sessionId, identity } = session;
@@ -413,14 +426,6 @@ var RedisStorageBackend = class {
413
426
 
414
427
  // src/server/storage/memory-backend.ts
415
428
  init_cjs_shims();
416
- var firstChar2 = nanoid.customAlphabet(
417
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
418
- 1
419
- );
420
- var rest2 = nanoid.customAlphabet(
421
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
422
- 11
423
- );
424
429
  var MemoryStorageBackend = class {
425
430
  constructor() {
426
431
  // Map<identity:sessionId, SessionData>
@@ -435,7 +440,7 @@ var MemoryStorageBackend = class {
435
440
  return `${identity}:${sessionId}`;
436
441
  }
437
442
  generateSessionId() {
438
- return firstChar2() + rest2();
443
+ return generateSessionId();
439
444
  }
440
445
  async createSession(session, ttl) {
441
446
  const { sessionId, identity } = session;
@@ -509,14 +514,6 @@ var MemoryStorageBackend = class {
509
514
 
510
515
  // src/server/storage/file-backend.ts
511
516
  init_cjs_shims();
512
- var firstChar3 = nanoid.customAlphabet(
513
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
514
- 1
515
- );
516
- var rest3 = nanoid.customAlphabet(
517
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
518
- 11
519
- );
520
517
  var FileStorageBackend = class {
521
518
  /**
522
519
  * @param options.path Path to the JSON file storage (default: ./sessions.json)
@@ -567,7 +564,7 @@ var FileStorageBackend = class {
567
564
  return `${identity}:${sessionId}`;
568
565
  }
569
566
  generateSessionId() {
570
- return firstChar3() + rest3();
567
+ return generateSessionId();
571
568
  }
572
569
  async createSession(session, ttl) {
573
570
  await this.ensureInitialized();
@@ -677,12 +674,7 @@ var SqliteStorage = class {
677
674
  }
678
675
  }
679
676
  generateSessionId() {
680
- const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
681
- let result = "";
682
- for (let i = 0; i < 32; i++) {
683
- result += chars.charAt(Math.floor(Math.random() * chars.length));
684
- }
685
- return result;
677
+ return generateSessionId();
686
678
  }
687
679
  async createSession(session, ttl) {
688
680
  this.ensureInitialized();
@@ -797,7 +789,7 @@ var SupabaseStorageBackend = class {
797
789
  console.log('[mcp-ts][Storage] Supabase: \u2713 "mcp_sessions" table verified.');
798
790
  }
799
791
  generateSessionId() {
800
- return crypto.randomUUID();
792
+ return generateSessionId();
801
793
  }
802
794
  mapRowToSessionData(row) {
803
795
  return {
@@ -1238,16 +1230,6 @@ var StorageOAuthClientProvider = class {
1238
1230
  }
1239
1231
  };
1240
1232
 
1241
- // src/shared/utils.ts
1242
- init_cjs_shims();
1243
- function sanitizeServerLabel(name) {
1244
- let sanitized = name.replace(/[^a-zA-Z0-9-_]/g, "_").replace(/_{2,}/g, "_").toLowerCase();
1245
- if (!/^[a-zA-Z]/.test(sanitized)) {
1246
- sanitized = "s_" + sanitized;
1247
- }
1248
- return sanitized;
1249
- }
1250
-
1251
1233
  // src/shared/events.ts
1252
1234
  init_cjs_shims();
1253
1235
  var Emitter = class {
@@ -2281,47 +2263,132 @@ var MCPClient = class _MCPClient {
2281
2263
 
2282
2264
  // src/server/mcp/multi-session-client.ts
2283
2265
  init_cjs_shims();
2266
+ var DEFAULT_TIMEOUT_MS = 15e3;
2267
+ var DEFAULT_MAX_RETRIES = 2;
2268
+ var DEFAULT_RETRY_DELAY_MS = 1e3;
2269
+ var CONNECTION_BATCH_SIZE = 5;
2284
2270
  var MultiSessionClient = class {
2271
+ /**
2272
+ * Creates a new MultiSessionClient for the given user identity.
2273
+ *
2274
+ * @param identity - A unique string identifying the user (e.g. user ID or email).
2275
+ * @param options - Optional tuning for connection timeout, retry count, and retry delay.
2276
+ * Falls back to sensible defaults if not provided.
2277
+ */
2285
2278
  constructor(identity, options = {}) {
2286
2279
  __publicField(this, "clients", []);
2287
2280
  __publicField(this, "identity");
2288
2281
  __publicField(this, "options");
2282
+ __publicField(this, "connectionPromises", /* @__PURE__ */ new Map());
2289
2283
  this.identity = identity;
2290
2284
  this.options = {
2291
- timeout: 15e3,
2292
- maxRetries: 2,
2293
- retryDelay: 1e3,
2285
+ timeout: DEFAULT_TIMEOUT_MS,
2286
+ maxRetries: DEFAULT_MAX_RETRIES,
2287
+ retryDelay: DEFAULT_RETRY_DELAY_MS,
2294
2288
  ...options
2295
2289
  };
2296
2290
  }
2291
+ /**
2292
+ * Fetches all sessions for this identity from storage and returns only the
2293
+ * ones that are ready to connect.
2294
+ *
2295
+ * A session is considered connectable when:
2296
+ * - It has a `serverId`, `serverUrl`, and `callbackUrl` (i.e. it was fully initialized)
2297
+ * - Its `active` flag is not explicitly `false` — sessions with `active: false` are
2298
+ * either mid-OAuth flow, auth-pending, or previously failed. We skip those here
2299
+ * and let the OAuth flow complete separately before we try to reconnect them.
2300
+ *
2301
+ * Note: Sessions where `active` is `undefined` (legacy records) are included
2302
+ * for backwards compatibility.
2303
+ */
2297
2304
  async getActiveSessions() {
2298
2305
  const sessions = await storage.getIdentitySessionsData(this.identity);
2299
- console.log(
2300
- `[MultiSessionClient] All sessions for ${this.identity}:`,
2301
- sessions.map((s) => ({ sessionId: s.sessionId, serverId: s.serverId }))
2306
+ const valid = sessions.filter(
2307
+ (s) => s.serverId && s.serverUrl && s.callbackUrl && s.active !== false
2308
+ // exclude OAuth-pending / failed sessions
2302
2309
  );
2303
- const valid = sessions.filter((s) => s.serverId && s.serverUrl && s.callbackUrl);
2304
- console.log(`[MultiSessionClient] Filtered valid sessions:`, valid.length);
2305
2310
  return valid;
2306
2311
  }
2312
+ /**
2313
+ * Connects to a list of sessions in controlled batches of `CONNECTION_BATCH_SIZE`.
2314
+ *
2315
+ * Batching prevents overwhelming the event loop or external servers when a user
2316
+ * has many active MCP sessions (e.g. 20+ servers). Within each batch, sessions
2317
+ * are connected concurrently using `Promise.all` for speed.
2318
+ */
2307
2319
  async connectInBatches(sessions) {
2308
- const BATCH_SIZE = 5;
2309
- for (let i = 0; i < sessions.length; i += BATCH_SIZE) {
2310
- const batch = sessions.slice(i, i + BATCH_SIZE);
2320
+ for (let i = 0; i < sessions.length; i += CONNECTION_BATCH_SIZE) {
2321
+ const batch = sessions.slice(i, i + CONNECTION_BATCH_SIZE);
2311
2322
  await Promise.all(batch.map((session) => this.connectSession(session)));
2312
2323
  }
2313
2324
  }
2325
+ /**
2326
+ * Connects a single session, with built-in deduplication to prevent race conditions.
2327
+ *
2328
+ * - If a client for this session already exists and is connected, returns immediately.
2329
+ * - If a connection attempt for this session is already in-flight (e.g. from a
2330
+ * concurrent call), it joins the existing promise instead of starting a new one.
2331
+ * This is the key concurrency lock — the `connectionPromises` map acts as a
2332
+ * per-session mutex so we never spin up two physical connections for the same session.
2333
+ * - On completion (success or failure), the promise is cleaned up from the map.
2334
+ */
2314
2335
  async connectSession(session) {
2315
2336
  const existingClient = this.clients.find((c) => c.getSessionId() === session.sessionId);
2316
2337
  if (existingClient?.isConnected()) {
2317
2338
  return;
2318
2339
  }
2319
- const maxRetries = this.options.maxRetries ?? 2;
2320
- const retryDelay = this.options.retryDelay ?? 1e3;
2340
+ if (this.connectionPromises.has(session.sessionId)) {
2341
+ return this.connectionPromises.get(session.sessionId);
2342
+ }
2343
+ const connectPromise = this.establishConnectionWithRetries(session);
2344
+ this.connectionPromises.set(session.sessionId, connectPromise);
2345
+ try {
2346
+ await connectPromise;
2347
+ } finally {
2348
+ this.connectionPromises.delete(session.sessionId);
2349
+ }
2350
+ }
2351
+ /**
2352
+ * The core connection loop for a single session.
2353
+ *
2354
+ * Attempts to establish a physical MCP connection, retrying up to `maxRetries` times
2355
+ * if the connection fails. Each attempt:
2356
+ * 1. Creates a fresh `MCPClient` instance from the session data.
2357
+ * 2. Races the connect call against a timeout promise — if the server doesn't respond
2358
+ * within `timeoutMs`, the attempt is aborted and counted as a failure.
2359
+ * 3. On success, replaces any stale client entry for this session in the `clients` array.
2360
+ * 4. On failure, waits `retryDelay` ms before the next attempt.
2361
+ *
2362
+ * If all attempts are exhausted, logs an error and returns silently (does not throw),
2363
+ * so a single bad server doesn't block the rest of the batch from connecting.
2364
+ */
2365
+ async establishConnectionWithRetries(session) {
2366
+ const maxRetries = this.options.maxRetries ?? DEFAULT_MAX_RETRIES;
2367
+ const retryDelay = this.options.retryDelay ?? DEFAULT_RETRY_DELAY_MS;
2321
2368
  let lastError;
2322
2369
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
2323
2370
  try {
2324
- const client = await this.createAndConnectClient(session);
2371
+ const client = new MCPClient({
2372
+ identity: this.identity,
2373
+ sessionId: session.sessionId,
2374
+ serverId: session.serverId,
2375
+ serverUrl: session.serverUrl,
2376
+ callbackUrl: session.callbackUrl,
2377
+ serverName: session.serverName,
2378
+ transportType: session.transportType,
2379
+ headers: session.headers
2380
+ });
2381
+ const timeoutMs = this.options.timeout ?? DEFAULT_TIMEOUT_MS;
2382
+ let timeoutTimer;
2383
+ const timeoutPromise = new Promise((_, reject) => {
2384
+ timeoutTimer = setTimeout(() => reject(new Error(`Connection timed out after ${timeoutMs}ms`)), timeoutMs);
2385
+ });
2386
+ try {
2387
+ await Promise.race([client.connect(), timeoutPromise]);
2388
+ } finally {
2389
+ clearTimeout(timeoutTimer);
2390
+ }
2391
+ this.clients = this.clients.filter((c) => c.getSessionId() !== session.sessionId);
2325
2392
  this.clients.push(client);
2326
2393
  return;
2327
2394
  } catch (error) {
@@ -2333,36 +2400,32 @@ var MultiSessionClient = class {
2333
2400
  }
2334
2401
  console.error(`[MultiSessionClient] Failed to connect to session ${session.sessionId} after ${maxRetries + 1} attempts:`, lastError);
2335
2402
  }
2336
- async createAndConnectClient(session) {
2337
- const client = new MCPClient({
2338
- identity: this.identity,
2339
- sessionId: session.sessionId,
2340
- serverId: session.serverId,
2341
- serverUrl: session.serverUrl,
2342
- callbackUrl: session.callbackUrl,
2343
- serverName: session.serverName,
2344
- transportType: session.transportType,
2345
- headers: session.headers
2346
- });
2347
- const timeoutMs = this.options.timeout ?? 15e3;
2348
- const timeoutPromise = new Promise((_, reject) => {
2349
- setTimeout(() => reject(new Error(`Connection timed out after ${timeoutMs}ms`)), timeoutMs);
2350
- });
2351
- await Promise.race([client.connect(), timeoutPromise]);
2352
- return client;
2353
- }
2403
+ /**
2404
+ * The main entry point. Fetches all active sessions for this identity from
2405
+ * storage and establishes connections to all of them in batches.
2406
+ *
2407
+ * Call this once after creating the client. On traditional servers, you can
2408
+ * cache the `MultiSessionClient` instance after calling `connect()` to avoid
2409
+ * re-fetching and re-connecting on every request.
2410
+ */
2354
2411
  async connect() {
2355
2412
  const sessions = await this.getActiveSessions();
2356
2413
  await this.connectInBatches(sessions);
2357
2414
  }
2358
2415
  /**
2359
- * Returns the array of currently connected clients.
2416
+ * Returns all currently connected `MCPClient` instances.
2417
+ *
2418
+ * Use this to enumerate available tools across all connected servers,
2419
+ * or to route a tool call to the right client by `serverId`.
2360
2420
  */
2361
2421
  getClients() {
2362
2422
  return this.clients;
2363
2423
  }
2364
2424
  /**
2365
- * Disconnects all clients.
2425
+ * Gracefully disconnects all active MCP clients and clears the internal client list.
2426
+ *
2427
+ * Call this during server shutdown or when a user logs out to free up
2428
+ * underlying transport resources (SSE streams, HTTP connections, etc.).
2366
2429
  */
2367
2430
  disconnect() {
2368
2431
  this.clients.forEach((client) => client.disconnect());