@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.mjs
CHANGED
|
@@ -149,35 +149,35 @@ var RedisStorageBackend = class {
|
|
|
149
149
|
this.redis = redis2;
|
|
150
150
|
__publicField(this, "DEFAULT_TTL", SESSION_TTL_SECONDS);
|
|
151
151
|
__publicField(this, "KEY_PREFIX", "mcp:session:");
|
|
152
|
-
__publicField(this, "
|
|
153
|
-
__publicField(this, "
|
|
152
|
+
__publicField(this, "USER_ID_KEY_PREFIX", "mcp:userId:");
|
|
153
|
+
__publicField(this, "USER_ID_KEY_SUFFIX", ":sessions");
|
|
154
154
|
}
|
|
155
155
|
async init() {
|
|
156
156
|
try {
|
|
157
157
|
await this.redis.ping();
|
|
158
158
|
console.log("[mcp-ts][Storage] Redis: \u2713 Connected to server.");
|
|
159
159
|
} catch (error) {
|
|
160
|
-
throw new Error(`[
|
|
160
|
+
throw new Error(`[RedisStorageBackend] Failed to connect to Redis: ${error.message}`);
|
|
161
161
|
}
|
|
162
162
|
}
|
|
163
163
|
/**
|
|
164
164
|
* Generates Redis key for a specific session
|
|
165
165
|
* @private
|
|
166
166
|
*/
|
|
167
|
-
getSessionKey(
|
|
168
|
-
return `${this.KEY_PREFIX}${
|
|
167
|
+
getSessionKey(userId, sessionId) {
|
|
168
|
+
return `${this.KEY_PREFIX}${userId}:${sessionId}`;
|
|
169
169
|
}
|
|
170
170
|
/**
|
|
171
|
-
* Generates Redis key for tracking all sessions for
|
|
171
|
+
* Generates Redis key for tracking all sessions for a user
|
|
172
172
|
* @private
|
|
173
173
|
*/
|
|
174
|
-
|
|
175
|
-
return `${this.
|
|
174
|
+
getUserIdKey(userId) {
|
|
175
|
+
return `${this.USER_ID_KEY_PREFIX}${userId}${this.USER_ID_KEY_SUFFIX}`;
|
|
176
176
|
}
|
|
177
|
-
|
|
178
|
-
return
|
|
179
|
-
this.
|
|
180
|
-
|
|
177
|
+
parseUserIdFromKey(userIdKey) {
|
|
178
|
+
return userIdKey.slice(
|
|
179
|
+
this.USER_ID_KEY_PREFIX.length,
|
|
180
|
+
userIdKey.length - this.USER_ID_KEY_SUFFIX.length
|
|
181
181
|
);
|
|
182
182
|
}
|
|
183
183
|
async scanKeys(pattern) {
|
|
@@ -196,7 +196,7 @@ var RedisStorageBackend = class {
|
|
|
196
196
|
}
|
|
197
197
|
} while (cursor !== "0");
|
|
198
198
|
} catch (error) {
|
|
199
|
-
console.warn("[
|
|
199
|
+
console.warn("[RedisStorageBackend] SCAN failed, falling back to KEYS:", error);
|
|
200
200
|
return await this.redis.keys(pattern);
|
|
201
201
|
}
|
|
202
202
|
return Array.from(keys);
|
|
@@ -204,11 +204,11 @@ var RedisStorageBackend = class {
|
|
|
204
204
|
generateSessionId() {
|
|
205
205
|
return generateSessionId();
|
|
206
206
|
}
|
|
207
|
-
async
|
|
208
|
-
const { sessionId,
|
|
209
|
-
if (!sessionId || !
|
|
210
|
-
const sessionKey = this.getSessionKey(
|
|
211
|
-
const
|
|
207
|
+
async create(session, ttl) {
|
|
208
|
+
const { sessionId, userId } = session;
|
|
209
|
+
if (!sessionId || !userId) throw new Error("userId and sessionId required");
|
|
210
|
+
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
211
|
+
const userIdKey = this.getUserIdKey(userId);
|
|
212
212
|
const effectiveTtl = ttl ?? this.DEFAULT_TTL;
|
|
213
213
|
const result = await this.redis.set(
|
|
214
214
|
sessionKey,
|
|
@@ -220,10 +220,10 @@ var RedisStorageBackend = class {
|
|
|
220
220
|
if (result !== "OK") {
|
|
221
221
|
throw new Error(`Session ${sessionId} already exists`);
|
|
222
222
|
}
|
|
223
|
-
await this.redis.sadd(
|
|
223
|
+
await this.redis.sadd(userIdKey, sessionId);
|
|
224
224
|
}
|
|
225
|
-
async
|
|
226
|
-
const sessionKey = this.getSessionKey(
|
|
225
|
+
async update(userId, sessionId, data, ttl) {
|
|
226
|
+
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
227
227
|
const effectiveTtl = ttl ?? this.DEFAULT_TTL;
|
|
228
228
|
const script = `
|
|
229
229
|
local currentStr = redis.call("GET", KEYS[1])
|
|
@@ -249,62 +249,62 @@ var RedisStorageBackend = class {
|
|
|
249
249
|
effectiveTtl
|
|
250
250
|
);
|
|
251
251
|
if (result === 0) {
|
|
252
|
-
throw new Error(`Session ${sessionId} not found for
|
|
252
|
+
throw new Error(`Session ${sessionId} not found for userId ${userId}`);
|
|
253
253
|
}
|
|
254
254
|
}
|
|
255
|
-
async
|
|
255
|
+
async get(userId, sessionId) {
|
|
256
256
|
try {
|
|
257
|
-
const sessionKey = this.getSessionKey(
|
|
257
|
+
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
258
258
|
const sessionDataStr = await this.redis.get(sessionKey);
|
|
259
259
|
if (!sessionDataStr) {
|
|
260
260
|
return null;
|
|
261
261
|
}
|
|
262
|
-
const
|
|
263
|
-
return
|
|
262
|
+
const Session = JSON.parse(sessionDataStr);
|
|
263
|
+
return Session;
|
|
264
264
|
} catch (error) {
|
|
265
|
-
console.error("[
|
|
265
|
+
console.error("[RedisStorageBackend] Failed to get session:", error);
|
|
266
266
|
return null;
|
|
267
267
|
}
|
|
268
268
|
}
|
|
269
|
-
async
|
|
270
|
-
const
|
|
271
|
-
return
|
|
269
|
+
async listIds(userId) {
|
|
270
|
+
const sessions2 = await this.list(userId);
|
|
271
|
+
return sessions2.map((session) => session.sessionId);
|
|
272
272
|
}
|
|
273
|
-
async
|
|
273
|
+
async list(userId) {
|
|
274
274
|
try {
|
|
275
|
-
const
|
|
276
|
-
const sessionIds = await this.redis.smembers(
|
|
275
|
+
const userIdKey = this.getUserIdKey(userId);
|
|
276
|
+
const sessionIds = await this.redis.smembers(userIdKey);
|
|
277
277
|
if (sessionIds.length === 0) return [];
|
|
278
278
|
const results = await Promise.all(
|
|
279
279
|
sessionIds.map(async (sessionId) => {
|
|
280
|
-
const data = await this.redis.get(this.getSessionKey(
|
|
280
|
+
const data = await this.redis.get(this.getSessionKey(userId, sessionId));
|
|
281
281
|
return data ? JSON.parse(data) : null;
|
|
282
282
|
})
|
|
283
283
|
);
|
|
284
284
|
const staleSessionIds = sessionIds.filter((_, index) => results[index] === null);
|
|
285
285
|
if (staleSessionIds.length > 0) {
|
|
286
|
-
await this.redis.srem(
|
|
286
|
+
await this.redis.srem(userIdKey, ...staleSessionIds);
|
|
287
287
|
}
|
|
288
288
|
return results.filter((session) => session !== null);
|
|
289
289
|
} catch (error) {
|
|
290
|
-
console.error(`[
|
|
290
|
+
console.error(`[RedisStorageBackend] Failed to get session data for ${userId}:`, error);
|
|
291
291
|
return [];
|
|
292
292
|
}
|
|
293
293
|
}
|
|
294
|
-
async
|
|
294
|
+
async delete(userId, sessionId) {
|
|
295
295
|
try {
|
|
296
|
-
const sessionKey = this.getSessionKey(
|
|
297
|
-
const
|
|
298
|
-
await this.redis.srem(
|
|
296
|
+
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
297
|
+
const userIdKey = this.getUserIdKey(userId);
|
|
298
|
+
await this.redis.srem(userIdKey, sessionId);
|
|
299
299
|
await this.redis.del(sessionKey);
|
|
300
300
|
} catch (error) {
|
|
301
|
-
console.error("[
|
|
301
|
+
console.error("[RedisStorageBackend] Failed to remove session:", error);
|
|
302
302
|
}
|
|
303
303
|
}
|
|
304
|
-
async
|
|
304
|
+
async listAllIds() {
|
|
305
305
|
try {
|
|
306
306
|
const keys = await this.scanKeys(`${this.KEY_PREFIX}*`);
|
|
307
|
-
const
|
|
307
|
+
const sessions2 = await Promise.all(
|
|
308
308
|
keys.map(async (key) => {
|
|
309
309
|
const data = await this.redis.get(key);
|
|
310
310
|
if (!data) {
|
|
@@ -313,60 +313,60 @@ var RedisStorageBackend = class {
|
|
|
313
313
|
try {
|
|
314
314
|
return JSON.parse(data).sessionId;
|
|
315
315
|
} catch (error) {
|
|
316
|
-
console.error("[
|
|
316
|
+
console.error("[RedisStorageBackend] Failed to parse session while listing all session IDs:", error);
|
|
317
317
|
return null;
|
|
318
318
|
}
|
|
319
319
|
})
|
|
320
320
|
);
|
|
321
|
-
return
|
|
321
|
+
return sessions2.filter((sessionId) => sessionId !== null);
|
|
322
322
|
} catch (error) {
|
|
323
|
-
console.error("[
|
|
323
|
+
console.error("[RedisStorageBackend] Failed to get all sessions:", error);
|
|
324
324
|
return [];
|
|
325
325
|
}
|
|
326
326
|
}
|
|
327
327
|
async clearAll() {
|
|
328
328
|
try {
|
|
329
329
|
const keys = await this.scanKeys(`${this.KEY_PREFIX}*`);
|
|
330
|
-
const
|
|
331
|
-
const allKeys = [...keys, ...
|
|
330
|
+
const userIdKeys = await this.scanKeys(`${this.USER_ID_KEY_PREFIX}*${this.USER_ID_KEY_SUFFIX}`);
|
|
331
|
+
const allKeys = [...keys, ...userIdKeys];
|
|
332
332
|
if (allKeys.length > 0) {
|
|
333
333
|
await this.redis.del(...allKeys);
|
|
334
334
|
}
|
|
335
335
|
} catch (error) {
|
|
336
|
-
console.error("[
|
|
336
|
+
console.error("[RedisStorageBackend] Failed to clear sessions:", error);
|
|
337
337
|
}
|
|
338
338
|
}
|
|
339
|
-
async
|
|
339
|
+
async cleanupExpired() {
|
|
340
340
|
try {
|
|
341
|
-
const
|
|
342
|
-
for (const
|
|
343
|
-
const
|
|
344
|
-
const sessionIds = await this.redis.smembers(
|
|
341
|
+
const userIdKeys = await this.scanKeys(`${this.USER_ID_KEY_PREFIX}*${this.USER_ID_KEY_SUFFIX}`);
|
|
342
|
+
for (const userIdKey of userIdKeys) {
|
|
343
|
+
const userId = this.parseUserIdFromKey(userIdKey);
|
|
344
|
+
const sessionIds = await this.redis.smembers(userIdKey);
|
|
345
345
|
if (sessionIds.length === 0) {
|
|
346
|
-
await this.redis.del(
|
|
346
|
+
await this.redis.del(userIdKey);
|
|
347
347
|
continue;
|
|
348
348
|
}
|
|
349
349
|
const existenceChecks = await Promise.all(
|
|
350
|
-
sessionIds.map((sessionId) => this.redis.exists(this.getSessionKey(
|
|
350
|
+
sessionIds.map((sessionId) => this.redis.exists(this.getSessionKey(userId, sessionId)))
|
|
351
351
|
);
|
|
352
352
|
const staleSessionIds = sessionIds.filter((_, index) => existenceChecks[index] === 0);
|
|
353
353
|
if (staleSessionIds.length > 0) {
|
|
354
|
-
await this.redis.srem(
|
|
354
|
+
await this.redis.srem(userIdKey, ...staleSessionIds);
|
|
355
355
|
}
|
|
356
|
-
const remainingCount = await this.redis.scard(
|
|
356
|
+
const remainingCount = await this.redis.scard(userIdKey);
|
|
357
357
|
if (remainingCount === 0) {
|
|
358
|
-
await this.redis.del(
|
|
358
|
+
await this.redis.del(userIdKey);
|
|
359
359
|
}
|
|
360
360
|
}
|
|
361
361
|
} catch (error) {
|
|
362
|
-
console.error("[
|
|
362
|
+
console.error("[RedisStorageBackend] Failed to cleanup expired sessions:", error);
|
|
363
363
|
}
|
|
364
364
|
}
|
|
365
365
|
async disconnect() {
|
|
366
366
|
try {
|
|
367
367
|
await this.redis.quit();
|
|
368
368
|
} catch (error) {
|
|
369
|
-
console.error("[
|
|
369
|
+
console.error("[RedisStorageBackend] Failed to disconnect:", error);
|
|
370
370
|
}
|
|
371
371
|
}
|
|
372
372
|
};
|
|
@@ -374,36 +374,36 @@ var RedisStorageBackend = class {
|
|
|
374
374
|
// src/server/storage/memory-backend.ts
|
|
375
375
|
var MemoryStorageBackend = class {
|
|
376
376
|
constructor() {
|
|
377
|
-
// Map<
|
|
377
|
+
// Map<userId:sessionId, Session>
|
|
378
378
|
__publicField(this, "sessions", /* @__PURE__ */ new Map());
|
|
379
|
-
// Map<
|
|
380
|
-
__publicField(this, "
|
|
379
|
+
// Map<userId, Set<sessionId>>
|
|
380
|
+
__publicField(this, "userIdSessions", /* @__PURE__ */ new Map());
|
|
381
381
|
}
|
|
382
382
|
async init() {
|
|
383
383
|
console.log("[mcp-ts][Storage] Memory: \u2713 internal memory store active.");
|
|
384
384
|
}
|
|
385
|
-
getSessionKey(
|
|
386
|
-
return `${
|
|
385
|
+
getSessionKey(userId, sessionId) {
|
|
386
|
+
return `${userId}:${sessionId}`;
|
|
387
387
|
}
|
|
388
388
|
generateSessionId() {
|
|
389
389
|
return generateSessionId();
|
|
390
390
|
}
|
|
391
|
-
async
|
|
392
|
-
const { sessionId,
|
|
393
|
-
if (!sessionId || !
|
|
394
|
-
const sessionKey = this.getSessionKey(
|
|
391
|
+
async create(session, ttl) {
|
|
392
|
+
const { sessionId, userId } = session;
|
|
393
|
+
if (!sessionId || !userId) throw new Error("userId and sessionId required");
|
|
394
|
+
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
395
395
|
if (this.sessions.has(sessionKey)) {
|
|
396
396
|
throw new Error(`Session ${sessionId} already exists`);
|
|
397
397
|
}
|
|
398
398
|
this.sessions.set(sessionKey, session);
|
|
399
|
-
if (!this.
|
|
400
|
-
this.
|
|
399
|
+
if (!this.userIdSessions.has(userId)) {
|
|
400
|
+
this.userIdSessions.set(userId, /* @__PURE__ */ new Set());
|
|
401
401
|
}
|
|
402
|
-
this.
|
|
402
|
+
this.userIdSessions.get(userId).add(sessionId);
|
|
403
403
|
}
|
|
404
|
-
async
|
|
405
|
-
if (!
|
|
406
|
-
const sessionKey = this.getSessionKey(
|
|
404
|
+
async update(userId, sessionId, data, ttl) {
|
|
405
|
+
if (!userId || !sessionId) throw new Error("userId and sessionId required");
|
|
406
|
+
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
407
407
|
const current = this.sessions.get(sessionKey);
|
|
408
408
|
if (!current) {
|
|
409
409
|
throw new Error(`Session ${sessionId} not found`);
|
|
@@ -414,45 +414,45 @@ var MemoryStorageBackend = class {
|
|
|
414
414
|
};
|
|
415
415
|
this.sessions.set(sessionKey, updated);
|
|
416
416
|
}
|
|
417
|
-
async
|
|
418
|
-
const sessionKey = this.getSessionKey(
|
|
417
|
+
async get(userId, sessionId) {
|
|
418
|
+
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
419
419
|
return this.sessions.get(sessionKey) || null;
|
|
420
420
|
}
|
|
421
|
-
async
|
|
422
|
-
const set = this.
|
|
421
|
+
async listIds(userId) {
|
|
422
|
+
const set = this.userIdSessions.get(userId);
|
|
423
423
|
return set ? Array.from(set) : [];
|
|
424
424
|
}
|
|
425
|
-
async
|
|
426
|
-
const set = this.
|
|
425
|
+
async list(userId) {
|
|
426
|
+
const set = this.userIdSessions.get(userId);
|
|
427
427
|
if (!set) return [];
|
|
428
428
|
const results = [];
|
|
429
429
|
for (const sessionId of set) {
|
|
430
|
-
const session = this.sessions.get(this.getSessionKey(
|
|
430
|
+
const session = this.sessions.get(this.getSessionKey(userId, sessionId));
|
|
431
431
|
if (session) {
|
|
432
432
|
results.push(session);
|
|
433
433
|
}
|
|
434
434
|
}
|
|
435
435
|
return results;
|
|
436
436
|
}
|
|
437
|
-
async
|
|
438
|
-
const sessionKey = this.getSessionKey(
|
|
437
|
+
async delete(userId, sessionId) {
|
|
438
|
+
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
439
439
|
this.sessions.delete(sessionKey);
|
|
440
|
-
const set = this.
|
|
440
|
+
const set = this.userIdSessions.get(userId);
|
|
441
441
|
if (set) {
|
|
442
442
|
set.delete(sessionId);
|
|
443
443
|
if (set.size === 0) {
|
|
444
|
-
this.
|
|
444
|
+
this.userIdSessions.delete(userId);
|
|
445
445
|
}
|
|
446
446
|
}
|
|
447
447
|
}
|
|
448
|
-
async
|
|
448
|
+
async listAllIds() {
|
|
449
449
|
return Array.from(this.sessions.values()).map((s) => s.sessionId);
|
|
450
450
|
}
|
|
451
451
|
async clearAll() {
|
|
452
452
|
this.sessions.clear();
|
|
453
|
-
this.
|
|
453
|
+
this.userIdSessions.clear();
|
|
454
454
|
}
|
|
455
|
-
async
|
|
455
|
+
async cleanupExpired() {
|
|
456
456
|
}
|
|
457
457
|
async disconnect() {
|
|
458
458
|
}
|
|
@@ -480,7 +480,7 @@ var FileStorageBackend = class {
|
|
|
480
480
|
this.memoryCache = /* @__PURE__ */ new Map();
|
|
481
481
|
if (Array.isArray(json)) {
|
|
482
482
|
json.forEach((s) => {
|
|
483
|
-
this.memoryCache.set(this.getSessionKey(s.
|
|
483
|
+
this.memoryCache.set(this.getSessionKey(s.userId || "unknown", s.sessionId), s);
|
|
484
484
|
});
|
|
485
485
|
}
|
|
486
486
|
} catch (error) {
|
|
@@ -500,30 +500,30 @@ var FileStorageBackend = class {
|
|
|
500
500
|
}
|
|
501
501
|
async flush() {
|
|
502
502
|
if (!this.memoryCache) return;
|
|
503
|
-
const
|
|
504
|
-
await promises.writeFile(this.filePath, JSON.stringify(
|
|
503
|
+
const sessions2 = Array.from(this.memoryCache.values());
|
|
504
|
+
await promises.writeFile(this.filePath, JSON.stringify(sessions2, null, 2), "utf-8");
|
|
505
505
|
}
|
|
506
|
-
getSessionKey(
|
|
507
|
-
return `${
|
|
506
|
+
getSessionKey(userId, sessionId) {
|
|
507
|
+
return `${userId}:${sessionId}`;
|
|
508
508
|
}
|
|
509
509
|
generateSessionId() {
|
|
510
510
|
return generateSessionId();
|
|
511
511
|
}
|
|
512
|
-
async
|
|
512
|
+
async create(session, ttl) {
|
|
513
513
|
await this.ensureInitialized();
|
|
514
|
-
const { sessionId,
|
|
515
|
-
if (!sessionId || !
|
|
516
|
-
const sessionKey = this.getSessionKey(
|
|
514
|
+
const { sessionId, userId } = session;
|
|
515
|
+
if (!sessionId || !userId) throw new Error("userId and sessionId required");
|
|
516
|
+
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
517
517
|
if (this.memoryCache.has(sessionKey)) {
|
|
518
518
|
throw new Error(`Session ${sessionId} already exists`);
|
|
519
519
|
}
|
|
520
520
|
this.memoryCache.set(sessionKey, session);
|
|
521
521
|
await this.flush();
|
|
522
522
|
}
|
|
523
|
-
async
|
|
523
|
+
async update(userId, sessionId, data, ttl) {
|
|
524
524
|
await this.ensureInitialized();
|
|
525
|
-
if (!
|
|
526
|
-
const sessionKey = this.getSessionKey(
|
|
525
|
+
if (!userId || !sessionId) throw new Error("userId and sessionId required");
|
|
526
|
+
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
527
527
|
const current = this.memoryCache.get(sessionKey);
|
|
528
528
|
if (!current) {
|
|
529
529
|
throw new Error(`Session ${sessionId} not found`);
|
|
@@ -535,27 +535,27 @@ var FileStorageBackend = class {
|
|
|
535
535
|
this.memoryCache.set(sessionKey, updated);
|
|
536
536
|
await this.flush();
|
|
537
537
|
}
|
|
538
|
-
async
|
|
538
|
+
async get(userId, sessionId) {
|
|
539
539
|
await this.ensureInitialized();
|
|
540
|
-
const sessionKey = this.getSessionKey(
|
|
540
|
+
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
541
541
|
return this.memoryCache.get(sessionKey) || null;
|
|
542
542
|
}
|
|
543
|
-
async
|
|
543
|
+
async list(userId) {
|
|
544
544
|
await this.ensureInitialized();
|
|
545
|
-
return Array.from(this.memoryCache.values()).filter((s) => s.
|
|
545
|
+
return Array.from(this.memoryCache.values()).filter((s) => s.userId === userId);
|
|
546
546
|
}
|
|
547
|
-
async
|
|
547
|
+
async listIds(userId) {
|
|
548
548
|
await this.ensureInitialized();
|
|
549
|
-
return Array.from(this.memoryCache.values()).filter((s) => s.
|
|
549
|
+
return Array.from(this.memoryCache.values()).filter((s) => s.userId === userId).map((s) => s.sessionId);
|
|
550
550
|
}
|
|
551
|
-
async
|
|
551
|
+
async delete(userId, sessionId) {
|
|
552
552
|
await this.ensureInitialized();
|
|
553
|
-
const sessionKey = this.getSessionKey(
|
|
553
|
+
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
554
554
|
if (this.memoryCache.delete(sessionKey)) {
|
|
555
555
|
await this.flush();
|
|
556
556
|
}
|
|
557
557
|
}
|
|
558
|
-
async
|
|
558
|
+
async listAllIds() {
|
|
559
559
|
await this.ensureInitialized();
|
|
560
560
|
return Array.from(this.memoryCache.values()).map((s) => s.sessionId);
|
|
561
561
|
}
|
|
@@ -564,7 +564,7 @@ var FileStorageBackend = class {
|
|
|
564
564
|
this.memoryCache.clear();
|
|
565
565
|
await this.flush();
|
|
566
566
|
}
|
|
567
|
-
async
|
|
567
|
+
async cleanupExpired() {
|
|
568
568
|
await this.ensureInitialized();
|
|
569
569
|
}
|
|
570
570
|
async disconnect() {
|
|
@@ -591,11 +591,11 @@ var SqliteStorage = class {
|
|
|
591
591
|
this.db.exec(`
|
|
592
592
|
CREATE TABLE IF NOT EXISTS ${this.table} (
|
|
593
593
|
sessionId TEXT PRIMARY KEY,
|
|
594
|
-
|
|
594
|
+
userId TEXT NOT NULL,
|
|
595
595
|
data TEXT NOT NULL,
|
|
596
596
|
expiresAt INTEGER
|
|
597
597
|
);
|
|
598
|
-
CREATE INDEX IF NOT EXISTS idx_${this.table}
|
|
598
|
+
CREATE INDEX IF NOT EXISTS idx_${this.table}_userId ON ${this.table}(userId);
|
|
599
599
|
`);
|
|
600
600
|
this.initialized = true;
|
|
601
601
|
console.log(`[mcp-ts][Storage] SQLite: \u2713 database at ${this.dbPath} verified.`);
|
|
@@ -616,18 +616,18 @@ var SqliteStorage = class {
|
|
|
616
616
|
generateSessionId() {
|
|
617
617
|
return generateSessionId();
|
|
618
618
|
}
|
|
619
|
-
async
|
|
619
|
+
async create(session, ttl) {
|
|
620
620
|
this.ensureInitialized();
|
|
621
|
-
const { sessionId,
|
|
622
|
-
if (!sessionId || !
|
|
623
|
-
throw new Error("
|
|
621
|
+
const { sessionId, userId } = session;
|
|
622
|
+
if (!sessionId || !userId) {
|
|
623
|
+
throw new Error("userId and sessionId required");
|
|
624
624
|
}
|
|
625
625
|
const expiresAt = ttl ? Date.now() + ttl * 1e3 : null;
|
|
626
626
|
try {
|
|
627
627
|
const stmt = this.db.prepare(
|
|
628
|
-
`INSERT INTO ${this.table} (sessionId,
|
|
628
|
+
`INSERT INTO ${this.table} (sessionId, userId, data, expiresAt) VALUES (?, ?, ?, ?)`
|
|
629
629
|
);
|
|
630
|
-
stmt.run(sessionId,
|
|
630
|
+
stmt.run(sessionId, userId, JSON.stringify(session), expiresAt);
|
|
631
631
|
} catch (error) {
|
|
632
632
|
if (error.code === "SQLITE_CONSTRAINT_PRIMARYKEY") {
|
|
633
633
|
throw new Error(`Session ${sessionId} already exists`);
|
|
@@ -635,55 +635,55 @@ var SqliteStorage = class {
|
|
|
635
635
|
throw error;
|
|
636
636
|
}
|
|
637
637
|
}
|
|
638
|
-
async
|
|
638
|
+
async update(userId, sessionId, data, ttl) {
|
|
639
639
|
this.ensureInitialized();
|
|
640
|
-
if (!sessionId || !
|
|
641
|
-
throw new Error("
|
|
640
|
+
if (!sessionId || !userId) {
|
|
641
|
+
throw new Error("userId and sessionId required");
|
|
642
642
|
}
|
|
643
|
-
const currentSession = await this.
|
|
643
|
+
const currentSession = await this.get(userId, sessionId);
|
|
644
644
|
if (!currentSession) {
|
|
645
|
-
throw new Error(`Session ${sessionId} not found for
|
|
645
|
+
throw new Error(`Session ${sessionId} not found for userId ${userId}`);
|
|
646
646
|
}
|
|
647
647
|
const updatedSession = { ...currentSession, ...data };
|
|
648
648
|
const expiresAt = ttl ? Date.now() + ttl * 1e3 : null;
|
|
649
649
|
const stmt = this.db.prepare(
|
|
650
|
-
`UPDATE ${this.table} SET data = ?, expiresAt = ? WHERE sessionId = ? AND
|
|
650
|
+
`UPDATE ${this.table} SET data = ?, expiresAt = ? WHERE sessionId = ? AND userId = ?`
|
|
651
651
|
);
|
|
652
|
-
stmt.run(JSON.stringify(updatedSession), expiresAt, sessionId,
|
|
652
|
+
stmt.run(JSON.stringify(updatedSession), expiresAt, sessionId, userId);
|
|
653
653
|
}
|
|
654
|
-
async
|
|
654
|
+
async get(userId, sessionId) {
|
|
655
655
|
this.ensureInitialized();
|
|
656
656
|
const stmt = this.db.prepare(
|
|
657
|
-
`SELECT data FROM ${this.table} WHERE sessionId = ? AND
|
|
657
|
+
`SELECT data FROM ${this.table} WHERE sessionId = ? AND userId = ?`
|
|
658
658
|
);
|
|
659
|
-
const row = stmt.get(sessionId,
|
|
659
|
+
const row = stmt.get(sessionId, userId);
|
|
660
660
|
if (!row) return null;
|
|
661
661
|
return JSON.parse(row.data);
|
|
662
662
|
}
|
|
663
|
-
async
|
|
663
|
+
async list(userId) {
|
|
664
664
|
this.ensureInitialized();
|
|
665
665
|
const stmt = this.db.prepare(
|
|
666
|
-
`SELECT data FROM ${this.table} WHERE
|
|
666
|
+
`SELECT data FROM ${this.table} WHERE userId = ?`
|
|
667
667
|
);
|
|
668
|
-
const rows = stmt.all(
|
|
668
|
+
const rows = stmt.all(userId);
|
|
669
669
|
return rows.map((row) => JSON.parse(row.data));
|
|
670
670
|
}
|
|
671
|
-
async
|
|
671
|
+
async listIds(userId) {
|
|
672
672
|
this.ensureInitialized();
|
|
673
673
|
const stmt = this.db.prepare(
|
|
674
|
-
`SELECT sessionId FROM ${this.table} WHERE
|
|
674
|
+
`SELECT sessionId FROM ${this.table} WHERE userId = ?`
|
|
675
675
|
);
|
|
676
|
-
const rows = stmt.all(
|
|
676
|
+
const rows = stmt.all(userId);
|
|
677
677
|
return rows.map((row) => row.sessionId);
|
|
678
678
|
}
|
|
679
|
-
async
|
|
679
|
+
async delete(userId, sessionId) {
|
|
680
680
|
this.ensureInitialized();
|
|
681
681
|
const stmt = this.db.prepare(
|
|
682
|
-
`DELETE FROM ${this.table} WHERE sessionId = ? AND
|
|
682
|
+
`DELETE FROM ${this.table} WHERE sessionId = ? AND userId = ?`
|
|
683
683
|
);
|
|
684
|
-
stmt.run(sessionId,
|
|
684
|
+
stmt.run(sessionId, userId);
|
|
685
685
|
}
|
|
686
|
-
async
|
|
686
|
+
async listAllIds() {
|
|
687
687
|
this.ensureInitialized();
|
|
688
688
|
const stmt = this.db.prepare(`SELECT sessionId FROM ${this.table}`);
|
|
689
689
|
const rows = stmt.all();
|
|
@@ -694,7 +694,7 @@ var SqliteStorage = class {
|
|
|
694
694
|
const stmt = this.db.prepare(`DELETE FROM ${this.table}`);
|
|
695
695
|
stmt.run();
|
|
696
696
|
}
|
|
697
|
-
async
|
|
697
|
+
async cleanupExpired() {
|
|
698
698
|
this.ensureInitialized();
|
|
699
699
|
const now = Date.now();
|
|
700
700
|
const stmt = this.db.prepare(
|
|
@@ -805,7 +805,7 @@ var SupabaseStorageBackend = class {
|
|
|
805
805
|
transportType: row.transport_type,
|
|
806
806
|
callbackUrl: row.callback_url,
|
|
807
807
|
createdAt: new Date(row.created_at).getTime(),
|
|
808
|
-
|
|
808
|
+
userId: row.user_id,
|
|
809
809
|
headers: decryptObject(row.headers),
|
|
810
810
|
active: row.active,
|
|
811
811
|
clientInformation: row.client_information,
|
|
@@ -814,22 +814,21 @@ var SupabaseStorageBackend = class {
|
|
|
814
814
|
clientId: row.client_id
|
|
815
815
|
};
|
|
816
816
|
}
|
|
817
|
-
async
|
|
818
|
-
const { sessionId,
|
|
819
|
-
if (!sessionId || !
|
|
817
|
+
async create(session, ttl) {
|
|
818
|
+
const { sessionId, userId } = session;
|
|
819
|
+
if (!sessionId || !userId) throw new Error("userId and sessionId required");
|
|
820
820
|
const effectiveTtl = ttl ?? this.DEFAULT_TTL;
|
|
821
821
|
const expiresAt = new Date(Date.now() + effectiveTtl * 1e3).toISOString();
|
|
822
822
|
const { error } = await this.supabase.from("mcp_sessions").insert({
|
|
823
823
|
session_id: sessionId,
|
|
824
|
-
user_id:
|
|
825
|
-
// Maps user_id to
|
|
824
|
+
user_id: userId,
|
|
825
|
+
// Maps user_id to userId to support RLS using auth.uid()
|
|
826
826
|
server_id: session.serverId,
|
|
827
827
|
server_name: session.serverName,
|
|
828
828
|
server_url: session.serverUrl,
|
|
829
829
|
transport_type: session.transportType,
|
|
830
830
|
callback_url: session.callbackUrl,
|
|
831
831
|
created_at: new Date(session.createdAt || Date.now()).toISOString(),
|
|
832
|
-
identity,
|
|
833
832
|
headers: encryptObject(session.headers),
|
|
834
833
|
active: session.active ?? false,
|
|
835
834
|
client_information: session.clientInformation,
|
|
@@ -845,7 +844,7 @@ var SupabaseStorageBackend = class {
|
|
|
845
844
|
throw new Error(`Failed to create session in Supabase: ${error.message}`);
|
|
846
845
|
}
|
|
847
846
|
}
|
|
848
|
-
async
|
|
847
|
+
async update(userId, sessionId, data, ttl) {
|
|
849
848
|
const effectiveTtl = ttl ?? this.DEFAULT_TTL;
|
|
850
849
|
const expiresAt = new Date(Date.now() + effectiveTtl * 1e3).toISOString();
|
|
851
850
|
const updateData = {
|
|
@@ -863,16 +862,16 @@ var SupabaseStorageBackend = class {
|
|
|
863
862
|
if ("tokens" in data) updateData.tokens = encryptObject(data.tokens);
|
|
864
863
|
if ("codeVerifier" in data) updateData.code_verifier = data.codeVerifier;
|
|
865
864
|
if ("clientId" in data) updateData.client_id = data.clientId;
|
|
866
|
-
const { data: updatedRows, error } = await this.supabase.from("mcp_sessions").update(updateData).eq("
|
|
865
|
+
const { data: updatedRows, error } = await this.supabase.from("mcp_sessions").update(updateData).eq("user_id", userId).eq("session_id", sessionId).select("id");
|
|
867
866
|
if (error) {
|
|
868
867
|
throw new Error(`Failed to update session: ${error.message}`);
|
|
869
868
|
}
|
|
870
869
|
if (!updatedRows || updatedRows.length === 0) {
|
|
871
|
-
throw new Error(`Session ${sessionId} not found for
|
|
870
|
+
throw new Error(`Session ${sessionId} not found for userId ${userId}`);
|
|
872
871
|
}
|
|
873
872
|
}
|
|
874
|
-
async
|
|
875
|
-
const { data, error } = await this.supabase.from("mcp_sessions").select("*").eq("
|
|
873
|
+
async get(userId, sessionId) {
|
|
874
|
+
const { data, error } = await this.supabase.from("mcp_sessions").select("*").eq("user_id", userId).eq("session_id", sessionId).maybeSingle();
|
|
876
875
|
if (error) {
|
|
877
876
|
console.error("[SupabaseStorage] Failed to get session:", error);
|
|
878
877
|
return null;
|
|
@@ -880,29 +879,29 @@ var SupabaseStorageBackend = class {
|
|
|
880
879
|
if (!data) return null;
|
|
881
880
|
return this.mapRowToSessionData(data);
|
|
882
881
|
}
|
|
883
|
-
async
|
|
884
|
-
const { data, error } = await this.supabase.from("mcp_sessions").select("*").eq("
|
|
882
|
+
async list(userId) {
|
|
883
|
+
const { data, error } = await this.supabase.from("mcp_sessions").select("*").eq("user_id", userId);
|
|
885
884
|
if (error) {
|
|
886
|
-
console.error(`[SupabaseStorage] Failed to get session data for ${
|
|
885
|
+
console.error(`[SupabaseStorage] Failed to get session data for ${userId}:`, error);
|
|
887
886
|
return [];
|
|
888
887
|
}
|
|
889
888
|
return data.map((row) => this.mapRowToSessionData(row));
|
|
890
889
|
}
|
|
891
|
-
async
|
|
892
|
-
const { error } = await this.supabase.from("mcp_sessions").delete().eq("
|
|
890
|
+
async delete(userId, sessionId) {
|
|
891
|
+
const { error } = await this.supabase.from("mcp_sessions").delete().eq("user_id", userId).eq("session_id", sessionId);
|
|
893
892
|
if (error) {
|
|
894
893
|
console.error("[SupabaseStorage] Failed to remove session:", error);
|
|
895
894
|
}
|
|
896
895
|
}
|
|
897
|
-
async
|
|
898
|
-
const { data, error } = await this.supabase.from("mcp_sessions").select("session_id").eq("
|
|
896
|
+
async listIds(userId) {
|
|
897
|
+
const { data, error } = await this.supabase.from("mcp_sessions").select("session_id").eq("user_id", userId);
|
|
899
898
|
if (error) {
|
|
900
|
-
console.error(`[SupabaseStorage] Failed to get sessions for ${
|
|
899
|
+
console.error(`[SupabaseStorage] Failed to get sessions for ${userId}:`, error);
|
|
901
900
|
return [];
|
|
902
901
|
}
|
|
903
902
|
return data.map((row) => row.session_id);
|
|
904
903
|
}
|
|
905
|
-
async
|
|
904
|
+
async listAllIds() {
|
|
906
905
|
const { data, error } = await this.supabase.from("mcp_sessions").select("session_id");
|
|
907
906
|
if (error) {
|
|
908
907
|
console.error("[SupabaseStorage] Failed to get all sessions:", error);
|
|
@@ -916,7 +915,7 @@ var SupabaseStorageBackend = class {
|
|
|
916
915
|
console.error("[SupabaseStorage] Failed to clear sessions:", error);
|
|
917
916
|
}
|
|
918
917
|
}
|
|
919
|
-
async
|
|
918
|
+
async cleanupExpired() {
|
|
920
919
|
const { error } = await this.supabase.from("mcp_sessions").delete().lt("expires_at", (/* @__PURE__ */ new Date()).toISOString());
|
|
921
920
|
if (error) {
|
|
922
921
|
console.error("[SupabaseStorage] Failed to cleanup expired sessions:", error);
|
|
@@ -926,7 +925,250 @@ var SupabaseStorageBackend = class {
|
|
|
926
925
|
}
|
|
927
926
|
};
|
|
928
927
|
|
|
928
|
+
// src/server/storage/neon-backend.ts
|
|
929
|
+
var NeonStorageBackend = class {
|
|
930
|
+
constructor(sql, options = {}) {
|
|
931
|
+
this.sql = sql;
|
|
932
|
+
__publicField(this, "DEFAULT_TTL", SESSION_TTL_SECONDS);
|
|
933
|
+
__publicField(this, "tableName");
|
|
934
|
+
const schema = options.schema || "public";
|
|
935
|
+
const table = options.table || "mcp_sessions";
|
|
936
|
+
this.tableName = `${this.quoteIdentifier(schema)}.${this.quoteIdentifier(table)}`;
|
|
937
|
+
}
|
|
938
|
+
async init() {
|
|
939
|
+
const [{ exists } = { exists: null }] = await this.sql.query(
|
|
940
|
+
"SELECT to_regclass($1) AS exists",
|
|
941
|
+
[this.tableName.replace(/"/g, "")]
|
|
942
|
+
);
|
|
943
|
+
if (!exists) {
|
|
944
|
+
throw new Error(
|
|
945
|
+
'[NeonStorage] Table "mcp_sessions" not found in your database. Please create it using the Neon storage guide in docs/storage-backends/neon.md.'
|
|
946
|
+
);
|
|
947
|
+
}
|
|
948
|
+
console.log('[mcp-ts][Storage] Neon: "mcp_sessions" table verified.');
|
|
949
|
+
}
|
|
950
|
+
generateSessionId() {
|
|
951
|
+
return generateSessionId();
|
|
952
|
+
}
|
|
953
|
+
quoteIdentifier(identifier) {
|
|
954
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(identifier)) {
|
|
955
|
+
throw new Error(`Invalid Neon storage identifier: ${identifier}`);
|
|
956
|
+
}
|
|
957
|
+
return `"${identifier}"`;
|
|
958
|
+
}
|
|
959
|
+
mapRowToSessionData(row) {
|
|
960
|
+
return {
|
|
961
|
+
sessionId: row.session_id,
|
|
962
|
+
serverId: row.server_id ?? void 0,
|
|
963
|
+
serverName: row.server_name ?? void 0,
|
|
964
|
+
serverUrl: row.server_url,
|
|
965
|
+
transportType: row.transport_type,
|
|
966
|
+
callbackUrl: row.callback_url,
|
|
967
|
+
createdAt: new Date(row.created_at).getTime(),
|
|
968
|
+
userId: row.user_id,
|
|
969
|
+
headers: decryptObject(row.headers),
|
|
970
|
+
active: row.active ?? false,
|
|
971
|
+
clientInformation: row.client_information,
|
|
972
|
+
tokens: decryptObject(row.tokens),
|
|
973
|
+
codeVerifier: row.code_verifier ?? void 0,
|
|
974
|
+
clientId: row.client_id ?? void 0
|
|
975
|
+
};
|
|
976
|
+
}
|
|
977
|
+
async create(session, ttl) {
|
|
978
|
+
const { sessionId, userId } = session;
|
|
979
|
+
if (!sessionId || !userId) throw new Error("userId and sessionId required");
|
|
980
|
+
const effectiveTtl = ttl ?? this.DEFAULT_TTL;
|
|
981
|
+
const expiresAt = new Date(Date.now() + effectiveTtl * 1e3).toISOString();
|
|
982
|
+
try {
|
|
983
|
+
await this.sql.query(
|
|
984
|
+
`INSERT INTO ${this.tableName} (
|
|
985
|
+
session_id,
|
|
986
|
+
user_id,
|
|
987
|
+
server_id,
|
|
988
|
+
server_name,
|
|
989
|
+
server_url,
|
|
990
|
+
transport_type,
|
|
991
|
+
callback_url,
|
|
992
|
+
created_at,
|
|
993
|
+
headers,
|
|
994
|
+
active,
|
|
995
|
+
client_information,
|
|
996
|
+
tokens,
|
|
997
|
+
code_verifier,
|
|
998
|
+
client_id,
|
|
999
|
+
expires_at
|
|
1000
|
+
) VALUES (
|
|
1001
|
+
$1, $2, $3, $4, $5, $6, $7, $8,
|
|
1002
|
+
$9, $10, $11, $12, $13, $14, $15
|
|
1003
|
+
)`,
|
|
1004
|
+
[
|
|
1005
|
+
sessionId,
|
|
1006
|
+
userId,
|
|
1007
|
+
session.serverId,
|
|
1008
|
+
session.serverName,
|
|
1009
|
+
session.serverUrl,
|
|
1010
|
+
session.transportType,
|
|
1011
|
+
session.callbackUrl,
|
|
1012
|
+
new Date(session.createdAt || Date.now()).toISOString(),
|
|
1013
|
+
encryptObject(session.headers),
|
|
1014
|
+
session.active ?? false,
|
|
1015
|
+
session.clientInformation,
|
|
1016
|
+
encryptObject(session.tokens),
|
|
1017
|
+
session.codeVerifier,
|
|
1018
|
+
session.clientId,
|
|
1019
|
+
expiresAt
|
|
1020
|
+
]
|
|
1021
|
+
);
|
|
1022
|
+
} catch (error) {
|
|
1023
|
+
if (error.code === "23505") {
|
|
1024
|
+
throw new Error(`Session ${sessionId} already exists`);
|
|
1025
|
+
}
|
|
1026
|
+
throw new Error(`Failed to create session in Neon: ${error.message}`);
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
async update(userId, sessionId, data, ttl) {
|
|
1030
|
+
const currentSession = await this.get(userId, sessionId);
|
|
1031
|
+
if (!currentSession) {
|
|
1032
|
+
throw new Error(`Session ${sessionId} not found for userId ${userId}`);
|
|
1033
|
+
}
|
|
1034
|
+
const updatedSession = { ...currentSession, ...data };
|
|
1035
|
+
const effectiveTtl = ttl ?? this.DEFAULT_TTL;
|
|
1036
|
+
const expiresAt = new Date(Date.now() + effectiveTtl * 1e3).toISOString();
|
|
1037
|
+
const updatedRows = await this.sql.query(
|
|
1038
|
+
`UPDATE ${this.tableName}
|
|
1039
|
+
SET
|
|
1040
|
+
server_id = $1,
|
|
1041
|
+
server_name = $2,
|
|
1042
|
+
server_url = $3,
|
|
1043
|
+
transport_type = $4,
|
|
1044
|
+
callback_url = $5,
|
|
1045
|
+
active = $6,
|
|
1046
|
+
headers = $7,
|
|
1047
|
+
client_information = $8,
|
|
1048
|
+
tokens = $9,
|
|
1049
|
+
code_verifier = $10,
|
|
1050
|
+
client_id = $11,
|
|
1051
|
+
expires_at = $12,
|
|
1052
|
+
updated_at = now()
|
|
1053
|
+
WHERE user_id = $13 AND session_id = $14
|
|
1054
|
+
RETURNING id`,
|
|
1055
|
+
[
|
|
1056
|
+
updatedSession.serverId,
|
|
1057
|
+
updatedSession.serverName,
|
|
1058
|
+
updatedSession.serverUrl,
|
|
1059
|
+
updatedSession.transportType,
|
|
1060
|
+
updatedSession.callbackUrl,
|
|
1061
|
+
updatedSession.active ?? false,
|
|
1062
|
+
encryptObject(updatedSession.headers),
|
|
1063
|
+
updatedSession.clientInformation,
|
|
1064
|
+
encryptObject(updatedSession.tokens),
|
|
1065
|
+
updatedSession.codeVerifier,
|
|
1066
|
+
updatedSession.clientId,
|
|
1067
|
+
expiresAt,
|
|
1068
|
+
userId,
|
|
1069
|
+
sessionId
|
|
1070
|
+
]
|
|
1071
|
+
);
|
|
1072
|
+
if (updatedRows.length === 0) {
|
|
1073
|
+
throw new Error(`Session ${sessionId} not found for userId ${userId}`);
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
async get(userId, sessionId) {
|
|
1077
|
+
try {
|
|
1078
|
+
const rows = await this.sql.query(
|
|
1079
|
+
`SELECT * FROM ${this.tableName} WHERE user_id = $1 AND session_id = $2`,
|
|
1080
|
+
[userId, sessionId]
|
|
1081
|
+
);
|
|
1082
|
+
return rows[0] ? this.mapRowToSessionData(rows[0]) : null;
|
|
1083
|
+
} catch (error) {
|
|
1084
|
+
console.error("[NeonStorage] Failed to get session:", error);
|
|
1085
|
+
return null;
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
async list(userId) {
|
|
1089
|
+
try {
|
|
1090
|
+
const rows = await this.sql.query(
|
|
1091
|
+
`SELECT * FROM ${this.tableName} WHERE user_id = $1`,
|
|
1092
|
+
[userId]
|
|
1093
|
+
);
|
|
1094
|
+
return rows.map((row) => this.mapRowToSessionData(row));
|
|
1095
|
+
} catch (error) {
|
|
1096
|
+
console.error(`[NeonStorage] Failed to get session data for ${userId}:`, error);
|
|
1097
|
+
return [];
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
async delete(userId, sessionId) {
|
|
1101
|
+
try {
|
|
1102
|
+
await this.sql.query(
|
|
1103
|
+
`DELETE FROM ${this.tableName} WHERE user_id = $1 AND session_id = $2`,
|
|
1104
|
+
[userId, sessionId]
|
|
1105
|
+
);
|
|
1106
|
+
} catch (error) {
|
|
1107
|
+
console.error("[NeonStorage] Failed to remove session:", error);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
async listIds(userId) {
|
|
1111
|
+
try {
|
|
1112
|
+
const rows = await this.sql.query(
|
|
1113
|
+
`SELECT session_id FROM ${this.tableName} WHERE user_id = $1`,
|
|
1114
|
+
[userId]
|
|
1115
|
+
);
|
|
1116
|
+
return rows.map((row) => row.session_id);
|
|
1117
|
+
} catch (error) {
|
|
1118
|
+
console.error(`[NeonStorage] Failed to get sessions for ${userId}:`, error);
|
|
1119
|
+
return [];
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
async listAllIds() {
|
|
1123
|
+
try {
|
|
1124
|
+
const rows = await this.sql.query(
|
|
1125
|
+
`SELECT session_id FROM ${this.tableName}`
|
|
1126
|
+
);
|
|
1127
|
+
return rows.map((row) => row.session_id);
|
|
1128
|
+
} catch (error) {
|
|
1129
|
+
console.error("[NeonStorage] Failed to get all sessions:", error);
|
|
1130
|
+
return [];
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
async clearAll() {
|
|
1134
|
+
try {
|
|
1135
|
+
await this.sql.query(`DELETE FROM ${this.tableName}`);
|
|
1136
|
+
} catch (error) {
|
|
1137
|
+
console.error("[NeonStorage] Failed to clear sessions:", error);
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
async cleanupExpired() {
|
|
1141
|
+
try {
|
|
1142
|
+
await this.sql.query(
|
|
1143
|
+
`DELETE FROM ${this.tableName} WHERE expires_at < $1`,
|
|
1144
|
+
[(/* @__PURE__ */ new Date()).toISOString()]
|
|
1145
|
+
);
|
|
1146
|
+
} catch (error) {
|
|
1147
|
+
console.error("[NeonStorage] Failed to cleanup expired sessions:", error);
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
async disconnect() {
|
|
1151
|
+
}
|
|
1152
|
+
};
|
|
1153
|
+
|
|
929
1154
|
// src/server/storage/index.ts
|
|
1155
|
+
function warnIfNeonConnectionStringIsInsecure(connectionString) {
|
|
1156
|
+
try {
|
|
1157
|
+
const url = new URL(connectionString);
|
|
1158
|
+
const sslMode = url.searchParams.get("sslmode");
|
|
1159
|
+
const channelBinding = url.searchParams.get("channel_binding");
|
|
1160
|
+
if (!sslMode) {
|
|
1161
|
+
console.warn("[mcp-ts][Storage] Neon connection string does not include sslmode. Neon recommends sslmode=verify-full for the strongest certificate verification.");
|
|
1162
|
+
} else if (!["verify-full", "require"].includes(sslMode)) {
|
|
1163
|
+
console.warn(`[mcp-ts][Storage] Neon connection string uses sslmode=${sslMode}. Use sslmode=verify-full or sslmode=require for secure connections.`);
|
|
1164
|
+
}
|
|
1165
|
+
if (!channelBinding) {
|
|
1166
|
+
console.warn("[mcp-ts][Storage] Neon connection string does not include channel_binding=require. Add it when supported by your runtime and connection path.");
|
|
1167
|
+
}
|
|
1168
|
+
} catch {
|
|
1169
|
+
console.warn("[mcp-ts][Storage] Neon connection string could not be parsed for SSL checks.");
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
930
1172
|
var storageInstance = null;
|
|
931
1173
|
var storagePromise = null;
|
|
932
1174
|
async function initializeStorage(store) {
|
|
@@ -983,6 +1225,24 @@ async function createStorage() {
|
|
|
983
1225
|
}
|
|
984
1226
|
}
|
|
985
1227
|
}
|
|
1228
|
+
if (type === "neon") {
|
|
1229
|
+
const connectionString = process.env.NEON_DATABASE_URL || process.env.DATABASE_URL;
|
|
1230
|
+
if (!connectionString) {
|
|
1231
|
+
console.warn('[mcp-ts][Storage] Explicit selection "neon" requires NEON_DATABASE_URL or DATABASE_URL.');
|
|
1232
|
+
} else {
|
|
1233
|
+
try {
|
|
1234
|
+
const { neon } = await import('@neondatabase/serverless');
|
|
1235
|
+
warnIfNeonConnectionStringIsInsecure(connectionString);
|
|
1236
|
+
const sql = neon(connectionString);
|
|
1237
|
+
console.log('[mcp-ts][Storage] Explicit selection: "neon"');
|
|
1238
|
+
return await initializeStorage(new NeonStorageBackend(sql));
|
|
1239
|
+
} catch (error) {
|
|
1240
|
+
console.error("[mcp-ts][Storage] Failed to initialize Neon:", error.message);
|
|
1241
|
+
console.log("[mcp-ts][Storage] Falling back to In-Memory storage");
|
|
1242
|
+
return await initializeStorage(new MemoryStorageBackend());
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
986
1246
|
if (type === "memory") {
|
|
987
1247
|
console.log('[mcp-ts][Storage] Explicit selection: "memory"');
|
|
988
1248
|
return await initializeStorage(new MemoryStorageBackend());
|
|
@@ -1021,6 +1281,17 @@ async function createStorage() {
|
|
|
1021
1281
|
console.error("[mcp-ts][Storage] Supabase auto-detection failed:", error.message);
|
|
1022
1282
|
}
|
|
1023
1283
|
}
|
|
1284
|
+
if (process.env.NEON_DATABASE_URL) {
|
|
1285
|
+
try {
|
|
1286
|
+
const { neon } = await import('@neondatabase/serverless');
|
|
1287
|
+
warnIfNeonConnectionStringIsInsecure(process.env.NEON_DATABASE_URL);
|
|
1288
|
+
const sql = neon(process.env.NEON_DATABASE_URL);
|
|
1289
|
+
console.log('[mcp-ts][Storage] Auto-detection: "neon" (via NEON_DATABASE_URL)');
|
|
1290
|
+
return await initializeStorage(new NeonStorageBackend(sql));
|
|
1291
|
+
} catch (error) {
|
|
1292
|
+
console.error("[mcp-ts][Storage] Neon auto-detection failed:", error.message);
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1024
1295
|
console.log('[mcp-ts][Storage] Defaulting to: "memory"');
|
|
1025
1296
|
return await initializeStorage(new MemoryStorageBackend());
|
|
1026
1297
|
}
|
|
@@ -1037,7 +1308,7 @@ async function getStorage() {
|
|
|
1037
1308
|
storageInstance = await storagePromise;
|
|
1038
1309
|
return storageInstance;
|
|
1039
1310
|
}
|
|
1040
|
-
var
|
|
1311
|
+
var sessions = new Proxy({}, {
|
|
1041
1312
|
get(_target, prop) {
|
|
1042
1313
|
return async (...args) => {
|
|
1043
1314
|
const instance = await getStorage();
|
|
@@ -1053,11 +1324,11 @@ var storage = new Proxy({}, {
|
|
|
1053
1324
|
// src/server/mcp/storage-oauth-provider.ts
|
|
1054
1325
|
var StorageOAuthClientProvider = class {
|
|
1055
1326
|
/**
|
|
1056
|
-
* Creates a new
|
|
1327
|
+
* Creates a new session-backed OAuth provider
|
|
1057
1328
|
* @param options - Provider configuration
|
|
1058
1329
|
*/
|
|
1059
1330
|
constructor(options) {
|
|
1060
|
-
__publicField(this, "
|
|
1331
|
+
__publicField(this, "userId");
|
|
1061
1332
|
__publicField(this, "serverId");
|
|
1062
1333
|
__publicField(this, "sessionId");
|
|
1063
1334
|
__publicField(this, "redirectUrl");
|
|
@@ -1070,7 +1341,7 @@ var StorageOAuthClientProvider = class {
|
|
|
1070
1341
|
__publicField(this, "_clientId");
|
|
1071
1342
|
__publicField(this, "onRedirectCallback");
|
|
1072
1343
|
__publicField(this, "tokenExpiresAt");
|
|
1073
|
-
this.
|
|
1344
|
+
this.userId = options.userId;
|
|
1074
1345
|
this.serverId = options.serverId;
|
|
1075
1346
|
this.sessionId = options.sessionId;
|
|
1076
1347
|
this.redirectUrl = options.redirectUrl;
|
|
@@ -1103,24 +1374,24 @@ var StorageOAuthClientProvider = class {
|
|
|
1103
1374
|
this._clientId = clientId_;
|
|
1104
1375
|
}
|
|
1105
1376
|
/**
|
|
1106
|
-
* Loads OAuth data from
|
|
1377
|
+
* Loads OAuth data from the session store
|
|
1107
1378
|
* @private
|
|
1108
1379
|
*/
|
|
1109
1380
|
async getSessionData() {
|
|
1110
|
-
const data = await
|
|
1381
|
+
const data = await sessions.get(this.userId, this.sessionId);
|
|
1111
1382
|
if (!data) {
|
|
1112
1383
|
return {};
|
|
1113
1384
|
}
|
|
1114
1385
|
return data;
|
|
1115
1386
|
}
|
|
1116
1387
|
/**
|
|
1117
|
-
* Saves OAuth data to
|
|
1388
|
+
* Saves OAuth data to the session store
|
|
1118
1389
|
* @param data - Partial OAuth data to save
|
|
1119
1390
|
* @private
|
|
1120
1391
|
* @throws Error if session doesn't exist (session must be created by controller layer)
|
|
1121
1392
|
*/
|
|
1122
1393
|
async saveSessionData(data) {
|
|
1123
|
-
await
|
|
1394
|
+
await sessions.update(this.userId, this.sessionId, data);
|
|
1124
1395
|
}
|
|
1125
1396
|
/**
|
|
1126
1397
|
* Retrieves stored OAuth client information
|
|
@@ -1168,7 +1439,7 @@ var StorageOAuthClientProvider = class {
|
|
|
1168
1439
|
return this.sessionId;
|
|
1169
1440
|
}
|
|
1170
1441
|
async checkState(_state) {
|
|
1171
|
-
const data = await
|
|
1442
|
+
const data = await sessions.get(this.userId, this.sessionId);
|
|
1172
1443
|
if (!data) {
|
|
1173
1444
|
return { valid: false, error: "Session not found" };
|
|
1174
1445
|
}
|
|
@@ -1184,7 +1455,7 @@ var StorageOAuthClientProvider = class {
|
|
|
1184
1455
|
}
|
|
1185
1456
|
async invalidateCredentials(scope) {
|
|
1186
1457
|
if (scope === "all") {
|
|
1187
|
-
await
|
|
1458
|
+
await sessions.delete(this.userId, this.sessionId);
|
|
1188
1459
|
} else {
|
|
1189
1460
|
const updates = {};
|
|
1190
1461
|
if (scope === "client") {
|
|
@@ -1310,14 +1581,14 @@ var RpcErrorCodes = {
|
|
|
1310
1581
|
var MCPClient = class _MCPClient {
|
|
1311
1582
|
/**
|
|
1312
1583
|
* Creates a new MCP client instance
|
|
1313
|
-
* Can be initialized with minimal options (
|
|
1584
|
+
* Can be initialized with minimal options (userId + sessionId) for session restoration
|
|
1314
1585
|
* @param options - Client configuration options
|
|
1315
1586
|
*/
|
|
1316
1587
|
constructor(options) {
|
|
1317
1588
|
__publicField(this, "client", null);
|
|
1318
1589
|
__publicField(this, "oauthProvider", null);
|
|
1319
1590
|
__publicField(this, "transport", null);
|
|
1320
|
-
__publicField(this, "
|
|
1591
|
+
__publicField(this, "userId");
|
|
1321
1592
|
__publicField(this, "serverId");
|
|
1322
1593
|
__publicField(this, "sessionId");
|
|
1323
1594
|
__publicField(this, "serverName");
|
|
@@ -1344,7 +1615,7 @@ var MCPClient = class _MCPClient {
|
|
|
1344
1615
|
this.serverName = options.serverName;
|
|
1345
1616
|
this.callbackUrl = options.callbackUrl;
|
|
1346
1617
|
this.onRedirect = options.onRedirect;
|
|
1347
|
-
this.
|
|
1618
|
+
this.userId = options.userId;
|
|
1348
1619
|
this.serverId = options.serverId;
|
|
1349
1620
|
this.sessionId = options.sessionId;
|
|
1350
1621
|
this.transportType = options.transportType;
|
|
@@ -1483,7 +1754,7 @@ var MCPClient = class _MCPClient {
|
|
|
1483
1754
|
this.emitStateChange("INITIALIZING");
|
|
1484
1755
|
this.emitProgress("Loading session configuration...");
|
|
1485
1756
|
if (!this.serverUrl || !this.callbackUrl || !this.serverId) {
|
|
1486
|
-
const sessionData = await
|
|
1757
|
+
const sessionData = await sessions.get(this.userId, this.sessionId);
|
|
1487
1758
|
if (!sessionData) {
|
|
1488
1759
|
throw new Error(`Session not found: ${this.sessionId}`);
|
|
1489
1760
|
}
|
|
@@ -1502,7 +1773,7 @@ var MCPClient = class _MCPClient {
|
|
|
1502
1773
|
throw new Error("serverId required for OAuth provider initialization");
|
|
1503
1774
|
}
|
|
1504
1775
|
this.oauthProvider = new StorageOAuthClientProvider({
|
|
1505
|
-
|
|
1776
|
+
userId: this.userId,
|
|
1506
1777
|
serverId: this.serverId,
|
|
1507
1778
|
sessionId: this.sessionId,
|
|
1508
1779
|
redirectUrl: this.callbackUrl,
|
|
@@ -1536,18 +1807,18 @@ var MCPClient = class _MCPClient {
|
|
|
1536
1807
|
}
|
|
1537
1808
|
);
|
|
1538
1809
|
}
|
|
1539
|
-
const existingSession = await
|
|
1810
|
+
const existingSession = await sessions.get(this.userId, this.sessionId);
|
|
1540
1811
|
if (!existingSession && this.serverId && this.serverUrl && this.callbackUrl) {
|
|
1541
1812
|
this.createdAt = Date.now();
|
|
1542
1813
|
console.log(`[MCPClient] Creating initial session ${this.sessionId} for OAuth flow`);
|
|
1543
|
-
await
|
|
1814
|
+
await sessions.create({
|
|
1544
1815
|
sessionId: this.sessionId,
|
|
1545
|
-
|
|
1816
|
+
userId: this.userId,
|
|
1546
1817
|
serverId: this.serverId,
|
|
1547
1818
|
serverName: this.serverName,
|
|
1548
1819
|
serverUrl: this.serverUrl,
|
|
1549
1820
|
callbackUrl: this.callbackUrl,
|
|
1550
|
-
transportType: this.transportType || "
|
|
1821
|
+
transportType: this.transportType || "streamable-http",
|
|
1551
1822
|
headers: this.headers,
|
|
1552
1823
|
createdAt: this.createdAt,
|
|
1553
1824
|
active: false
|
|
@@ -1555,7 +1826,7 @@ var MCPClient = class _MCPClient {
|
|
|
1555
1826
|
}
|
|
1556
1827
|
}
|
|
1557
1828
|
/**
|
|
1558
|
-
* Saves current session state to
|
|
1829
|
+
* Saves current session state to the session store
|
|
1559
1830
|
* Creates new session if it doesn't exist, updates if it does
|
|
1560
1831
|
* @param ttl - Time-to-live in seconds (defaults to 12hr for connected sessions)
|
|
1561
1832
|
* @param active - Session status marker used to avoid unnecessary TTL rewrites
|
|
@@ -1567,21 +1838,21 @@ var MCPClient = class _MCPClient {
|
|
|
1567
1838
|
}
|
|
1568
1839
|
const sessionData = {
|
|
1569
1840
|
sessionId: this.sessionId,
|
|
1570
|
-
|
|
1841
|
+
userId: this.userId,
|
|
1571
1842
|
serverId: this.serverId,
|
|
1572
1843
|
serverName: this.serverName,
|
|
1573
1844
|
serverUrl: this.serverUrl,
|
|
1574
1845
|
callbackUrl: this.callbackUrl,
|
|
1575
|
-
transportType: this.transportType || "
|
|
1846
|
+
transportType: this.transportType || "streamable-http",
|
|
1576
1847
|
headers: this.headers,
|
|
1577
1848
|
createdAt: this.createdAt || Date.now(),
|
|
1578
1849
|
active
|
|
1579
1850
|
};
|
|
1580
|
-
const existingSession = await
|
|
1851
|
+
const existingSession = await sessions.get(this.userId, this.sessionId);
|
|
1581
1852
|
if (existingSession) {
|
|
1582
|
-
await
|
|
1853
|
+
await sessions.update(this.userId, this.sessionId, sessionData, ttl);
|
|
1583
1854
|
} else {
|
|
1584
|
-
await
|
|
1855
|
+
await sessions.create(sessionData, ttl);
|
|
1585
1856
|
}
|
|
1586
1857
|
}
|
|
1587
1858
|
/**
|
|
@@ -1590,7 +1861,7 @@ var MCPClient = class _MCPClient {
|
|
|
1590
1861
|
* @private
|
|
1591
1862
|
*/
|
|
1592
1863
|
async tryConnect() {
|
|
1593
|
-
const transportsToTry = this.transportType ? [this.transportType] : ["
|
|
1864
|
+
const transportsToTry = this.transportType ? [this.transportType] : ["streamable-http", "sse"];
|
|
1594
1865
|
let lastError;
|
|
1595
1866
|
for (const currentType of transportsToTry) {
|
|
1596
1867
|
const isLastAttempt = currentType === transportsToTry[transportsToTry.length - 1];
|
|
@@ -1662,7 +1933,7 @@ var MCPClient = class _MCPClient {
|
|
|
1662
1933
|
this.emitError(message, "auth");
|
|
1663
1934
|
this.emitStateChange("FAILED");
|
|
1664
1935
|
try {
|
|
1665
|
-
await
|
|
1936
|
+
await sessions.delete(this.userId, this.sessionId);
|
|
1666
1937
|
} catch {
|
|
1667
1938
|
}
|
|
1668
1939
|
throw new Error(message);
|
|
@@ -1688,9 +1959,9 @@ var MCPClient = class _MCPClient {
|
|
|
1688
1959
|
this.emitError(errorMessage, "connection");
|
|
1689
1960
|
this.emitStateChange("FAILED");
|
|
1690
1961
|
try {
|
|
1691
|
-
const existingSession = await
|
|
1962
|
+
const existingSession = await sessions.get(this.userId, this.sessionId);
|
|
1692
1963
|
if (!existingSession || existingSession.active !== true) {
|
|
1693
|
-
await
|
|
1964
|
+
await sessions.delete(this.userId, this.sessionId);
|
|
1694
1965
|
}
|
|
1695
1966
|
} catch {
|
|
1696
1967
|
}
|
|
@@ -1714,7 +1985,7 @@ var MCPClient = class _MCPClient {
|
|
|
1714
1985
|
this.emitStateChange("FAILED");
|
|
1715
1986
|
throw new Error(error);
|
|
1716
1987
|
}
|
|
1717
|
-
const transportsToTry = this.transportType ? [this.transportType] : ["
|
|
1988
|
+
const transportsToTry = this.transportType ? [this.transportType] : ["streamable-http", "sse"];
|
|
1718
1989
|
let lastError;
|
|
1719
1990
|
let tokensExchanged = false;
|
|
1720
1991
|
let authenticatedStateEmitted = false;
|
|
@@ -2038,7 +2309,7 @@ var MCPClient = class _MCPClient {
|
|
|
2038
2309
|
},
|
|
2039
2310
|
{ capabilities: {} }
|
|
2040
2311
|
);
|
|
2041
|
-
const tt = this.transportType || "
|
|
2312
|
+
const tt = this.transportType || "streamable-http";
|
|
2042
2313
|
this.transport = this.getTransport(tt);
|
|
2043
2314
|
await this.client.connect(this.transport);
|
|
2044
2315
|
}
|
|
@@ -2055,7 +2326,7 @@ var MCPClient = class _MCPClient {
|
|
|
2055
2326
|
if (this.oauthProvider) {
|
|
2056
2327
|
await this.oauthProvider.invalidateCredentials("all");
|
|
2057
2328
|
}
|
|
2058
|
-
await
|
|
2329
|
+
await sessions.delete(this.userId, this.sessionId);
|
|
2059
2330
|
this.disconnect();
|
|
2060
2331
|
}
|
|
2061
2332
|
/**
|
|
@@ -2123,10 +2394,10 @@ var MCPClient = class _MCPClient {
|
|
|
2123
2394
|
}
|
|
2124
2395
|
/**
|
|
2125
2396
|
* Gets the transport type being used
|
|
2126
|
-
* @returns Transport type (defaults to '
|
|
2397
|
+
* @returns Transport type (defaults to 'streamable-http')
|
|
2127
2398
|
*/
|
|
2128
2399
|
getTransportType() {
|
|
2129
|
-
return this.transportType || "
|
|
2400
|
+
return this.transportType || "streamable-http";
|
|
2130
2401
|
}
|
|
2131
2402
|
/**
|
|
2132
2403
|
* Gets the human-readable server name
|
|
@@ -2153,25 +2424,25 @@ var MCPClient = class _MCPClient {
|
|
|
2153
2424
|
* Gets MCP server configuration for all active user sessions
|
|
2154
2425
|
* Loads sessions from Redis, validates OAuth tokens, refreshes if expired
|
|
2155
2426
|
* Returns ready-to-use configuration with valid auth headers
|
|
2156
|
-
* @param
|
|
2427
|
+
* @param userId - User ID to fetch sessions for
|
|
2157
2428
|
* @returns Object keyed by sanitized server labels containing transport, url, headers, etc.
|
|
2158
2429
|
* @static
|
|
2159
2430
|
*/
|
|
2160
|
-
static async getMcpServerConfig(
|
|
2431
|
+
static async getMcpServerConfig(userId) {
|
|
2161
2432
|
const mcpConfig = {};
|
|
2162
|
-
const
|
|
2433
|
+
const sessionList = await sessions.list(userId);
|
|
2163
2434
|
await Promise.all(
|
|
2164
|
-
|
|
2435
|
+
sessionList.map(async (sessionData) => {
|
|
2165
2436
|
const { sessionId } = sessionData;
|
|
2166
2437
|
try {
|
|
2167
2438
|
if (!sessionData.serverId || !sessionData.transportType || !sessionData.serverUrl || !sessionData.callbackUrl) {
|
|
2168
|
-
await
|
|
2439
|
+
await sessions.delete(userId, sessionId);
|
|
2169
2440
|
return;
|
|
2170
2441
|
}
|
|
2171
2442
|
let headers;
|
|
2172
2443
|
try {
|
|
2173
2444
|
const client = new _MCPClient({
|
|
2174
|
-
|
|
2445
|
+
userId,
|
|
2175
2446
|
sessionId,
|
|
2176
2447
|
serverId: sessionData.serverId,
|
|
2177
2448
|
serverUrl: sessionData.serverUrl,
|
|
@@ -2204,7 +2475,7 @@ var MCPClient = class _MCPClient {
|
|
|
2204
2475
|
...headers && { headers }
|
|
2205
2476
|
};
|
|
2206
2477
|
} catch (error) {
|
|
2207
|
-
await
|
|
2478
|
+
await sessions.delete(userId, sessionId);
|
|
2208
2479
|
console.warn(`[MCP] Failed to process session ${sessionId}:`, error);
|
|
2209
2480
|
}
|
|
2210
2481
|
})
|
|
@@ -2220,18 +2491,18 @@ var DEFAULT_RETRY_DELAY_MS = 1e3;
|
|
|
2220
2491
|
var CONNECTION_BATCH_SIZE = 5;
|
|
2221
2492
|
var MultiSessionClient = class {
|
|
2222
2493
|
/**
|
|
2223
|
-
* Creates a new MultiSessionClient for the given user
|
|
2494
|
+
* Creates a new MultiSessionClient for the given user userId.
|
|
2224
2495
|
*
|
|
2225
|
-
* @param
|
|
2496
|
+
* @param userId - A unique string identifying the user (e.g. user ID or email).
|
|
2226
2497
|
* @param options - Optional tuning for connection timeout, retry count, and retry delay.
|
|
2227
2498
|
* Falls back to sensible defaults if not provided.
|
|
2228
2499
|
*/
|
|
2229
|
-
constructor(
|
|
2500
|
+
constructor(userId, options = {}) {
|
|
2230
2501
|
__publicField(this, "clients", []);
|
|
2231
|
-
__publicField(this, "
|
|
2502
|
+
__publicField(this, "userId");
|
|
2232
2503
|
__publicField(this, "options");
|
|
2233
2504
|
__publicField(this, "connectionPromises", /* @__PURE__ */ new Map());
|
|
2234
|
-
this.
|
|
2505
|
+
this.userId = userId;
|
|
2235
2506
|
this.options = {
|
|
2236
2507
|
timeout: DEFAULT_TIMEOUT_MS,
|
|
2237
2508
|
maxRetries: DEFAULT_MAX_RETRIES,
|
|
@@ -2240,7 +2511,7 @@ var MultiSessionClient = class {
|
|
|
2240
2511
|
};
|
|
2241
2512
|
}
|
|
2242
2513
|
/**
|
|
2243
|
-
* Fetches all sessions for this
|
|
2514
|
+
* Fetches all sessions for this userId from storage and returns only the
|
|
2244
2515
|
* ones that are ready to connect.
|
|
2245
2516
|
*
|
|
2246
2517
|
* A session is considered connectable when:
|
|
@@ -2253,8 +2524,8 @@ var MultiSessionClient = class {
|
|
|
2253
2524
|
* for backwards compatibility.
|
|
2254
2525
|
*/
|
|
2255
2526
|
async getActiveSessions() {
|
|
2256
|
-
const
|
|
2257
|
-
const valid =
|
|
2527
|
+
const sessionList = await sessions.list(this.userId);
|
|
2528
|
+
const valid = sessionList.filter(
|
|
2258
2529
|
(s) => s.serverId && s.serverUrl && s.callbackUrl && s.active !== false
|
|
2259
2530
|
// exclude OAuth-pending / failed sessions
|
|
2260
2531
|
);
|
|
@@ -2267,9 +2538,9 @@ var MultiSessionClient = class {
|
|
|
2267
2538
|
* has many active MCP sessions (e.g. 20+ servers). Within each batch, sessions
|
|
2268
2539
|
* are connected concurrently using `Promise.all` for speed.
|
|
2269
2540
|
*/
|
|
2270
|
-
async connectInBatches(
|
|
2271
|
-
for (let i = 0; i <
|
|
2272
|
-
const batch =
|
|
2541
|
+
async connectInBatches(sessions2) {
|
|
2542
|
+
for (let i = 0; i < sessions2.length; i += CONNECTION_BATCH_SIZE) {
|
|
2543
|
+
const batch = sessions2.slice(i, i + CONNECTION_BATCH_SIZE);
|
|
2273
2544
|
await Promise.all(batch.map((session) => this.connectSession(session)));
|
|
2274
2545
|
}
|
|
2275
2546
|
}
|
|
@@ -2320,7 +2591,7 @@ var MultiSessionClient = class {
|
|
|
2320
2591
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
2321
2592
|
try {
|
|
2322
2593
|
const client = new MCPClient({
|
|
2323
|
-
|
|
2594
|
+
userId: this.userId,
|
|
2324
2595
|
sessionId: session.sessionId,
|
|
2325
2596
|
serverId: session.serverId,
|
|
2326
2597
|
serverUrl: session.serverUrl,
|
|
@@ -2352,7 +2623,7 @@ var MultiSessionClient = class {
|
|
|
2352
2623
|
console.error(`[MultiSessionClient] Failed to connect to session ${session.sessionId} after ${maxRetries + 1} attempts:`, lastError);
|
|
2353
2624
|
}
|
|
2354
2625
|
/**
|
|
2355
|
-
* The main entry point. Fetches all active sessions for this
|
|
2626
|
+
* The main entry point. Fetches all active sessions for this userId from
|
|
2356
2627
|
* storage and establishes connections to all of them in batches.
|
|
2357
2628
|
*
|
|
2358
2629
|
* Call this once after creating the client. On traditional servers, you can
|
|
@@ -2360,8 +2631,8 @@ var MultiSessionClient = class {
|
|
|
2360
2631
|
* re-fetching and re-connecting on every request.
|
|
2361
2632
|
*/
|
|
2362
2633
|
async connect() {
|
|
2363
|
-
const
|
|
2364
|
-
await this.connectInBatches(
|
|
2634
|
+
const sessions2 = await this.getActiveSessions();
|
|
2635
|
+
await this.connectInBatches(sessions2);
|
|
2365
2636
|
}
|
|
2366
2637
|
/**
|
|
2367
2638
|
* Returns all currently connected `MCPClient` instances.
|
|
@@ -2416,11 +2687,11 @@ var SSEConnectionManager = class {
|
|
|
2416
2687
|
constructor(options, sendEvent) {
|
|
2417
2688
|
this.options = options;
|
|
2418
2689
|
this.sendEvent = sendEvent;
|
|
2419
|
-
__publicField(this, "
|
|
2690
|
+
__publicField(this, "userId");
|
|
2420
2691
|
__publicField(this, "clients", /* @__PURE__ */ new Map());
|
|
2421
2692
|
__publicField(this, "heartbeatTimer");
|
|
2422
2693
|
__publicField(this, "isActive", true);
|
|
2423
|
-
this.
|
|
2694
|
+
this.userId = options.userId;
|
|
2424
2695
|
this.startHeartbeat();
|
|
2425
2696
|
}
|
|
2426
2697
|
/**
|
|
@@ -2460,8 +2731,8 @@ var SSEConnectionManager = class {
|
|
|
2460
2731
|
try {
|
|
2461
2732
|
let result;
|
|
2462
2733
|
switch (request.method) {
|
|
2463
|
-
case "
|
|
2464
|
-
result = await this.
|
|
2734
|
+
case "listSessions":
|
|
2735
|
+
result = await this.listSessions();
|
|
2465
2736
|
break;
|
|
2466
2737
|
case "connect":
|
|
2467
2738
|
result = await this.connect(request.params);
|
|
@@ -2475,8 +2746,8 @@ var SSEConnectionManager = class {
|
|
|
2475
2746
|
case "callTool":
|
|
2476
2747
|
result = await this.callTool(request.params);
|
|
2477
2748
|
break;
|
|
2478
|
-
case "
|
|
2479
|
-
result = await this.
|
|
2749
|
+
case "getSession":
|
|
2750
|
+
result = await this.getSession(request.params);
|
|
2480
2751
|
break;
|
|
2481
2752
|
case "finishAuth":
|
|
2482
2753
|
result = await this.finishAuth(request.params);
|
|
@@ -2515,12 +2786,12 @@ var SSEConnectionManager = class {
|
|
|
2515
2786
|
}
|
|
2516
2787
|
}
|
|
2517
2788
|
/**
|
|
2518
|
-
* Get all sessions for the current
|
|
2789
|
+
* Get all sessions for the current userId
|
|
2519
2790
|
*/
|
|
2520
|
-
async
|
|
2521
|
-
const
|
|
2791
|
+
async listSessions() {
|
|
2792
|
+
const sessionList = await sessions.list(this.userId);
|
|
2522
2793
|
return {
|
|
2523
|
-
sessions:
|
|
2794
|
+
sessions: sessionList.map((s) => ({
|
|
2524
2795
|
sessionId: s.sessionId,
|
|
2525
2796
|
serverId: s.serverId,
|
|
2526
2797
|
serverName: s.serverName,
|
|
@@ -2537,14 +2808,14 @@ var SSEConnectionManager = class {
|
|
|
2537
2808
|
async connect(params) {
|
|
2538
2809
|
const { serverName, serverUrl, callbackUrl, transportType } = params;
|
|
2539
2810
|
const headers = normalizeHeaders(params.headers);
|
|
2540
|
-
const serverId = params.serverId && params.serverId.length <= 12 ? params.serverId : await
|
|
2541
|
-
const existingSessions = await
|
|
2811
|
+
const serverId = params.serverId && params.serverId.length <= 12 ? params.serverId : await sessions.generateSessionId();
|
|
2812
|
+
const existingSessions = await sessions.list(this.userId);
|
|
2542
2813
|
const duplicate = existingSessions.find(
|
|
2543
2814
|
(s) => s.serverId === serverId || s.serverUrl === serverUrl
|
|
2544
2815
|
);
|
|
2545
2816
|
if (duplicate) {
|
|
2546
2817
|
if (duplicate.active === false) {
|
|
2547
|
-
await this.
|
|
2818
|
+
await this.getSession({ sessionId: duplicate.sessionId });
|
|
2548
2819
|
return {
|
|
2549
2820
|
sessionId: duplicate.sessionId,
|
|
2550
2821
|
success: true
|
|
@@ -2552,11 +2823,11 @@ var SSEConnectionManager = class {
|
|
|
2552
2823
|
}
|
|
2553
2824
|
throw new Error(`Connection already exists for server: ${duplicate.serverUrl || duplicate.serverId} (${duplicate.serverName})`);
|
|
2554
2825
|
}
|
|
2555
|
-
const sessionId = await
|
|
2826
|
+
const sessionId = await sessions.generateSessionId();
|
|
2556
2827
|
try {
|
|
2557
2828
|
const clientMetadata = await this.getResolvedClientMetadata();
|
|
2558
2829
|
const client = new MCPClient({
|
|
2559
|
-
|
|
2830
|
+
userId: this.userId,
|
|
2560
2831
|
sessionId,
|
|
2561
2832
|
serverId,
|
|
2562
2833
|
serverName,
|
|
@@ -2611,7 +2882,7 @@ var SSEConnectionManager = class {
|
|
|
2611
2882
|
client.disconnect();
|
|
2612
2883
|
this.clients.delete(sessionId);
|
|
2613
2884
|
} else {
|
|
2614
|
-
await
|
|
2885
|
+
await sessions.delete(this.userId, sessionId);
|
|
2615
2886
|
}
|
|
2616
2887
|
return { success: true };
|
|
2617
2888
|
}
|
|
@@ -2623,12 +2894,12 @@ var SSEConnectionManager = class {
|
|
|
2623
2894
|
if (existing) {
|
|
2624
2895
|
return existing;
|
|
2625
2896
|
}
|
|
2626
|
-
const session = await
|
|
2897
|
+
const session = await sessions.get(this.userId, sessionId);
|
|
2627
2898
|
if (!session) {
|
|
2628
2899
|
throw new Error("Session not found");
|
|
2629
2900
|
}
|
|
2630
2901
|
const client = new MCPClient({
|
|
2631
|
-
|
|
2902
|
+
userId: this.userId,
|
|
2632
2903
|
sessionId,
|
|
2633
2904
|
// These fields are optional in MCPClient, but when rehydrating a known
|
|
2634
2905
|
// stored session on the server we pass them explicitly to preserve the
|
|
@@ -2675,9 +2946,9 @@ var SSEConnectionManager = class {
|
|
|
2675
2946
|
/**
|
|
2676
2947
|
* Restore and validate an existing session
|
|
2677
2948
|
*/
|
|
2678
|
-
async
|
|
2949
|
+
async getSession(params) {
|
|
2679
2950
|
const { sessionId } = params;
|
|
2680
|
-
const session = await
|
|
2951
|
+
const session = await sessions.get(this.userId, sessionId);
|
|
2681
2952
|
if (!session) {
|
|
2682
2953
|
throw new Error("Session not found");
|
|
2683
2954
|
}
|
|
@@ -2694,7 +2965,7 @@ var SSEConnectionManager = class {
|
|
|
2694
2965
|
try {
|
|
2695
2966
|
const clientMetadata = await this.getResolvedClientMetadata();
|
|
2696
2967
|
const client = new MCPClient({
|
|
2697
|
-
|
|
2968
|
+
userId: this.userId,
|
|
2698
2969
|
sessionId,
|
|
2699
2970
|
// These fields are optional in MCPClient, but when rehydrating a known
|
|
2700
2971
|
// stored session on the server we pass them explicitly to preserve the
|
|
@@ -2731,13 +3002,13 @@ var SSEConnectionManager = class {
|
|
|
2731
3002
|
*/
|
|
2732
3003
|
async finishAuth(params) {
|
|
2733
3004
|
const { sessionId, code } = params;
|
|
2734
|
-
const session = await
|
|
3005
|
+
const session = await sessions.get(this.userId, sessionId);
|
|
2735
3006
|
if (!session) {
|
|
2736
3007
|
throw new Error("Session not found");
|
|
2737
3008
|
}
|
|
2738
3009
|
try {
|
|
2739
3010
|
const client = new MCPClient({
|
|
2740
|
-
|
|
3011
|
+
userId: this.userId,
|
|
2741
3012
|
sessionId,
|
|
2742
3013
|
// These fields are optional in MCPClient, but when rehydrating a known
|
|
2743
3014
|
// stored session on the server we pass them explicitly to preserve the
|
|
@@ -2748,7 +3019,7 @@ var SSEConnectionManager = class {
|
|
|
2748
3019
|
serverUrl: session.serverUrl,
|
|
2749
3020
|
callbackUrl: session.callbackUrl,
|
|
2750
3021
|
// NOTE: transportType is intentionally omitted here.
|
|
2751
|
-
// The session's stored transportType is a placeholder ('
|
|
3022
|
+
// The session's stored transportType is a placeholder ('streamable-http')
|
|
2752
3023
|
// set before transport negotiation. Omitting it lets MCPClient auto-negotiate
|
|
2753
3024
|
// (try streamable_http → SSE fallback), which is critical for servers like
|
|
2754
3025
|
// Neon that only support SSE transport.
|
|
@@ -2870,18 +3141,21 @@ function writeSSEEvent(res, event, data) {
|
|
|
2870
3141
|
// src/server/handlers/nextjs-handler.ts
|
|
2871
3142
|
function createNextMcpHandler(options = {}) {
|
|
2872
3143
|
const {
|
|
2873
|
-
|
|
3144
|
+
getUserId = (request) => request.headers.get("x-mcp-user-id"),
|
|
2874
3145
|
getAuthToken = (request) => {
|
|
2875
|
-
const
|
|
2876
|
-
|
|
3146
|
+
const authHeader = request.headers.get("authorization");
|
|
3147
|
+
if (authHeader?.toLowerCase().startsWith("bearer ")) {
|
|
3148
|
+
return authHeader.slice(7);
|
|
3149
|
+
}
|
|
3150
|
+
return authHeader;
|
|
2877
3151
|
},
|
|
2878
3152
|
authenticate = () => true,
|
|
2879
3153
|
heartbeatInterval = 3e4,
|
|
2880
3154
|
clientDefaults,
|
|
2881
3155
|
getClientMetadata
|
|
2882
3156
|
} = options;
|
|
2883
|
-
const toManagerOptions = (
|
|
2884
|
-
|
|
3157
|
+
const toManagerOptions = (userId, resolvedClientMetadata) => ({
|
|
3158
|
+
userId,
|
|
2885
3159
|
heartbeatInterval,
|
|
2886
3160
|
clientDefaults: resolvedClientMetadata
|
|
2887
3161
|
});
|
|
@@ -2900,13 +3174,13 @@ function createNextMcpHandler(options = {}) {
|
|
|
2900
3174
|
);
|
|
2901
3175
|
}
|
|
2902
3176
|
async function POST(request) {
|
|
2903
|
-
const
|
|
3177
|
+
const userId = getUserId(request);
|
|
2904
3178
|
const authToken = getAuthToken(request);
|
|
2905
3179
|
const acceptsEventStream = (request.headers.get("accept") || "").toLowerCase().includes("text/event-stream");
|
|
2906
|
-
if (!
|
|
2907
|
-
return Response.json({ error: { code: "
|
|
3180
|
+
if (!userId) {
|
|
3181
|
+
return Response.json({ error: { code: "MISSING_userId", message: "Missing userId" } }, { status: 400 });
|
|
2908
3182
|
}
|
|
2909
|
-
const isAuthorized = await authenticate(
|
|
3183
|
+
const isAuthorized = await authenticate(userId, authToken);
|
|
2910
3184
|
if (!isAuthorized) {
|
|
2911
3185
|
return Response.json({ error: { code: "UNAUTHORIZED", message: "Unauthorized" } }, { status: 401 });
|
|
2912
3186
|
}
|
|
@@ -2928,7 +3202,7 @@ function createNextMcpHandler(options = {}) {
|
|
|
2928
3202
|
const resolvedClientMetadata = await resolveClientMetadata(request);
|
|
2929
3203
|
if (!acceptsEventStream) {
|
|
2930
3204
|
const manager2 = new SSEConnectionManager(
|
|
2931
|
-
toManagerOptions(
|
|
3205
|
+
toManagerOptions(userId, resolvedClientMetadata),
|
|
2932
3206
|
() => {
|
|
2933
3207
|
}
|
|
2934
3208
|
);
|
|
@@ -2954,7 +3228,7 @@ data: ${JSON.stringify(data)}
|
|
|
2954
3228
|
});
|
|
2955
3229
|
};
|
|
2956
3230
|
const manager = new SSEConnectionManager(
|
|
2957
|
-
toManagerOptions(
|
|
3231
|
+
toManagerOptions(userId, resolvedClientMetadata),
|
|
2958
3232
|
(event) => {
|
|
2959
3233
|
if (isRpcResponseEvent(event)) {
|
|
2960
3234
|
sendSSE("rpc-response", event);
|
|
@@ -2997,7 +3271,7 @@ data: ${JSON.stringify(data)}
|
|
|
2997
3271
|
} catch (error) {
|
|
2998
3272
|
const err = error instanceof Error ? error : new Error("Unknown error");
|
|
2999
3273
|
console.error("[MCP Next Handler] Failed to handle RPC", {
|
|
3000
|
-
|
|
3274
|
+
userId,
|
|
3001
3275
|
message: err.message,
|
|
3002
3276
|
stack: err.stack,
|
|
3003
3277
|
rawBody: rawBody.slice(0, 500)
|
|
@@ -3016,6 +3290,6 @@ data: ${JSON.stringify(data)}
|
|
|
3016
3290
|
return { GET, POST };
|
|
3017
3291
|
}
|
|
3018
3292
|
|
|
3019
|
-
export { MCPClient, MultiSessionClient, SSEConnectionManager, StorageOAuthClientProvider, UnauthorizedError, createNextMcpHandler, createSSEHandler, sanitizeServerLabel,
|
|
3293
|
+
export { MCPClient, MultiSessionClient, SSEConnectionManager, StorageOAuthClientProvider, UnauthorizedError, createNextMcpHandler, createSSEHandler, sanitizeServerLabel, sessions };
|
|
3020
3294
|
//# sourceMappingURL=index.mjs.map
|
|
3021
3295
|
//# sourceMappingURL=index.mjs.map
|