@mcp-ts/sdk 1.3.6 → 1.3.9
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.
- package/LICENSE +21 -21
- package/README.md +398 -404
- package/dist/adapters/agui-adapter.d.mts +1 -1
- package/dist/adapters/agui-adapter.d.ts +1 -1
- package/dist/adapters/agui-adapter.js +2 -2
- package/dist/adapters/agui-adapter.js.map +1 -1
- package/dist/adapters/agui-adapter.mjs +2 -2
- package/dist/adapters/agui-adapter.mjs.map +1 -1
- package/dist/adapters/agui-middleware.d.mts +1 -1
- package/dist/adapters/agui-middleware.d.ts +1 -1
- package/dist/adapters/agui-middleware.js.map +1 -1
- package/dist/adapters/agui-middleware.mjs.map +1 -1
- package/dist/adapters/ai-adapter.d.mts +1 -1
- package/dist/adapters/ai-adapter.d.ts +1 -1
- package/dist/adapters/ai-adapter.js +1 -1
- package/dist/adapters/ai-adapter.js.map +1 -1
- package/dist/adapters/ai-adapter.mjs +1 -1
- package/dist/adapters/ai-adapter.mjs.map +1 -1
- package/dist/adapters/langchain-adapter.d.mts +1 -1
- package/dist/adapters/langchain-adapter.d.ts +1 -1
- package/dist/adapters/langchain-adapter.js +1 -1
- package/dist/adapters/langchain-adapter.js.map +1 -1
- package/dist/adapters/langchain-adapter.mjs +1 -1
- package/dist/adapters/langchain-adapter.mjs.map +1 -1
- package/dist/adapters/mastra-adapter.d.mts +1 -1
- package/dist/adapters/mastra-adapter.d.ts +1 -1
- package/dist/adapters/mastra-adapter.js +1 -1
- package/dist/adapters/mastra-adapter.js.map +1 -1
- package/dist/adapters/mastra-adapter.mjs +1 -1
- package/dist/adapters/mastra-adapter.mjs.map +1 -1
- package/dist/bin/mcp-ts.js +0 -0
- package/dist/bin/mcp-ts.js.map +1 -1
- package/dist/bin/mcp-ts.mjs +0 -0
- package/dist/bin/mcp-ts.mjs.map +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs.map +1 -1
- package/dist/client/react.d.mts +2 -2
- package/dist/client/react.d.ts +2 -2
- package/dist/client/react.js +25 -2
- package/dist/client/react.js.map +1 -1
- package/dist/client/react.mjs +26 -3
- package/dist/client/react.mjs.map +1 -1
- package/dist/client/vue.js.map +1 -1
- package/dist/client/vue.mjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +134 -71
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +134 -71
- package/dist/index.mjs.map +1 -1
- package/dist/{multi-session-client-BYLarghq.d.ts → multi-session-client-CHE8QpVE.d.ts} +75 -5
- package/dist/{multi-session-client-CzhMkE0k.d.mts → multi-session-client-CQsRbxYI.d.mts} +75 -5
- package/dist/server/index.d.mts +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +134 -71
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +134 -71
- package/dist/server/index.mjs.map +1 -1
- package/dist/shared/index.js +10 -2
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/index.mjs +10 -2
- package/dist/shared/index.mjs.map +1 -1
- package/package.json +185 -185
- package/src/adapters/agui-adapter.ts +222 -222
- package/src/adapters/agui-middleware.ts +382 -382
- package/src/adapters/ai-adapter.ts +115 -115
- package/src/adapters/langchain-adapter.ts +127 -127
- package/src/adapters/mastra-adapter.ts +126 -126
- package/src/bin/mcp-ts.ts +102 -102
- package/src/client/core/app-host.ts +417 -417
- package/src/client/core/sse-client.ts +371 -371
- package/src/client/core/types.ts +31 -31
- package/src/client/index.ts +27 -27
- package/src/client/react/index.ts +16 -16
- package/src/client/react/use-app-host.ts +73 -73
- package/src/client/react/use-mcp-apps.tsx +247 -214
- package/src/client/react/use-mcp.ts +641 -641
- package/src/client/vue/index.ts +10 -10
- package/src/client/vue/use-mcp.ts +617 -617
- package/src/index.ts +11 -11
- package/src/server/handlers/nextjs-handler.ts +204 -204
- package/src/server/handlers/sse-handler.ts +631 -631
- package/src/server/index.ts +57 -57
- package/src/server/mcp/multi-session-client.ts +228 -132
- package/src/server/mcp/oauth-client.ts +1188 -1188
- package/src/server/mcp/storage-oauth-provider.ts +272 -272
- package/src/server/storage/file-backend.ts +157 -170
- package/src/server/storage/index.ts +176 -175
- package/src/server/storage/memory-backend.ts +123 -136
- package/src/server/storage/redis-backend.ts +276 -289
- package/src/server/storage/redis.ts +160 -160
- package/src/server/storage/sqlite-backend.ts +182 -186
- package/src/server/storage/supabase-backend.ts +228 -227
- package/src/server/storage/types.ts +116 -116
- package/src/shared/constants.ts +29 -29
- package/src/shared/errors.ts +133 -133
- package/src/shared/event-routing.ts +28 -28
- package/src/shared/events.ts +180 -180
- package/src/shared/index.ts +75 -75
- package/src/shared/tool-utils.ts +61 -61
- package/src/shared/types.ts +282 -282
- package/src/shared/utils.ts +38 -16
- package/supabase/migrations/20260330195700_install_mcp_sessions.sql +84 -84
|
@@ -367,25 +367,95 @@ interface MultiSessionOptions {
|
|
|
367
367
|
retryDelay?: number;
|
|
368
368
|
}
|
|
369
369
|
/**
|
|
370
|
-
* Manages multiple MCP connections for a single user identity.
|
|
371
|
-
*
|
|
370
|
+
* Manages multiple MCP client connections for a single user identity.
|
|
371
|
+
*
|
|
372
|
+
* On a traditional long-running server, you can cache this instance per user
|
|
373
|
+
* so the connections stay alive between requests. On serverless, a new instance
|
|
374
|
+
* will be created per invocation, but the underlying session data is always
|
|
375
|
+
* read from the storage backend so nothing is lost between calls.
|
|
372
376
|
*/
|
|
373
377
|
declare class MultiSessionClient {
|
|
374
378
|
private clients;
|
|
375
379
|
private identity;
|
|
376
380
|
private options;
|
|
381
|
+
/**
|
|
382
|
+
* Creates a new MultiSessionClient for the given user identity.
|
|
383
|
+
*
|
|
384
|
+
* @param identity - A unique string identifying the user (e.g. user ID or email).
|
|
385
|
+
* @param options - Optional tuning for connection timeout, retry count, and retry delay.
|
|
386
|
+
* Falls back to sensible defaults if not provided.
|
|
387
|
+
*/
|
|
377
388
|
constructor(identity: string, options?: MultiSessionOptions);
|
|
389
|
+
/**
|
|
390
|
+
* Fetches all sessions for this identity from storage and returns only the
|
|
391
|
+
* ones that are ready to connect.
|
|
392
|
+
*
|
|
393
|
+
* A session is considered connectable when:
|
|
394
|
+
* - It has a `serverId`, `serverUrl`, and `callbackUrl` (i.e. it was fully initialized)
|
|
395
|
+
* - Its `active` flag is not explicitly `false` — sessions with `active: false` are
|
|
396
|
+
* either mid-OAuth flow, auth-pending, or previously failed. We skip those here
|
|
397
|
+
* and let the OAuth flow complete separately before we try to reconnect them.
|
|
398
|
+
*
|
|
399
|
+
* Note: Sessions where `active` is `undefined` (legacy records) are included
|
|
400
|
+
* for backwards compatibility.
|
|
401
|
+
*/
|
|
378
402
|
private getActiveSessions;
|
|
403
|
+
/**
|
|
404
|
+
* Connects to a list of sessions in controlled batches of `CONNECTION_BATCH_SIZE`.
|
|
405
|
+
*
|
|
406
|
+
* Batching prevents overwhelming the event loop or external servers when a user
|
|
407
|
+
* has many active MCP sessions (e.g. 20+ servers). Within each batch, sessions
|
|
408
|
+
* are connected concurrently using `Promise.all` for speed.
|
|
409
|
+
*/
|
|
379
410
|
private connectInBatches;
|
|
411
|
+
private connectionPromises;
|
|
412
|
+
/**
|
|
413
|
+
* Connects a single session, with built-in deduplication to prevent race conditions.
|
|
414
|
+
*
|
|
415
|
+
* - If a client for this session already exists and is connected, returns immediately.
|
|
416
|
+
* - If a connection attempt for this session is already in-flight (e.g. from a
|
|
417
|
+
* concurrent call), it joins the existing promise instead of starting a new one.
|
|
418
|
+
* This is the key concurrency lock — the `connectionPromises` map acts as a
|
|
419
|
+
* per-session mutex so we never spin up two physical connections for the same session.
|
|
420
|
+
* - On completion (success or failure), the promise is cleaned up from the map.
|
|
421
|
+
*/
|
|
380
422
|
private connectSession;
|
|
381
|
-
|
|
423
|
+
/**
|
|
424
|
+
* The core connection loop for a single session.
|
|
425
|
+
*
|
|
426
|
+
* Attempts to establish a physical MCP connection, retrying up to `maxRetries` times
|
|
427
|
+
* if the connection fails. Each attempt:
|
|
428
|
+
* 1. Creates a fresh `MCPClient` instance from the session data.
|
|
429
|
+
* 2. Races the connect call against a timeout promise — if the server doesn't respond
|
|
430
|
+
* within `timeoutMs`, the attempt is aborted and counted as a failure.
|
|
431
|
+
* 3. On success, replaces any stale client entry for this session in the `clients` array.
|
|
432
|
+
* 4. On failure, waits `retryDelay` ms before the next attempt.
|
|
433
|
+
*
|
|
434
|
+
* If all attempts are exhausted, logs an error and returns silently (does not throw),
|
|
435
|
+
* so a single bad server doesn't block the rest of the batch from connecting.
|
|
436
|
+
*/
|
|
437
|
+
private establishConnectionWithRetries;
|
|
438
|
+
/**
|
|
439
|
+
* The main entry point. Fetches all active sessions for this identity from
|
|
440
|
+
* storage and establishes connections to all of them in batches.
|
|
441
|
+
*
|
|
442
|
+
* Call this once after creating the client. On traditional servers, you can
|
|
443
|
+
* cache the `MultiSessionClient` instance after calling `connect()` to avoid
|
|
444
|
+
* re-fetching and re-connecting on every request.
|
|
445
|
+
*/
|
|
382
446
|
connect(): Promise<void>;
|
|
383
447
|
/**
|
|
384
|
-
* Returns
|
|
448
|
+
* Returns all currently connected `MCPClient` instances.
|
|
449
|
+
*
|
|
450
|
+
* Use this to enumerate available tools across all connected servers,
|
|
451
|
+
* or to route a tool call to the right client by `serverId`.
|
|
385
452
|
*/
|
|
386
453
|
getClients(): MCPClient[];
|
|
387
454
|
/**
|
|
388
|
-
*
|
|
455
|
+
* Gracefully disconnects all active MCP clients and clears the internal client list.
|
|
456
|
+
*
|
|
457
|
+
* Call this during server shutdown or when a user logs out to free up
|
|
458
|
+
* underlying transport resources (SSE streams, HTTP connections, etc.).
|
|
389
459
|
*/
|
|
390
460
|
disconnect(): void;
|
|
391
461
|
}
|
|
@@ -367,25 +367,95 @@ interface MultiSessionOptions {
|
|
|
367
367
|
retryDelay?: number;
|
|
368
368
|
}
|
|
369
369
|
/**
|
|
370
|
-
* Manages multiple MCP connections for a single user identity.
|
|
371
|
-
*
|
|
370
|
+
* Manages multiple MCP client connections for a single user identity.
|
|
371
|
+
*
|
|
372
|
+
* On a traditional long-running server, you can cache this instance per user
|
|
373
|
+
* so the connections stay alive between requests. On serverless, a new instance
|
|
374
|
+
* will be created per invocation, but the underlying session data is always
|
|
375
|
+
* read from the storage backend so nothing is lost between calls.
|
|
372
376
|
*/
|
|
373
377
|
declare class MultiSessionClient {
|
|
374
378
|
private clients;
|
|
375
379
|
private identity;
|
|
376
380
|
private options;
|
|
381
|
+
/**
|
|
382
|
+
* Creates a new MultiSessionClient for the given user identity.
|
|
383
|
+
*
|
|
384
|
+
* @param identity - A unique string identifying the user (e.g. user ID or email).
|
|
385
|
+
* @param options - Optional tuning for connection timeout, retry count, and retry delay.
|
|
386
|
+
* Falls back to sensible defaults if not provided.
|
|
387
|
+
*/
|
|
377
388
|
constructor(identity: string, options?: MultiSessionOptions);
|
|
389
|
+
/**
|
|
390
|
+
* Fetches all sessions for this identity from storage and returns only the
|
|
391
|
+
* ones that are ready to connect.
|
|
392
|
+
*
|
|
393
|
+
* A session is considered connectable when:
|
|
394
|
+
* - It has a `serverId`, `serverUrl`, and `callbackUrl` (i.e. it was fully initialized)
|
|
395
|
+
* - Its `active` flag is not explicitly `false` — sessions with `active: false` are
|
|
396
|
+
* either mid-OAuth flow, auth-pending, or previously failed. We skip those here
|
|
397
|
+
* and let the OAuth flow complete separately before we try to reconnect them.
|
|
398
|
+
*
|
|
399
|
+
* Note: Sessions where `active` is `undefined` (legacy records) are included
|
|
400
|
+
* for backwards compatibility.
|
|
401
|
+
*/
|
|
378
402
|
private getActiveSessions;
|
|
403
|
+
/**
|
|
404
|
+
* Connects to a list of sessions in controlled batches of `CONNECTION_BATCH_SIZE`.
|
|
405
|
+
*
|
|
406
|
+
* Batching prevents overwhelming the event loop or external servers when a user
|
|
407
|
+
* has many active MCP sessions (e.g. 20+ servers). Within each batch, sessions
|
|
408
|
+
* are connected concurrently using `Promise.all` for speed.
|
|
409
|
+
*/
|
|
379
410
|
private connectInBatches;
|
|
411
|
+
private connectionPromises;
|
|
412
|
+
/**
|
|
413
|
+
* Connects a single session, with built-in deduplication to prevent race conditions.
|
|
414
|
+
*
|
|
415
|
+
* - If a client for this session already exists and is connected, returns immediately.
|
|
416
|
+
* - If a connection attempt for this session is already in-flight (e.g. from a
|
|
417
|
+
* concurrent call), it joins the existing promise instead of starting a new one.
|
|
418
|
+
* This is the key concurrency lock — the `connectionPromises` map acts as a
|
|
419
|
+
* per-session mutex so we never spin up two physical connections for the same session.
|
|
420
|
+
* - On completion (success or failure), the promise is cleaned up from the map.
|
|
421
|
+
*/
|
|
380
422
|
private connectSession;
|
|
381
|
-
|
|
423
|
+
/**
|
|
424
|
+
* The core connection loop for a single session.
|
|
425
|
+
*
|
|
426
|
+
* Attempts to establish a physical MCP connection, retrying up to `maxRetries` times
|
|
427
|
+
* if the connection fails. Each attempt:
|
|
428
|
+
* 1. Creates a fresh `MCPClient` instance from the session data.
|
|
429
|
+
* 2. Races the connect call against a timeout promise — if the server doesn't respond
|
|
430
|
+
* within `timeoutMs`, the attempt is aborted and counted as a failure.
|
|
431
|
+
* 3. On success, replaces any stale client entry for this session in the `clients` array.
|
|
432
|
+
* 4. On failure, waits `retryDelay` ms before the next attempt.
|
|
433
|
+
*
|
|
434
|
+
* If all attempts are exhausted, logs an error and returns silently (does not throw),
|
|
435
|
+
* so a single bad server doesn't block the rest of the batch from connecting.
|
|
436
|
+
*/
|
|
437
|
+
private establishConnectionWithRetries;
|
|
438
|
+
/**
|
|
439
|
+
* The main entry point. Fetches all active sessions for this identity from
|
|
440
|
+
* storage and establishes connections to all of them in batches.
|
|
441
|
+
*
|
|
442
|
+
* Call this once after creating the client. On traditional servers, you can
|
|
443
|
+
* cache the `MultiSessionClient` instance after calling `connect()` to avoid
|
|
444
|
+
* re-fetching and re-connecting on every request.
|
|
445
|
+
*/
|
|
382
446
|
connect(): Promise<void>;
|
|
383
447
|
/**
|
|
384
|
-
* Returns
|
|
448
|
+
* Returns all currently connected `MCPClient` instances.
|
|
449
|
+
*
|
|
450
|
+
* Use this to enumerate available tools across all connected servers,
|
|
451
|
+
* or to route a tool call to the right client by `serverId`.
|
|
385
452
|
*/
|
|
386
453
|
getClients(): MCPClient[];
|
|
387
454
|
/**
|
|
388
|
-
*
|
|
455
|
+
* Gracefully disconnects all active MCP clients and clears the internal client list.
|
|
456
|
+
*
|
|
457
|
+
* Call this during server shutdown or when a user logs out to free up
|
|
458
|
+
* underlying transport resources (SSE streams, HTTP connections, etc.).
|
|
389
459
|
*/
|
|
390
460
|
disconnect(): void;
|
|
391
461
|
}
|
package/dist/server/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { M as MCPClient, a as MultiSessionClient, S as StorageOAuthClientProvider } from '../multi-session-client-
|
|
1
|
+
export { M as MCPClient, a as MultiSessionClient, S as StorageOAuthClientProvider } from '../multi-session-client-CQsRbxYI.mjs';
|
|
2
2
|
export { U as UnauthorizedError, s as sanitizeServerLabel } from '../utils-0qmYrqoa.mjs';
|
|
3
3
|
import { OAuthClientInformationMixed, OAuthTokens } from '@modelcontextprotocol/sdk/shared/auth.js';
|
|
4
4
|
export { OAuthClientInformation, OAuthClientInformationFull, OAuthClientMetadata, OAuthTokens } from '@modelcontextprotocol/sdk/shared/auth.js';
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { M as MCPClient, a as MultiSessionClient, S as StorageOAuthClientProvider } from '../multi-session-client-
|
|
1
|
+
export { M as MCPClient, a as MultiSessionClient, S as StorageOAuthClientProvider } from '../multi-session-client-CHE8QpVE.js';
|
|
2
2
|
export { U as UnauthorizedError, s as sanitizeServerLabel } from '../utils-0qmYrqoa.js';
|
|
3
3
|
import { OAuthClientInformationMixed, OAuthTokens } from '@modelcontextprotocol/sdk/shared/auth.js';
|
|
4
4
|
export { OAuthClientInformation, OAuthClientInformationFull, OAuthClientMetadata, OAuthTokens } from '@modelcontextprotocol/sdk/shared/auth.js';
|
package/dist/server/index.js
CHANGED
|
@@ -169,7 +169,8 @@ var SOFTWARE_VERSION = "1.3.4";
|
|
|
169
169
|
var MCP_CLIENT_NAME = "mcp-ts-oauth-client";
|
|
170
170
|
var MCP_CLIENT_VERSION = "2.0";
|
|
171
171
|
|
|
172
|
-
// src/
|
|
172
|
+
// src/shared/utils.ts
|
|
173
|
+
init_cjs_shims();
|
|
173
174
|
var firstChar = nanoid.customAlphabet(
|
|
174
175
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
|
|
175
176
|
1
|
|
@@ -178,6 +179,18 @@ var rest = nanoid.customAlphabet(
|
|
|
178
179
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
|
|
179
180
|
11
|
|
180
181
|
);
|
|
182
|
+
function sanitizeServerLabel(name) {
|
|
183
|
+
let sanitized = name.replace(/[^a-zA-Z0-9-_]/g, "_").replace(/_{2,}/g, "_").toLowerCase();
|
|
184
|
+
if (!/^[a-zA-Z]/.test(sanitized)) {
|
|
185
|
+
sanitized = "s_" + sanitized;
|
|
186
|
+
}
|
|
187
|
+
return sanitized;
|
|
188
|
+
}
|
|
189
|
+
function generateSessionId() {
|
|
190
|
+
return firstChar() + rest();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// src/server/storage/redis-backend.ts
|
|
181
194
|
var RedisStorageBackend = class {
|
|
182
195
|
constructor(redis2) {
|
|
183
196
|
this.redis = redis2;
|
|
@@ -236,7 +249,7 @@ var RedisStorageBackend = class {
|
|
|
236
249
|
return Array.from(keys);
|
|
237
250
|
}
|
|
238
251
|
generateSessionId() {
|
|
239
|
-
return
|
|
252
|
+
return generateSessionId();
|
|
240
253
|
}
|
|
241
254
|
async createSession(session, ttl) {
|
|
242
255
|
const { sessionId, identity } = session;
|
|
@@ -407,14 +420,6 @@ var RedisStorageBackend = class {
|
|
|
407
420
|
|
|
408
421
|
// src/server/storage/memory-backend.ts
|
|
409
422
|
init_cjs_shims();
|
|
410
|
-
var firstChar2 = nanoid.customAlphabet(
|
|
411
|
-
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
|
|
412
|
-
1
|
|
413
|
-
);
|
|
414
|
-
var rest2 = nanoid.customAlphabet(
|
|
415
|
-
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
|
|
416
|
-
11
|
|
417
|
-
);
|
|
418
423
|
var MemoryStorageBackend = class {
|
|
419
424
|
constructor() {
|
|
420
425
|
// Map<identity:sessionId, SessionData>
|
|
@@ -429,7 +434,7 @@ var MemoryStorageBackend = class {
|
|
|
429
434
|
return `${identity}:${sessionId}`;
|
|
430
435
|
}
|
|
431
436
|
generateSessionId() {
|
|
432
|
-
return
|
|
437
|
+
return generateSessionId();
|
|
433
438
|
}
|
|
434
439
|
async createSession(session, ttl) {
|
|
435
440
|
const { sessionId, identity } = session;
|
|
@@ -503,14 +508,6 @@ var MemoryStorageBackend = class {
|
|
|
503
508
|
|
|
504
509
|
// src/server/storage/file-backend.ts
|
|
505
510
|
init_cjs_shims();
|
|
506
|
-
var firstChar3 = nanoid.customAlphabet(
|
|
507
|
-
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
|
|
508
|
-
1
|
|
509
|
-
);
|
|
510
|
-
var rest3 = nanoid.customAlphabet(
|
|
511
|
-
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
|
|
512
|
-
11
|
|
513
|
-
);
|
|
514
511
|
var FileStorageBackend = class {
|
|
515
512
|
/**
|
|
516
513
|
* @param options.path Path to the JSON file storage (default: ./sessions.json)
|
|
@@ -561,7 +558,7 @@ var FileStorageBackend = class {
|
|
|
561
558
|
return `${identity}:${sessionId}`;
|
|
562
559
|
}
|
|
563
560
|
generateSessionId() {
|
|
564
|
-
return
|
|
561
|
+
return generateSessionId();
|
|
565
562
|
}
|
|
566
563
|
async createSession(session, ttl) {
|
|
567
564
|
await this.ensureInitialized();
|
|
@@ -671,12 +668,7 @@ var SqliteStorage = class {
|
|
|
671
668
|
}
|
|
672
669
|
}
|
|
673
670
|
generateSessionId() {
|
|
674
|
-
|
|
675
|
-
let result = "";
|
|
676
|
-
for (let i = 0; i < 32; i++) {
|
|
677
|
-
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
678
|
-
}
|
|
679
|
-
return result;
|
|
671
|
+
return generateSessionId();
|
|
680
672
|
}
|
|
681
673
|
async createSession(session, ttl) {
|
|
682
674
|
this.ensureInitialized();
|
|
@@ -791,7 +783,7 @@ var SupabaseStorageBackend = class {
|
|
|
791
783
|
console.log('[mcp-ts][Storage] Supabase: \u2713 "mcp_sessions" table verified.');
|
|
792
784
|
}
|
|
793
785
|
generateSessionId() {
|
|
794
|
-
return
|
|
786
|
+
return generateSessionId();
|
|
795
787
|
}
|
|
796
788
|
mapRowToSessionData(row) {
|
|
797
789
|
return {
|
|
@@ -1232,16 +1224,6 @@ var StorageOAuthClientProvider = class {
|
|
|
1232
1224
|
}
|
|
1233
1225
|
};
|
|
1234
1226
|
|
|
1235
|
-
// src/shared/utils.ts
|
|
1236
|
-
init_cjs_shims();
|
|
1237
|
-
function sanitizeServerLabel(name) {
|
|
1238
|
-
let sanitized = name.replace(/[^a-zA-Z0-9-_]/g, "_").replace(/_{2,}/g, "_").toLowerCase();
|
|
1239
|
-
if (!/^[a-zA-Z]/.test(sanitized)) {
|
|
1240
|
-
sanitized = "s_" + sanitized;
|
|
1241
|
-
}
|
|
1242
|
-
return sanitized;
|
|
1243
|
-
}
|
|
1244
|
-
|
|
1245
1227
|
// src/shared/events.ts
|
|
1246
1228
|
init_cjs_shims();
|
|
1247
1229
|
var Emitter = class {
|
|
@@ -2207,47 +2189,132 @@ var MCPClient = class _MCPClient {
|
|
|
2207
2189
|
|
|
2208
2190
|
// src/server/mcp/multi-session-client.ts
|
|
2209
2191
|
init_cjs_shims();
|
|
2192
|
+
var DEFAULT_TIMEOUT_MS = 15e3;
|
|
2193
|
+
var DEFAULT_MAX_RETRIES = 2;
|
|
2194
|
+
var DEFAULT_RETRY_DELAY_MS = 1e3;
|
|
2195
|
+
var CONNECTION_BATCH_SIZE = 5;
|
|
2210
2196
|
var MultiSessionClient = class {
|
|
2197
|
+
/**
|
|
2198
|
+
* Creates a new MultiSessionClient for the given user identity.
|
|
2199
|
+
*
|
|
2200
|
+
* @param identity - A unique string identifying the user (e.g. user ID or email).
|
|
2201
|
+
* @param options - Optional tuning for connection timeout, retry count, and retry delay.
|
|
2202
|
+
* Falls back to sensible defaults if not provided.
|
|
2203
|
+
*/
|
|
2211
2204
|
constructor(identity, options = {}) {
|
|
2212
2205
|
__publicField(this, "clients", []);
|
|
2213
2206
|
__publicField(this, "identity");
|
|
2214
2207
|
__publicField(this, "options");
|
|
2208
|
+
__publicField(this, "connectionPromises", /* @__PURE__ */ new Map());
|
|
2215
2209
|
this.identity = identity;
|
|
2216
2210
|
this.options = {
|
|
2217
|
-
timeout:
|
|
2218
|
-
maxRetries:
|
|
2219
|
-
retryDelay:
|
|
2211
|
+
timeout: DEFAULT_TIMEOUT_MS,
|
|
2212
|
+
maxRetries: DEFAULT_MAX_RETRIES,
|
|
2213
|
+
retryDelay: DEFAULT_RETRY_DELAY_MS,
|
|
2220
2214
|
...options
|
|
2221
2215
|
};
|
|
2222
2216
|
}
|
|
2217
|
+
/**
|
|
2218
|
+
* Fetches all sessions for this identity from storage and returns only the
|
|
2219
|
+
* ones that are ready to connect.
|
|
2220
|
+
*
|
|
2221
|
+
* A session is considered connectable when:
|
|
2222
|
+
* - It has a `serverId`, `serverUrl`, and `callbackUrl` (i.e. it was fully initialized)
|
|
2223
|
+
* - Its `active` flag is not explicitly `false` — sessions with `active: false` are
|
|
2224
|
+
* either mid-OAuth flow, auth-pending, or previously failed. We skip those here
|
|
2225
|
+
* and let the OAuth flow complete separately before we try to reconnect them.
|
|
2226
|
+
*
|
|
2227
|
+
* Note: Sessions where `active` is `undefined` (legacy records) are included
|
|
2228
|
+
* for backwards compatibility.
|
|
2229
|
+
*/
|
|
2223
2230
|
async getActiveSessions() {
|
|
2224
2231
|
const sessions = await storage.getIdentitySessionsData(this.identity);
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2232
|
+
const valid = sessions.filter(
|
|
2233
|
+
(s) => s.serverId && s.serverUrl && s.callbackUrl && s.active !== false
|
|
2234
|
+
// exclude OAuth-pending / failed sessions
|
|
2228
2235
|
);
|
|
2229
|
-
const valid = sessions.filter((s) => s.serverId && s.serverUrl && s.callbackUrl);
|
|
2230
|
-
console.log(`[MultiSessionClient] Filtered valid sessions:`, valid.length);
|
|
2231
2236
|
return valid;
|
|
2232
2237
|
}
|
|
2238
|
+
/**
|
|
2239
|
+
* Connects to a list of sessions in controlled batches of `CONNECTION_BATCH_SIZE`.
|
|
2240
|
+
*
|
|
2241
|
+
* Batching prevents overwhelming the event loop or external servers when a user
|
|
2242
|
+
* has many active MCP sessions (e.g. 20+ servers). Within each batch, sessions
|
|
2243
|
+
* are connected concurrently using `Promise.all` for speed.
|
|
2244
|
+
*/
|
|
2233
2245
|
async connectInBatches(sessions) {
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
const batch = sessions.slice(i, i + BATCH_SIZE);
|
|
2246
|
+
for (let i = 0; i < sessions.length; i += CONNECTION_BATCH_SIZE) {
|
|
2247
|
+
const batch = sessions.slice(i, i + CONNECTION_BATCH_SIZE);
|
|
2237
2248
|
await Promise.all(batch.map((session) => this.connectSession(session)));
|
|
2238
2249
|
}
|
|
2239
2250
|
}
|
|
2251
|
+
/**
|
|
2252
|
+
* Connects a single session, with built-in deduplication to prevent race conditions.
|
|
2253
|
+
*
|
|
2254
|
+
* - If a client for this session already exists and is connected, returns immediately.
|
|
2255
|
+
* - If a connection attempt for this session is already in-flight (e.g. from a
|
|
2256
|
+
* concurrent call), it joins the existing promise instead of starting a new one.
|
|
2257
|
+
* This is the key concurrency lock — the `connectionPromises` map acts as a
|
|
2258
|
+
* per-session mutex so we never spin up two physical connections for the same session.
|
|
2259
|
+
* - On completion (success or failure), the promise is cleaned up from the map.
|
|
2260
|
+
*/
|
|
2240
2261
|
async connectSession(session) {
|
|
2241
2262
|
const existingClient = this.clients.find((c) => c.getSessionId() === session.sessionId);
|
|
2242
2263
|
if (existingClient?.isConnected()) {
|
|
2243
2264
|
return;
|
|
2244
2265
|
}
|
|
2245
|
-
|
|
2246
|
-
|
|
2266
|
+
if (this.connectionPromises.has(session.sessionId)) {
|
|
2267
|
+
return this.connectionPromises.get(session.sessionId);
|
|
2268
|
+
}
|
|
2269
|
+
const connectPromise = this.establishConnectionWithRetries(session);
|
|
2270
|
+
this.connectionPromises.set(session.sessionId, connectPromise);
|
|
2271
|
+
try {
|
|
2272
|
+
await connectPromise;
|
|
2273
|
+
} finally {
|
|
2274
|
+
this.connectionPromises.delete(session.sessionId);
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
/**
|
|
2278
|
+
* The core connection loop for a single session.
|
|
2279
|
+
*
|
|
2280
|
+
* Attempts to establish a physical MCP connection, retrying up to `maxRetries` times
|
|
2281
|
+
* if the connection fails. Each attempt:
|
|
2282
|
+
* 1. Creates a fresh `MCPClient` instance from the session data.
|
|
2283
|
+
* 2. Races the connect call against a timeout promise — if the server doesn't respond
|
|
2284
|
+
* within `timeoutMs`, the attempt is aborted and counted as a failure.
|
|
2285
|
+
* 3. On success, replaces any stale client entry for this session in the `clients` array.
|
|
2286
|
+
* 4. On failure, waits `retryDelay` ms before the next attempt.
|
|
2287
|
+
*
|
|
2288
|
+
* If all attempts are exhausted, logs an error and returns silently (does not throw),
|
|
2289
|
+
* so a single bad server doesn't block the rest of the batch from connecting.
|
|
2290
|
+
*/
|
|
2291
|
+
async establishConnectionWithRetries(session) {
|
|
2292
|
+
const maxRetries = this.options.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
2293
|
+
const retryDelay = this.options.retryDelay ?? DEFAULT_RETRY_DELAY_MS;
|
|
2247
2294
|
let lastError;
|
|
2248
2295
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
2249
2296
|
try {
|
|
2250
|
-
const client =
|
|
2297
|
+
const client = new MCPClient({
|
|
2298
|
+
identity: this.identity,
|
|
2299
|
+
sessionId: session.sessionId,
|
|
2300
|
+
serverId: session.serverId,
|
|
2301
|
+
serverUrl: session.serverUrl,
|
|
2302
|
+
callbackUrl: session.callbackUrl,
|
|
2303
|
+
serverName: session.serverName,
|
|
2304
|
+
transportType: session.transportType,
|
|
2305
|
+
headers: session.headers
|
|
2306
|
+
});
|
|
2307
|
+
const timeoutMs = this.options.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
2308
|
+
let timeoutTimer;
|
|
2309
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
2310
|
+
timeoutTimer = setTimeout(() => reject(new Error(`Connection timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
2311
|
+
});
|
|
2312
|
+
try {
|
|
2313
|
+
await Promise.race([client.connect(), timeoutPromise]);
|
|
2314
|
+
} finally {
|
|
2315
|
+
clearTimeout(timeoutTimer);
|
|
2316
|
+
}
|
|
2317
|
+
this.clients = this.clients.filter((c) => c.getSessionId() !== session.sessionId);
|
|
2251
2318
|
this.clients.push(client);
|
|
2252
2319
|
return;
|
|
2253
2320
|
} catch (error) {
|
|
@@ -2259,36 +2326,32 @@ var MultiSessionClient = class {
|
|
|
2259
2326
|
}
|
|
2260
2327
|
console.error(`[MultiSessionClient] Failed to connect to session ${session.sessionId} after ${maxRetries + 1} attempts:`, lastError);
|
|
2261
2328
|
}
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
transportType: session.transportType,
|
|
2271
|
-
headers: session.headers
|
|
2272
|
-
});
|
|
2273
|
-
const timeoutMs = this.options.timeout ?? 15e3;
|
|
2274
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
2275
|
-
setTimeout(() => reject(new Error(`Connection timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
2276
|
-
});
|
|
2277
|
-
await Promise.race([client.connect(), timeoutPromise]);
|
|
2278
|
-
return client;
|
|
2279
|
-
}
|
|
2329
|
+
/**
|
|
2330
|
+
* The main entry point. Fetches all active sessions for this identity from
|
|
2331
|
+
* storage and establishes connections to all of them in batches.
|
|
2332
|
+
*
|
|
2333
|
+
* Call this once after creating the client. On traditional servers, you can
|
|
2334
|
+
* cache the `MultiSessionClient` instance after calling `connect()` to avoid
|
|
2335
|
+
* re-fetching and re-connecting on every request.
|
|
2336
|
+
*/
|
|
2280
2337
|
async connect() {
|
|
2281
2338
|
const sessions = await this.getActiveSessions();
|
|
2282
2339
|
await this.connectInBatches(sessions);
|
|
2283
2340
|
}
|
|
2284
2341
|
/**
|
|
2285
|
-
* Returns
|
|
2342
|
+
* Returns all currently connected `MCPClient` instances.
|
|
2343
|
+
*
|
|
2344
|
+
* Use this to enumerate available tools across all connected servers,
|
|
2345
|
+
* or to route a tool call to the right client by `serverId`.
|
|
2286
2346
|
*/
|
|
2287
2347
|
getClients() {
|
|
2288
2348
|
return this.clients;
|
|
2289
2349
|
}
|
|
2290
2350
|
/**
|
|
2291
|
-
*
|
|
2351
|
+
* Gracefully disconnects all active MCP clients and clears the internal client list.
|
|
2352
|
+
*
|
|
2353
|
+
* Call this during server shutdown or when a user logs out to free up
|
|
2354
|
+
* underlying transport resources (SSE streams, HTTP connections, etc.).
|
|
2292
2355
|
*/
|
|
2293
2356
|
disconnect() {
|
|
2294
2357
|
this.clients.forEach((client) => client.disconnect());
|