@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.mjs CHANGED
@@ -126,8 +126,6 @@ var SOFTWARE_ID = "@mcp-ts";
126
126
  var SOFTWARE_VERSION = "1.3.4";
127
127
  var MCP_CLIENT_NAME = "mcp-ts-oauth-client";
128
128
  var MCP_CLIENT_VERSION = "2.0";
129
-
130
- // src/server/storage/redis-backend.ts
131
129
  var firstChar = customAlphabet(
132
130
  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
133
131
  1
@@ -136,6 +134,18 @@ var rest = customAlphabet(
136
134
  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
137
135
  11
138
136
  );
137
+ function sanitizeServerLabel(name) {
138
+ let sanitized = name.replace(/[^a-zA-Z0-9-_]/g, "_").replace(/_{2,}/g, "_").toLowerCase();
139
+ if (!/^[a-zA-Z]/.test(sanitized)) {
140
+ sanitized = "s_" + sanitized;
141
+ }
142
+ return sanitized;
143
+ }
144
+ function generateSessionId() {
145
+ return firstChar() + rest();
146
+ }
147
+
148
+ // src/server/storage/redis-backend.ts
139
149
  var RedisStorageBackend = class {
140
150
  constructor(redis2) {
141
151
  this.redis = redis2;
@@ -194,7 +204,7 @@ var RedisStorageBackend = class {
194
204
  return Array.from(keys);
195
205
  }
196
206
  generateSessionId() {
197
- return firstChar() + rest();
207
+ return generateSessionId();
198
208
  }
199
209
  async createSession(session, ttl) {
200
210
  const { sessionId, identity } = session;
@@ -362,14 +372,8 @@ var RedisStorageBackend = class {
362
372
  }
363
373
  }
364
374
  };
365
- var firstChar2 = customAlphabet(
366
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
367
- 1
368
- );
369
- var rest2 = customAlphabet(
370
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
371
- 11
372
- );
375
+
376
+ // src/server/storage/memory-backend.ts
373
377
  var MemoryStorageBackend = class {
374
378
  constructor() {
375
379
  // Map<identity:sessionId, SessionData>
@@ -384,7 +388,7 @@ var MemoryStorageBackend = class {
384
388
  return `${identity}:${sessionId}`;
385
389
  }
386
390
  generateSessionId() {
387
- return firstChar2() + rest2();
391
+ return generateSessionId();
388
392
  }
389
393
  async createSession(session, ttl) {
390
394
  const { sessionId, identity } = session;
@@ -455,14 +459,6 @@ var MemoryStorageBackend = class {
455
459
  async disconnect() {
456
460
  }
457
461
  };
458
- var firstChar3 = customAlphabet(
459
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
460
- 1
461
- );
462
- var rest3 = customAlphabet(
463
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
464
- 11
465
- );
466
462
  var FileStorageBackend = class {
467
463
  /**
468
464
  * @param options.path Path to the JSON file storage (default: ./sessions.json)
@@ -513,7 +509,7 @@ var FileStorageBackend = class {
513
509
  return `${identity}:${sessionId}`;
514
510
  }
515
511
  generateSessionId() {
516
- return firstChar3() + rest3();
512
+ return generateSessionId();
517
513
  }
518
514
  async createSession(session, ttl) {
519
515
  await this.ensureInitialized();
@@ -620,12 +616,7 @@ var SqliteStorage = class {
620
616
  }
621
617
  }
622
618
  generateSessionId() {
623
- const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
624
- let result = "";
625
- for (let i = 0; i < 32; i++) {
626
- result += chars.charAt(Math.floor(Math.random() * chars.length));
627
- }
628
- return result;
619
+ return generateSessionId();
629
620
  }
630
621
  async createSession(session, ttl) {
631
622
  this.ensureInitialized();
@@ -739,7 +730,7 @@ var SupabaseStorageBackend = class {
739
730
  console.log('[mcp-ts][Storage] Supabase: \u2713 "mcp_sessions" table verified.');
740
731
  }
741
732
  generateSessionId() {
742
- return crypto.randomUUID();
733
+ return generateSessionId();
743
734
  }
744
735
  mapRowToSessionData(row) {
745
736
  return {
@@ -1177,15 +1168,6 @@ var StorageOAuthClientProvider = class {
1177
1168
  }
1178
1169
  };
1179
1170
 
1180
- // src/shared/utils.ts
1181
- function sanitizeServerLabel(name) {
1182
- let sanitized = name.replace(/[^a-zA-Z0-9-_]/g, "_").replace(/_{2,}/g, "_").toLowerCase();
1183
- if (!/^[a-zA-Z]/.test(sanitized)) {
1184
- sanitized = "s_" + sanitized;
1185
- }
1186
- return sanitized;
1187
- }
1188
-
1189
1171
  // src/shared/events.ts
1190
1172
  var Emitter = class {
1191
1173
  constructor() {
@@ -2216,47 +2198,132 @@ var MCPClient = class _MCPClient {
2216
2198
  };
2217
2199
 
2218
2200
  // src/server/mcp/multi-session-client.ts
2201
+ var DEFAULT_TIMEOUT_MS = 15e3;
2202
+ var DEFAULT_MAX_RETRIES = 2;
2203
+ var DEFAULT_RETRY_DELAY_MS = 1e3;
2204
+ var CONNECTION_BATCH_SIZE = 5;
2219
2205
  var MultiSessionClient = class {
2206
+ /**
2207
+ * Creates a new MultiSessionClient for the given user identity.
2208
+ *
2209
+ * @param identity - A unique string identifying the user (e.g. user ID or email).
2210
+ * @param options - Optional tuning for connection timeout, retry count, and retry delay.
2211
+ * Falls back to sensible defaults if not provided.
2212
+ */
2220
2213
  constructor(identity, options = {}) {
2221
2214
  __publicField(this, "clients", []);
2222
2215
  __publicField(this, "identity");
2223
2216
  __publicField(this, "options");
2217
+ __publicField(this, "connectionPromises", /* @__PURE__ */ new Map());
2224
2218
  this.identity = identity;
2225
2219
  this.options = {
2226
- timeout: 15e3,
2227
- maxRetries: 2,
2228
- retryDelay: 1e3,
2220
+ timeout: DEFAULT_TIMEOUT_MS,
2221
+ maxRetries: DEFAULT_MAX_RETRIES,
2222
+ retryDelay: DEFAULT_RETRY_DELAY_MS,
2229
2223
  ...options
2230
2224
  };
2231
2225
  }
2226
+ /**
2227
+ * Fetches all sessions for this identity from storage and returns only the
2228
+ * ones that are ready to connect.
2229
+ *
2230
+ * A session is considered connectable when:
2231
+ * - It has a `serverId`, `serverUrl`, and `callbackUrl` (i.e. it was fully initialized)
2232
+ * - Its `active` flag is not explicitly `false` — sessions with `active: false` are
2233
+ * either mid-OAuth flow, auth-pending, or previously failed. We skip those here
2234
+ * and let the OAuth flow complete separately before we try to reconnect them.
2235
+ *
2236
+ * Note: Sessions where `active` is `undefined` (legacy records) are included
2237
+ * for backwards compatibility.
2238
+ */
2232
2239
  async getActiveSessions() {
2233
2240
  const sessions = await storage.getIdentitySessionsData(this.identity);
2234
- console.log(
2235
- `[MultiSessionClient] All sessions for ${this.identity}:`,
2236
- sessions.map((s) => ({ sessionId: s.sessionId, serverId: s.serverId }))
2241
+ const valid = sessions.filter(
2242
+ (s) => s.serverId && s.serverUrl && s.callbackUrl && s.active !== false
2243
+ // exclude OAuth-pending / failed sessions
2237
2244
  );
2238
- const valid = sessions.filter((s) => s.serverId && s.serverUrl && s.callbackUrl);
2239
- console.log(`[MultiSessionClient] Filtered valid sessions:`, valid.length);
2240
2245
  return valid;
2241
2246
  }
2247
+ /**
2248
+ * Connects to a list of sessions in controlled batches of `CONNECTION_BATCH_SIZE`.
2249
+ *
2250
+ * Batching prevents overwhelming the event loop or external servers when a user
2251
+ * has many active MCP sessions (e.g. 20+ servers). Within each batch, sessions
2252
+ * are connected concurrently using `Promise.all` for speed.
2253
+ */
2242
2254
  async connectInBatches(sessions) {
2243
- const BATCH_SIZE = 5;
2244
- for (let i = 0; i < sessions.length; i += BATCH_SIZE) {
2245
- const batch = sessions.slice(i, i + BATCH_SIZE);
2255
+ for (let i = 0; i < sessions.length; i += CONNECTION_BATCH_SIZE) {
2256
+ const batch = sessions.slice(i, i + CONNECTION_BATCH_SIZE);
2246
2257
  await Promise.all(batch.map((session) => this.connectSession(session)));
2247
2258
  }
2248
2259
  }
2260
+ /**
2261
+ * Connects a single session, with built-in deduplication to prevent race conditions.
2262
+ *
2263
+ * - If a client for this session already exists and is connected, returns immediately.
2264
+ * - If a connection attempt for this session is already in-flight (e.g. from a
2265
+ * concurrent call), it joins the existing promise instead of starting a new one.
2266
+ * This is the key concurrency lock — the `connectionPromises` map acts as a
2267
+ * per-session mutex so we never spin up two physical connections for the same session.
2268
+ * - On completion (success or failure), the promise is cleaned up from the map.
2269
+ */
2249
2270
  async connectSession(session) {
2250
2271
  const existingClient = this.clients.find((c) => c.getSessionId() === session.sessionId);
2251
2272
  if (existingClient?.isConnected()) {
2252
2273
  return;
2253
2274
  }
2254
- const maxRetries = this.options.maxRetries ?? 2;
2255
- const retryDelay = this.options.retryDelay ?? 1e3;
2275
+ if (this.connectionPromises.has(session.sessionId)) {
2276
+ return this.connectionPromises.get(session.sessionId);
2277
+ }
2278
+ const connectPromise = this.establishConnectionWithRetries(session);
2279
+ this.connectionPromises.set(session.sessionId, connectPromise);
2280
+ try {
2281
+ await connectPromise;
2282
+ } finally {
2283
+ this.connectionPromises.delete(session.sessionId);
2284
+ }
2285
+ }
2286
+ /**
2287
+ * The core connection loop for a single session.
2288
+ *
2289
+ * Attempts to establish a physical MCP connection, retrying up to `maxRetries` times
2290
+ * if the connection fails. Each attempt:
2291
+ * 1. Creates a fresh `MCPClient` instance from the session data.
2292
+ * 2. Races the connect call against a timeout promise — if the server doesn't respond
2293
+ * within `timeoutMs`, the attempt is aborted and counted as a failure.
2294
+ * 3. On success, replaces any stale client entry for this session in the `clients` array.
2295
+ * 4. On failure, waits `retryDelay` ms before the next attempt.
2296
+ *
2297
+ * If all attempts are exhausted, logs an error and returns silently (does not throw),
2298
+ * so a single bad server doesn't block the rest of the batch from connecting.
2299
+ */
2300
+ async establishConnectionWithRetries(session) {
2301
+ const maxRetries = this.options.maxRetries ?? DEFAULT_MAX_RETRIES;
2302
+ const retryDelay = this.options.retryDelay ?? DEFAULT_RETRY_DELAY_MS;
2256
2303
  let lastError;
2257
2304
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
2258
2305
  try {
2259
- const client = await this.createAndConnectClient(session);
2306
+ const client = new MCPClient({
2307
+ identity: this.identity,
2308
+ sessionId: session.sessionId,
2309
+ serverId: session.serverId,
2310
+ serverUrl: session.serverUrl,
2311
+ callbackUrl: session.callbackUrl,
2312
+ serverName: session.serverName,
2313
+ transportType: session.transportType,
2314
+ headers: session.headers
2315
+ });
2316
+ const timeoutMs = this.options.timeout ?? DEFAULT_TIMEOUT_MS;
2317
+ let timeoutTimer;
2318
+ const timeoutPromise = new Promise((_, reject) => {
2319
+ timeoutTimer = setTimeout(() => reject(new Error(`Connection timed out after ${timeoutMs}ms`)), timeoutMs);
2320
+ });
2321
+ try {
2322
+ await Promise.race([client.connect(), timeoutPromise]);
2323
+ } finally {
2324
+ clearTimeout(timeoutTimer);
2325
+ }
2326
+ this.clients = this.clients.filter((c) => c.getSessionId() !== session.sessionId);
2260
2327
  this.clients.push(client);
2261
2328
  return;
2262
2329
  } catch (error) {
@@ -2268,36 +2335,32 @@ var MultiSessionClient = class {
2268
2335
  }
2269
2336
  console.error(`[MultiSessionClient] Failed to connect to session ${session.sessionId} after ${maxRetries + 1} attempts:`, lastError);
2270
2337
  }
2271
- async createAndConnectClient(session) {
2272
- const client = new MCPClient({
2273
- identity: this.identity,
2274
- sessionId: session.sessionId,
2275
- serverId: session.serverId,
2276
- serverUrl: session.serverUrl,
2277
- callbackUrl: session.callbackUrl,
2278
- serverName: session.serverName,
2279
- transportType: session.transportType,
2280
- headers: session.headers
2281
- });
2282
- const timeoutMs = this.options.timeout ?? 15e3;
2283
- const timeoutPromise = new Promise((_, reject) => {
2284
- setTimeout(() => reject(new Error(`Connection timed out after ${timeoutMs}ms`)), timeoutMs);
2285
- });
2286
- await Promise.race([client.connect(), timeoutPromise]);
2287
- return client;
2288
- }
2338
+ /**
2339
+ * The main entry point. Fetches all active sessions for this identity from
2340
+ * storage and establishes connections to all of them in batches.
2341
+ *
2342
+ * Call this once after creating the client. On traditional servers, you can
2343
+ * cache the `MultiSessionClient` instance after calling `connect()` to avoid
2344
+ * re-fetching and re-connecting on every request.
2345
+ */
2289
2346
  async connect() {
2290
2347
  const sessions = await this.getActiveSessions();
2291
2348
  await this.connectInBatches(sessions);
2292
2349
  }
2293
2350
  /**
2294
- * Returns the array of currently connected clients.
2351
+ * Returns all currently connected `MCPClient` instances.
2352
+ *
2353
+ * Use this to enumerate available tools across all connected servers,
2354
+ * or to route a tool call to the right client by `serverId`.
2295
2355
  */
2296
2356
  getClients() {
2297
2357
  return this.clients;
2298
2358
  }
2299
2359
  /**
2300
- * Disconnects all clients.
2360
+ * Gracefully disconnects all active MCP clients and clears the internal client list.
2361
+ *
2362
+ * Call this during server shutdown or when a user logs out to free up
2363
+ * underlying transport resources (SSE streams, HTTP connections, etc.).
2301
2364
  */
2302
2365
  disconnect() {
2303
2366
  this.clients.forEach((client) => client.disconnect());