@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/server/index.mjs
CHANGED
|
@@ -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
|
|
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
|
-
|
|
363
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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:
|
|
2156
|
-
maxRetries:
|
|
2157
|
-
retryDelay:
|
|
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
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
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
|
-
|
|
2173
|
-
|
|
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
|
-
|
|
2184
|
-
|
|
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 =
|
|
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
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
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
|
|
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
|
-
*
|
|
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());
|