@mcp-ts/sdk 1.6.2 → 2.0.0
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/README.md +12 -6
- package/dist/adapters/agui-adapter.d.mts +3 -3
- package/dist/adapters/agui-adapter.d.ts +3 -3
- package/dist/adapters/agui-adapter.js +4 -5
- package/dist/adapters/agui-adapter.js.map +1 -1
- package/dist/adapters/agui-adapter.mjs +4 -5
- package/dist/adapters/agui-adapter.mjs.map +1 -1
- package/dist/adapters/agui-middleware.d.mts +3 -3
- package/dist/adapters/agui-middleware.d.ts +3 -3
- package/dist/adapters/ai-adapter.d.mts +9 -3
- package/dist/adapters/ai-adapter.d.ts +9 -3
- package/dist/adapters/ai-adapter.js +20 -6
- package/dist/adapters/ai-adapter.js.map +1 -1
- package/dist/adapters/ai-adapter.mjs +20 -6
- package/dist/adapters/ai-adapter.mjs.map +1 -1
- package/dist/adapters/langchain-adapter.d.mts +3 -3
- package/dist/adapters/langchain-adapter.d.ts +3 -3
- package/dist/adapters/langchain-adapter.js +9 -6
- package/dist/adapters/langchain-adapter.js.map +1 -1
- package/dist/adapters/langchain-adapter.mjs +9 -6
- 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 +5 -1
- package/dist/adapters/mastra-adapter.js.map +1 -1
- package/dist/adapters/mastra-adapter.mjs +5 -1
- package/dist/adapters/mastra-adapter.mjs.map +1 -1
- package/dist/bin/mcp-ts.js +7 -1
- package/dist/bin/mcp-ts.js.map +1 -1
- package/dist/bin/mcp-ts.mjs +7 -1
- package/dist/bin/mcp-ts.mjs.map +1 -1
- package/dist/client/index.d.mts +2 -2
- package/dist/client/index.d.ts +2 -2
- package/dist/client/index.js +9 -13
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +9 -13
- package/dist/client/index.mjs.map +1 -1
- package/dist/client/react.d.mts +7 -7
- package/dist/client/react.d.ts +7 -7
- package/dist/client/react.js +15 -19
- package/dist/client/react.js.map +1 -1
- package/dist/client/react.mjs +15 -19
- package/dist/client/react.mjs.map +1 -1
- package/dist/client/vue.d.mts +7 -7
- package/dist/client/vue.d.ts +7 -7
- package/dist/client/vue.js +14 -18
- package/dist/client/vue.js.map +1 -1
- package/dist/client/vue.mjs +14 -18
- package/dist/client/vue.mjs.map +1 -1
- package/dist/{index-DhA-OEAe.d.ts → index-C9gvpxy5.d.ts} +5 -5
- package/dist/{index-bFL4ZF2N.d.mts → index-eaH14_5u.d.mts} +5 -5
- package/dist/index.d.mts +6 -6
- package/dist/index.d.ts +6 -6
- package/dist/index.js +616 -370
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +615 -370
- package/dist/index.mjs.map +1 -1
- package/dist/{multi-session-client-CHE8QpVE.d.ts → multi-session-client-BYtguGJm.d.ts} +22 -22
- package/dist/{multi-session-client-CQsRbxYI.d.mts → multi-session-client-DYNe6az3.d.mts} +22 -22
- package/dist/server/index.d.mts +31 -34
- package/dist/server/index.d.ts +31 -34
- package/dist/server/index.js +531 -256
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +530 -256
- package/dist/server/index.mjs.map +1 -1
- package/dist/shared/index.d.mts +5 -5
- package/dist/shared/index.d.ts +5 -5
- package/dist/shared/index.js +76 -101
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/index.mjs +76 -101
- package/dist/shared/index.mjs.map +1 -1
- package/dist/{tool-router-Dh2804tM.d.ts → tool-router-Ddtybmr0.d.ts} +71 -73
- package/dist/{tool-router-BVaV1udm.d.mts → tool-router-Dnd6IOKC.d.mts} +71 -73
- package/dist/{types-rIuN1CQi.d.mts → types-BCAG20P6.d.mts} +4 -4
- package/dist/{types-rIuN1CQi.d.ts → types-BCAG20P6.d.ts} +4 -4
- package/dist/{utils-0qmYrqoa.d.mts → utils-DELRKQPU.d.mts} +1 -1
- package/dist/{utils-0qmYrqoa.d.ts → utils-DELRKQPU.d.ts} +1 -1
- package/migrations/neon/20260513010000_install_mcp_sessions.sql +69 -0
- package/migrations/neon/20260513020000_add_session_cleanup_cron.sql +35 -0
- package/{supabase/migrations → migrations/supabase}/20260330195700_install_mcp_sessions.sql +7 -9
- package/package.json +14 -5
- package/src/adapters/ai-adapter.ts +30 -1
- package/src/adapters/langchain-adapter.ts +6 -2
- package/src/adapters/mastra-adapter.ts +6 -2
- package/src/bin/mcp-ts.ts +8 -1
- package/src/client/core/app-host.ts +1 -1
- package/src/client/core/sse-client.ts +12 -14
- package/src/client/core/types.ts +1 -1
- package/src/client/react/use-mcp-apps.tsx +1 -1
- package/src/client/react/use-mcp.ts +11 -11
- package/src/client/vue/use-mcp.ts +10 -10
- package/src/server/handlers/nextjs-handler.ts +18 -15
- package/src/server/handlers/sse-handler.ts +29 -29
- package/src/server/index.ts +1 -1
- package/src/server/mcp/multi-session-client.ts +17 -17
- package/src/server/mcp/oauth-client.ts +37 -37
- package/src/server/mcp/storage-oauth-provider.ts +17 -17
- package/src/server/storage/file-backend.ts +25 -25
- package/src/server/storage/index.ts +67 -10
- package/src/server/storage/memory-backend.ts +34 -34
- package/src/server/storage/neon-backend.ts +281 -0
- package/src/server/storage/redis-backend.ts +64 -64
- package/src/server/storage/sqlite-backend.ts +33 -33
- package/src/server/storage/supabase-backend.ts +23 -24
- package/src/server/storage/types.ts +18 -21
- package/src/shared/errors.ts +1 -1
- package/src/shared/index.ts +1 -2
- package/src/shared/meta-tools.ts +4 -6
- package/src/shared/schema-compressor.ts +2 -42
- package/src/shared/tool-index.ts +89 -84
- package/src/shared/tool-router.ts +0 -24
- package/src/shared/types.ts +4 -4
- /package/{supabase/migrations → migrations/supabase}/20260421010000_add_session_cleanup_cron.sql +0 -0
package/dist/server/index.js
CHANGED
|
@@ -197,35 +197,35 @@ var RedisStorageBackend = class {
|
|
|
197
197
|
this.redis = redis2;
|
|
198
198
|
__publicField(this, "DEFAULT_TTL", SESSION_TTL_SECONDS);
|
|
199
199
|
__publicField(this, "KEY_PREFIX", "mcp:session:");
|
|
200
|
-
__publicField(this, "
|
|
201
|
-
__publicField(this, "
|
|
200
|
+
__publicField(this, "USER_ID_KEY_PREFIX", "mcp:userId:");
|
|
201
|
+
__publicField(this, "USER_ID_KEY_SUFFIX", ":sessions");
|
|
202
202
|
}
|
|
203
203
|
async init() {
|
|
204
204
|
try {
|
|
205
205
|
await this.redis.ping();
|
|
206
206
|
console.log("[mcp-ts][Storage] Redis: \u2713 Connected to server.");
|
|
207
207
|
} catch (error) {
|
|
208
|
-
throw new Error(`[
|
|
208
|
+
throw new Error(`[RedisStorageBackend] Failed to connect to Redis: ${error.message}`);
|
|
209
209
|
}
|
|
210
210
|
}
|
|
211
211
|
/**
|
|
212
212
|
* Generates Redis key for a specific session
|
|
213
213
|
* @private
|
|
214
214
|
*/
|
|
215
|
-
getSessionKey(
|
|
216
|
-
return `${this.KEY_PREFIX}${
|
|
215
|
+
getSessionKey(userId, sessionId) {
|
|
216
|
+
return `${this.KEY_PREFIX}${userId}:${sessionId}`;
|
|
217
217
|
}
|
|
218
218
|
/**
|
|
219
|
-
* Generates Redis key for tracking all sessions for
|
|
219
|
+
* Generates Redis key for tracking all sessions for a user
|
|
220
220
|
* @private
|
|
221
221
|
*/
|
|
222
|
-
|
|
223
|
-
return `${this.
|
|
222
|
+
getUserIdKey(userId) {
|
|
223
|
+
return `${this.USER_ID_KEY_PREFIX}${userId}${this.USER_ID_KEY_SUFFIX}`;
|
|
224
224
|
}
|
|
225
|
-
|
|
226
|
-
return
|
|
227
|
-
this.
|
|
228
|
-
|
|
225
|
+
parseUserIdFromKey(userIdKey) {
|
|
226
|
+
return userIdKey.slice(
|
|
227
|
+
this.USER_ID_KEY_PREFIX.length,
|
|
228
|
+
userIdKey.length - this.USER_ID_KEY_SUFFIX.length
|
|
229
229
|
);
|
|
230
230
|
}
|
|
231
231
|
async scanKeys(pattern) {
|
|
@@ -244,7 +244,7 @@ var RedisStorageBackend = class {
|
|
|
244
244
|
}
|
|
245
245
|
} while (cursor !== "0");
|
|
246
246
|
} catch (error) {
|
|
247
|
-
console.warn("[
|
|
247
|
+
console.warn("[RedisStorageBackend] SCAN failed, falling back to KEYS:", error);
|
|
248
248
|
return await this.redis.keys(pattern);
|
|
249
249
|
}
|
|
250
250
|
return Array.from(keys);
|
|
@@ -252,11 +252,11 @@ var RedisStorageBackend = class {
|
|
|
252
252
|
generateSessionId() {
|
|
253
253
|
return generateSessionId();
|
|
254
254
|
}
|
|
255
|
-
async
|
|
256
|
-
const { sessionId,
|
|
257
|
-
if (!sessionId || !
|
|
258
|
-
const sessionKey = this.getSessionKey(
|
|
259
|
-
const
|
|
255
|
+
async create(session, ttl) {
|
|
256
|
+
const { sessionId, userId } = session;
|
|
257
|
+
if (!sessionId || !userId) throw new Error("userId and sessionId required");
|
|
258
|
+
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
259
|
+
const userIdKey = this.getUserIdKey(userId);
|
|
260
260
|
const effectiveTtl = ttl ?? this.DEFAULT_TTL;
|
|
261
261
|
const result = await this.redis.set(
|
|
262
262
|
sessionKey,
|
|
@@ -268,10 +268,10 @@ var RedisStorageBackend = class {
|
|
|
268
268
|
if (result !== "OK") {
|
|
269
269
|
throw new Error(`Session ${sessionId} already exists`);
|
|
270
270
|
}
|
|
271
|
-
await this.redis.sadd(
|
|
271
|
+
await this.redis.sadd(userIdKey, sessionId);
|
|
272
272
|
}
|
|
273
|
-
async
|
|
274
|
-
const sessionKey = this.getSessionKey(
|
|
273
|
+
async update(userId, sessionId, data, ttl) {
|
|
274
|
+
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
275
275
|
const effectiveTtl = ttl ?? this.DEFAULT_TTL;
|
|
276
276
|
const script = `
|
|
277
277
|
local currentStr = redis.call("GET", KEYS[1])
|
|
@@ -297,62 +297,62 @@ var RedisStorageBackend = class {
|
|
|
297
297
|
effectiveTtl
|
|
298
298
|
);
|
|
299
299
|
if (result === 0) {
|
|
300
|
-
throw new Error(`Session ${sessionId} not found for
|
|
300
|
+
throw new Error(`Session ${sessionId} not found for userId ${userId}`);
|
|
301
301
|
}
|
|
302
302
|
}
|
|
303
|
-
async
|
|
303
|
+
async get(userId, sessionId) {
|
|
304
304
|
try {
|
|
305
|
-
const sessionKey = this.getSessionKey(
|
|
305
|
+
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
306
306
|
const sessionDataStr = await this.redis.get(sessionKey);
|
|
307
307
|
if (!sessionDataStr) {
|
|
308
308
|
return null;
|
|
309
309
|
}
|
|
310
|
-
const
|
|
311
|
-
return
|
|
310
|
+
const Session = JSON.parse(sessionDataStr);
|
|
311
|
+
return Session;
|
|
312
312
|
} catch (error) {
|
|
313
|
-
console.error("[
|
|
313
|
+
console.error("[RedisStorageBackend] Failed to get session:", error);
|
|
314
314
|
return null;
|
|
315
315
|
}
|
|
316
316
|
}
|
|
317
|
-
async
|
|
318
|
-
const
|
|
319
|
-
return
|
|
317
|
+
async listIds(userId) {
|
|
318
|
+
const sessions2 = await this.list(userId);
|
|
319
|
+
return sessions2.map((session) => session.sessionId);
|
|
320
320
|
}
|
|
321
|
-
async
|
|
321
|
+
async list(userId) {
|
|
322
322
|
try {
|
|
323
|
-
const
|
|
324
|
-
const sessionIds = await this.redis.smembers(
|
|
323
|
+
const userIdKey = this.getUserIdKey(userId);
|
|
324
|
+
const sessionIds = await this.redis.smembers(userIdKey);
|
|
325
325
|
if (sessionIds.length === 0) return [];
|
|
326
326
|
const results = await Promise.all(
|
|
327
327
|
sessionIds.map(async (sessionId) => {
|
|
328
|
-
const data = await this.redis.get(this.getSessionKey(
|
|
328
|
+
const data = await this.redis.get(this.getSessionKey(userId, sessionId));
|
|
329
329
|
return data ? JSON.parse(data) : null;
|
|
330
330
|
})
|
|
331
331
|
);
|
|
332
332
|
const staleSessionIds = sessionIds.filter((_, index) => results[index] === null);
|
|
333
333
|
if (staleSessionIds.length > 0) {
|
|
334
|
-
await this.redis.srem(
|
|
334
|
+
await this.redis.srem(userIdKey, ...staleSessionIds);
|
|
335
335
|
}
|
|
336
336
|
return results.filter((session) => session !== null);
|
|
337
337
|
} catch (error) {
|
|
338
|
-
console.error(`[
|
|
338
|
+
console.error(`[RedisStorageBackend] Failed to get session data for ${userId}:`, error);
|
|
339
339
|
return [];
|
|
340
340
|
}
|
|
341
341
|
}
|
|
342
|
-
async
|
|
342
|
+
async delete(userId, sessionId) {
|
|
343
343
|
try {
|
|
344
|
-
const sessionKey = this.getSessionKey(
|
|
345
|
-
const
|
|
346
|
-
await this.redis.srem(
|
|
344
|
+
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
345
|
+
const userIdKey = this.getUserIdKey(userId);
|
|
346
|
+
await this.redis.srem(userIdKey, sessionId);
|
|
347
347
|
await this.redis.del(sessionKey);
|
|
348
348
|
} catch (error) {
|
|
349
|
-
console.error("[
|
|
349
|
+
console.error("[RedisStorageBackend] Failed to remove session:", error);
|
|
350
350
|
}
|
|
351
351
|
}
|
|
352
|
-
async
|
|
352
|
+
async listAllIds() {
|
|
353
353
|
try {
|
|
354
354
|
const keys = await this.scanKeys(`${this.KEY_PREFIX}*`);
|
|
355
|
-
const
|
|
355
|
+
const sessions2 = await Promise.all(
|
|
356
356
|
keys.map(async (key) => {
|
|
357
357
|
const data = await this.redis.get(key);
|
|
358
358
|
if (!data) {
|
|
@@ -361,60 +361,60 @@ var RedisStorageBackend = class {
|
|
|
361
361
|
try {
|
|
362
362
|
return JSON.parse(data).sessionId;
|
|
363
363
|
} catch (error) {
|
|
364
|
-
console.error("[
|
|
364
|
+
console.error("[RedisStorageBackend] Failed to parse session while listing all session IDs:", error);
|
|
365
365
|
return null;
|
|
366
366
|
}
|
|
367
367
|
})
|
|
368
368
|
);
|
|
369
|
-
return
|
|
369
|
+
return sessions2.filter((sessionId) => sessionId !== null);
|
|
370
370
|
} catch (error) {
|
|
371
|
-
console.error("[
|
|
371
|
+
console.error("[RedisStorageBackend] Failed to get all sessions:", error);
|
|
372
372
|
return [];
|
|
373
373
|
}
|
|
374
374
|
}
|
|
375
375
|
async clearAll() {
|
|
376
376
|
try {
|
|
377
377
|
const keys = await this.scanKeys(`${this.KEY_PREFIX}*`);
|
|
378
|
-
const
|
|
379
|
-
const allKeys = [...keys, ...
|
|
378
|
+
const userIdKeys = await this.scanKeys(`${this.USER_ID_KEY_PREFIX}*${this.USER_ID_KEY_SUFFIX}`);
|
|
379
|
+
const allKeys = [...keys, ...userIdKeys];
|
|
380
380
|
if (allKeys.length > 0) {
|
|
381
381
|
await this.redis.del(...allKeys);
|
|
382
382
|
}
|
|
383
383
|
} catch (error) {
|
|
384
|
-
console.error("[
|
|
384
|
+
console.error("[RedisStorageBackend] Failed to clear sessions:", error);
|
|
385
385
|
}
|
|
386
386
|
}
|
|
387
|
-
async
|
|
387
|
+
async cleanupExpired() {
|
|
388
388
|
try {
|
|
389
|
-
const
|
|
390
|
-
for (const
|
|
391
|
-
const
|
|
392
|
-
const sessionIds = await this.redis.smembers(
|
|
389
|
+
const userIdKeys = await this.scanKeys(`${this.USER_ID_KEY_PREFIX}*${this.USER_ID_KEY_SUFFIX}`);
|
|
390
|
+
for (const userIdKey of userIdKeys) {
|
|
391
|
+
const userId = this.parseUserIdFromKey(userIdKey);
|
|
392
|
+
const sessionIds = await this.redis.smembers(userIdKey);
|
|
393
393
|
if (sessionIds.length === 0) {
|
|
394
|
-
await this.redis.del(
|
|
394
|
+
await this.redis.del(userIdKey);
|
|
395
395
|
continue;
|
|
396
396
|
}
|
|
397
397
|
const existenceChecks = await Promise.all(
|
|
398
|
-
sessionIds.map((sessionId) => this.redis.exists(this.getSessionKey(
|
|
398
|
+
sessionIds.map((sessionId) => this.redis.exists(this.getSessionKey(userId, sessionId)))
|
|
399
399
|
);
|
|
400
400
|
const staleSessionIds = sessionIds.filter((_, index) => existenceChecks[index] === 0);
|
|
401
401
|
if (staleSessionIds.length > 0) {
|
|
402
|
-
await this.redis.srem(
|
|
402
|
+
await this.redis.srem(userIdKey, ...staleSessionIds);
|
|
403
403
|
}
|
|
404
|
-
const remainingCount = await this.redis.scard(
|
|
404
|
+
const remainingCount = await this.redis.scard(userIdKey);
|
|
405
405
|
if (remainingCount === 0) {
|
|
406
|
-
await this.redis.del(
|
|
406
|
+
await this.redis.del(userIdKey);
|
|
407
407
|
}
|
|
408
408
|
}
|
|
409
409
|
} catch (error) {
|
|
410
|
-
console.error("[
|
|
410
|
+
console.error("[RedisStorageBackend] Failed to cleanup expired sessions:", error);
|
|
411
411
|
}
|
|
412
412
|
}
|
|
413
413
|
async disconnect() {
|
|
414
414
|
try {
|
|
415
415
|
await this.redis.quit();
|
|
416
416
|
} catch (error) {
|
|
417
|
-
console.error("[
|
|
417
|
+
console.error("[RedisStorageBackend] Failed to disconnect:", error);
|
|
418
418
|
}
|
|
419
419
|
}
|
|
420
420
|
};
|
|
@@ -423,36 +423,36 @@ var RedisStorageBackend = class {
|
|
|
423
423
|
init_cjs_shims();
|
|
424
424
|
var MemoryStorageBackend = class {
|
|
425
425
|
constructor() {
|
|
426
|
-
// Map<
|
|
426
|
+
// Map<userId:sessionId, Session>
|
|
427
427
|
__publicField(this, "sessions", /* @__PURE__ */ new Map());
|
|
428
|
-
// Map<
|
|
429
|
-
__publicField(this, "
|
|
428
|
+
// Map<userId, Set<sessionId>>
|
|
429
|
+
__publicField(this, "userIdSessions", /* @__PURE__ */ new Map());
|
|
430
430
|
}
|
|
431
431
|
async init() {
|
|
432
432
|
console.log("[mcp-ts][Storage] Memory: \u2713 internal memory store active.");
|
|
433
433
|
}
|
|
434
|
-
getSessionKey(
|
|
435
|
-
return `${
|
|
434
|
+
getSessionKey(userId, sessionId) {
|
|
435
|
+
return `${userId}:${sessionId}`;
|
|
436
436
|
}
|
|
437
437
|
generateSessionId() {
|
|
438
438
|
return generateSessionId();
|
|
439
439
|
}
|
|
440
|
-
async
|
|
441
|
-
const { sessionId,
|
|
442
|
-
if (!sessionId || !
|
|
443
|
-
const sessionKey = this.getSessionKey(
|
|
440
|
+
async create(session, ttl) {
|
|
441
|
+
const { sessionId, userId } = session;
|
|
442
|
+
if (!sessionId || !userId) throw new Error("userId and sessionId required");
|
|
443
|
+
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
444
444
|
if (this.sessions.has(sessionKey)) {
|
|
445
445
|
throw new Error(`Session ${sessionId} already exists`);
|
|
446
446
|
}
|
|
447
447
|
this.sessions.set(sessionKey, session);
|
|
448
|
-
if (!this.
|
|
449
|
-
this.
|
|
448
|
+
if (!this.userIdSessions.has(userId)) {
|
|
449
|
+
this.userIdSessions.set(userId, /* @__PURE__ */ new Set());
|
|
450
450
|
}
|
|
451
|
-
this.
|
|
451
|
+
this.userIdSessions.get(userId).add(sessionId);
|
|
452
452
|
}
|
|
453
|
-
async
|
|
454
|
-
if (!
|
|
455
|
-
const sessionKey = this.getSessionKey(
|
|
453
|
+
async update(userId, sessionId, data, ttl) {
|
|
454
|
+
if (!userId || !sessionId) throw new Error("userId and sessionId required");
|
|
455
|
+
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
456
456
|
const current = this.sessions.get(sessionKey);
|
|
457
457
|
if (!current) {
|
|
458
458
|
throw new Error(`Session ${sessionId} not found`);
|
|
@@ -463,45 +463,45 @@ var MemoryStorageBackend = class {
|
|
|
463
463
|
};
|
|
464
464
|
this.sessions.set(sessionKey, updated);
|
|
465
465
|
}
|
|
466
|
-
async
|
|
467
|
-
const sessionKey = this.getSessionKey(
|
|
466
|
+
async get(userId, sessionId) {
|
|
467
|
+
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
468
468
|
return this.sessions.get(sessionKey) || null;
|
|
469
469
|
}
|
|
470
|
-
async
|
|
471
|
-
const set = this.
|
|
470
|
+
async listIds(userId) {
|
|
471
|
+
const set = this.userIdSessions.get(userId);
|
|
472
472
|
return set ? Array.from(set) : [];
|
|
473
473
|
}
|
|
474
|
-
async
|
|
475
|
-
const set = this.
|
|
474
|
+
async list(userId) {
|
|
475
|
+
const set = this.userIdSessions.get(userId);
|
|
476
476
|
if (!set) return [];
|
|
477
477
|
const results = [];
|
|
478
478
|
for (const sessionId of set) {
|
|
479
|
-
const session = this.sessions.get(this.getSessionKey(
|
|
479
|
+
const session = this.sessions.get(this.getSessionKey(userId, sessionId));
|
|
480
480
|
if (session) {
|
|
481
481
|
results.push(session);
|
|
482
482
|
}
|
|
483
483
|
}
|
|
484
484
|
return results;
|
|
485
485
|
}
|
|
486
|
-
async
|
|
487
|
-
const sessionKey = this.getSessionKey(
|
|
486
|
+
async delete(userId, sessionId) {
|
|
487
|
+
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
488
488
|
this.sessions.delete(sessionKey);
|
|
489
|
-
const set = this.
|
|
489
|
+
const set = this.userIdSessions.get(userId);
|
|
490
490
|
if (set) {
|
|
491
491
|
set.delete(sessionId);
|
|
492
492
|
if (set.size === 0) {
|
|
493
|
-
this.
|
|
493
|
+
this.userIdSessions.delete(userId);
|
|
494
494
|
}
|
|
495
495
|
}
|
|
496
496
|
}
|
|
497
|
-
async
|
|
497
|
+
async listAllIds() {
|
|
498
498
|
return Array.from(this.sessions.values()).map((s) => s.sessionId);
|
|
499
499
|
}
|
|
500
500
|
async clearAll() {
|
|
501
501
|
this.sessions.clear();
|
|
502
|
-
this.
|
|
502
|
+
this.userIdSessions.clear();
|
|
503
503
|
}
|
|
504
|
-
async
|
|
504
|
+
async cleanupExpired() {
|
|
505
505
|
}
|
|
506
506
|
async disconnect() {
|
|
507
507
|
}
|
|
@@ -532,7 +532,7 @@ var FileStorageBackend = class {
|
|
|
532
532
|
this.memoryCache = /* @__PURE__ */ new Map();
|
|
533
533
|
if (Array.isArray(json)) {
|
|
534
534
|
json.forEach((s) => {
|
|
535
|
-
this.memoryCache.set(this.getSessionKey(s.
|
|
535
|
+
this.memoryCache.set(this.getSessionKey(s.userId || "unknown", s.sessionId), s);
|
|
536
536
|
});
|
|
537
537
|
}
|
|
538
538
|
} catch (error) {
|
|
@@ -552,30 +552,30 @@ var FileStorageBackend = class {
|
|
|
552
552
|
}
|
|
553
553
|
async flush() {
|
|
554
554
|
if (!this.memoryCache) return;
|
|
555
|
-
const
|
|
556
|
-
await fs2.promises.writeFile(this.filePath, JSON.stringify(
|
|
555
|
+
const sessions2 = Array.from(this.memoryCache.values());
|
|
556
|
+
await fs2.promises.writeFile(this.filePath, JSON.stringify(sessions2, null, 2), "utf-8");
|
|
557
557
|
}
|
|
558
|
-
getSessionKey(
|
|
559
|
-
return `${
|
|
558
|
+
getSessionKey(userId, sessionId) {
|
|
559
|
+
return `${userId}:${sessionId}`;
|
|
560
560
|
}
|
|
561
561
|
generateSessionId() {
|
|
562
562
|
return generateSessionId();
|
|
563
563
|
}
|
|
564
|
-
async
|
|
564
|
+
async create(session, ttl) {
|
|
565
565
|
await this.ensureInitialized();
|
|
566
|
-
const { sessionId,
|
|
567
|
-
if (!sessionId || !
|
|
568
|
-
const sessionKey = this.getSessionKey(
|
|
566
|
+
const { sessionId, userId } = session;
|
|
567
|
+
if (!sessionId || !userId) throw new Error("userId and sessionId required");
|
|
568
|
+
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
569
569
|
if (this.memoryCache.has(sessionKey)) {
|
|
570
570
|
throw new Error(`Session ${sessionId} already exists`);
|
|
571
571
|
}
|
|
572
572
|
this.memoryCache.set(sessionKey, session);
|
|
573
573
|
await this.flush();
|
|
574
574
|
}
|
|
575
|
-
async
|
|
575
|
+
async update(userId, sessionId, data, ttl) {
|
|
576
576
|
await this.ensureInitialized();
|
|
577
|
-
if (!
|
|
578
|
-
const sessionKey = this.getSessionKey(
|
|
577
|
+
if (!userId || !sessionId) throw new Error("userId and sessionId required");
|
|
578
|
+
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
579
579
|
const current = this.memoryCache.get(sessionKey);
|
|
580
580
|
if (!current) {
|
|
581
581
|
throw new Error(`Session ${sessionId} not found`);
|
|
@@ -587,27 +587,27 @@ var FileStorageBackend = class {
|
|
|
587
587
|
this.memoryCache.set(sessionKey, updated);
|
|
588
588
|
await this.flush();
|
|
589
589
|
}
|
|
590
|
-
async
|
|
590
|
+
async get(userId, sessionId) {
|
|
591
591
|
await this.ensureInitialized();
|
|
592
|
-
const sessionKey = this.getSessionKey(
|
|
592
|
+
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
593
593
|
return this.memoryCache.get(sessionKey) || null;
|
|
594
594
|
}
|
|
595
|
-
async
|
|
595
|
+
async list(userId) {
|
|
596
596
|
await this.ensureInitialized();
|
|
597
|
-
return Array.from(this.memoryCache.values()).filter((s) => s.
|
|
597
|
+
return Array.from(this.memoryCache.values()).filter((s) => s.userId === userId);
|
|
598
598
|
}
|
|
599
|
-
async
|
|
599
|
+
async listIds(userId) {
|
|
600
600
|
await this.ensureInitialized();
|
|
601
|
-
return Array.from(this.memoryCache.values()).filter((s) => s.
|
|
601
|
+
return Array.from(this.memoryCache.values()).filter((s) => s.userId === userId).map((s) => s.sessionId);
|
|
602
602
|
}
|
|
603
|
-
async
|
|
603
|
+
async delete(userId, sessionId) {
|
|
604
604
|
await this.ensureInitialized();
|
|
605
|
-
const sessionKey = this.getSessionKey(
|
|
605
|
+
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
606
606
|
if (this.memoryCache.delete(sessionKey)) {
|
|
607
607
|
await this.flush();
|
|
608
608
|
}
|
|
609
609
|
}
|
|
610
|
-
async
|
|
610
|
+
async listAllIds() {
|
|
611
611
|
await this.ensureInitialized();
|
|
612
612
|
return Array.from(this.memoryCache.values()).map((s) => s.sessionId);
|
|
613
613
|
}
|
|
@@ -616,7 +616,7 @@ var FileStorageBackend = class {
|
|
|
616
616
|
this.memoryCache.clear();
|
|
617
617
|
await this.flush();
|
|
618
618
|
}
|
|
619
|
-
async
|
|
619
|
+
async cleanupExpired() {
|
|
620
620
|
await this.ensureInitialized();
|
|
621
621
|
}
|
|
622
622
|
async disconnect() {
|
|
@@ -646,11 +646,11 @@ var SqliteStorage = class {
|
|
|
646
646
|
this.db.exec(`
|
|
647
647
|
CREATE TABLE IF NOT EXISTS ${this.table} (
|
|
648
648
|
sessionId TEXT PRIMARY KEY,
|
|
649
|
-
|
|
649
|
+
userId TEXT NOT NULL,
|
|
650
650
|
data TEXT NOT NULL,
|
|
651
651
|
expiresAt INTEGER
|
|
652
652
|
);
|
|
653
|
-
CREATE INDEX IF NOT EXISTS idx_${this.table}
|
|
653
|
+
CREATE INDEX IF NOT EXISTS idx_${this.table}_userId ON ${this.table}(userId);
|
|
654
654
|
`);
|
|
655
655
|
this.initialized = true;
|
|
656
656
|
console.log(`[mcp-ts][Storage] SQLite: \u2713 database at ${this.dbPath} verified.`);
|
|
@@ -671,18 +671,18 @@ var SqliteStorage = class {
|
|
|
671
671
|
generateSessionId() {
|
|
672
672
|
return generateSessionId();
|
|
673
673
|
}
|
|
674
|
-
async
|
|
674
|
+
async create(session, ttl) {
|
|
675
675
|
this.ensureInitialized();
|
|
676
|
-
const { sessionId,
|
|
677
|
-
if (!sessionId || !
|
|
678
|
-
throw new Error("
|
|
676
|
+
const { sessionId, userId } = session;
|
|
677
|
+
if (!sessionId || !userId) {
|
|
678
|
+
throw new Error("userId and sessionId required");
|
|
679
679
|
}
|
|
680
680
|
const expiresAt = ttl ? Date.now() + ttl * 1e3 : null;
|
|
681
681
|
try {
|
|
682
682
|
const stmt = this.db.prepare(
|
|
683
|
-
`INSERT INTO ${this.table} (sessionId,
|
|
683
|
+
`INSERT INTO ${this.table} (sessionId, userId, data, expiresAt) VALUES (?, ?, ?, ?)`
|
|
684
684
|
);
|
|
685
|
-
stmt.run(sessionId,
|
|
685
|
+
stmt.run(sessionId, userId, JSON.stringify(session), expiresAt);
|
|
686
686
|
} catch (error) {
|
|
687
687
|
if (error.code === "SQLITE_CONSTRAINT_PRIMARYKEY") {
|
|
688
688
|
throw new Error(`Session ${sessionId} already exists`);
|
|
@@ -690,55 +690,55 @@ var SqliteStorage = class {
|
|
|
690
690
|
throw error;
|
|
691
691
|
}
|
|
692
692
|
}
|
|
693
|
-
async
|
|
693
|
+
async update(userId, sessionId, data, ttl) {
|
|
694
694
|
this.ensureInitialized();
|
|
695
|
-
if (!sessionId || !
|
|
696
|
-
throw new Error("
|
|
695
|
+
if (!sessionId || !userId) {
|
|
696
|
+
throw new Error("userId and sessionId required");
|
|
697
697
|
}
|
|
698
|
-
const currentSession = await this.
|
|
698
|
+
const currentSession = await this.get(userId, sessionId);
|
|
699
699
|
if (!currentSession) {
|
|
700
|
-
throw new Error(`Session ${sessionId} not found for
|
|
700
|
+
throw new Error(`Session ${sessionId} not found for userId ${userId}`);
|
|
701
701
|
}
|
|
702
702
|
const updatedSession = { ...currentSession, ...data };
|
|
703
703
|
const expiresAt = ttl ? Date.now() + ttl * 1e3 : null;
|
|
704
704
|
const stmt = this.db.prepare(
|
|
705
|
-
`UPDATE ${this.table} SET data = ?, expiresAt = ? WHERE sessionId = ? AND
|
|
705
|
+
`UPDATE ${this.table} SET data = ?, expiresAt = ? WHERE sessionId = ? AND userId = ?`
|
|
706
706
|
);
|
|
707
|
-
stmt.run(JSON.stringify(updatedSession), expiresAt, sessionId,
|
|
707
|
+
stmt.run(JSON.stringify(updatedSession), expiresAt, sessionId, userId);
|
|
708
708
|
}
|
|
709
|
-
async
|
|
709
|
+
async get(userId, sessionId) {
|
|
710
710
|
this.ensureInitialized();
|
|
711
711
|
const stmt = this.db.prepare(
|
|
712
|
-
`SELECT data FROM ${this.table} WHERE sessionId = ? AND
|
|
712
|
+
`SELECT data FROM ${this.table} WHERE sessionId = ? AND userId = ?`
|
|
713
713
|
);
|
|
714
|
-
const row = stmt.get(sessionId,
|
|
714
|
+
const row = stmt.get(sessionId, userId);
|
|
715
715
|
if (!row) return null;
|
|
716
716
|
return JSON.parse(row.data);
|
|
717
717
|
}
|
|
718
|
-
async
|
|
718
|
+
async list(userId) {
|
|
719
719
|
this.ensureInitialized();
|
|
720
720
|
const stmt = this.db.prepare(
|
|
721
|
-
`SELECT data FROM ${this.table} WHERE
|
|
721
|
+
`SELECT data FROM ${this.table} WHERE userId = ?`
|
|
722
722
|
);
|
|
723
|
-
const rows = stmt.all(
|
|
723
|
+
const rows = stmt.all(userId);
|
|
724
724
|
return rows.map((row) => JSON.parse(row.data));
|
|
725
725
|
}
|
|
726
|
-
async
|
|
726
|
+
async listIds(userId) {
|
|
727
727
|
this.ensureInitialized();
|
|
728
728
|
const stmt = this.db.prepare(
|
|
729
|
-
`SELECT sessionId FROM ${this.table} WHERE
|
|
729
|
+
`SELECT sessionId FROM ${this.table} WHERE userId = ?`
|
|
730
730
|
);
|
|
731
|
-
const rows = stmt.all(
|
|
731
|
+
const rows = stmt.all(userId);
|
|
732
732
|
return rows.map((row) => row.sessionId);
|
|
733
733
|
}
|
|
734
|
-
async
|
|
734
|
+
async delete(userId, sessionId) {
|
|
735
735
|
this.ensureInitialized();
|
|
736
736
|
const stmt = this.db.prepare(
|
|
737
|
-
`DELETE FROM ${this.table} WHERE sessionId = ? AND
|
|
737
|
+
`DELETE FROM ${this.table} WHERE sessionId = ? AND userId = ?`
|
|
738
738
|
);
|
|
739
|
-
stmt.run(sessionId,
|
|
739
|
+
stmt.run(sessionId, userId);
|
|
740
740
|
}
|
|
741
|
-
async
|
|
741
|
+
async listAllIds() {
|
|
742
742
|
this.ensureInitialized();
|
|
743
743
|
const stmt = this.db.prepare(`SELECT sessionId FROM ${this.table}`);
|
|
744
744
|
const rows = stmt.all();
|
|
@@ -749,7 +749,7 @@ var SqliteStorage = class {
|
|
|
749
749
|
const stmt = this.db.prepare(`DELETE FROM ${this.table}`);
|
|
750
750
|
stmt.run();
|
|
751
751
|
}
|
|
752
|
-
async
|
|
752
|
+
async cleanupExpired() {
|
|
753
753
|
this.ensureInitialized();
|
|
754
754
|
const now = Date.now();
|
|
755
755
|
const stmt = this.db.prepare(
|
|
@@ -866,7 +866,7 @@ var SupabaseStorageBackend = class {
|
|
|
866
866
|
transportType: row.transport_type,
|
|
867
867
|
callbackUrl: row.callback_url,
|
|
868
868
|
createdAt: new Date(row.created_at).getTime(),
|
|
869
|
-
|
|
869
|
+
userId: row.user_id,
|
|
870
870
|
headers: decryptObject(row.headers),
|
|
871
871
|
active: row.active,
|
|
872
872
|
clientInformation: row.client_information,
|
|
@@ -875,22 +875,21 @@ var SupabaseStorageBackend = class {
|
|
|
875
875
|
clientId: row.client_id
|
|
876
876
|
};
|
|
877
877
|
}
|
|
878
|
-
async
|
|
879
|
-
const { sessionId,
|
|
880
|
-
if (!sessionId || !
|
|
878
|
+
async create(session, ttl) {
|
|
879
|
+
const { sessionId, userId } = session;
|
|
880
|
+
if (!sessionId || !userId) throw new Error("userId and sessionId required");
|
|
881
881
|
const effectiveTtl = ttl ?? this.DEFAULT_TTL;
|
|
882
882
|
const expiresAt = new Date(Date.now() + effectiveTtl * 1e3).toISOString();
|
|
883
883
|
const { error } = await this.supabase.from("mcp_sessions").insert({
|
|
884
884
|
session_id: sessionId,
|
|
885
|
-
user_id:
|
|
886
|
-
// Maps user_id to
|
|
885
|
+
user_id: userId,
|
|
886
|
+
// Maps user_id to userId to support RLS using auth.uid()
|
|
887
887
|
server_id: session.serverId,
|
|
888
888
|
server_name: session.serverName,
|
|
889
889
|
server_url: session.serverUrl,
|
|
890
890
|
transport_type: session.transportType,
|
|
891
891
|
callback_url: session.callbackUrl,
|
|
892
892
|
created_at: new Date(session.createdAt || Date.now()).toISOString(),
|
|
893
|
-
identity,
|
|
894
893
|
headers: encryptObject(session.headers),
|
|
895
894
|
active: session.active ?? false,
|
|
896
895
|
client_information: session.clientInformation,
|
|
@@ -906,7 +905,7 @@ var SupabaseStorageBackend = class {
|
|
|
906
905
|
throw new Error(`Failed to create session in Supabase: ${error.message}`);
|
|
907
906
|
}
|
|
908
907
|
}
|
|
909
|
-
async
|
|
908
|
+
async update(userId, sessionId, data, ttl) {
|
|
910
909
|
const effectiveTtl = ttl ?? this.DEFAULT_TTL;
|
|
911
910
|
const expiresAt = new Date(Date.now() + effectiveTtl * 1e3).toISOString();
|
|
912
911
|
const updateData = {
|
|
@@ -924,16 +923,16 @@ var SupabaseStorageBackend = class {
|
|
|
924
923
|
if ("tokens" in data) updateData.tokens = encryptObject(data.tokens);
|
|
925
924
|
if ("codeVerifier" in data) updateData.code_verifier = data.codeVerifier;
|
|
926
925
|
if ("clientId" in data) updateData.client_id = data.clientId;
|
|
927
|
-
const { data: updatedRows, error } = await this.supabase.from("mcp_sessions").update(updateData).eq("
|
|
926
|
+
const { data: updatedRows, error } = await this.supabase.from("mcp_sessions").update(updateData).eq("user_id", userId).eq("session_id", sessionId).select("id");
|
|
928
927
|
if (error) {
|
|
929
928
|
throw new Error(`Failed to update session: ${error.message}`);
|
|
930
929
|
}
|
|
931
930
|
if (!updatedRows || updatedRows.length === 0) {
|
|
932
|
-
throw new Error(`Session ${sessionId} not found for
|
|
931
|
+
throw new Error(`Session ${sessionId} not found for userId ${userId}`);
|
|
933
932
|
}
|
|
934
933
|
}
|
|
935
|
-
async
|
|
936
|
-
const { data, error } = await this.supabase.from("mcp_sessions").select("*").eq("
|
|
934
|
+
async get(userId, sessionId) {
|
|
935
|
+
const { data, error } = await this.supabase.from("mcp_sessions").select("*").eq("user_id", userId).eq("session_id", sessionId).maybeSingle();
|
|
937
936
|
if (error) {
|
|
938
937
|
console.error("[SupabaseStorage] Failed to get session:", error);
|
|
939
938
|
return null;
|
|
@@ -941,29 +940,29 @@ var SupabaseStorageBackend = class {
|
|
|
941
940
|
if (!data) return null;
|
|
942
941
|
return this.mapRowToSessionData(data);
|
|
943
942
|
}
|
|
944
|
-
async
|
|
945
|
-
const { data, error } = await this.supabase.from("mcp_sessions").select("*").eq("
|
|
943
|
+
async list(userId) {
|
|
944
|
+
const { data, error } = await this.supabase.from("mcp_sessions").select("*").eq("user_id", userId);
|
|
946
945
|
if (error) {
|
|
947
|
-
console.error(`[SupabaseStorage] Failed to get session data for ${
|
|
946
|
+
console.error(`[SupabaseStorage] Failed to get session data for ${userId}:`, error);
|
|
948
947
|
return [];
|
|
949
948
|
}
|
|
950
949
|
return data.map((row) => this.mapRowToSessionData(row));
|
|
951
950
|
}
|
|
952
|
-
async
|
|
953
|
-
const { error } = await this.supabase.from("mcp_sessions").delete().eq("
|
|
951
|
+
async delete(userId, sessionId) {
|
|
952
|
+
const { error } = await this.supabase.from("mcp_sessions").delete().eq("user_id", userId).eq("session_id", sessionId);
|
|
954
953
|
if (error) {
|
|
955
954
|
console.error("[SupabaseStorage] Failed to remove session:", error);
|
|
956
955
|
}
|
|
957
956
|
}
|
|
958
|
-
async
|
|
959
|
-
const { data, error } = await this.supabase.from("mcp_sessions").select("session_id").eq("
|
|
957
|
+
async listIds(userId) {
|
|
958
|
+
const { data, error } = await this.supabase.from("mcp_sessions").select("session_id").eq("user_id", userId);
|
|
960
959
|
if (error) {
|
|
961
|
-
console.error(`[SupabaseStorage] Failed to get sessions for ${
|
|
960
|
+
console.error(`[SupabaseStorage] Failed to get sessions for ${userId}:`, error);
|
|
962
961
|
return [];
|
|
963
962
|
}
|
|
964
963
|
return data.map((row) => row.session_id);
|
|
965
964
|
}
|
|
966
|
-
async
|
|
965
|
+
async listAllIds() {
|
|
967
966
|
const { data, error } = await this.supabase.from("mcp_sessions").select("session_id");
|
|
968
967
|
if (error) {
|
|
969
968
|
console.error("[SupabaseStorage] Failed to get all sessions:", error);
|
|
@@ -977,7 +976,7 @@ var SupabaseStorageBackend = class {
|
|
|
977
976
|
console.error("[SupabaseStorage] Failed to clear sessions:", error);
|
|
978
977
|
}
|
|
979
978
|
}
|
|
980
|
-
async
|
|
979
|
+
async cleanupExpired() {
|
|
981
980
|
const { error } = await this.supabase.from("mcp_sessions").delete().lt("expires_at", (/* @__PURE__ */ new Date()).toISOString());
|
|
982
981
|
if (error) {
|
|
983
982
|
console.error("[SupabaseStorage] Failed to cleanup expired sessions:", error);
|
|
@@ -987,10 +986,254 @@ var SupabaseStorageBackend = class {
|
|
|
987
986
|
}
|
|
988
987
|
};
|
|
989
988
|
|
|
989
|
+
// src/server/storage/neon-backend.ts
|
|
990
|
+
init_cjs_shims();
|
|
991
|
+
var NeonStorageBackend = class {
|
|
992
|
+
constructor(sql, options = {}) {
|
|
993
|
+
this.sql = sql;
|
|
994
|
+
__publicField(this, "DEFAULT_TTL", SESSION_TTL_SECONDS);
|
|
995
|
+
__publicField(this, "tableName");
|
|
996
|
+
const schema = options.schema || "public";
|
|
997
|
+
const table = options.table || "mcp_sessions";
|
|
998
|
+
this.tableName = `${this.quoteIdentifier(schema)}.${this.quoteIdentifier(table)}`;
|
|
999
|
+
}
|
|
1000
|
+
async init() {
|
|
1001
|
+
const [{ exists } = { exists: null }] = await this.sql.query(
|
|
1002
|
+
"SELECT to_regclass($1) AS exists",
|
|
1003
|
+
[this.tableName.replace(/"/g, "")]
|
|
1004
|
+
);
|
|
1005
|
+
if (!exists) {
|
|
1006
|
+
throw new Error(
|
|
1007
|
+
'[NeonStorage] Table "mcp_sessions" not found in your database. Please create it using the Neon storage guide in docs/storage-backends/neon.md.'
|
|
1008
|
+
);
|
|
1009
|
+
}
|
|
1010
|
+
console.log('[mcp-ts][Storage] Neon: "mcp_sessions" table verified.');
|
|
1011
|
+
}
|
|
1012
|
+
generateSessionId() {
|
|
1013
|
+
return generateSessionId();
|
|
1014
|
+
}
|
|
1015
|
+
quoteIdentifier(identifier) {
|
|
1016
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(identifier)) {
|
|
1017
|
+
throw new Error(`Invalid Neon storage identifier: ${identifier}`);
|
|
1018
|
+
}
|
|
1019
|
+
return `"${identifier}"`;
|
|
1020
|
+
}
|
|
1021
|
+
mapRowToSessionData(row) {
|
|
1022
|
+
return {
|
|
1023
|
+
sessionId: row.session_id,
|
|
1024
|
+
serverId: row.server_id ?? void 0,
|
|
1025
|
+
serverName: row.server_name ?? void 0,
|
|
1026
|
+
serverUrl: row.server_url,
|
|
1027
|
+
transportType: row.transport_type,
|
|
1028
|
+
callbackUrl: row.callback_url,
|
|
1029
|
+
createdAt: new Date(row.created_at).getTime(),
|
|
1030
|
+
userId: row.user_id,
|
|
1031
|
+
headers: decryptObject(row.headers),
|
|
1032
|
+
active: row.active ?? false,
|
|
1033
|
+
clientInformation: row.client_information,
|
|
1034
|
+
tokens: decryptObject(row.tokens),
|
|
1035
|
+
codeVerifier: row.code_verifier ?? void 0,
|
|
1036
|
+
clientId: row.client_id ?? void 0
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
1039
|
+
async create(session, ttl) {
|
|
1040
|
+
const { sessionId, userId } = session;
|
|
1041
|
+
if (!sessionId || !userId) throw new Error("userId and sessionId required");
|
|
1042
|
+
const effectiveTtl = ttl ?? this.DEFAULT_TTL;
|
|
1043
|
+
const expiresAt = new Date(Date.now() + effectiveTtl * 1e3).toISOString();
|
|
1044
|
+
try {
|
|
1045
|
+
await this.sql.query(
|
|
1046
|
+
`INSERT INTO ${this.tableName} (
|
|
1047
|
+
session_id,
|
|
1048
|
+
user_id,
|
|
1049
|
+
server_id,
|
|
1050
|
+
server_name,
|
|
1051
|
+
server_url,
|
|
1052
|
+
transport_type,
|
|
1053
|
+
callback_url,
|
|
1054
|
+
created_at,
|
|
1055
|
+
headers,
|
|
1056
|
+
active,
|
|
1057
|
+
client_information,
|
|
1058
|
+
tokens,
|
|
1059
|
+
code_verifier,
|
|
1060
|
+
client_id,
|
|
1061
|
+
expires_at
|
|
1062
|
+
) VALUES (
|
|
1063
|
+
$1, $2, $3, $4, $5, $6, $7, $8,
|
|
1064
|
+
$9, $10, $11, $12, $13, $14, $15
|
|
1065
|
+
)`,
|
|
1066
|
+
[
|
|
1067
|
+
sessionId,
|
|
1068
|
+
userId,
|
|
1069
|
+
session.serverId,
|
|
1070
|
+
session.serverName,
|
|
1071
|
+
session.serverUrl,
|
|
1072
|
+
session.transportType,
|
|
1073
|
+
session.callbackUrl,
|
|
1074
|
+
new Date(session.createdAt || Date.now()).toISOString(),
|
|
1075
|
+
encryptObject(session.headers),
|
|
1076
|
+
session.active ?? false,
|
|
1077
|
+
session.clientInformation,
|
|
1078
|
+
encryptObject(session.tokens),
|
|
1079
|
+
session.codeVerifier,
|
|
1080
|
+
session.clientId,
|
|
1081
|
+
expiresAt
|
|
1082
|
+
]
|
|
1083
|
+
);
|
|
1084
|
+
} catch (error) {
|
|
1085
|
+
if (error.code === "23505") {
|
|
1086
|
+
throw new Error(`Session ${sessionId} already exists`);
|
|
1087
|
+
}
|
|
1088
|
+
throw new Error(`Failed to create session in Neon: ${error.message}`);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
async update(userId, sessionId, data, ttl) {
|
|
1092
|
+
const currentSession = await this.get(userId, sessionId);
|
|
1093
|
+
if (!currentSession) {
|
|
1094
|
+
throw new Error(`Session ${sessionId} not found for userId ${userId}`);
|
|
1095
|
+
}
|
|
1096
|
+
const updatedSession = { ...currentSession, ...data };
|
|
1097
|
+
const effectiveTtl = ttl ?? this.DEFAULT_TTL;
|
|
1098
|
+
const expiresAt = new Date(Date.now() + effectiveTtl * 1e3).toISOString();
|
|
1099
|
+
const updatedRows = await this.sql.query(
|
|
1100
|
+
`UPDATE ${this.tableName}
|
|
1101
|
+
SET
|
|
1102
|
+
server_id = $1,
|
|
1103
|
+
server_name = $2,
|
|
1104
|
+
server_url = $3,
|
|
1105
|
+
transport_type = $4,
|
|
1106
|
+
callback_url = $5,
|
|
1107
|
+
active = $6,
|
|
1108
|
+
headers = $7,
|
|
1109
|
+
client_information = $8,
|
|
1110
|
+
tokens = $9,
|
|
1111
|
+
code_verifier = $10,
|
|
1112
|
+
client_id = $11,
|
|
1113
|
+
expires_at = $12,
|
|
1114
|
+
updated_at = now()
|
|
1115
|
+
WHERE user_id = $13 AND session_id = $14
|
|
1116
|
+
RETURNING id`,
|
|
1117
|
+
[
|
|
1118
|
+
updatedSession.serverId,
|
|
1119
|
+
updatedSession.serverName,
|
|
1120
|
+
updatedSession.serverUrl,
|
|
1121
|
+
updatedSession.transportType,
|
|
1122
|
+
updatedSession.callbackUrl,
|
|
1123
|
+
updatedSession.active ?? false,
|
|
1124
|
+
encryptObject(updatedSession.headers),
|
|
1125
|
+
updatedSession.clientInformation,
|
|
1126
|
+
encryptObject(updatedSession.tokens),
|
|
1127
|
+
updatedSession.codeVerifier,
|
|
1128
|
+
updatedSession.clientId,
|
|
1129
|
+
expiresAt,
|
|
1130
|
+
userId,
|
|
1131
|
+
sessionId
|
|
1132
|
+
]
|
|
1133
|
+
);
|
|
1134
|
+
if (updatedRows.length === 0) {
|
|
1135
|
+
throw new Error(`Session ${sessionId} not found for userId ${userId}`);
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
async get(userId, sessionId) {
|
|
1139
|
+
try {
|
|
1140
|
+
const rows = await this.sql.query(
|
|
1141
|
+
`SELECT * FROM ${this.tableName} WHERE user_id = $1 AND session_id = $2`,
|
|
1142
|
+
[userId, sessionId]
|
|
1143
|
+
);
|
|
1144
|
+
return rows[0] ? this.mapRowToSessionData(rows[0]) : null;
|
|
1145
|
+
} catch (error) {
|
|
1146
|
+
console.error("[NeonStorage] Failed to get session:", error);
|
|
1147
|
+
return null;
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
async list(userId) {
|
|
1151
|
+
try {
|
|
1152
|
+
const rows = await this.sql.query(
|
|
1153
|
+
`SELECT * FROM ${this.tableName} WHERE user_id = $1`,
|
|
1154
|
+
[userId]
|
|
1155
|
+
);
|
|
1156
|
+
return rows.map((row) => this.mapRowToSessionData(row));
|
|
1157
|
+
} catch (error) {
|
|
1158
|
+
console.error(`[NeonStorage] Failed to get session data for ${userId}:`, error);
|
|
1159
|
+
return [];
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
async delete(userId, sessionId) {
|
|
1163
|
+
try {
|
|
1164
|
+
await this.sql.query(
|
|
1165
|
+
`DELETE FROM ${this.tableName} WHERE user_id = $1 AND session_id = $2`,
|
|
1166
|
+
[userId, sessionId]
|
|
1167
|
+
);
|
|
1168
|
+
} catch (error) {
|
|
1169
|
+
console.error("[NeonStorage] Failed to remove session:", error);
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
async listIds(userId) {
|
|
1173
|
+
try {
|
|
1174
|
+
const rows = await this.sql.query(
|
|
1175
|
+
`SELECT session_id FROM ${this.tableName} WHERE user_id = $1`,
|
|
1176
|
+
[userId]
|
|
1177
|
+
);
|
|
1178
|
+
return rows.map((row) => row.session_id);
|
|
1179
|
+
} catch (error) {
|
|
1180
|
+
console.error(`[NeonStorage] Failed to get sessions for ${userId}:`, error);
|
|
1181
|
+
return [];
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
async listAllIds() {
|
|
1185
|
+
try {
|
|
1186
|
+
const rows = await this.sql.query(
|
|
1187
|
+
`SELECT session_id FROM ${this.tableName}`
|
|
1188
|
+
);
|
|
1189
|
+
return rows.map((row) => row.session_id);
|
|
1190
|
+
} catch (error) {
|
|
1191
|
+
console.error("[NeonStorage] Failed to get all sessions:", error);
|
|
1192
|
+
return [];
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
async clearAll() {
|
|
1196
|
+
try {
|
|
1197
|
+
await this.sql.query(`DELETE FROM ${this.tableName}`);
|
|
1198
|
+
} catch (error) {
|
|
1199
|
+
console.error("[NeonStorage] Failed to clear sessions:", error);
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
async cleanupExpired() {
|
|
1203
|
+
try {
|
|
1204
|
+
await this.sql.query(
|
|
1205
|
+
`DELETE FROM ${this.tableName} WHERE expires_at < $1`,
|
|
1206
|
+
[(/* @__PURE__ */ new Date()).toISOString()]
|
|
1207
|
+
);
|
|
1208
|
+
} catch (error) {
|
|
1209
|
+
console.error("[NeonStorage] Failed to cleanup expired sessions:", error);
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
async disconnect() {
|
|
1213
|
+
}
|
|
1214
|
+
};
|
|
1215
|
+
|
|
990
1216
|
// src/server/storage/types.ts
|
|
991
1217
|
init_cjs_shims();
|
|
992
1218
|
|
|
993
1219
|
// src/server/storage/index.ts
|
|
1220
|
+
function warnIfNeonConnectionStringIsInsecure(connectionString) {
|
|
1221
|
+
try {
|
|
1222
|
+
const url = new URL(connectionString);
|
|
1223
|
+
const sslMode = url.searchParams.get("sslmode");
|
|
1224
|
+
const channelBinding = url.searchParams.get("channel_binding");
|
|
1225
|
+
if (!sslMode) {
|
|
1226
|
+
console.warn("[mcp-ts][Storage] Neon connection string does not include sslmode. Neon recommends sslmode=verify-full for the strongest certificate verification.");
|
|
1227
|
+
} else if (!["verify-full", "require"].includes(sslMode)) {
|
|
1228
|
+
console.warn(`[mcp-ts][Storage] Neon connection string uses sslmode=${sslMode}. Use sslmode=verify-full or sslmode=require for secure connections.`);
|
|
1229
|
+
}
|
|
1230
|
+
if (!channelBinding) {
|
|
1231
|
+
console.warn("[mcp-ts][Storage] Neon connection string does not include channel_binding=require. Add it when supported by your runtime and connection path.");
|
|
1232
|
+
}
|
|
1233
|
+
} catch {
|
|
1234
|
+
console.warn("[mcp-ts][Storage] Neon connection string could not be parsed for SSL checks.");
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
994
1237
|
var storageInstance = null;
|
|
995
1238
|
var storagePromise = null;
|
|
996
1239
|
async function initializeStorage(store) {
|
|
@@ -1047,6 +1290,24 @@ async function createStorage() {
|
|
|
1047
1290
|
}
|
|
1048
1291
|
}
|
|
1049
1292
|
}
|
|
1293
|
+
if (type === "neon") {
|
|
1294
|
+
const connectionString = process.env.NEON_DATABASE_URL || process.env.DATABASE_URL;
|
|
1295
|
+
if (!connectionString) {
|
|
1296
|
+
console.warn('[mcp-ts][Storage] Explicit selection "neon" requires NEON_DATABASE_URL or DATABASE_URL.');
|
|
1297
|
+
} else {
|
|
1298
|
+
try {
|
|
1299
|
+
const { neon } = await import('@neondatabase/serverless');
|
|
1300
|
+
warnIfNeonConnectionStringIsInsecure(connectionString);
|
|
1301
|
+
const sql = neon(connectionString);
|
|
1302
|
+
console.log('[mcp-ts][Storage] Explicit selection: "neon"');
|
|
1303
|
+
return await initializeStorage(new NeonStorageBackend(sql));
|
|
1304
|
+
} catch (error) {
|
|
1305
|
+
console.error("[mcp-ts][Storage] Failed to initialize Neon:", error.message);
|
|
1306
|
+
console.log("[mcp-ts][Storage] Falling back to In-Memory storage");
|
|
1307
|
+
return await initializeStorage(new MemoryStorageBackend());
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1050
1311
|
if (type === "memory") {
|
|
1051
1312
|
console.log('[mcp-ts][Storage] Explicit selection: "memory"');
|
|
1052
1313
|
return await initializeStorage(new MemoryStorageBackend());
|
|
@@ -1085,6 +1346,17 @@ async function createStorage() {
|
|
|
1085
1346
|
console.error("[mcp-ts][Storage] Supabase auto-detection failed:", error.message);
|
|
1086
1347
|
}
|
|
1087
1348
|
}
|
|
1349
|
+
if (process.env.NEON_DATABASE_URL) {
|
|
1350
|
+
try {
|
|
1351
|
+
const { neon } = await import('@neondatabase/serverless');
|
|
1352
|
+
warnIfNeonConnectionStringIsInsecure(process.env.NEON_DATABASE_URL);
|
|
1353
|
+
const sql = neon(process.env.NEON_DATABASE_URL);
|
|
1354
|
+
console.log('[mcp-ts][Storage] Auto-detection: "neon" (via NEON_DATABASE_URL)');
|
|
1355
|
+
return await initializeStorage(new NeonStorageBackend(sql));
|
|
1356
|
+
} catch (error) {
|
|
1357
|
+
console.error("[mcp-ts][Storage] Neon auto-detection failed:", error.message);
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1088
1360
|
console.log('[mcp-ts][Storage] Defaulting to: "memory"');
|
|
1089
1361
|
return await initializeStorage(new MemoryStorageBackend());
|
|
1090
1362
|
}
|
|
@@ -1101,7 +1373,7 @@ async function getStorage() {
|
|
|
1101
1373
|
storageInstance = await storagePromise;
|
|
1102
1374
|
return storageInstance;
|
|
1103
1375
|
}
|
|
1104
|
-
var
|
|
1376
|
+
var sessions = new Proxy({}, {
|
|
1105
1377
|
get(_target, prop) {
|
|
1106
1378
|
return async (...args) => {
|
|
1107
1379
|
const instance = await getStorage();
|
|
@@ -1117,11 +1389,11 @@ var storage = new Proxy({}, {
|
|
|
1117
1389
|
// src/server/mcp/storage-oauth-provider.ts
|
|
1118
1390
|
var StorageOAuthClientProvider = class {
|
|
1119
1391
|
/**
|
|
1120
|
-
* Creates a new
|
|
1392
|
+
* Creates a new session-backed OAuth provider
|
|
1121
1393
|
* @param options - Provider configuration
|
|
1122
1394
|
*/
|
|
1123
1395
|
constructor(options) {
|
|
1124
|
-
__publicField(this, "
|
|
1396
|
+
__publicField(this, "userId");
|
|
1125
1397
|
__publicField(this, "serverId");
|
|
1126
1398
|
__publicField(this, "sessionId");
|
|
1127
1399
|
__publicField(this, "redirectUrl");
|
|
@@ -1134,7 +1406,7 @@ var StorageOAuthClientProvider = class {
|
|
|
1134
1406
|
__publicField(this, "_clientId");
|
|
1135
1407
|
__publicField(this, "onRedirectCallback");
|
|
1136
1408
|
__publicField(this, "tokenExpiresAt");
|
|
1137
|
-
this.
|
|
1409
|
+
this.userId = options.userId;
|
|
1138
1410
|
this.serverId = options.serverId;
|
|
1139
1411
|
this.sessionId = options.sessionId;
|
|
1140
1412
|
this.redirectUrl = options.redirectUrl;
|
|
@@ -1167,24 +1439,24 @@ var StorageOAuthClientProvider = class {
|
|
|
1167
1439
|
this._clientId = clientId_;
|
|
1168
1440
|
}
|
|
1169
1441
|
/**
|
|
1170
|
-
* Loads OAuth data from
|
|
1442
|
+
* Loads OAuth data from the session store
|
|
1171
1443
|
* @private
|
|
1172
1444
|
*/
|
|
1173
1445
|
async getSessionData() {
|
|
1174
|
-
const data = await
|
|
1446
|
+
const data = await sessions.get(this.userId, this.sessionId);
|
|
1175
1447
|
if (!data) {
|
|
1176
1448
|
return {};
|
|
1177
1449
|
}
|
|
1178
1450
|
return data;
|
|
1179
1451
|
}
|
|
1180
1452
|
/**
|
|
1181
|
-
* Saves OAuth data to
|
|
1453
|
+
* Saves OAuth data to the session store
|
|
1182
1454
|
* @param data - Partial OAuth data to save
|
|
1183
1455
|
* @private
|
|
1184
1456
|
* @throws Error if session doesn't exist (session must be created by controller layer)
|
|
1185
1457
|
*/
|
|
1186
1458
|
async saveSessionData(data) {
|
|
1187
|
-
await
|
|
1459
|
+
await sessions.update(this.userId, this.sessionId, data);
|
|
1188
1460
|
}
|
|
1189
1461
|
/**
|
|
1190
1462
|
* Retrieves stored OAuth client information
|
|
@@ -1232,7 +1504,7 @@ var StorageOAuthClientProvider = class {
|
|
|
1232
1504
|
return this.sessionId;
|
|
1233
1505
|
}
|
|
1234
1506
|
async checkState(_state) {
|
|
1235
|
-
const data = await
|
|
1507
|
+
const data = await sessions.get(this.userId, this.sessionId);
|
|
1236
1508
|
if (!data) {
|
|
1237
1509
|
return { valid: false, error: "Session not found" };
|
|
1238
1510
|
}
|
|
@@ -1248,7 +1520,7 @@ var StorageOAuthClientProvider = class {
|
|
|
1248
1520
|
}
|
|
1249
1521
|
async invalidateCredentials(scope) {
|
|
1250
1522
|
if (scope === "all") {
|
|
1251
|
-
await
|
|
1523
|
+
await sessions.delete(this.userId, this.sessionId);
|
|
1252
1524
|
} else {
|
|
1253
1525
|
const updates = {};
|
|
1254
1526
|
if (scope === "client") {
|
|
@@ -1376,14 +1648,14 @@ var RpcErrorCodes = {
|
|
|
1376
1648
|
var MCPClient = class _MCPClient {
|
|
1377
1649
|
/**
|
|
1378
1650
|
* Creates a new MCP client instance
|
|
1379
|
-
* Can be initialized with minimal options (
|
|
1651
|
+
* Can be initialized with minimal options (userId + sessionId) for session restoration
|
|
1380
1652
|
* @param options - Client configuration options
|
|
1381
1653
|
*/
|
|
1382
1654
|
constructor(options) {
|
|
1383
1655
|
__publicField(this, "client", null);
|
|
1384
1656
|
__publicField(this, "oauthProvider", null);
|
|
1385
1657
|
__publicField(this, "transport", null);
|
|
1386
|
-
__publicField(this, "
|
|
1658
|
+
__publicField(this, "userId");
|
|
1387
1659
|
__publicField(this, "serverId");
|
|
1388
1660
|
__publicField(this, "sessionId");
|
|
1389
1661
|
__publicField(this, "serverName");
|
|
@@ -1410,7 +1682,7 @@ var MCPClient = class _MCPClient {
|
|
|
1410
1682
|
this.serverName = options.serverName;
|
|
1411
1683
|
this.callbackUrl = options.callbackUrl;
|
|
1412
1684
|
this.onRedirect = options.onRedirect;
|
|
1413
|
-
this.
|
|
1685
|
+
this.userId = options.userId;
|
|
1414
1686
|
this.serverId = options.serverId;
|
|
1415
1687
|
this.sessionId = options.sessionId;
|
|
1416
1688
|
this.transportType = options.transportType;
|
|
@@ -1549,7 +1821,7 @@ var MCPClient = class _MCPClient {
|
|
|
1549
1821
|
this.emitStateChange("INITIALIZING");
|
|
1550
1822
|
this.emitProgress("Loading session configuration...");
|
|
1551
1823
|
if (!this.serverUrl || !this.callbackUrl || !this.serverId) {
|
|
1552
|
-
const sessionData = await
|
|
1824
|
+
const sessionData = await sessions.get(this.userId, this.sessionId);
|
|
1553
1825
|
if (!sessionData) {
|
|
1554
1826
|
throw new Error(`Session not found: ${this.sessionId}`);
|
|
1555
1827
|
}
|
|
@@ -1568,7 +1840,7 @@ var MCPClient = class _MCPClient {
|
|
|
1568
1840
|
throw new Error("serverId required for OAuth provider initialization");
|
|
1569
1841
|
}
|
|
1570
1842
|
this.oauthProvider = new StorageOAuthClientProvider({
|
|
1571
|
-
|
|
1843
|
+
userId: this.userId,
|
|
1572
1844
|
serverId: this.serverId,
|
|
1573
1845
|
sessionId: this.sessionId,
|
|
1574
1846
|
redirectUrl: this.callbackUrl,
|
|
@@ -1602,18 +1874,18 @@ var MCPClient = class _MCPClient {
|
|
|
1602
1874
|
}
|
|
1603
1875
|
);
|
|
1604
1876
|
}
|
|
1605
|
-
const existingSession = await
|
|
1877
|
+
const existingSession = await sessions.get(this.userId, this.sessionId);
|
|
1606
1878
|
if (!existingSession && this.serverId && this.serverUrl && this.callbackUrl) {
|
|
1607
1879
|
this.createdAt = Date.now();
|
|
1608
1880
|
console.log(`[MCPClient] Creating initial session ${this.sessionId} for OAuth flow`);
|
|
1609
|
-
await
|
|
1881
|
+
await sessions.create({
|
|
1610
1882
|
sessionId: this.sessionId,
|
|
1611
|
-
|
|
1883
|
+
userId: this.userId,
|
|
1612
1884
|
serverId: this.serverId,
|
|
1613
1885
|
serverName: this.serverName,
|
|
1614
1886
|
serverUrl: this.serverUrl,
|
|
1615
1887
|
callbackUrl: this.callbackUrl,
|
|
1616
|
-
transportType: this.transportType || "
|
|
1888
|
+
transportType: this.transportType || "streamable-http",
|
|
1617
1889
|
headers: this.headers,
|
|
1618
1890
|
createdAt: this.createdAt,
|
|
1619
1891
|
active: false
|
|
@@ -1621,7 +1893,7 @@ var MCPClient = class _MCPClient {
|
|
|
1621
1893
|
}
|
|
1622
1894
|
}
|
|
1623
1895
|
/**
|
|
1624
|
-
* Saves current session state to
|
|
1896
|
+
* Saves current session state to the session store
|
|
1625
1897
|
* Creates new session if it doesn't exist, updates if it does
|
|
1626
1898
|
* @param ttl - Time-to-live in seconds (defaults to 12hr for connected sessions)
|
|
1627
1899
|
* @param active - Session status marker used to avoid unnecessary TTL rewrites
|
|
@@ -1633,21 +1905,21 @@ var MCPClient = class _MCPClient {
|
|
|
1633
1905
|
}
|
|
1634
1906
|
const sessionData = {
|
|
1635
1907
|
sessionId: this.sessionId,
|
|
1636
|
-
|
|
1908
|
+
userId: this.userId,
|
|
1637
1909
|
serverId: this.serverId,
|
|
1638
1910
|
serverName: this.serverName,
|
|
1639
1911
|
serverUrl: this.serverUrl,
|
|
1640
1912
|
callbackUrl: this.callbackUrl,
|
|
1641
|
-
transportType: this.transportType || "
|
|
1913
|
+
transportType: this.transportType || "streamable-http",
|
|
1642
1914
|
headers: this.headers,
|
|
1643
1915
|
createdAt: this.createdAt || Date.now(),
|
|
1644
1916
|
active
|
|
1645
1917
|
};
|
|
1646
|
-
const existingSession = await
|
|
1918
|
+
const existingSession = await sessions.get(this.userId, this.sessionId);
|
|
1647
1919
|
if (existingSession) {
|
|
1648
|
-
await
|
|
1920
|
+
await sessions.update(this.userId, this.sessionId, sessionData, ttl);
|
|
1649
1921
|
} else {
|
|
1650
|
-
await
|
|
1922
|
+
await sessions.create(sessionData, ttl);
|
|
1651
1923
|
}
|
|
1652
1924
|
}
|
|
1653
1925
|
/**
|
|
@@ -1656,7 +1928,7 @@ var MCPClient = class _MCPClient {
|
|
|
1656
1928
|
* @private
|
|
1657
1929
|
*/
|
|
1658
1930
|
async tryConnect() {
|
|
1659
|
-
const transportsToTry = this.transportType ? [this.transportType] : ["
|
|
1931
|
+
const transportsToTry = this.transportType ? [this.transportType] : ["streamable-http", "sse"];
|
|
1660
1932
|
let lastError;
|
|
1661
1933
|
for (const currentType of transportsToTry) {
|
|
1662
1934
|
const isLastAttempt = currentType === transportsToTry[transportsToTry.length - 1];
|
|
@@ -1728,7 +2000,7 @@ var MCPClient = class _MCPClient {
|
|
|
1728
2000
|
this.emitError(message, "auth");
|
|
1729
2001
|
this.emitStateChange("FAILED");
|
|
1730
2002
|
try {
|
|
1731
|
-
await
|
|
2003
|
+
await sessions.delete(this.userId, this.sessionId);
|
|
1732
2004
|
} catch {
|
|
1733
2005
|
}
|
|
1734
2006
|
throw new Error(message);
|
|
@@ -1754,9 +2026,9 @@ var MCPClient = class _MCPClient {
|
|
|
1754
2026
|
this.emitError(errorMessage, "connection");
|
|
1755
2027
|
this.emitStateChange("FAILED");
|
|
1756
2028
|
try {
|
|
1757
|
-
const existingSession = await
|
|
2029
|
+
const existingSession = await sessions.get(this.userId, this.sessionId);
|
|
1758
2030
|
if (!existingSession || existingSession.active !== true) {
|
|
1759
|
-
await
|
|
2031
|
+
await sessions.delete(this.userId, this.sessionId);
|
|
1760
2032
|
}
|
|
1761
2033
|
} catch {
|
|
1762
2034
|
}
|
|
@@ -1780,7 +2052,7 @@ var MCPClient = class _MCPClient {
|
|
|
1780
2052
|
this.emitStateChange("FAILED");
|
|
1781
2053
|
throw new Error(error);
|
|
1782
2054
|
}
|
|
1783
|
-
const transportsToTry = this.transportType ? [this.transportType] : ["
|
|
2055
|
+
const transportsToTry = this.transportType ? [this.transportType] : ["streamable-http", "sse"];
|
|
1784
2056
|
let lastError;
|
|
1785
2057
|
let tokensExchanged = false;
|
|
1786
2058
|
let authenticatedStateEmitted = false;
|
|
@@ -2104,7 +2376,7 @@ var MCPClient = class _MCPClient {
|
|
|
2104
2376
|
},
|
|
2105
2377
|
{ capabilities: {} }
|
|
2106
2378
|
);
|
|
2107
|
-
const tt = this.transportType || "
|
|
2379
|
+
const tt = this.transportType || "streamable-http";
|
|
2108
2380
|
this.transport = this.getTransport(tt);
|
|
2109
2381
|
await this.client.connect(this.transport);
|
|
2110
2382
|
}
|
|
@@ -2121,7 +2393,7 @@ var MCPClient = class _MCPClient {
|
|
|
2121
2393
|
if (this.oauthProvider) {
|
|
2122
2394
|
await this.oauthProvider.invalidateCredentials("all");
|
|
2123
2395
|
}
|
|
2124
|
-
await
|
|
2396
|
+
await sessions.delete(this.userId, this.sessionId);
|
|
2125
2397
|
this.disconnect();
|
|
2126
2398
|
}
|
|
2127
2399
|
/**
|
|
@@ -2189,10 +2461,10 @@ var MCPClient = class _MCPClient {
|
|
|
2189
2461
|
}
|
|
2190
2462
|
/**
|
|
2191
2463
|
* Gets the transport type being used
|
|
2192
|
-
* @returns Transport type (defaults to '
|
|
2464
|
+
* @returns Transport type (defaults to 'streamable-http')
|
|
2193
2465
|
*/
|
|
2194
2466
|
getTransportType() {
|
|
2195
|
-
return this.transportType || "
|
|
2467
|
+
return this.transportType || "streamable-http";
|
|
2196
2468
|
}
|
|
2197
2469
|
/**
|
|
2198
2470
|
* Gets the human-readable server name
|
|
@@ -2219,25 +2491,25 @@ var MCPClient = class _MCPClient {
|
|
|
2219
2491
|
* Gets MCP server configuration for all active user sessions
|
|
2220
2492
|
* Loads sessions from Redis, validates OAuth tokens, refreshes if expired
|
|
2221
2493
|
* Returns ready-to-use configuration with valid auth headers
|
|
2222
|
-
* @param
|
|
2494
|
+
* @param userId - User ID to fetch sessions for
|
|
2223
2495
|
* @returns Object keyed by sanitized server labels containing transport, url, headers, etc.
|
|
2224
2496
|
* @static
|
|
2225
2497
|
*/
|
|
2226
|
-
static async getMcpServerConfig(
|
|
2498
|
+
static async getMcpServerConfig(userId) {
|
|
2227
2499
|
const mcpConfig = {};
|
|
2228
|
-
const
|
|
2500
|
+
const sessionList = await sessions.list(userId);
|
|
2229
2501
|
await Promise.all(
|
|
2230
|
-
|
|
2502
|
+
sessionList.map(async (sessionData) => {
|
|
2231
2503
|
const { sessionId } = sessionData;
|
|
2232
2504
|
try {
|
|
2233
2505
|
if (!sessionData.serverId || !sessionData.transportType || !sessionData.serverUrl || !sessionData.callbackUrl) {
|
|
2234
|
-
await
|
|
2506
|
+
await sessions.delete(userId, sessionId);
|
|
2235
2507
|
return;
|
|
2236
2508
|
}
|
|
2237
2509
|
let headers;
|
|
2238
2510
|
try {
|
|
2239
2511
|
const client = new _MCPClient({
|
|
2240
|
-
|
|
2512
|
+
userId,
|
|
2241
2513
|
sessionId,
|
|
2242
2514
|
serverId: sessionData.serverId,
|
|
2243
2515
|
serverUrl: sessionData.serverUrl,
|
|
@@ -2270,7 +2542,7 @@ var MCPClient = class _MCPClient {
|
|
|
2270
2542
|
...headers && { headers }
|
|
2271
2543
|
};
|
|
2272
2544
|
} catch (error) {
|
|
2273
|
-
await
|
|
2545
|
+
await sessions.delete(userId, sessionId);
|
|
2274
2546
|
console.warn(`[MCP] Failed to process session ${sessionId}:`, error);
|
|
2275
2547
|
}
|
|
2276
2548
|
})
|
|
@@ -2287,18 +2559,18 @@ var DEFAULT_RETRY_DELAY_MS = 1e3;
|
|
|
2287
2559
|
var CONNECTION_BATCH_SIZE = 5;
|
|
2288
2560
|
var MultiSessionClient = class {
|
|
2289
2561
|
/**
|
|
2290
|
-
* Creates a new MultiSessionClient for the given user
|
|
2562
|
+
* Creates a new MultiSessionClient for the given user userId.
|
|
2291
2563
|
*
|
|
2292
|
-
* @param
|
|
2564
|
+
* @param userId - A unique string identifying the user (e.g. user ID or email).
|
|
2293
2565
|
* @param options - Optional tuning for connection timeout, retry count, and retry delay.
|
|
2294
2566
|
* Falls back to sensible defaults if not provided.
|
|
2295
2567
|
*/
|
|
2296
|
-
constructor(
|
|
2568
|
+
constructor(userId, options = {}) {
|
|
2297
2569
|
__publicField(this, "clients", []);
|
|
2298
|
-
__publicField(this, "
|
|
2570
|
+
__publicField(this, "userId");
|
|
2299
2571
|
__publicField(this, "options");
|
|
2300
2572
|
__publicField(this, "connectionPromises", /* @__PURE__ */ new Map());
|
|
2301
|
-
this.
|
|
2573
|
+
this.userId = userId;
|
|
2302
2574
|
this.options = {
|
|
2303
2575
|
timeout: DEFAULT_TIMEOUT_MS,
|
|
2304
2576
|
maxRetries: DEFAULT_MAX_RETRIES,
|
|
@@ -2307,7 +2579,7 @@ var MultiSessionClient = class {
|
|
|
2307
2579
|
};
|
|
2308
2580
|
}
|
|
2309
2581
|
/**
|
|
2310
|
-
* Fetches all sessions for this
|
|
2582
|
+
* Fetches all sessions for this userId from storage and returns only the
|
|
2311
2583
|
* ones that are ready to connect.
|
|
2312
2584
|
*
|
|
2313
2585
|
* A session is considered connectable when:
|
|
@@ -2320,8 +2592,8 @@ var MultiSessionClient = class {
|
|
|
2320
2592
|
* for backwards compatibility.
|
|
2321
2593
|
*/
|
|
2322
2594
|
async getActiveSessions() {
|
|
2323
|
-
const
|
|
2324
|
-
const valid =
|
|
2595
|
+
const sessionList = await sessions.list(this.userId);
|
|
2596
|
+
const valid = sessionList.filter(
|
|
2325
2597
|
(s) => s.serverId && s.serverUrl && s.callbackUrl && s.active !== false
|
|
2326
2598
|
// exclude OAuth-pending / failed sessions
|
|
2327
2599
|
);
|
|
@@ -2334,9 +2606,9 @@ var MultiSessionClient = class {
|
|
|
2334
2606
|
* has many active MCP sessions (e.g. 20+ servers). Within each batch, sessions
|
|
2335
2607
|
* are connected concurrently using `Promise.all` for speed.
|
|
2336
2608
|
*/
|
|
2337
|
-
async connectInBatches(
|
|
2338
|
-
for (let i = 0; i <
|
|
2339
|
-
const batch =
|
|
2609
|
+
async connectInBatches(sessions2) {
|
|
2610
|
+
for (let i = 0; i < sessions2.length; i += CONNECTION_BATCH_SIZE) {
|
|
2611
|
+
const batch = sessions2.slice(i, i + CONNECTION_BATCH_SIZE);
|
|
2340
2612
|
await Promise.all(batch.map((session) => this.connectSession(session)));
|
|
2341
2613
|
}
|
|
2342
2614
|
}
|
|
@@ -2387,7 +2659,7 @@ var MultiSessionClient = class {
|
|
|
2387
2659
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
2388
2660
|
try {
|
|
2389
2661
|
const client = new MCPClient({
|
|
2390
|
-
|
|
2662
|
+
userId: this.userId,
|
|
2391
2663
|
sessionId: session.sessionId,
|
|
2392
2664
|
serverId: session.serverId,
|
|
2393
2665
|
serverUrl: session.serverUrl,
|
|
@@ -2419,7 +2691,7 @@ var MultiSessionClient = class {
|
|
|
2419
2691
|
console.error(`[MultiSessionClient] Failed to connect to session ${session.sessionId} after ${maxRetries + 1} attempts:`, lastError);
|
|
2420
2692
|
}
|
|
2421
2693
|
/**
|
|
2422
|
-
* The main entry point. Fetches all active sessions for this
|
|
2694
|
+
* The main entry point. Fetches all active sessions for this userId from
|
|
2423
2695
|
* storage and establishes connections to all of them in batches.
|
|
2424
2696
|
*
|
|
2425
2697
|
* Call this once after creating the client. On traditional servers, you can
|
|
@@ -2427,8 +2699,8 @@ var MultiSessionClient = class {
|
|
|
2427
2699
|
* re-fetching and re-connecting on every request.
|
|
2428
2700
|
*/
|
|
2429
2701
|
async connect() {
|
|
2430
|
-
const
|
|
2431
|
-
await this.connectInBatches(
|
|
2702
|
+
const sessions2 = await this.getActiveSessions();
|
|
2703
|
+
await this.connectInBatches(sessions2);
|
|
2432
2704
|
}
|
|
2433
2705
|
/**
|
|
2434
2706
|
* Returns all currently connected `MCPClient` instances.
|
|
@@ -2487,11 +2759,11 @@ var SSEConnectionManager = class {
|
|
|
2487
2759
|
constructor(options, sendEvent) {
|
|
2488
2760
|
this.options = options;
|
|
2489
2761
|
this.sendEvent = sendEvent;
|
|
2490
|
-
__publicField(this, "
|
|
2762
|
+
__publicField(this, "userId");
|
|
2491
2763
|
__publicField(this, "clients", /* @__PURE__ */ new Map());
|
|
2492
2764
|
__publicField(this, "heartbeatTimer");
|
|
2493
2765
|
__publicField(this, "isActive", true);
|
|
2494
|
-
this.
|
|
2766
|
+
this.userId = options.userId;
|
|
2495
2767
|
this.startHeartbeat();
|
|
2496
2768
|
}
|
|
2497
2769
|
/**
|
|
@@ -2531,8 +2803,8 @@ var SSEConnectionManager = class {
|
|
|
2531
2803
|
try {
|
|
2532
2804
|
let result;
|
|
2533
2805
|
switch (request.method) {
|
|
2534
|
-
case "
|
|
2535
|
-
result = await this.
|
|
2806
|
+
case "listSessions":
|
|
2807
|
+
result = await this.listSessions();
|
|
2536
2808
|
break;
|
|
2537
2809
|
case "connect":
|
|
2538
2810
|
result = await this.connect(request.params);
|
|
@@ -2546,8 +2818,8 @@ var SSEConnectionManager = class {
|
|
|
2546
2818
|
case "callTool":
|
|
2547
2819
|
result = await this.callTool(request.params);
|
|
2548
2820
|
break;
|
|
2549
|
-
case "
|
|
2550
|
-
result = await this.
|
|
2821
|
+
case "getSession":
|
|
2822
|
+
result = await this.getSession(request.params);
|
|
2551
2823
|
break;
|
|
2552
2824
|
case "finishAuth":
|
|
2553
2825
|
result = await this.finishAuth(request.params);
|
|
@@ -2586,12 +2858,12 @@ var SSEConnectionManager = class {
|
|
|
2586
2858
|
}
|
|
2587
2859
|
}
|
|
2588
2860
|
/**
|
|
2589
|
-
* Get all sessions for the current
|
|
2861
|
+
* Get all sessions for the current userId
|
|
2590
2862
|
*/
|
|
2591
|
-
async
|
|
2592
|
-
const
|
|
2863
|
+
async listSessions() {
|
|
2864
|
+
const sessionList = await sessions.list(this.userId);
|
|
2593
2865
|
return {
|
|
2594
|
-
sessions:
|
|
2866
|
+
sessions: sessionList.map((s) => ({
|
|
2595
2867
|
sessionId: s.sessionId,
|
|
2596
2868
|
serverId: s.serverId,
|
|
2597
2869
|
serverName: s.serverName,
|
|
@@ -2608,14 +2880,14 @@ var SSEConnectionManager = class {
|
|
|
2608
2880
|
async connect(params) {
|
|
2609
2881
|
const { serverName, serverUrl, callbackUrl, transportType } = params;
|
|
2610
2882
|
const headers = normalizeHeaders(params.headers);
|
|
2611
|
-
const serverId = params.serverId && params.serverId.length <= 12 ? params.serverId : await
|
|
2612
|
-
const existingSessions = await
|
|
2883
|
+
const serverId = params.serverId && params.serverId.length <= 12 ? params.serverId : await sessions.generateSessionId();
|
|
2884
|
+
const existingSessions = await sessions.list(this.userId);
|
|
2613
2885
|
const duplicate = existingSessions.find(
|
|
2614
2886
|
(s) => s.serverId === serverId || s.serverUrl === serverUrl
|
|
2615
2887
|
);
|
|
2616
2888
|
if (duplicate) {
|
|
2617
2889
|
if (duplicate.active === false) {
|
|
2618
|
-
await this.
|
|
2890
|
+
await this.getSession({ sessionId: duplicate.sessionId });
|
|
2619
2891
|
return {
|
|
2620
2892
|
sessionId: duplicate.sessionId,
|
|
2621
2893
|
success: true
|
|
@@ -2623,11 +2895,11 @@ var SSEConnectionManager = class {
|
|
|
2623
2895
|
}
|
|
2624
2896
|
throw new Error(`Connection already exists for server: ${duplicate.serverUrl || duplicate.serverId} (${duplicate.serverName})`);
|
|
2625
2897
|
}
|
|
2626
|
-
const sessionId = await
|
|
2898
|
+
const sessionId = await sessions.generateSessionId();
|
|
2627
2899
|
try {
|
|
2628
2900
|
const clientMetadata = await this.getResolvedClientMetadata();
|
|
2629
2901
|
const client = new MCPClient({
|
|
2630
|
-
|
|
2902
|
+
userId: this.userId,
|
|
2631
2903
|
sessionId,
|
|
2632
2904
|
serverId,
|
|
2633
2905
|
serverName,
|
|
@@ -2682,7 +2954,7 @@ var SSEConnectionManager = class {
|
|
|
2682
2954
|
client.disconnect();
|
|
2683
2955
|
this.clients.delete(sessionId);
|
|
2684
2956
|
} else {
|
|
2685
|
-
await
|
|
2957
|
+
await sessions.delete(this.userId, sessionId);
|
|
2686
2958
|
}
|
|
2687
2959
|
return { success: true };
|
|
2688
2960
|
}
|
|
@@ -2694,12 +2966,12 @@ var SSEConnectionManager = class {
|
|
|
2694
2966
|
if (existing) {
|
|
2695
2967
|
return existing;
|
|
2696
2968
|
}
|
|
2697
|
-
const session = await
|
|
2969
|
+
const session = await sessions.get(this.userId, sessionId);
|
|
2698
2970
|
if (!session) {
|
|
2699
2971
|
throw new Error("Session not found");
|
|
2700
2972
|
}
|
|
2701
2973
|
const client = new MCPClient({
|
|
2702
|
-
|
|
2974
|
+
userId: this.userId,
|
|
2703
2975
|
sessionId,
|
|
2704
2976
|
// These fields are optional in MCPClient, but when rehydrating a known
|
|
2705
2977
|
// stored session on the server we pass them explicitly to preserve the
|
|
@@ -2746,9 +3018,9 @@ var SSEConnectionManager = class {
|
|
|
2746
3018
|
/**
|
|
2747
3019
|
* Restore and validate an existing session
|
|
2748
3020
|
*/
|
|
2749
|
-
async
|
|
3021
|
+
async getSession(params) {
|
|
2750
3022
|
const { sessionId } = params;
|
|
2751
|
-
const session = await
|
|
3023
|
+
const session = await sessions.get(this.userId, sessionId);
|
|
2752
3024
|
if (!session) {
|
|
2753
3025
|
throw new Error("Session not found");
|
|
2754
3026
|
}
|
|
@@ -2765,7 +3037,7 @@ var SSEConnectionManager = class {
|
|
|
2765
3037
|
try {
|
|
2766
3038
|
const clientMetadata = await this.getResolvedClientMetadata();
|
|
2767
3039
|
const client = new MCPClient({
|
|
2768
|
-
|
|
3040
|
+
userId: this.userId,
|
|
2769
3041
|
sessionId,
|
|
2770
3042
|
// These fields are optional in MCPClient, but when rehydrating a known
|
|
2771
3043
|
// stored session on the server we pass them explicitly to preserve the
|
|
@@ -2802,13 +3074,13 @@ var SSEConnectionManager = class {
|
|
|
2802
3074
|
*/
|
|
2803
3075
|
async finishAuth(params) {
|
|
2804
3076
|
const { sessionId, code } = params;
|
|
2805
|
-
const session = await
|
|
3077
|
+
const session = await sessions.get(this.userId, sessionId);
|
|
2806
3078
|
if (!session) {
|
|
2807
3079
|
throw new Error("Session not found");
|
|
2808
3080
|
}
|
|
2809
3081
|
try {
|
|
2810
3082
|
const client = new MCPClient({
|
|
2811
|
-
|
|
3083
|
+
userId: this.userId,
|
|
2812
3084
|
sessionId,
|
|
2813
3085
|
// These fields are optional in MCPClient, but when rehydrating a known
|
|
2814
3086
|
// stored session on the server we pass them explicitly to preserve the
|
|
@@ -2819,7 +3091,7 @@ var SSEConnectionManager = class {
|
|
|
2819
3091
|
serverUrl: session.serverUrl,
|
|
2820
3092
|
callbackUrl: session.callbackUrl,
|
|
2821
3093
|
// NOTE: transportType is intentionally omitted here.
|
|
2822
|
-
// The session's stored transportType is a placeholder ('
|
|
3094
|
+
// The session's stored transportType is a placeholder ('streamable-http')
|
|
2823
3095
|
// set before transport negotiation. Omitting it lets MCPClient auto-negotiate
|
|
2824
3096
|
// (try streamable_http → SSE fallback), which is critical for servers like
|
|
2825
3097
|
// Neon that only support SSE transport.
|
|
@@ -2942,18 +3214,21 @@ function writeSSEEvent(res, event, data) {
|
|
|
2942
3214
|
init_cjs_shims();
|
|
2943
3215
|
function createNextMcpHandler(options = {}) {
|
|
2944
3216
|
const {
|
|
2945
|
-
|
|
3217
|
+
getUserId = (request) => request.headers.get("x-mcp-user-id"),
|
|
2946
3218
|
getAuthToken = (request) => {
|
|
2947
|
-
const
|
|
2948
|
-
|
|
3219
|
+
const authHeader = request.headers.get("authorization");
|
|
3220
|
+
if (authHeader?.toLowerCase().startsWith("bearer ")) {
|
|
3221
|
+
return authHeader.slice(7);
|
|
3222
|
+
}
|
|
3223
|
+
return authHeader;
|
|
2949
3224
|
},
|
|
2950
3225
|
authenticate = () => true,
|
|
2951
3226
|
heartbeatInterval = 3e4,
|
|
2952
3227
|
clientDefaults,
|
|
2953
3228
|
getClientMetadata
|
|
2954
3229
|
} = options;
|
|
2955
|
-
const toManagerOptions = (
|
|
2956
|
-
|
|
3230
|
+
const toManagerOptions = (userId, resolvedClientMetadata) => ({
|
|
3231
|
+
userId,
|
|
2957
3232
|
heartbeatInterval,
|
|
2958
3233
|
clientDefaults: resolvedClientMetadata
|
|
2959
3234
|
});
|
|
@@ -2972,13 +3247,13 @@ function createNextMcpHandler(options = {}) {
|
|
|
2972
3247
|
);
|
|
2973
3248
|
}
|
|
2974
3249
|
async function POST(request) {
|
|
2975
|
-
const
|
|
3250
|
+
const userId = getUserId(request);
|
|
2976
3251
|
const authToken = getAuthToken(request);
|
|
2977
3252
|
const acceptsEventStream = (request.headers.get("accept") || "").toLowerCase().includes("text/event-stream");
|
|
2978
|
-
if (!
|
|
2979
|
-
return Response.json({ error: { code: "
|
|
3253
|
+
if (!userId) {
|
|
3254
|
+
return Response.json({ error: { code: "MISSING_userId", message: "Missing userId" } }, { status: 400 });
|
|
2980
3255
|
}
|
|
2981
|
-
const isAuthorized = await authenticate(
|
|
3256
|
+
const isAuthorized = await authenticate(userId, authToken);
|
|
2982
3257
|
if (!isAuthorized) {
|
|
2983
3258
|
return Response.json({ error: { code: "UNAUTHORIZED", message: "Unauthorized" } }, { status: 401 });
|
|
2984
3259
|
}
|
|
@@ -3000,7 +3275,7 @@ function createNextMcpHandler(options = {}) {
|
|
|
3000
3275
|
const resolvedClientMetadata = await resolveClientMetadata(request);
|
|
3001
3276
|
if (!acceptsEventStream) {
|
|
3002
3277
|
const manager2 = new SSEConnectionManager(
|
|
3003
|
-
toManagerOptions(
|
|
3278
|
+
toManagerOptions(userId, resolvedClientMetadata),
|
|
3004
3279
|
() => {
|
|
3005
3280
|
}
|
|
3006
3281
|
);
|
|
@@ -3026,7 +3301,7 @@ data: ${JSON.stringify(data)}
|
|
|
3026
3301
|
});
|
|
3027
3302
|
};
|
|
3028
3303
|
const manager = new SSEConnectionManager(
|
|
3029
|
-
toManagerOptions(
|
|
3304
|
+
toManagerOptions(userId, resolvedClientMetadata),
|
|
3030
3305
|
(event) => {
|
|
3031
3306
|
if (isRpcResponseEvent(event)) {
|
|
3032
3307
|
sendSSE("rpc-response", event);
|
|
@@ -3069,7 +3344,7 @@ data: ${JSON.stringify(data)}
|
|
|
3069
3344
|
} catch (error) {
|
|
3070
3345
|
const err = error instanceof Error ? error : new Error("Unknown error");
|
|
3071
3346
|
console.error("[MCP Next Handler] Failed to handle RPC", {
|
|
3072
|
-
|
|
3347
|
+
userId,
|
|
3073
3348
|
message: err.message,
|
|
3074
3349
|
stack: err.stack,
|
|
3075
3350
|
rawBody: rawBody.slice(0, 500)
|
|
@@ -3096,6 +3371,6 @@ exports.UnauthorizedError = UnauthorizedError;
|
|
|
3096
3371
|
exports.createNextMcpHandler = createNextMcpHandler;
|
|
3097
3372
|
exports.createSSEHandler = createSSEHandler;
|
|
3098
3373
|
exports.sanitizeServerLabel = sanitizeServerLabel;
|
|
3099
|
-
exports.
|
|
3374
|
+
exports.sessions = sessions;
|
|
3100
3375
|
//# sourceMappingURL=index.js.map
|
|
3101
3376
|
//# sourceMappingURL=index.js.map
|