@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.
- 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/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 +2 -2
- package/src/adapters/agui-adapter.ts +222 -222
- 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/server/mcp/multi-session-client.ts +135 -39
- package/src/server/storage/file-backend.ts +3 -16
- package/src/server/storage/index.ts +1 -0
- package/src/server/storage/memory-backend.ts +3 -16
- package/src/server/storage/redis-backend.ts +3 -16
- package/src/server/storage/sqlite-backend.ts +2 -6
- package/src/server/storage/supabase-backend.ts +2 -1
- 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
|
|
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
|
-
|
|
366
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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:
|
|
2227
|
-
maxRetries:
|
|
2228
|
-
retryDelay:
|
|
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
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
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
|
-
|
|
2244
|
-
|
|
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
|
-
|
|
2255
|
-
|
|
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 =
|
|
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
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
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
|
|
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
|
-
*
|
|
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());
|