@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/index.mjs
CHANGED
|
@@ -152,35 +152,35 @@ var RedisStorageBackend = class {
|
|
|
152
152
|
this.redis = redis2;
|
|
153
153
|
__publicField(this, "DEFAULT_TTL", SESSION_TTL_SECONDS);
|
|
154
154
|
__publicField(this, "KEY_PREFIX", "mcp:session:");
|
|
155
|
-
__publicField(this, "
|
|
156
|
-
__publicField(this, "
|
|
155
|
+
__publicField(this, "USER_ID_KEY_PREFIX", "mcp:userId:");
|
|
156
|
+
__publicField(this, "USER_ID_KEY_SUFFIX", ":sessions");
|
|
157
157
|
}
|
|
158
158
|
async init() {
|
|
159
159
|
try {
|
|
160
160
|
await this.redis.ping();
|
|
161
161
|
console.log("[mcp-ts][Storage] Redis: \u2713 Connected to server.");
|
|
162
162
|
} catch (error) {
|
|
163
|
-
throw new Error(`[
|
|
163
|
+
throw new Error(`[RedisStorageBackend] Failed to connect to Redis: ${error.message}`);
|
|
164
164
|
}
|
|
165
165
|
}
|
|
166
166
|
/**
|
|
167
167
|
* Generates Redis key for a specific session
|
|
168
168
|
* @private
|
|
169
169
|
*/
|
|
170
|
-
getSessionKey(
|
|
171
|
-
return `${this.KEY_PREFIX}${
|
|
170
|
+
getSessionKey(userId, sessionId) {
|
|
171
|
+
return `${this.KEY_PREFIX}${userId}:${sessionId}`;
|
|
172
172
|
}
|
|
173
173
|
/**
|
|
174
|
-
* Generates Redis key for tracking all sessions for
|
|
174
|
+
* Generates Redis key for tracking all sessions for a user
|
|
175
175
|
* @private
|
|
176
176
|
*/
|
|
177
|
-
|
|
178
|
-
return `${this.
|
|
177
|
+
getUserIdKey(userId) {
|
|
178
|
+
return `${this.USER_ID_KEY_PREFIX}${userId}${this.USER_ID_KEY_SUFFIX}`;
|
|
179
179
|
}
|
|
180
|
-
|
|
181
|
-
return
|
|
182
|
-
this.
|
|
183
|
-
|
|
180
|
+
parseUserIdFromKey(userIdKey) {
|
|
181
|
+
return userIdKey.slice(
|
|
182
|
+
this.USER_ID_KEY_PREFIX.length,
|
|
183
|
+
userIdKey.length - this.USER_ID_KEY_SUFFIX.length
|
|
184
184
|
);
|
|
185
185
|
}
|
|
186
186
|
async scanKeys(pattern) {
|
|
@@ -199,7 +199,7 @@ var RedisStorageBackend = class {
|
|
|
199
199
|
}
|
|
200
200
|
} while (cursor !== "0");
|
|
201
201
|
} catch (error) {
|
|
202
|
-
console.warn("[
|
|
202
|
+
console.warn("[RedisStorageBackend] SCAN failed, falling back to KEYS:", error);
|
|
203
203
|
return await this.redis.keys(pattern);
|
|
204
204
|
}
|
|
205
205
|
return Array.from(keys);
|
|
@@ -207,11 +207,11 @@ var RedisStorageBackend = class {
|
|
|
207
207
|
generateSessionId() {
|
|
208
208
|
return generateSessionId();
|
|
209
209
|
}
|
|
210
|
-
async
|
|
211
|
-
const { sessionId,
|
|
212
|
-
if (!sessionId || !
|
|
213
|
-
const sessionKey = this.getSessionKey(
|
|
214
|
-
const
|
|
210
|
+
async create(session, ttl) {
|
|
211
|
+
const { sessionId, userId } = session;
|
|
212
|
+
if (!sessionId || !userId) throw new Error("userId and sessionId required");
|
|
213
|
+
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
214
|
+
const userIdKey = this.getUserIdKey(userId);
|
|
215
215
|
const effectiveTtl = ttl ?? this.DEFAULT_TTL;
|
|
216
216
|
const result = await this.redis.set(
|
|
217
217
|
sessionKey,
|
|
@@ -223,10 +223,10 @@ var RedisStorageBackend = class {
|
|
|
223
223
|
if (result !== "OK") {
|
|
224
224
|
throw new Error(`Session ${sessionId} already exists`);
|
|
225
225
|
}
|
|
226
|
-
await this.redis.sadd(
|
|
226
|
+
await this.redis.sadd(userIdKey, sessionId);
|
|
227
227
|
}
|
|
228
|
-
async
|
|
229
|
-
const sessionKey = this.getSessionKey(
|
|
228
|
+
async update(userId, sessionId, data, ttl) {
|
|
229
|
+
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
230
230
|
const effectiveTtl = ttl ?? this.DEFAULT_TTL;
|
|
231
231
|
const script = `
|
|
232
232
|
local currentStr = redis.call("GET", KEYS[1])
|
|
@@ -252,62 +252,62 @@ var RedisStorageBackend = class {
|
|
|
252
252
|
effectiveTtl
|
|
253
253
|
);
|
|
254
254
|
if (result === 0) {
|
|
255
|
-
throw new Error(`Session ${sessionId} not found for
|
|
255
|
+
throw new Error(`Session ${sessionId} not found for userId ${userId}`);
|
|
256
256
|
}
|
|
257
257
|
}
|
|
258
|
-
async
|
|
258
|
+
async get(userId, sessionId) {
|
|
259
259
|
try {
|
|
260
|
-
const sessionKey = this.getSessionKey(
|
|
260
|
+
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
261
261
|
const sessionDataStr = await this.redis.get(sessionKey);
|
|
262
262
|
if (!sessionDataStr) {
|
|
263
263
|
return null;
|
|
264
264
|
}
|
|
265
|
-
const
|
|
266
|
-
return
|
|
265
|
+
const Session = JSON.parse(sessionDataStr);
|
|
266
|
+
return Session;
|
|
267
267
|
} catch (error) {
|
|
268
|
-
console.error("[
|
|
268
|
+
console.error("[RedisStorageBackend] Failed to get session:", error);
|
|
269
269
|
return null;
|
|
270
270
|
}
|
|
271
271
|
}
|
|
272
|
-
async
|
|
273
|
-
const
|
|
274
|
-
return
|
|
272
|
+
async listIds(userId) {
|
|
273
|
+
const sessions2 = await this.list(userId);
|
|
274
|
+
return sessions2.map((session) => session.sessionId);
|
|
275
275
|
}
|
|
276
|
-
async
|
|
276
|
+
async list(userId) {
|
|
277
277
|
try {
|
|
278
|
-
const
|
|
279
|
-
const sessionIds = await this.redis.smembers(
|
|
278
|
+
const userIdKey = this.getUserIdKey(userId);
|
|
279
|
+
const sessionIds = await this.redis.smembers(userIdKey);
|
|
280
280
|
if (sessionIds.length === 0) return [];
|
|
281
281
|
const results = await Promise.all(
|
|
282
282
|
sessionIds.map(async (sessionId) => {
|
|
283
|
-
const data = await this.redis.get(this.getSessionKey(
|
|
283
|
+
const data = await this.redis.get(this.getSessionKey(userId, sessionId));
|
|
284
284
|
return data ? JSON.parse(data) : null;
|
|
285
285
|
})
|
|
286
286
|
);
|
|
287
287
|
const staleSessionIds = sessionIds.filter((_, index) => results[index] === null);
|
|
288
288
|
if (staleSessionIds.length > 0) {
|
|
289
|
-
await this.redis.srem(
|
|
289
|
+
await this.redis.srem(userIdKey, ...staleSessionIds);
|
|
290
290
|
}
|
|
291
291
|
return results.filter((session) => session !== null);
|
|
292
292
|
} catch (error) {
|
|
293
|
-
console.error(`[
|
|
293
|
+
console.error(`[RedisStorageBackend] Failed to get session data for ${userId}:`, error);
|
|
294
294
|
return [];
|
|
295
295
|
}
|
|
296
296
|
}
|
|
297
|
-
async
|
|
297
|
+
async delete(userId, sessionId) {
|
|
298
298
|
try {
|
|
299
|
-
const sessionKey = this.getSessionKey(
|
|
300
|
-
const
|
|
301
|
-
await this.redis.srem(
|
|
299
|
+
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
300
|
+
const userIdKey = this.getUserIdKey(userId);
|
|
301
|
+
await this.redis.srem(userIdKey, sessionId);
|
|
302
302
|
await this.redis.del(sessionKey);
|
|
303
303
|
} catch (error) {
|
|
304
|
-
console.error("[
|
|
304
|
+
console.error("[RedisStorageBackend] Failed to remove session:", error);
|
|
305
305
|
}
|
|
306
306
|
}
|
|
307
|
-
async
|
|
307
|
+
async listAllIds() {
|
|
308
308
|
try {
|
|
309
309
|
const keys = await this.scanKeys(`${this.KEY_PREFIX}*`);
|
|
310
|
-
const
|
|
310
|
+
const sessions2 = await Promise.all(
|
|
311
311
|
keys.map(async (key) => {
|
|
312
312
|
const data = await this.redis.get(key);
|
|
313
313
|
if (!data) {
|
|
@@ -316,60 +316,60 @@ var RedisStorageBackend = class {
|
|
|
316
316
|
try {
|
|
317
317
|
return JSON.parse(data).sessionId;
|
|
318
318
|
} catch (error) {
|
|
319
|
-
console.error("[
|
|
319
|
+
console.error("[RedisStorageBackend] Failed to parse session while listing all session IDs:", error);
|
|
320
320
|
return null;
|
|
321
321
|
}
|
|
322
322
|
})
|
|
323
323
|
);
|
|
324
|
-
return
|
|
324
|
+
return sessions2.filter((sessionId) => sessionId !== null);
|
|
325
325
|
} catch (error) {
|
|
326
|
-
console.error("[
|
|
326
|
+
console.error("[RedisStorageBackend] Failed to get all sessions:", error);
|
|
327
327
|
return [];
|
|
328
328
|
}
|
|
329
329
|
}
|
|
330
330
|
async clearAll() {
|
|
331
331
|
try {
|
|
332
332
|
const keys = await this.scanKeys(`${this.KEY_PREFIX}*`);
|
|
333
|
-
const
|
|
334
|
-
const allKeys = [...keys, ...
|
|
333
|
+
const userIdKeys = await this.scanKeys(`${this.USER_ID_KEY_PREFIX}*${this.USER_ID_KEY_SUFFIX}`);
|
|
334
|
+
const allKeys = [...keys, ...userIdKeys];
|
|
335
335
|
if (allKeys.length > 0) {
|
|
336
336
|
await this.redis.del(...allKeys);
|
|
337
337
|
}
|
|
338
338
|
} catch (error) {
|
|
339
|
-
console.error("[
|
|
339
|
+
console.error("[RedisStorageBackend] Failed to clear sessions:", error);
|
|
340
340
|
}
|
|
341
341
|
}
|
|
342
|
-
async
|
|
342
|
+
async cleanupExpired() {
|
|
343
343
|
try {
|
|
344
|
-
const
|
|
345
|
-
for (const
|
|
346
|
-
const
|
|
347
|
-
const sessionIds = await this.redis.smembers(
|
|
344
|
+
const userIdKeys = await this.scanKeys(`${this.USER_ID_KEY_PREFIX}*${this.USER_ID_KEY_SUFFIX}`);
|
|
345
|
+
for (const userIdKey of userIdKeys) {
|
|
346
|
+
const userId = this.parseUserIdFromKey(userIdKey);
|
|
347
|
+
const sessionIds = await this.redis.smembers(userIdKey);
|
|
348
348
|
if (sessionIds.length === 0) {
|
|
349
|
-
await this.redis.del(
|
|
349
|
+
await this.redis.del(userIdKey);
|
|
350
350
|
continue;
|
|
351
351
|
}
|
|
352
352
|
const existenceChecks = await Promise.all(
|
|
353
|
-
sessionIds.map((sessionId) => this.redis.exists(this.getSessionKey(
|
|
353
|
+
sessionIds.map((sessionId) => this.redis.exists(this.getSessionKey(userId, sessionId)))
|
|
354
354
|
);
|
|
355
355
|
const staleSessionIds = sessionIds.filter((_, index) => existenceChecks[index] === 0);
|
|
356
356
|
if (staleSessionIds.length > 0) {
|
|
357
|
-
await this.redis.srem(
|
|
357
|
+
await this.redis.srem(userIdKey, ...staleSessionIds);
|
|
358
358
|
}
|
|
359
|
-
const remainingCount = await this.redis.scard(
|
|
359
|
+
const remainingCount = await this.redis.scard(userIdKey);
|
|
360
360
|
if (remainingCount === 0) {
|
|
361
|
-
await this.redis.del(
|
|
361
|
+
await this.redis.del(userIdKey);
|
|
362
362
|
}
|
|
363
363
|
}
|
|
364
364
|
} catch (error) {
|
|
365
|
-
console.error("[
|
|
365
|
+
console.error("[RedisStorageBackend] Failed to cleanup expired sessions:", error);
|
|
366
366
|
}
|
|
367
367
|
}
|
|
368
368
|
async disconnect() {
|
|
369
369
|
try {
|
|
370
370
|
await this.redis.quit();
|
|
371
371
|
} catch (error) {
|
|
372
|
-
console.error("[
|
|
372
|
+
console.error("[RedisStorageBackend] Failed to disconnect:", error);
|
|
373
373
|
}
|
|
374
374
|
}
|
|
375
375
|
};
|
|
@@ -377,36 +377,36 @@ var RedisStorageBackend = class {
|
|
|
377
377
|
// src/server/storage/memory-backend.ts
|
|
378
378
|
var MemoryStorageBackend = class {
|
|
379
379
|
constructor() {
|
|
380
|
-
// Map<
|
|
380
|
+
// Map<userId:sessionId, Session>
|
|
381
381
|
__publicField(this, "sessions", /* @__PURE__ */ new Map());
|
|
382
|
-
// Map<
|
|
383
|
-
__publicField(this, "
|
|
382
|
+
// Map<userId, Set<sessionId>>
|
|
383
|
+
__publicField(this, "userIdSessions", /* @__PURE__ */ new Map());
|
|
384
384
|
}
|
|
385
385
|
async init() {
|
|
386
386
|
console.log("[mcp-ts][Storage] Memory: \u2713 internal memory store active.");
|
|
387
387
|
}
|
|
388
|
-
getSessionKey(
|
|
389
|
-
return `${
|
|
388
|
+
getSessionKey(userId, sessionId) {
|
|
389
|
+
return `${userId}:${sessionId}`;
|
|
390
390
|
}
|
|
391
391
|
generateSessionId() {
|
|
392
392
|
return generateSessionId();
|
|
393
393
|
}
|
|
394
|
-
async
|
|
395
|
-
const { sessionId,
|
|
396
|
-
if (!sessionId || !
|
|
397
|
-
const sessionKey = this.getSessionKey(
|
|
394
|
+
async create(session, ttl) {
|
|
395
|
+
const { sessionId, userId } = session;
|
|
396
|
+
if (!sessionId || !userId) throw new Error("userId and sessionId required");
|
|
397
|
+
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
398
398
|
if (this.sessions.has(sessionKey)) {
|
|
399
399
|
throw new Error(`Session ${sessionId} already exists`);
|
|
400
400
|
}
|
|
401
401
|
this.sessions.set(sessionKey, session);
|
|
402
|
-
if (!this.
|
|
403
|
-
this.
|
|
402
|
+
if (!this.userIdSessions.has(userId)) {
|
|
403
|
+
this.userIdSessions.set(userId, /* @__PURE__ */ new Set());
|
|
404
404
|
}
|
|
405
|
-
this.
|
|
405
|
+
this.userIdSessions.get(userId).add(sessionId);
|
|
406
406
|
}
|
|
407
|
-
async
|
|
408
|
-
if (!
|
|
409
|
-
const sessionKey = this.getSessionKey(
|
|
407
|
+
async update(userId, sessionId, data, ttl) {
|
|
408
|
+
if (!userId || !sessionId) throw new Error("userId and sessionId required");
|
|
409
|
+
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
410
410
|
const current = this.sessions.get(sessionKey);
|
|
411
411
|
if (!current) {
|
|
412
412
|
throw new Error(`Session ${sessionId} not found`);
|
|
@@ -417,45 +417,45 @@ var MemoryStorageBackend = class {
|
|
|
417
417
|
};
|
|
418
418
|
this.sessions.set(sessionKey, updated);
|
|
419
419
|
}
|
|
420
|
-
async
|
|
421
|
-
const sessionKey = this.getSessionKey(
|
|
420
|
+
async get(userId, sessionId) {
|
|
421
|
+
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
422
422
|
return this.sessions.get(sessionKey) || null;
|
|
423
423
|
}
|
|
424
|
-
async
|
|
425
|
-
const set = this.
|
|
424
|
+
async listIds(userId) {
|
|
425
|
+
const set = this.userIdSessions.get(userId);
|
|
426
426
|
return set ? Array.from(set) : [];
|
|
427
427
|
}
|
|
428
|
-
async
|
|
429
|
-
const set = this.
|
|
428
|
+
async list(userId) {
|
|
429
|
+
const set = this.userIdSessions.get(userId);
|
|
430
430
|
if (!set) return [];
|
|
431
431
|
const results = [];
|
|
432
432
|
for (const sessionId of set) {
|
|
433
|
-
const session = this.sessions.get(this.getSessionKey(
|
|
433
|
+
const session = this.sessions.get(this.getSessionKey(userId, sessionId));
|
|
434
434
|
if (session) {
|
|
435
435
|
results.push(session);
|
|
436
436
|
}
|
|
437
437
|
}
|
|
438
438
|
return results;
|
|
439
439
|
}
|
|
440
|
-
async
|
|
441
|
-
const sessionKey = this.getSessionKey(
|
|
440
|
+
async delete(userId, sessionId) {
|
|
441
|
+
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
442
442
|
this.sessions.delete(sessionKey);
|
|
443
|
-
const set = this.
|
|
443
|
+
const set = this.userIdSessions.get(userId);
|
|
444
444
|
if (set) {
|
|
445
445
|
set.delete(sessionId);
|
|
446
446
|
if (set.size === 0) {
|
|
447
|
-
this.
|
|
447
|
+
this.userIdSessions.delete(userId);
|
|
448
448
|
}
|
|
449
449
|
}
|
|
450
450
|
}
|
|
451
|
-
async
|
|
451
|
+
async listAllIds() {
|
|
452
452
|
return Array.from(this.sessions.values()).map((s) => s.sessionId);
|
|
453
453
|
}
|
|
454
454
|
async clearAll() {
|
|
455
455
|
this.sessions.clear();
|
|
456
|
-
this.
|
|
456
|
+
this.userIdSessions.clear();
|
|
457
457
|
}
|
|
458
|
-
async
|
|
458
|
+
async cleanupExpired() {
|
|
459
459
|
}
|
|
460
460
|
async disconnect() {
|
|
461
461
|
}
|
|
@@ -483,7 +483,7 @@ var FileStorageBackend = class {
|
|
|
483
483
|
this.memoryCache = /* @__PURE__ */ new Map();
|
|
484
484
|
if (Array.isArray(json)) {
|
|
485
485
|
json.forEach((s) => {
|
|
486
|
-
this.memoryCache.set(this.getSessionKey(s.
|
|
486
|
+
this.memoryCache.set(this.getSessionKey(s.userId || "unknown", s.sessionId), s);
|
|
487
487
|
});
|
|
488
488
|
}
|
|
489
489
|
} catch (error) {
|
|
@@ -503,30 +503,30 @@ var FileStorageBackend = class {
|
|
|
503
503
|
}
|
|
504
504
|
async flush() {
|
|
505
505
|
if (!this.memoryCache) return;
|
|
506
|
-
const
|
|
507
|
-
await promises.writeFile(this.filePath, JSON.stringify(
|
|
506
|
+
const sessions2 = Array.from(this.memoryCache.values());
|
|
507
|
+
await promises.writeFile(this.filePath, JSON.stringify(sessions2, null, 2), "utf-8");
|
|
508
508
|
}
|
|
509
|
-
getSessionKey(
|
|
510
|
-
return `${
|
|
509
|
+
getSessionKey(userId, sessionId) {
|
|
510
|
+
return `${userId}:${sessionId}`;
|
|
511
511
|
}
|
|
512
512
|
generateSessionId() {
|
|
513
513
|
return generateSessionId();
|
|
514
514
|
}
|
|
515
|
-
async
|
|
515
|
+
async create(session, ttl) {
|
|
516
516
|
await this.ensureInitialized();
|
|
517
|
-
const { sessionId,
|
|
518
|
-
if (!sessionId || !
|
|
519
|
-
const sessionKey = this.getSessionKey(
|
|
517
|
+
const { sessionId, userId } = session;
|
|
518
|
+
if (!sessionId || !userId) throw new Error("userId and sessionId required");
|
|
519
|
+
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
520
520
|
if (this.memoryCache.has(sessionKey)) {
|
|
521
521
|
throw new Error(`Session ${sessionId} already exists`);
|
|
522
522
|
}
|
|
523
523
|
this.memoryCache.set(sessionKey, session);
|
|
524
524
|
await this.flush();
|
|
525
525
|
}
|
|
526
|
-
async
|
|
526
|
+
async update(userId, sessionId, data, ttl) {
|
|
527
527
|
await this.ensureInitialized();
|
|
528
|
-
if (!
|
|
529
|
-
const sessionKey = this.getSessionKey(
|
|
528
|
+
if (!userId || !sessionId) throw new Error("userId and sessionId required");
|
|
529
|
+
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
530
530
|
const current = this.memoryCache.get(sessionKey);
|
|
531
531
|
if (!current) {
|
|
532
532
|
throw new Error(`Session ${sessionId} not found`);
|
|
@@ -538,27 +538,27 @@ var FileStorageBackend = class {
|
|
|
538
538
|
this.memoryCache.set(sessionKey, updated);
|
|
539
539
|
await this.flush();
|
|
540
540
|
}
|
|
541
|
-
async
|
|
541
|
+
async get(userId, sessionId) {
|
|
542
542
|
await this.ensureInitialized();
|
|
543
|
-
const sessionKey = this.getSessionKey(
|
|
543
|
+
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
544
544
|
return this.memoryCache.get(sessionKey) || null;
|
|
545
545
|
}
|
|
546
|
-
async
|
|
546
|
+
async list(userId) {
|
|
547
547
|
await this.ensureInitialized();
|
|
548
|
-
return Array.from(this.memoryCache.values()).filter((s) => s.
|
|
548
|
+
return Array.from(this.memoryCache.values()).filter((s) => s.userId === userId);
|
|
549
549
|
}
|
|
550
|
-
async
|
|
550
|
+
async listIds(userId) {
|
|
551
551
|
await this.ensureInitialized();
|
|
552
|
-
return Array.from(this.memoryCache.values()).filter((s) => s.
|
|
552
|
+
return Array.from(this.memoryCache.values()).filter((s) => s.userId === userId).map((s) => s.sessionId);
|
|
553
553
|
}
|
|
554
|
-
async
|
|
554
|
+
async delete(userId, sessionId) {
|
|
555
555
|
await this.ensureInitialized();
|
|
556
|
-
const sessionKey = this.getSessionKey(
|
|
556
|
+
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
557
557
|
if (this.memoryCache.delete(sessionKey)) {
|
|
558
558
|
await this.flush();
|
|
559
559
|
}
|
|
560
560
|
}
|
|
561
|
-
async
|
|
561
|
+
async listAllIds() {
|
|
562
562
|
await this.ensureInitialized();
|
|
563
563
|
return Array.from(this.memoryCache.values()).map((s) => s.sessionId);
|
|
564
564
|
}
|
|
@@ -567,7 +567,7 @@ var FileStorageBackend = class {
|
|
|
567
567
|
this.memoryCache.clear();
|
|
568
568
|
await this.flush();
|
|
569
569
|
}
|
|
570
|
-
async
|
|
570
|
+
async cleanupExpired() {
|
|
571
571
|
await this.ensureInitialized();
|
|
572
572
|
}
|
|
573
573
|
async disconnect() {
|
|
@@ -594,11 +594,11 @@ var SqliteStorage = class {
|
|
|
594
594
|
this.db.exec(`
|
|
595
595
|
CREATE TABLE IF NOT EXISTS ${this.table} (
|
|
596
596
|
sessionId TEXT PRIMARY KEY,
|
|
597
|
-
|
|
597
|
+
userId TEXT NOT NULL,
|
|
598
598
|
data TEXT NOT NULL,
|
|
599
599
|
expiresAt INTEGER
|
|
600
600
|
);
|
|
601
|
-
CREATE INDEX IF NOT EXISTS idx_${this.table}
|
|
601
|
+
CREATE INDEX IF NOT EXISTS idx_${this.table}_userId ON ${this.table}(userId);
|
|
602
602
|
`);
|
|
603
603
|
this.initialized = true;
|
|
604
604
|
console.log(`[mcp-ts][Storage] SQLite: \u2713 database at ${this.dbPath} verified.`);
|
|
@@ -619,18 +619,18 @@ var SqliteStorage = class {
|
|
|
619
619
|
generateSessionId() {
|
|
620
620
|
return generateSessionId();
|
|
621
621
|
}
|
|
622
|
-
async
|
|
622
|
+
async create(session, ttl) {
|
|
623
623
|
this.ensureInitialized();
|
|
624
|
-
const { sessionId,
|
|
625
|
-
if (!sessionId || !
|
|
626
|
-
throw new Error("
|
|
624
|
+
const { sessionId, userId } = session;
|
|
625
|
+
if (!sessionId || !userId) {
|
|
626
|
+
throw new Error("userId and sessionId required");
|
|
627
627
|
}
|
|
628
628
|
const expiresAt = ttl ? Date.now() + ttl * 1e3 : null;
|
|
629
629
|
try {
|
|
630
630
|
const stmt = this.db.prepare(
|
|
631
|
-
`INSERT INTO ${this.table} (sessionId,
|
|
631
|
+
`INSERT INTO ${this.table} (sessionId, userId, data, expiresAt) VALUES (?, ?, ?, ?)`
|
|
632
632
|
);
|
|
633
|
-
stmt.run(sessionId,
|
|
633
|
+
stmt.run(sessionId, userId, JSON.stringify(session), expiresAt);
|
|
634
634
|
} catch (error) {
|
|
635
635
|
if (error.code === "SQLITE_CONSTRAINT_PRIMARYKEY") {
|
|
636
636
|
throw new Error(`Session ${sessionId} already exists`);
|
|
@@ -638,55 +638,55 @@ var SqliteStorage = class {
|
|
|
638
638
|
throw error;
|
|
639
639
|
}
|
|
640
640
|
}
|
|
641
|
-
async
|
|
641
|
+
async update(userId, sessionId, data, ttl) {
|
|
642
642
|
this.ensureInitialized();
|
|
643
|
-
if (!sessionId || !
|
|
644
|
-
throw new Error("
|
|
643
|
+
if (!sessionId || !userId) {
|
|
644
|
+
throw new Error("userId and sessionId required");
|
|
645
645
|
}
|
|
646
|
-
const currentSession = await this.
|
|
646
|
+
const currentSession = await this.get(userId, sessionId);
|
|
647
647
|
if (!currentSession) {
|
|
648
|
-
throw new Error(`Session ${sessionId} not found for
|
|
648
|
+
throw new Error(`Session ${sessionId} not found for userId ${userId}`);
|
|
649
649
|
}
|
|
650
650
|
const updatedSession = { ...currentSession, ...data };
|
|
651
651
|
const expiresAt = ttl ? Date.now() + ttl * 1e3 : null;
|
|
652
652
|
const stmt = this.db.prepare(
|
|
653
|
-
`UPDATE ${this.table} SET data = ?, expiresAt = ? WHERE sessionId = ? AND
|
|
653
|
+
`UPDATE ${this.table} SET data = ?, expiresAt = ? WHERE sessionId = ? AND userId = ?`
|
|
654
654
|
);
|
|
655
|
-
stmt.run(JSON.stringify(updatedSession), expiresAt, sessionId,
|
|
655
|
+
stmt.run(JSON.stringify(updatedSession), expiresAt, sessionId, userId);
|
|
656
656
|
}
|
|
657
|
-
async
|
|
657
|
+
async get(userId, sessionId) {
|
|
658
658
|
this.ensureInitialized();
|
|
659
659
|
const stmt = this.db.prepare(
|
|
660
|
-
`SELECT data FROM ${this.table} WHERE sessionId = ? AND
|
|
660
|
+
`SELECT data FROM ${this.table} WHERE sessionId = ? AND userId = ?`
|
|
661
661
|
);
|
|
662
|
-
const row = stmt.get(sessionId,
|
|
662
|
+
const row = stmt.get(sessionId, userId);
|
|
663
663
|
if (!row) return null;
|
|
664
664
|
return JSON.parse(row.data);
|
|
665
665
|
}
|
|
666
|
-
async
|
|
666
|
+
async list(userId) {
|
|
667
667
|
this.ensureInitialized();
|
|
668
668
|
const stmt = this.db.prepare(
|
|
669
|
-
`SELECT data FROM ${this.table} WHERE
|
|
669
|
+
`SELECT data FROM ${this.table} WHERE userId = ?`
|
|
670
670
|
);
|
|
671
|
-
const rows = stmt.all(
|
|
671
|
+
const rows = stmt.all(userId);
|
|
672
672
|
return rows.map((row) => JSON.parse(row.data));
|
|
673
673
|
}
|
|
674
|
-
async
|
|
674
|
+
async listIds(userId) {
|
|
675
675
|
this.ensureInitialized();
|
|
676
676
|
const stmt = this.db.prepare(
|
|
677
|
-
`SELECT sessionId FROM ${this.table} WHERE
|
|
677
|
+
`SELECT sessionId FROM ${this.table} WHERE userId = ?`
|
|
678
678
|
);
|
|
679
|
-
const rows = stmt.all(
|
|
679
|
+
const rows = stmt.all(userId);
|
|
680
680
|
return rows.map((row) => row.sessionId);
|
|
681
681
|
}
|
|
682
|
-
async
|
|
682
|
+
async delete(userId, sessionId) {
|
|
683
683
|
this.ensureInitialized();
|
|
684
684
|
const stmt = this.db.prepare(
|
|
685
|
-
`DELETE FROM ${this.table} WHERE sessionId = ? AND
|
|
685
|
+
`DELETE FROM ${this.table} WHERE sessionId = ? AND userId = ?`
|
|
686
686
|
);
|
|
687
|
-
stmt.run(sessionId,
|
|
687
|
+
stmt.run(sessionId, userId);
|
|
688
688
|
}
|
|
689
|
-
async
|
|
689
|
+
async listAllIds() {
|
|
690
690
|
this.ensureInitialized();
|
|
691
691
|
const stmt = this.db.prepare(`SELECT sessionId FROM ${this.table}`);
|
|
692
692
|
const rows = stmt.all();
|
|
@@ -697,7 +697,7 @@ var SqliteStorage = class {
|
|
|
697
697
|
const stmt = this.db.prepare(`DELETE FROM ${this.table}`);
|
|
698
698
|
stmt.run();
|
|
699
699
|
}
|
|
700
|
-
async
|
|
700
|
+
async cleanupExpired() {
|
|
701
701
|
this.ensureInitialized();
|
|
702
702
|
const now = Date.now();
|
|
703
703
|
const stmt = this.db.prepare(
|
|
@@ -808,7 +808,7 @@ var SupabaseStorageBackend = class {
|
|
|
808
808
|
transportType: row.transport_type,
|
|
809
809
|
callbackUrl: row.callback_url,
|
|
810
810
|
createdAt: new Date(row.created_at).getTime(),
|
|
811
|
-
|
|
811
|
+
userId: row.user_id,
|
|
812
812
|
headers: decryptObject(row.headers),
|
|
813
813
|
active: row.active,
|
|
814
814
|
clientInformation: row.client_information,
|
|
@@ -817,22 +817,21 @@ var SupabaseStorageBackend = class {
|
|
|
817
817
|
clientId: row.client_id
|
|
818
818
|
};
|
|
819
819
|
}
|
|
820
|
-
async
|
|
821
|
-
const { sessionId,
|
|
822
|
-
if (!sessionId || !
|
|
820
|
+
async create(session, ttl) {
|
|
821
|
+
const { sessionId, userId } = session;
|
|
822
|
+
if (!sessionId || !userId) throw new Error("userId and sessionId required");
|
|
823
823
|
const effectiveTtl = ttl ?? this.DEFAULT_TTL;
|
|
824
824
|
const expiresAt = new Date(Date.now() + effectiveTtl * 1e3).toISOString();
|
|
825
825
|
const { error } = await this.supabase.from("mcp_sessions").insert({
|
|
826
826
|
session_id: sessionId,
|
|
827
|
-
user_id:
|
|
828
|
-
// Maps user_id to
|
|
827
|
+
user_id: userId,
|
|
828
|
+
// Maps user_id to userId to support RLS using auth.uid()
|
|
829
829
|
server_id: session.serverId,
|
|
830
830
|
server_name: session.serverName,
|
|
831
831
|
server_url: session.serverUrl,
|
|
832
832
|
transport_type: session.transportType,
|
|
833
833
|
callback_url: session.callbackUrl,
|
|
834
834
|
created_at: new Date(session.createdAt || Date.now()).toISOString(),
|
|
835
|
-
identity,
|
|
836
835
|
headers: encryptObject(session.headers),
|
|
837
836
|
active: session.active ?? false,
|
|
838
837
|
client_information: session.clientInformation,
|
|
@@ -848,7 +847,7 @@ var SupabaseStorageBackend = class {
|
|
|
848
847
|
throw new Error(`Failed to create session in Supabase: ${error.message}`);
|
|
849
848
|
}
|
|
850
849
|
}
|
|
851
|
-
async
|
|
850
|
+
async update(userId, sessionId, data, ttl) {
|
|
852
851
|
const effectiveTtl = ttl ?? this.DEFAULT_TTL;
|
|
853
852
|
const expiresAt = new Date(Date.now() + effectiveTtl * 1e3).toISOString();
|
|
854
853
|
const updateData = {
|
|
@@ -866,16 +865,16 @@ var SupabaseStorageBackend = class {
|
|
|
866
865
|
if ("tokens" in data) updateData.tokens = encryptObject(data.tokens);
|
|
867
866
|
if ("codeVerifier" in data) updateData.code_verifier = data.codeVerifier;
|
|
868
867
|
if ("clientId" in data) updateData.client_id = data.clientId;
|
|
869
|
-
const { data: updatedRows, error } = await this.supabase.from("mcp_sessions").update(updateData).eq("
|
|
868
|
+
const { data: updatedRows, error } = await this.supabase.from("mcp_sessions").update(updateData).eq("user_id", userId).eq("session_id", sessionId).select("id");
|
|
870
869
|
if (error) {
|
|
871
870
|
throw new Error(`Failed to update session: ${error.message}`);
|
|
872
871
|
}
|
|
873
872
|
if (!updatedRows || updatedRows.length === 0) {
|
|
874
|
-
throw new Error(`Session ${sessionId} not found for
|
|
873
|
+
throw new Error(`Session ${sessionId} not found for userId ${userId}`);
|
|
875
874
|
}
|
|
876
875
|
}
|
|
877
|
-
async
|
|
878
|
-
const { data, error } = await this.supabase.from("mcp_sessions").select("*").eq("
|
|
876
|
+
async get(userId, sessionId) {
|
|
877
|
+
const { data, error } = await this.supabase.from("mcp_sessions").select("*").eq("user_id", userId).eq("session_id", sessionId).maybeSingle();
|
|
879
878
|
if (error) {
|
|
880
879
|
console.error("[SupabaseStorage] Failed to get session:", error);
|
|
881
880
|
return null;
|
|
@@ -883,29 +882,29 @@ var SupabaseStorageBackend = class {
|
|
|
883
882
|
if (!data) return null;
|
|
884
883
|
return this.mapRowToSessionData(data);
|
|
885
884
|
}
|
|
886
|
-
async
|
|
887
|
-
const { data, error } = await this.supabase.from("mcp_sessions").select("*").eq("
|
|
885
|
+
async list(userId) {
|
|
886
|
+
const { data, error } = await this.supabase.from("mcp_sessions").select("*").eq("user_id", userId);
|
|
888
887
|
if (error) {
|
|
889
|
-
console.error(`[SupabaseStorage] Failed to get session data for ${
|
|
888
|
+
console.error(`[SupabaseStorage] Failed to get session data for ${userId}:`, error);
|
|
890
889
|
return [];
|
|
891
890
|
}
|
|
892
891
|
return data.map((row) => this.mapRowToSessionData(row));
|
|
893
892
|
}
|
|
894
|
-
async
|
|
895
|
-
const { error } = await this.supabase.from("mcp_sessions").delete().eq("
|
|
893
|
+
async delete(userId, sessionId) {
|
|
894
|
+
const { error } = await this.supabase.from("mcp_sessions").delete().eq("user_id", userId).eq("session_id", sessionId);
|
|
896
895
|
if (error) {
|
|
897
896
|
console.error("[SupabaseStorage] Failed to remove session:", error);
|
|
898
897
|
}
|
|
899
898
|
}
|
|
900
|
-
async
|
|
901
|
-
const { data, error } = await this.supabase.from("mcp_sessions").select("session_id").eq("
|
|
899
|
+
async listIds(userId) {
|
|
900
|
+
const { data, error } = await this.supabase.from("mcp_sessions").select("session_id").eq("user_id", userId);
|
|
902
901
|
if (error) {
|
|
903
|
-
console.error(`[SupabaseStorage] Failed to get sessions for ${
|
|
902
|
+
console.error(`[SupabaseStorage] Failed to get sessions for ${userId}:`, error);
|
|
904
903
|
return [];
|
|
905
904
|
}
|
|
906
905
|
return data.map((row) => row.session_id);
|
|
907
906
|
}
|
|
908
|
-
async
|
|
907
|
+
async listAllIds() {
|
|
909
908
|
const { data, error } = await this.supabase.from("mcp_sessions").select("session_id");
|
|
910
909
|
if (error) {
|
|
911
910
|
console.error("[SupabaseStorage] Failed to get all sessions:", error);
|
|
@@ -919,7 +918,7 @@ var SupabaseStorageBackend = class {
|
|
|
919
918
|
console.error("[SupabaseStorage] Failed to clear sessions:", error);
|
|
920
919
|
}
|
|
921
920
|
}
|
|
922
|
-
async
|
|
921
|
+
async cleanupExpired() {
|
|
923
922
|
const { error } = await this.supabase.from("mcp_sessions").delete().lt("expires_at", (/* @__PURE__ */ new Date()).toISOString());
|
|
924
923
|
if (error) {
|
|
925
924
|
console.error("[SupabaseStorage] Failed to cleanup expired sessions:", error);
|
|
@@ -929,7 +928,250 @@ var SupabaseStorageBackend = class {
|
|
|
929
928
|
}
|
|
930
929
|
};
|
|
931
930
|
|
|
931
|
+
// src/server/storage/neon-backend.ts
|
|
932
|
+
var NeonStorageBackend = class {
|
|
933
|
+
constructor(sql, options = {}) {
|
|
934
|
+
this.sql = sql;
|
|
935
|
+
__publicField(this, "DEFAULT_TTL", SESSION_TTL_SECONDS);
|
|
936
|
+
__publicField(this, "tableName");
|
|
937
|
+
const schema = options.schema || "public";
|
|
938
|
+
const table = options.table || "mcp_sessions";
|
|
939
|
+
this.tableName = `${this.quoteIdentifier(schema)}.${this.quoteIdentifier(table)}`;
|
|
940
|
+
}
|
|
941
|
+
async init() {
|
|
942
|
+
const [{ exists } = { exists: null }] = await this.sql.query(
|
|
943
|
+
"SELECT to_regclass($1) AS exists",
|
|
944
|
+
[this.tableName.replace(/"/g, "")]
|
|
945
|
+
);
|
|
946
|
+
if (!exists) {
|
|
947
|
+
throw new Error(
|
|
948
|
+
'[NeonStorage] Table "mcp_sessions" not found in your database. Please create it using the Neon storage guide in docs/storage-backends/neon.md.'
|
|
949
|
+
);
|
|
950
|
+
}
|
|
951
|
+
console.log('[mcp-ts][Storage] Neon: "mcp_sessions" table verified.');
|
|
952
|
+
}
|
|
953
|
+
generateSessionId() {
|
|
954
|
+
return generateSessionId();
|
|
955
|
+
}
|
|
956
|
+
quoteIdentifier(identifier) {
|
|
957
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(identifier)) {
|
|
958
|
+
throw new Error(`Invalid Neon storage identifier: ${identifier}`);
|
|
959
|
+
}
|
|
960
|
+
return `"${identifier}"`;
|
|
961
|
+
}
|
|
962
|
+
mapRowToSessionData(row) {
|
|
963
|
+
return {
|
|
964
|
+
sessionId: row.session_id,
|
|
965
|
+
serverId: row.server_id ?? void 0,
|
|
966
|
+
serverName: row.server_name ?? void 0,
|
|
967
|
+
serverUrl: row.server_url,
|
|
968
|
+
transportType: row.transport_type,
|
|
969
|
+
callbackUrl: row.callback_url,
|
|
970
|
+
createdAt: new Date(row.created_at).getTime(),
|
|
971
|
+
userId: row.user_id,
|
|
972
|
+
headers: decryptObject(row.headers),
|
|
973
|
+
active: row.active ?? false,
|
|
974
|
+
clientInformation: row.client_information,
|
|
975
|
+
tokens: decryptObject(row.tokens),
|
|
976
|
+
codeVerifier: row.code_verifier ?? void 0,
|
|
977
|
+
clientId: row.client_id ?? void 0
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
async create(session, ttl) {
|
|
981
|
+
const { sessionId, userId } = session;
|
|
982
|
+
if (!sessionId || !userId) throw new Error("userId and sessionId required");
|
|
983
|
+
const effectiveTtl = ttl ?? this.DEFAULT_TTL;
|
|
984
|
+
const expiresAt = new Date(Date.now() + effectiveTtl * 1e3).toISOString();
|
|
985
|
+
try {
|
|
986
|
+
await this.sql.query(
|
|
987
|
+
`INSERT INTO ${this.tableName} (
|
|
988
|
+
session_id,
|
|
989
|
+
user_id,
|
|
990
|
+
server_id,
|
|
991
|
+
server_name,
|
|
992
|
+
server_url,
|
|
993
|
+
transport_type,
|
|
994
|
+
callback_url,
|
|
995
|
+
created_at,
|
|
996
|
+
headers,
|
|
997
|
+
active,
|
|
998
|
+
client_information,
|
|
999
|
+
tokens,
|
|
1000
|
+
code_verifier,
|
|
1001
|
+
client_id,
|
|
1002
|
+
expires_at
|
|
1003
|
+
) VALUES (
|
|
1004
|
+
$1, $2, $3, $4, $5, $6, $7, $8,
|
|
1005
|
+
$9, $10, $11, $12, $13, $14, $15
|
|
1006
|
+
)`,
|
|
1007
|
+
[
|
|
1008
|
+
sessionId,
|
|
1009
|
+
userId,
|
|
1010
|
+
session.serverId,
|
|
1011
|
+
session.serverName,
|
|
1012
|
+
session.serverUrl,
|
|
1013
|
+
session.transportType,
|
|
1014
|
+
session.callbackUrl,
|
|
1015
|
+
new Date(session.createdAt || Date.now()).toISOString(),
|
|
1016
|
+
encryptObject(session.headers),
|
|
1017
|
+
session.active ?? false,
|
|
1018
|
+
session.clientInformation,
|
|
1019
|
+
encryptObject(session.tokens),
|
|
1020
|
+
session.codeVerifier,
|
|
1021
|
+
session.clientId,
|
|
1022
|
+
expiresAt
|
|
1023
|
+
]
|
|
1024
|
+
);
|
|
1025
|
+
} catch (error) {
|
|
1026
|
+
if (error.code === "23505") {
|
|
1027
|
+
throw new Error(`Session ${sessionId} already exists`);
|
|
1028
|
+
}
|
|
1029
|
+
throw new Error(`Failed to create session in Neon: ${error.message}`);
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
async update(userId, sessionId, data, ttl) {
|
|
1033
|
+
const currentSession = await this.get(userId, sessionId);
|
|
1034
|
+
if (!currentSession) {
|
|
1035
|
+
throw new Error(`Session ${sessionId} not found for userId ${userId}`);
|
|
1036
|
+
}
|
|
1037
|
+
const updatedSession = { ...currentSession, ...data };
|
|
1038
|
+
const effectiveTtl = ttl ?? this.DEFAULT_TTL;
|
|
1039
|
+
const expiresAt = new Date(Date.now() + effectiveTtl * 1e3).toISOString();
|
|
1040
|
+
const updatedRows = await this.sql.query(
|
|
1041
|
+
`UPDATE ${this.tableName}
|
|
1042
|
+
SET
|
|
1043
|
+
server_id = $1,
|
|
1044
|
+
server_name = $2,
|
|
1045
|
+
server_url = $3,
|
|
1046
|
+
transport_type = $4,
|
|
1047
|
+
callback_url = $5,
|
|
1048
|
+
active = $6,
|
|
1049
|
+
headers = $7,
|
|
1050
|
+
client_information = $8,
|
|
1051
|
+
tokens = $9,
|
|
1052
|
+
code_verifier = $10,
|
|
1053
|
+
client_id = $11,
|
|
1054
|
+
expires_at = $12,
|
|
1055
|
+
updated_at = now()
|
|
1056
|
+
WHERE user_id = $13 AND session_id = $14
|
|
1057
|
+
RETURNING id`,
|
|
1058
|
+
[
|
|
1059
|
+
updatedSession.serverId,
|
|
1060
|
+
updatedSession.serverName,
|
|
1061
|
+
updatedSession.serverUrl,
|
|
1062
|
+
updatedSession.transportType,
|
|
1063
|
+
updatedSession.callbackUrl,
|
|
1064
|
+
updatedSession.active ?? false,
|
|
1065
|
+
encryptObject(updatedSession.headers),
|
|
1066
|
+
updatedSession.clientInformation,
|
|
1067
|
+
encryptObject(updatedSession.tokens),
|
|
1068
|
+
updatedSession.codeVerifier,
|
|
1069
|
+
updatedSession.clientId,
|
|
1070
|
+
expiresAt,
|
|
1071
|
+
userId,
|
|
1072
|
+
sessionId
|
|
1073
|
+
]
|
|
1074
|
+
);
|
|
1075
|
+
if (updatedRows.length === 0) {
|
|
1076
|
+
throw new Error(`Session ${sessionId} not found for userId ${userId}`);
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
async get(userId, sessionId) {
|
|
1080
|
+
try {
|
|
1081
|
+
const rows = await this.sql.query(
|
|
1082
|
+
`SELECT * FROM ${this.tableName} WHERE user_id = $1 AND session_id = $2`,
|
|
1083
|
+
[userId, sessionId]
|
|
1084
|
+
);
|
|
1085
|
+
return rows[0] ? this.mapRowToSessionData(rows[0]) : null;
|
|
1086
|
+
} catch (error) {
|
|
1087
|
+
console.error("[NeonStorage] Failed to get session:", error);
|
|
1088
|
+
return null;
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
async list(userId) {
|
|
1092
|
+
try {
|
|
1093
|
+
const rows = await this.sql.query(
|
|
1094
|
+
`SELECT * FROM ${this.tableName} WHERE user_id = $1`,
|
|
1095
|
+
[userId]
|
|
1096
|
+
);
|
|
1097
|
+
return rows.map((row) => this.mapRowToSessionData(row));
|
|
1098
|
+
} catch (error) {
|
|
1099
|
+
console.error(`[NeonStorage] Failed to get session data for ${userId}:`, error);
|
|
1100
|
+
return [];
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
async delete(userId, sessionId) {
|
|
1104
|
+
try {
|
|
1105
|
+
await this.sql.query(
|
|
1106
|
+
`DELETE FROM ${this.tableName} WHERE user_id = $1 AND session_id = $2`,
|
|
1107
|
+
[userId, sessionId]
|
|
1108
|
+
);
|
|
1109
|
+
} catch (error) {
|
|
1110
|
+
console.error("[NeonStorage] Failed to remove session:", error);
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
async listIds(userId) {
|
|
1114
|
+
try {
|
|
1115
|
+
const rows = await this.sql.query(
|
|
1116
|
+
`SELECT session_id FROM ${this.tableName} WHERE user_id = $1`,
|
|
1117
|
+
[userId]
|
|
1118
|
+
);
|
|
1119
|
+
return rows.map((row) => row.session_id);
|
|
1120
|
+
} catch (error) {
|
|
1121
|
+
console.error(`[NeonStorage] Failed to get sessions for ${userId}:`, error);
|
|
1122
|
+
return [];
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
async listAllIds() {
|
|
1126
|
+
try {
|
|
1127
|
+
const rows = await this.sql.query(
|
|
1128
|
+
`SELECT session_id FROM ${this.tableName}`
|
|
1129
|
+
);
|
|
1130
|
+
return rows.map((row) => row.session_id);
|
|
1131
|
+
} catch (error) {
|
|
1132
|
+
console.error("[NeonStorage] Failed to get all sessions:", error);
|
|
1133
|
+
return [];
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
async clearAll() {
|
|
1137
|
+
try {
|
|
1138
|
+
await this.sql.query(`DELETE FROM ${this.tableName}`);
|
|
1139
|
+
} catch (error) {
|
|
1140
|
+
console.error("[NeonStorage] Failed to clear sessions:", error);
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
async cleanupExpired() {
|
|
1144
|
+
try {
|
|
1145
|
+
await this.sql.query(
|
|
1146
|
+
`DELETE FROM ${this.tableName} WHERE expires_at < $1`,
|
|
1147
|
+
[(/* @__PURE__ */ new Date()).toISOString()]
|
|
1148
|
+
);
|
|
1149
|
+
} catch (error) {
|
|
1150
|
+
console.error("[NeonStorage] Failed to cleanup expired sessions:", error);
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
async disconnect() {
|
|
1154
|
+
}
|
|
1155
|
+
};
|
|
1156
|
+
|
|
932
1157
|
// src/server/storage/index.ts
|
|
1158
|
+
function warnIfNeonConnectionStringIsInsecure(connectionString) {
|
|
1159
|
+
try {
|
|
1160
|
+
const url = new URL(connectionString);
|
|
1161
|
+
const sslMode = url.searchParams.get("sslmode");
|
|
1162
|
+
const channelBinding = url.searchParams.get("channel_binding");
|
|
1163
|
+
if (!sslMode) {
|
|
1164
|
+
console.warn("[mcp-ts][Storage] Neon connection string does not include sslmode. Neon recommends sslmode=verify-full for the strongest certificate verification.");
|
|
1165
|
+
} else if (!["verify-full", "require"].includes(sslMode)) {
|
|
1166
|
+
console.warn(`[mcp-ts][Storage] Neon connection string uses sslmode=${sslMode}. Use sslmode=verify-full or sslmode=require for secure connections.`);
|
|
1167
|
+
}
|
|
1168
|
+
if (!channelBinding) {
|
|
1169
|
+
console.warn("[mcp-ts][Storage] Neon connection string does not include channel_binding=require. Add it when supported by your runtime and connection path.");
|
|
1170
|
+
}
|
|
1171
|
+
} catch {
|
|
1172
|
+
console.warn("[mcp-ts][Storage] Neon connection string could not be parsed for SSL checks.");
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
933
1175
|
var storageInstance = null;
|
|
934
1176
|
var storagePromise = null;
|
|
935
1177
|
async function initializeStorage(store) {
|
|
@@ -986,6 +1228,24 @@ async function createStorage() {
|
|
|
986
1228
|
}
|
|
987
1229
|
}
|
|
988
1230
|
}
|
|
1231
|
+
if (type === "neon") {
|
|
1232
|
+
const connectionString = process.env.NEON_DATABASE_URL || process.env.DATABASE_URL;
|
|
1233
|
+
if (!connectionString) {
|
|
1234
|
+
console.warn('[mcp-ts][Storage] Explicit selection "neon" requires NEON_DATABASE_URL or DATABASE_URL.');
|
|
1235
|
+
} else {
|
|
1236
|
+
try {
|
|
1237
|
+
const { neon } = await import('@neondatabase/serverless');
|
|
1238
|
+
warnIfNeonConnectionStringIsInsecure(connectionString);
|
|
1239
|
+
const sql = neon(connectionString);
|
|
1240
|
+
console.log('[mcp-ts][Storage] Explicit selection: "neon"');
|
|
1241
|
+
return await initializeStorage(new NeonStorageBackend(sql));
|
|
1242
|
+
} catch (error) {
|
|
1243
|
+
console.error("[mcp-ts][Storage] Failed to initialize Neon:", error.message);
|
|
1244
|
+
console.log("[mcp-ts][Storage] Falling back to In-Memory storage");
|
|
1245
|
+
return await initializeStorage(new MemoryStorageBackend());
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
989
1249
|
if (type === "memory") {
|
|
990
1250
|
console.log('[mcp-ts][Storage] Explicit selection: "memory"');
|
|
991
1251
|
return await initializeStorage(new MemoryStorageBackend());
|
|
@@ -1024,6 +1284,17 @@ async function createStorage() {
|
|
|
1024
1284
|
console.error("[mcp-ts][Storage] Supabase auto-detection failed:", error.message);
|
|
1025
1285
|
}
|
|
1026
1286
|
}
|
|
1287
|
+
if (process.env.NEON_DATABASE_URL) {
|
|
1288
|
+
try {
|
|
1289
|
+
const { neon } = await import('@neondatabase/serverless');
|
|
1290
|
+
warnIfNeonConnectionStringIsInsecure(process.env.NEON_DATABASE_URL);
|
|
1291
|
+
const sql = neon(process.env.NEON_DATABASE_URL);
|
|
1292
|
+
console.log('[mcp-ts][Storage] Auto-detection: "neon" (via NEON_DATABASE_URL)');
|
|
1293
|
+
return await initializeStorage(new NeonStorageBackend(sql));
|
|
1294
|
+
} catch (error) {
|
|
1295
|
+
console.error("[mcp-ts][Storage] Neon auto-detection failed:", error.message);
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1027
1298
|
console.log('[mcp-ts][Storage] Defaulting to: "memory"');
|
|
1028
1299
|
return await initializeStorage(new MemoryStorageBackend());
|
|
1029
1300
|
}
|
|
@@ -1040,7 +1311,7 @@ async function getStorage() {
|
|
|
1040
1311
|
storageInstance = await storagePromise;
|
|
1041
1312
|
return storageInstance;
|
|
1042
1313
|
}
|
|
1043
|
-
var
|
|
1314
|
+
var sessions = new Proxy({}, {
|
|
1044
1315
|
get(_target, prop) {
|
|
1045
1316
|
return async (...args) => {
|
|
1046
1317
|
const instance = await getStorage();
|
|
@@ -1056,11 +1327,11 @@ var storage = new Proxy({}, {
|
|
|
1056
1327
|
// src/server/mcp/storage-oauth-provider.ts
|
|
1057
1328
|
var StorageOAuthClientProvider = class {
|
|
1058
1329
|
/**
|
|
1059
|
-
* Creates a new
|
|
1330
|
+
* Creates a new session-backed OAuth provider
|
|
1060
1331
|
* @param options - Provider configuration
|
|
1061
1332
|
*/
|
|
1062
1333
|
constructor(options) {
|
|
1063
|
-
__publicField(this, "
|
|
1334
|
+
__publicField(this, "userId");
|
|
1064
1335
|
__publicField(this, "serverId");
|
|
1065
1336
|
__publicField(this, "sessionId");
|
|
1066
1337
|
__publicField(this, "redirectUrl");
|
|
@@ -1073,7 +1344,7 @@ var StorageOAuthClientProvider = class {
|
|
|
1073
1344
|
__publicField(this, "_clientId");
|
|
1074
1345
|
__publicField(this, "onRedirectCallback");
|
|
1075
1346
|
__publicField(this, "tokenExpiresAt");
|
|
1076
|
-
this.
|
|
1347
|
+
this.userId = options.userId;
|
|
1077
1348
|
this.serverId = options.serverId;
|
|
1078
1349
|
this.sessionId = options.sessionId;
|
|
1079
1350
|
this.redirectUrl = options.redirectUrl;
|
|
@@ -1106,24 +1377,24 @@ var StorageOAuthClientProvider = class {
|
|
|
1106
1377
|
this._clientId = clientId_;
|
|
1107
1378
|
}
|
|
1108
1379
|
/**
|
|
1109
|
-
* Loads OAuth data from
|
|
1380
|
+
* Loads OAuth data from the session store
|
|
1110
1381
|
* @private
|
|
1111
1382
|
*/
|
|
1112
1383
|
async getSessionData() {
|
|
1113
|
-
const data = await
|
|
1384
|
+
const data = await sessions.get(this.userId, this.sessionId);
|
|
1114
1385
|
if (!data) {
|
|
1115
1386
|
return {};
|
|
1116
1387
|
}
|
|
1117
1388
|
return data;
|
|
1118
1389
|
}
|
|
1119
1390
|
/**
|
|
1120
|
-
* Saves OAuth data to
|
|
1391
|
+
* Saves OAuth data to the session store
|
|
1121
1392
|
* @param data - Partial OAuth data to save
|
|
1122
1393
|
* @private
|
|
1123
1394
|
* @throws Error if session doesn't exist (session must be created by controller layer)
|
|
1124
1395
|
*/
|
|
1125
1396
|
async saveSessionData(data) {
|
|
1126
|
-
await
|
|
1397
|
+
await sessions.update(this.userId, this.sessionId, data);
|
|
1127
1398
|
}
|
|
1128
1399
|
/**
|
|
1129
1400
|
* Retrieves stored OAuth client information
|
|
@@ -1171,7 +1442,7 @@ var StorageOAuthClientProvider = class {
|
|
|
1171
1442
|
return this.sessionId;
|
|
1172
1443
|
}
|
|
1173
1444
|
async checkState(_state) {
|
|
1174
|
-
const data = await
|
|
1445
|
+
const data = await sessions.get(this.userId, this.sessionId);
|
|
1175
1446
|
if (!data) {
|
|
1176
1447
|
return { valid: false, error: "Session not found" };
|
|
1177
1448
|
}
|
|
@@ -1187,7 +1458,7 @@ var StorageOAuthClientProvider = class {
|
|
|
1187
1458
|
}
|
|
1188
1459
|
async invalidateCredentials(scope) {
|
|
1189
1460
|
if (scope === "all") {
|
|
1190
|
-
await
|
|
1461
|
+
await sessions.delete(this.userId, this.sessionId);
|
|
1191
1462
|
} else {
|
|
1192
1463
|
const updates = {};
|
|
1193
1464
|
if (scope === "client") {
|
|
@@ -1370,7 +1641,7 @@ var ToolExecutionError = class extends McpError {
|
|
|
1370
1641
|
};
|
|
1371
1642
|
var RpcErrorCodes = {
|
|
1372
1643
|
EXECUTION_ERROR: "EXECUTION_ERROR",
|
|
1373
|
-
|
|
1644
|
+
MISSING_USER_ID: "MISSING_USER_ID",
|
|
1374
1645
|
UNAUTHORIZED: "UNAUTHORIZED",
|
|
1375
1646
|
NO_CONNECTION: "NO_CONNECTION",
|
|
1376
1647
|
UNKNOWN_METHOD: "UNKNOWN_METHOD",
|
|
@@ -1381,14 +1652,14 @@ var RpcErrorCodes = {
|
|
|
1381
1652
|
var MCPClient = class _MCPClient {
|
|
1382
1653
|
/**
|
|
1383
1654
|
* Creates a new MCP client instance
|
|
1384
|
-
* Can be initialized with minimal options (
|
|
1655
|
+
* Can be initialized with minimal options (userId + sessionId) for session restoration
|
|
1385
1656
|
* @param options - Client configuration options
|
|
1386
1657
|
*/
|
|
1387
1658
|
constructor(options) {
|
|
1388
1659
|
__publicField(this, "client", null);
|
|
1389
1660
|
__publicField(this, "oauthProvider", null);
|
|
1390
1661
|
__publicField(this, "transport", null);
|
|
1391
|
-
__publicField(this, "
|
|
1662
|
+
__publicField(this, "userId");
|
|
1392
1663
|
__publicField(this, "serverId");
|
|
1393
1664
|
__publicField(this, "sessionId");
|
|
1394
1665
|
__publicField(this, "serverName");
|
|
@@ -1415,7 +1686,7 @@ var MCPClient = class _MCPClient {
|
|
|
1415
1686
|
this.serverName = options.serverName;
|
|
1416
1687
|
this.callbackUrl = options.callbackUrl;
|
|
1417
1688
|
this.onRedirect = options.onRedirect;
|
|
1418
|
-
this.
|
|
1689
|
+
this.userId = options.userId;
|
|
1419
1690
|
this.serverId = options.serverId;
|
|
1420
1691
|
this.sessionId = options.sessionId;
|
|
1421
1692
|
this.transportType = options.transportType;
|
|
@@ -1554,7 +1825,7 @@ var MCPClient = class _MCPClient {
|
|
|
1554
1825
|
this.emitStateChange("INITIALIZING");
|
|
1555
1826
|
this.emitProgress("Loading session configuration...");
|
|
1556
1827
|
if (!this.serverUrl || !this.callbackUrl || !this.serverId) {
|
|
1557
|
-
const sessionData = await
|
|
1828
|
+
const sessionData = await sessions.get(this.userId, this.sessionId);
|
|
1558
1829
|
if (!sessionData) {
|
|
1559
1830
|
throw new Error(`Session not found: ${this.sessionId}`);
|
|
1560
1831
|
}
|
|
@@ -1573,7 +1844,7 @@ var MCPClient = class _MCPClient {
|
|
|
1573
1844
|
throw new Error("serverId required for OAuth provider initialization");
|
|
1574
1845
|
}
|
|
1575
1846
|
this.oauthProvider = new StorageOAuthClientProvider({
|
|
1576
|
-
|
|
1847
|
+
userId: this.userId,
|
|
1577
1848
|
serverId: this.serverId,
|
|
1578
1849
|
sessionId: this.sessionId,
|
|
1579
1850
|
redirectUrl: this.callbackUrl,
|
|
@@ -1607,18 +1878,18 @@ var MCPClient = class _MCPClient {
|
|
|
1607
1878
|
}
|
|
1608
1879
|
);
|
|
1609
1880
|
}
|
|
1610
|
-
const existingSession = await
|
|
1881
|
+
const existingSession = await sessions.get(this.userId, this.sessionId);
|
|
1611
1882
|
if (!existingSession && this.serverId && this.serverUrl && this.callbackUrl) {
|
|
1612
1883
|
this.createdAt = Date.now();
|
|
1613
1884
|
console.log(`[MCPClient] Creating initial session ${this.sessionId} for OAuth flow`);
|
|
1614
|
-
await
|
|
1885
|
+
await sessions.create({
|
|
1615
1886
|
sessionId: this.sessionId,
|
|
1616
|
-
|
|
1887
|
+
userId: this.userId,
|
|
1617
1888
|
serverId: this.serverId,
|
|
1618
1889
|
serverName: this.serverName,
|
|
1619
1890
|
serverUrl: this.serverUrl,
|
|
1620
1891
|
callbackUrl: this.callbackUrl,
|
|
1621
|
-
transportType: this.transportType || "
|
|
1892
|
+
transportType: this.transportType || "streamable-http",
|
|
1622
1893
|
headers: this.headers,
|
|
1623
1894
|
createdAt: this.createdAt,
|
|
1624
1895
|
active: false
|
|
@@ -1626,7 +1897,7 @@ var MCPClient = class _MCPClient {
|
|
|
1626
1897
|
}
|
|
1627
1898
|
}
|
|
1628
1899
|
/**
|
|
1629
|
-
* Saves current session state to
|
|
1900
|
+
* Saves current session state to the session store
|
|
1630
1901
|
* Creates new session if it doesn't exist, updates if it does
|
|
1631
1902
|
* @param ttl - Time-to-live in seconds (defaults to 12hr for connected sessions)
|
|
1632
1903
|
* @param active - Session status marker used to avoid unnecessary TTL rewrites
|
|
@@ -1638,21 +1909,21 @@ var MCPClient = class _MCPClient {
|
|
|
1638
1909
|
}
|
|
1639
1910
|
const sessionData = {
|
|
1640
1911
|
sessionId: this.sessionId,
|
|
1641
|
-
|
|
1912
|
+
userId: this.userId,
|
|
1642
1913
|
serverId: this.serverId,
|
|
1643
1914
|
serverName: this.serverName,
|
|
1644
1915
|
serverUrl: this.serverUrl,
|
|
1645
1916
|
callbackUrl: this.callbackUrl,
|
|
1646
|
-
transportType: this.transportType || "
|
|
1917
|
+
transportType: this.transportType || "streamable-http",
|
|
1647
1918
|
headers: this.headers,
|
|
1648
1919
|
createdAt: this.createdAt || Date.now(),
|
|
1649
1920
|
active
|
|
1650
1921
|
};
|
|
1651
|
-
const existingSession = await
|
|
1922
|
+
const existingSession = await sessions.get(this.userId, this.sessionId);
|
|
1652
1923
|
if (existingSession) {
|
|
1653
|
-
await
|
|
1924
|
+
await sessions.update(this.userId, this.sessionId, sessionData, ttl);
|
|
1654
1925
|
} else {
|
|
1655
|
-
await
|
|
1926
|
+
await sessions.create(sessionData, ttl);
|
|
1656
1927
|
}
|
|
1657
1928
|
}
|
|
1658
1929
|
/**
|
|
@@ -1661,7 +1932,7 @@ var MCPClient = class _MCPClient {
|
|
|
1661
1932
|
* @private
|
|
1662
1933
|
*/
|
|
1663
1934
|
async tryConnect() {
|
|
1664
|
-
const transportsToTry = this.transportType ? [this.transportType] : ["
|
|
1935
|
+
const transportsToTry = this.transportType ? [this.transportType] : ["streamable-http", "sse"];
|
|
1665
1936
|
let lastError;
|
|
1666
1937
|
for (const currentType of transportsToTry) {
|
|
1667
1938
|
const isLastAttempt = currentType === transportsToTry[transportsToTry.length - 1];
|
|
@@ -1733,7 +2004,7 @@ var MCPClient = class _MCPClient {
|
|
|
1733
2004
|
this.emitError(message, "auth");
|
|
1734
2005
|
this.emitStateChange("FAILED");
|
|
1735
2006
|
try {
|
|
1736
|
-
await
|
|
2007
|
+
await sessions.delete(this.userId, this.sessionId);
|
|
1737
2008
|
} catch {
|
|
1738
2009
|
}
|
|
1739
2010
|
throw new Error(message);
|
|
@@ -1759,9 +2030,9 @@ var MCPClient = class _MCPClient {
|
|
|
1759
2030
|
this.emitError(errorMessage, "connection");
|
|
1760
2031
|
this.emitStateChange("FAILED");
|
|
1761
2032
|
try {
|
|
1762
|
-
const existingSession = await
|
|
2033
|
+
const existingSession = await sessions.get(this.userId, this.sessionId);
|
|
1763
2034
|
if (!existingSession || existingSession.active !== true) {
|
|
1764
|
-
await
|
|
2035
|
+
await sessions.delete(this.userId, this.sessionId);
|
|
1765
2036
|
}
|
|
1766
2037
|
} catch {
|
|
1767
2038
|
}
|
|
@@ -1785,7 +2056,7 @@ var MCPClient = class _MCPClient {
|
|
|
1785
2056
|
this.emitStateChange("FAILED");
|
|
1786
2057
|
throw new Error(error);
|
|
1787
2058
|
}
|
|
1788
|
-
const transportsToTry = this.transportType ? [this.transportType] : ["
|
|
2059
|
+
const transportsToTry = this.transportType ? [this.transportType] : ["streamable-http", "sse"];
|
|
1789
2060
|
let lastError;
|
|
1790
2061
|
let tokensExchanged = false;
|
|
1791
2062
|
let authenticatedStateEmitted = false;
|
|
@@ -2109,7 +2380,7 @@ var MCPClient = class _MCPClient {
|
|
|
2109
2380
|
},
|
|
2110
2381
|
{ capabilities: {} }
|
|
2111
2382
|
);
|
|
2112
|
-
const tt = this.transportType || "
|
|
2383
|
+
const tt = this.transportType || "streamable-http";
|
|
2113
2384
|
this.transport = this.getTransport(tt);
|
|
2114
2385
|
await this.client.connect(this.transport);
|
|
2115
2386
|
}
|
|
@@ -2126,7 +2397,7 @@ var MCPClient = class _MCPClient {
|
|
|
2126
2397
|
if (this.oauthProvider) {
|
|
2127
2398
|
await this.oauthProvider.invalidateCredentials("all");
|
|
2128
2399
|
}
|
|
2129
|
-
await
|
|
2400
|
+
await sessions.delete(this.userId, this.sessionId);
|
|
2130
2401
|
this.disconnect();
|
|
2131
2402
|
}
|
|
2132
2403
|
/**
|
|
@@ -2194,10 +2465,10 @@ var MCPClient = class _MCPClient {
|
|
|
2194
2465
|
}
|
|
2195
2466
|
/**
|
|
2196
2467
|
* Gets the transport type being used
|
|
2197
|
-
* @returns Transport type (defaults to '
|
|
2468
|
+
* @returns Transport type (defaults to 'streamable-http')
|
|
2198
2469
|
*/
|
|
2199
2470
|
getTransportType() {
|
|
2200
|
-
return this.transportType || "
|
|
2471
|
+
return this.transportType || "streamable-http";
|
|
2201
2472
|
}
|
|
2202
2473
|
/**
|
|
2203
2474
|
* Gets the human-readable server name
|
|
@@ -2224,25 +2495,25 @@ var MCPClient = class _MCPClient {
|
|
|
2224
2495
|
* Gets MCP server configuration for all active user sessions
|
|
2225
2496
|
* Loads sessions from Redis, validates OAuth tokens, refreshes if expired
|
|
2226
2497
|
* Returns ready-to-use configuration with valid auth headers
|
|
2227
|
-
* @param
|
|
2498
|
+
* @param userId - User ID to fetch sessions for
|
|
2228
2499
|
* @returns Object keyed by sanitized server labels containing transport, url, headers, etc.
|
|
2229
2500
|
* @static
|
|
2230
2501
|
*/
|
|
2231
|
-
static async getMcpServerConfig(
|
|
2502
|
+
static async getMcpServerConfig(userId) {
|
|
2232
2503
|
const mcpConfig = {};
|
|
2233
|
-
const
|
|
2504
|
+
const sessionList = await sessions.list(userId);
|
|
2234
2505
|
await Promise.all(
|
|
2235
|
-
|
|
2506
|
+
sessionList.map(async (sessionData) => {
|
|
2236
2507
|
const { sessionId } = sessionData;
|
|
2237
2508
|
try {
|
|
2238
2509
|
if (!sessionData.serverId || !sessionData.transportType || !sessionData.serverUrl || !sessionData.callbackUrl) {
|
|
2239
|
-
await
|
|
2510
|
+
await sessions.delete(userId, sessionId);
|
|
2240
2511
|
return;
|
|
2241
2512
|
}
|
|
2242
2513
|
let headers;
|
|
2243
2514
|
try {
|
|
2244
2515
|
const client = new _MCPClient({
|
|
2245
|
-
|
|
2516
|
+
userId,
|
|
2246
2517
|
sessionId,
|
|
2247
2518
|
serverId: sessionData.serverId,
|
|
2248
2519
|
serverUrl: sessionData.serverUrl,
|
|
@@ -2275,7 +2546,7 @@ var MCPClient = class _MCPClient {
|
|
|
2275
2546
|
...headers && { headers }
|
|
2276
2547
|
};
|
|
2277
2548
|
} catch (error) {
|
|
2278
|
-
await
|
|
2549
|
+
await sessions.delete(userId, sessionId);
|
|
2279
2550
|
console.warn(`[MCP] Failed to process session ${sessionId}:`, error);
|
|
2280
2551
|
}
|
|
2281
2552
|
})
|
|
@@ -2291,18 +2562,18 @@ var DEFAULT_RETRY_DELAY_MS = 1e3;
|
|
|
2291
2562
|
var CONNECTION_BATCH_SIZE = 5;
|
|
2292
2563
|
var MultiSessionClient = class {
|
|
2293
2564
|
/**
|
|
2294
|
-
* Creates a new MultiSessionClient for the given user
|
|
2565
|
+
* Creates a new MultiSessionClient for the given user userId.
|
|
2295
2566
|
*
|
|
2296
|
-
* @param
|
|
2567
|
+
* @param userId - A unique string identifying the user (e.g. user ID or email).
|
|
2297
2568
|
* @param options - Optional tuning for connection timeout, retry count, and retry delay.
|
|
2298
2569
|
* Falls back to sensible defaults if not provided.
|
|
2299
2570
|
*/
|
|
2300
|
-
constructor(
|
|
2571
|
+
constructor(userId, options = {}) {
|
|
2301
2572
|
__publicField(this, "clients", []);
|
|
2302
|
-
__publicField(this, "
|
|
2573
|
+
__publicField(this, "userId");
|
|
2303
2574
|
__publicField(this, "options");
|
|
2304
2575
|
__publicField(this, "connectionPromises", /* @__PURE__ */ new Map());
|
|
2305
|
-
this.
|
|
2576
|
+
this.userId = userId;
|
|
2306
2577
|
this.options = {
|
|
2307
2578
|
timeout: DEFAULT_TIMEOUT_MS,
|
|
2308
2579
|
maxRetries: DEFAULT_MAX_RETRIES,
|
|
@@ -2311,7 +2582,7 @@ var MultiSessionClient = class {
|
|
|
2311
2582
|
};
|
|
2312
2583
|
}
|
|
2313
2584
|
/**
|
|
2314
|
-
* Fetches all sessions for this
|
|
2585
|
+
* Fetches all sessions for this userId from storage and returns only the
|
|
2315
2586
|
* ones that are ready to connect.
|
|
2316
2587
|
*
|
|
2317
2588
|
* A session is considered connectable when:
|
|
@@ -2324,8 +2595,8 @@ var MultiSessionClient = class {
|
|
|
2324
2595
|
* for backwards compatibility.
|
|
2325
2596
|
*/
|
|
2326
2597
|
async getActiveSessions() {
|
|
2327
|
-
const
|
|
2328
|
-
const valid =
|
|
2598
|
+
const sessionList = await sessions.list(this.userId);
|
|
2599
|
+
const valid = sessionList.filter(
|
|
2329
2600
|
(s) => s.serverId && s.serverUrl && s.callbackUrl && s.active !== false
|
|
2330
2601
|
// exclude OAuth-pending / failed sessions
|
|
2331
2602
|
);
|
|
@@ -2338,9 +2609,9 @@ var MultiSessionClient = class {
|
|
|
2338
2609
|
* has many active MCP sessions (e.g. 20+ servers). Within each batch, sessions
|
|
2339
2610
|
* are connected concurrently using `Promise.all` for speed.
|
|
2340
2611
|
*/
|
|
2341
|
-
async connectInBatches(
|
|
2342
|
-
for (let i = 0; i <
|
|
2343
|
-
const batch =
|
|
2612
|
+
async connectInBatches(sessions2) {
|
|
2613
|
+
for (let i = 0; i < sessions2.length; i += CONNECTION_BATCH_SIZE) {
|
|
2614
|
+
const batch = sessions2.slice(i, i + CONNECTION_BATCH_SIZE);
|
|
2344
2615
|
await Promise.all(batch.map((session) => this.connectSession(session)));
|
|
2345
2616
|
}
|
|
2346
2617
|
}
|
|
@@ -2391,7 +2662,7 @@ var MultiSessionClient = class {
|
|
|
2391
2662
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
2392
2663
|
try {
|
|
2393
2664
|
const client = new MCPClient({
|
|
2394
|
-
|
|
2665
|
+
userId: this.userId,
|
|
2395
2666
|
sessionId: session.sessionId,
|
|
2396
2667
|
serverId: session.serverId,
|
|
2397
2668
|
serverUrl: session.serverUrl,
|
|
@@ -2423,7 +2694,7 @@ var MultiSessionClient = class {
|
|
|
2423
2694
|
console.error(`[MultiSessionClient] Failed to connect to session ${session.sessionId} after ${maxRetries + 1} attempts:`, lastError);
|
|
2424
2695
|
}
|
|
2425
2696
|
/**
|
|
2426
|
-
* The main entry point. Fetches all active sessions for this
|
|
2697
|
+
* The main entry point. Fetches all active sessions for this userId from
|
|
2427
2698
|
* storage and establishes connections to all of them in batches.
|
|
2428
2699
|
*
|
|
2429
2700
|
* Call this once after creating the client. On traditional servers, you can
|
|
@@ -2431,8 +2702,8 @@ var MultiSessionClient = class {
|
|
|
2431
2702
|
* re-fetching and re-connecting on every request.
|
|
2432
2703
|
*/
|
|
2433
2704
|
async connect() {
|
|
2434
|
-
const
|
|
2435
|
-
await this.connectInBatches(
|
|
2705
|
+
const sessions2 = await this.getActiveSessions();
|
|
2706
|
+
await this.connectInBatches(sessions2);
|
|
2436
2707
|
}
|
|
2437
2708
|
/**
|
|
2438
2709
|
* Returns all currently connected `MCPClient` instances.
|
|
@@ -2487,11 +2758,11 @@ var SSEConnectionManager = class {
|
|
|
2487
2758
|
constructor(options, sendEvent) {
|
|
2488
2759
|
this.options = options;
|
|
2489
2760
|
this.sendEvent = sendEvent;
|
|
2490
|
-
__publicField(this, "
|
|
2761
|
+
__publicField(this, "userId");
|
|
2491
2762
|
__publicField(this, "clients", /* @__PURE__ */ new Map());
|
|
2492
2763
|
__publicField(this, "heartbeatTimer");
|
|
2493
2764
|
__publicField(this, "isActive", true);
|
|
2494
|
-
this.
|
|
2765
|
+
this.userId = options.userId;
|
|
2495
2766
|
this.startHeartbeat();
|
|
2496
2767
|
}
|
|
2497
2768
|
/**
|
|
@@ -2531,8 +2802,8 @@ var SSEConnectionManager = class {
|
|
|
2531
2802
|
try {
|
|
2532
2803
|
let result;
|
|
2533
2804
|
switch (request.method) {
|
|
2534
|
-
case "
|
|
2535
|
-
result = await this.
|
|
2805
|
+
case "listSessions":
|
|
2806
|
+
result = await this.listSessions();
|
|
2536
2807
|
break;
|
|
2537
2808
|
case "connect":
|
|
2538
2809
|
result = await this.connect(request.params);
|
|
@@ -2546,8 +2817,8 @@ var SSEConnectionManager = class {
|
|
|
2546
2817
|
case "callTool":
|
|
2547
2818
|
result = await this.callTool(request.params);
|
|
2548
2819
|
break;
|
|
2549
|
-
case "
|
|
2550
|
-
result = await this.
|
|
2820
|
+
case "getSession":
|
|
2821
|
+
result = await this.getSession(request.params);
|
|
2551
2822
|
break;
|
|
2552
2823
|
case "finishAuth":
|
|
2553
2824
|
result = await this.finishAuth(request.params);
|
|
@@ -2586,12 +2857,12 @@ var SSEConnectionManager = class {
|
|
|
2586
2857
|
}
|
|
2587
2858
|
}
|
|
2588
2859
|
/**
|
|
2589
|
-
* Get all sessions for the current
|
|
2860
|
+
* Get all sessions for the current userId
|
|
2590
2861
|
*/
|
|
2591
|
-
async
|
|
2592
|
-
const
|
|
2862
|
+
async listSessions() {
|
|
2863
|
+
const sessionList = await sessions.list(this.userId);
|
|
2593
2864
|
return {
|
|
2594
|
-
sessions:
|
|
2865
|
+
sessions: sessionList.map((s) => ({
|
|
2595
2866
|
sessionId: s.sessionId,
|
|
2596
2867
|
serverId: s.serverId,
|
|
2597
2868
|
serverName: s.serverName,
|
|
@@ -2608,14 +2879,14 @@ var SSEConnectionManager = class {
|
|
|
2608
2879
|
async connect(params) {
|
|
2609
2880
|
const { serverName, serverUrl, callbackUrl, transportType } = params;
|
|
2610
2881
|
const headers = normalizeHeaders(params.headers);
|
|
2611
|
-
const serverId = params.serverId && params.serverId.length <= 12 ? params.serverId : await
|
|
2612
|
-
const existingSessions = await
|
|
2882
|
+
const serverId = params.serverId && params.serverId.length <= 12 ? params.serverId : await sessions.generateSessionId();
|
|
2883
|
+
const existingSessions = await sessions.list(this.userId);
|
|
2613
2884
|
const duplicate = existingSessions.find(
|
|
2614
2885
|
(s) => s.serverId === serverId || s.serverUrl === serverUrl
|
|
2615
2886
|
);
|
|
2616
2887
|
if (duplicate) {
|
|
2617
2888
|
if (duplicate.active === false) {
|
|
2618
|
-
await this.
|
|
2889
|
+
await this.getSession({ sessionId: duplicate.sessionId });
|
|
2619
2890
|
return {
|
|
2620
2891
|
sessionId: duplicate.sessionId,
|
|
2621
2892
|
success: true
|
|
@@ -2623,11 +2894,11 @@ var SSEConnectionManager = class {
|
|
|
2623
2894
|
}
|
|
2624
2895
|
throw new Error(`Connection already exists for server: ${duplicate.serverUrl || duplicate.serverId} (${duplicate.serverName})`);
|
|
2625
2896
|
}
|
|
2626
|
-
const sessionId = await
|
|
2897
|
+
const sessionId = await sessions.generateSessionId();
|
|
2627
2898
|
try {
|
|
2628
2899
|
const clientMetadata = await this.getResolvedClientMetadata();
|
|
2629
2900
|
const client = new MCPClient({
|
|
2630
|
-
|
|
2901
|
+
userId: this.userId,
|
|
2631
2902
|
sessionId,
|
|
2632
2903
|
serverId,
|
|
2633
2904
|
serverName,
|
|
@@ -2682,7 +2953,7 @@ var SSEConnectionManager = class {
|
|
|
2682
2953
|
client.disconnect();
|
|
2683
2954
|
this.clients.delete(sessionId);
|
|
2684
2955
|
} else {
|
|
2685
|
-
await
|
|
2956
|
+
await sessions.delete(this.userId, sessionId);
|
|
2686
2957
|
}
|
|
2687
2958
|
return { success: true };
|
|
2688
2959
|
}
|
|
@@ -2694,12 +2965,12 @@ var SSEConnectionManager = class {
|
|
|
2694
2965
|
if (existing) {
|
|
2695
2966
|
return existing;
|
|
2696
2967
|
}
|
|
2697
|
-
const session = await
|
|
2968
|
+
const session = await sessions.get(this.userId, sessionId);
|
|
2698
2969
|
if (!session) {
|
|
2699
2970
|
throw new Error("Session not found");
|
|
2700
2971
|
}
|
|
2701
2972
|
const client = new MCPClient({
|
|
2702
|
-
|
|
2973
|
+
userId: this.userId,
|
|
2703
2974
|
sessionId,
|
|
2704
2975
|
// These fields are optional in MCPClient, but when rehydrating a known
|
|
2705
2976
|
// stored session on the server we pass them explicitly to preserve the
|
|
@@ -2746,9 +3017,9 @@ var SSEConnectionManager = class {
|
|
|
2746
3017
|
/**
|
|
2747
3018
|
* Restore and validate an existing session
|
|
2748
3019
|
*/
|
|
2749
|
-
async
|
|
3020
|
+
async getSession(params) {
|
|
2750
3021
|
const { sessionId } = params;
|
|
2751
|
-
const session = await
|
|
3022
|
+
const session = await sessions.get(this.userId, sessionId);
|
|
2752
3023
|
if (!session) {
|
|
2753
3024
|
throw new Error("Session not found");
|
|
2754
3025
|
}
|
|
@@ -2765,7 +3036,7 @@ var SSEConnectionManager = class {
|
|
|
2765
3036
|
try {
|
|
2766
3037
|
const clientMetadata = await this.getResolvedClientMetadata();
|
|
2767
3038
|
const client = new MCPClient({
|
|
2768
|
-
|
|
3039
|
+
userId: this.userId,
|
|
2769
3040
|
sessionId,
|
|
2770
3041
|
// These fields are optional in MCPClient, but when rehydrating a known
|
|
2771
3042
|
// stored session on the server we pass them explicitly to preserve the
|
|
@@ -2802,13 +3073,13 @@ var SSEConnectionManager = class {
|
|
|
2802
3073
|
*/
|
|
2803
3074
|
async finishAuth(params) {
|
|
2804
3075
|
const { sessionId, code } = params;
|
|
2805
|
-
const session = await
|
|
3076
|
+
const session = await sessions.get(this.userId, sessionId);
|
|
2806
3077
|
if (!session) {
|
|
2807
3078
|
throw new Error("Session not found");
|
|
2808
3079
|
}
|
|
2809
3080
|
try {
|
|
2810
3081
|
const client = new MCPClient({
|
|
2811
|
-
|
|
3082
|
+
userId: this.userId,
|
|
2812
3083
|
sessionId,
|
|
2813
3084
|
// These fields are optional in MCPClient, but when rehydrating a known
|
|
2814
3085
|
// stored session on the server we pass them explicitly to preserve the
|
|
@@ -2819,7 +3090,7 @@ var SSEConnectionManager = class {
|
|
|
2819
3090
|
serverUrl: session.serverUrl,
|
|
2820
3091
|
callbackUrl: session.callbackUrl,
|
|
2821
3092
|
// NOTE: transportType is intentionally omitted here.
|
|
2822
|
-
// The session's stored transportType is a placeholder ('
|
|
3093
|
+
// The session's stored transportType is a placeholder ('streamable-http')
|
|
2823
3094
|
// set before transport negotiation. Omitting it lets MCPClient auto-negotiate
|
|
2824
3095
|
// (try streamable_http → SSE fallback), which is critical for servers like
|
|
2825
3096
|
// Neon that only support SSE transport.
|
|
@@ -2941,18 +3212,21 @@ function writeSSEEvent(res, event, data) {
|
|
|
2941
3212
|
// src/server/handlers/nextjs-handler.ts
|
|
2942
3213
|
function createNextMcpHandler(options = {}) {
|
|
2943
3214
|
const {
|
|
2944
|
-
|
|
3215
|
+
getUserId = (request) => request.headers.get("x-mcp-user-id"),
|
|
2945
3216
|
getAuthToken = (request) => {
|
|
2946
|
-
const
|
|
2947
|
-
|
|
3217
|
+
const authHeader = request.headers.get("authorization");
|
|
3218
|
+
if (authHeader?.toLowerCase().startsWith("bearer ")) {
|
|
3219
|
+
return authHeader.slice(7);
|
|
3220
|
+
}
|
|
3221
|
+
return authHeader;
|
|
2948
3222
|
},
|
|
2949
3223
|
authenticate = () => true,
|
|
2950
3224
|
heartbeatInterval = 3e4,
|
|
2951
3225
|
clientDefaults,
|
|
2952
3226
|
getClientMetadata
|
|
2953
3227
|
} = options;
|
|
2954
|
-
const toManagerOptions = (
|
|
2955
|
-
|
|
3228
|
+
const toManagerOptions = (userId, resolvedClientMetadata) => ({
|
|
3229
|
+
userId,
|
|
2956
3230
|
heartbeatInterval,
|
|
2957
3231
|
clientDefaults: resolvedClientMetadata
|
|
2958
3232
|
});
|
|
@@ -2971,13 +3245,13 @@ function createNextMcpHandler(options = {}) {
|
|
|
2971
3245
|
);
|
|
2972
3246
|
}
|
|
2973
3247
|
async function POST(request) {
|
|
2974
|
-
const
|
|
3248
|
+
const userId = getUserId(request);
|
|
2975
3249
|
const authToken = getAuthToken(request);
|
|
2976
3250
|
const acceptsEventStream = (request.headers.get("accept") || "").toLowerCase().includes("text/event-stream");
|
|
2977
|
-
if (!
|
|
2978
|
-
return Response.json({ error: { code: "
|
|
3251
|
+
if (!userId) {
|
|
3252
|
+
return Response.json({ error: { code: "MISSING_userId", message: "Missing userId" } }, { status: 400 });
|
|
2979
3253
|
}
|
|
2980
|
-
const isAuthorized = await authenticate(
|
|
3254
|
+
const isAuthorized = await authenticate(userId, authToken);
|
|
2981
3255
|
if (!isAuthorized) {
|
|
2982
3256
|
return Response.json({ error: { code: "UNAUTHORIZED", message: "Unauthorized" } }, { status: 401 });
|
|
2983
3257
|
}
|
|
@@ -2999,7 +3273,7 @@ function createNextMcpHandler(options = {}) {
|
|
|
2999
3273
|
const resolvedClientMetadata = await resolveClientMetadata(request);
|
|
3000
3274
|
if (!acceptsEventStream) {
|
|
3001
3275
|
const manager2 = new SSEConnectionManager(
|
|
3002
|
-
toManagerOptions(
|
|
3276
|
+
toManagerOptions(userId, resolvedClientMetadata),
|
|
3003
3277
|
() => {
|
|
3004
3278
|
}
|
|
3005
3279
|
);
|
|
@@ -3025,7 +3299,7 @@ data: ${JSON.stringify(data)}
|
|
|
3025
3299
|
});
|
|
3026
3300
|
};
|
|
3027
3301
|
const manager = new SSEConnectionManager(
|
|
3028
|
-
toManagerOptions(
|
|
3302
|
+
toManagerOptions(userId, resolvedClientMetadata),
|
|
3029
3303
|
(event) => {
|
|
3030
3304
|
if (isRpcResponseEvent(event)) {
|
|
3031
3305
|
sendSSE("rpc-response", event);
|
|
@@ -3068,7 +3342,7 @@ data: ${JSON.stringify(data)}
|
|
|
3068
3342
|
} catch (error) {
|
|
3069
3343
|
const err = error instanceof Error ? error : new Error("Unknown error");
|
|
3070
3344
|
console.error("[MCP Next Handler] Failed to handle RPC", {
|
|
3071
|
-
|
|
3345
|
+
userId,
|
|
3072
3346
|
message: err.message,
|
|
3073
3347
|
stack: err.stack,
|
|
3074
3348
|
rawBody: rawBody.slice(0, 500)
|
|
@@ -3108,8 +3382,8 @@ var SSEClient = class {
|
|
|
3108
3382
|
isConnected() {
|
|
3109
3383
|
return this.connected;
|
|
3110
3384
|
}
|
|
3111
|
-
async
|
|
3112
|
-
return this.sendRequest("
|
|
3385
|
+
async listSessions() {
|
|
3386
|
+
return this.sendRequest("listSessions");
|
|
3113
3387
|
}
|
|
3114
3388
|
async connectToServer(params) {
|
|
3115
3389
|
return this.sendRequest("connect", params);
|
|
@@ -3125,8 +3399,8 @@ var SSEClient = class {
|
|
|
3125
3399
|
this.emitUiEventIfPresent(result, sessionId, toolName);
|
|
3126
3400
|
return result;
|
|
3127
3401
|
}
|
|
3128
|
-
async
|
|
3129
|
-
return this.sendRequest("
|
|
3402
|
+
async getSession(sessionId) {
|
|
3403
|
+
return this.sendRequest("getSession", { sessionId });
|
|
3130
3404
|
}
|
|
3131
3405
|
async finishAuth(sessionId, code) {
|
|
3132
3406
|
return this.sendRequest("finishAuth", { sessionId, code });
|
|
@@ -3192,7 +3466,7 @@ var SSEClient = class {
|
|
|
3192
3466
|
return this.parseRpcResponse(data2);
|
|
3193
3467
|
}
|
|
3194
3468
|
const data = await this.readRpcResponseFromStream(response, {
|
|
3195
|
-
delayConnectionEvents: method === "connect" || method === "
|
|
3469
|
+
delayConnectionEvents: method === "connect" || method === "getSession" || method === "finishAuth"
|
|
3196
3470
|
});
|
|
3197
3471
|
return this.parseRpcResponse(data);
|
|
3198
3472
|
}
|
|
@@ -3282,17 +3556,13 @@ var SSEClient = class {
|
|
|
3282
3556
|
throw new Error("Invalid RPC response format");
|
|
3283
3557
|
}
|
|
3284
3558
|
buildUrl() {
|
|
3285
|
-
|
|
3286
|
-
url.searchParams.set("identity", this.options.identity);
|
|
3287
|
-
if (this.options.authToken) {
|
|
3288
|
-
url.searchParams.set("token", this.options.authToken);
|
|
3289
|
-
}
|
|
3290
|
-
return url.toString();
|
|
3559
|
+
return new URL(this.options.url, globalThis.location?.origin).toString();
|
|
3291
3560
|
}
|
|
3292
3561
|
buildHeaders() {
|
|
3293
3562
|
const headers = {
|
|
3294
3563
|
"Content-Type": "application/json",
|
|
3295
|
-
"Accept": "text/event-stream"
|
|
3564
|
+
"Accept": "text/event-stream",
|
|
3565
|
+
"x-mcp-user-id": this.options.userId
|
|
3296
3566
|
};
|
|
3297
3567
|
if (this.options.authToken) {
|
|
3298
3568
|
headers["Authorization"] = `Bearer ${this.options.authToken}`;
|
|
@@ -3754,7 +4024,7 @@ var AppHost = class {
|
|
|
3754
4024
|
async getSessionId() {
|
|
3755
4025
|
if (this.sessionId) return this.sessionId;
|
|
3756
4026
|
if (!this.client) return void 0;
|
|
3757
|
-
const result = await this.client.
|
|
4027
|
+
const result = await this.client.listSessions();
|
|
3758
4028
|
return result.sessions?.[0]?.sessionId;
|
|
3759
4029
|
}
|
|
3760
4030
|
isMcpUri(url) {
|
|
@@ -3826,17 +4096,7 @@ function findToolByName(connections, toolName) {
|
|
|
3826
4096
|
}
|
|
3827
4097
|
|
|
3828
4098
|
// src/shared/tool-index.ts
|
|
3829
|
-
var
|
|
3830
|
-
function classifyChar(ch) {
|
|
3831
|
-
const code = ch.charCodeAt(0);
|
|
3832
|
-
if (code <= 32 || ch === "{" || ch === "}" || ch === "[" || ch === "]" || ch === ":" || ch === ",") return 1;
|
|
3833
|
-
if (code >= 33 && code <= 47) return 1.5;
|
|
3834
|
-
if (code >= 48 && code <= 57) return 2;
|
|
3835
|
-
if (code >= 65 && code <= 90) return 3.5;
|
|
3836
|
-
if (code >= 97 && code <= 122) return 4;
|
|
3837
|
-
return 2.5;
|
|
3838
|
-
}
|
|
3839
|
-
var ToolIndex = class _ToolIndex {
|
|
4099
|
+
var ToolIndex = class {
|
|
3840
4100
|
constructor(options = {}) {
|
|
3841
4101
|
/** All indexed tools keyed by name (supports duplicates). */
|
|
3842
4102
|
__publicField(this, "tools", /* @__PURE__ */ new Map());
|
|
@@ -3854,8 +4114,6 @@ var ToolIndex = class _ToolIndex {
|
|
|
3854
4114
|
__publicField(this, "docLengths", /* @__PURE__ */ new Map());
|
|
3855
4115
|
/** BM25: average document length across the entire index. */
|
|
3856
4116
|
__publicField(this, "avgDocLength", 0);
|
|
3857
|
-
/** Cached total estimated token cost across all indexed tools. */
|
|
3858
|
-
__publicField(this, "totalTokenCost", 0);
|
|
3859
4117
|
__publicField(this, "options");
|
|
3860
4118
|
this.options = {
|
|
3861
4119
|
embedFn: options.embedFn ?? void 0,
|
|
@@ -3878,7 +4136,6 @@ var ToolIndex = class _ToolIndex {
|
|
|
3878
4136
|
this.embeddings.clear();
|
|
3879
4137
|
this.docLengths.clear();
|
|
3880
4138
|
this.avgDocLength = 0;
|
|
3881
|
-
this.totalTokenCost = 0;
|
|
3882
4139
|
const allTokenSets = /* @__PURE__ */ new Map();
|
|
3883
4140
|
let totalLength = 0;
|
|
3884
4141
|
for (const tool of tools) {
|
|
@@ -3887,19 +4144,17 @@ var ToolIndex = class _ToolIndex {
|
|
|
3887
4144
|
this.tools.set(tool.name, []);
|
|
3888
4145
|
}
|
|
3889
4146
|
this.tools.get(tool.name).push(tool);
|
|
3890
|
-
const estimatedTokens = _ToolIndex.estimateTokens(tool);
|
|
3891
4147
|
this.toolSummaries.set(docKey, {
|
|
3892
4148
|
name: tool.name,
|
|
3893
4149
|
description: tool.description ?? "",
|
|
3894
4150
|
serverName: tool.serverName,
|
|
3895
4151
|
serverId: tool.serverId,
|
|
3896
|
-
sessionId: tool.sessionId
|
|
3897
|
-
estimatedTokens
|
|
4152
|
+
sessionId: tool.sessionId
|
|
3898
4153
|
});
|
|
3899
|
-
this.
|
|
3900
|
-
const text =
|
|
4154
|
+
const rawText = this.buildSearchableText(tool);
|
|
4155
|
+
const text = rawText.toLowerCase();
|
|
3901
4156
|
this.searchTexts.set(docKey, text);
|
|
3902
|
-
const tokens = this.tokenize(
|
|
4157
|
+
const tokens = this.tokenize(rawText);
|
|
3903
4158
|
const tf = /* @__PURE__ */ new Map();
|
|
3904
4159
|
const uniqueTokens = /* @__PURE__ */ new Set();
|
|
3905
4160
|
for (const tok of tokens) {
|
|
@@ -4163,30 +4418,6 @@ var ToolIndex = class _ToolIndex {
|
|
|
4163
4418
|
}
|
|
4164
4419
|
return count;
|
|
4165
4420
|
}
|
|
4166
|
-
/** Total estimated token cost of all indexed tool schemas. */
|
|
4167
|
-
getTotalTokenCost() {
|
|
4168
|
-
return this.totalTokenCost;
|
|
4169
|
-
}
|
|
4170
|
-
// -----------------------------------------------------------------------
|
|
4171
|
-
// Static Helpers
|
|
4172
|
-
// -----------------------------------------------------------------------
|
|
4173
|
-
/**
|
|
4174
|
-
* Estimate token count of a tool's full schema (name + description + inputSchema).
|
|
4175
|
-
*
|
|
4176
|
-
* Uses character-class weighted counting calibrated against cl100k_base.
|
|
4177
|
-
* Accuracy is typically within ±10% for JSON Schema payloads.
|
|
4178
|
-
*/
|
|
4179
|
-
static estimateTokens(tool) {
|
|
4180
|
-
const parts = [tool.name];
|
|
4181
|
-
if (tool.description) parts.push(tool.description);
|
|
4182
|
-
if (tool.inputSchema) parts.push(JSON.stringify(tool.inputSchema));
|
|
4183
|
-
const text = parts.join(" ");
|
|
4184
|
-
let weightedLen = 0;
|
|
4185
|
-
for (let i = 0; i < text.length; i++) {
|
|
4186
|
-
weightedLen += 1 / classifyChar(text[i]);
|
|
4187
|
-
}
|
|
4188
|
-
return Math.ceil(weightedLen / (1 / CALIBRATION_DIVISOR));
|
|
4189
|
-
}
|
|
4190
4421
|
// -----------------------------------------------------------------------
|
|
4191
4422
|
// Internals
|
|
4192
4423
|
// -----------------------------------------------------------------------
|
|
@@ -4195,18 +4426,74 @@ var ToolIndex = class _ToolIndex {
|
|
|
4195
4426
|
const parts = [tool.name];
|
|
4196
4427
|
if (tool.description) parts.push(tool.description);
|
|
4197
4428
|
if (tool.inputSchema && typeof tool.inputSchema === "object") {
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4429
|
+
this.collectSchemaSearchText(tool.inputSchema, parts);
|
|
4430
|
+
}
|
|
4431
|
+
return parts.join(" ");
|
|
4432
|
+
}
|
|
4433
|
+
/** Recursively collect JSON Schema argument names and descriptions. */
|
|
4434
|
+
collectSchemaSearchText(schema, parts, seen = /* @__PURE__ */ new WeakSet()) {
|
|
4435
|
+
if (!schema || typeof schema !== "object") return;
|
|
4436
|
+
if (seen.has(schema)) return;
|
|
4437
|
+
seen.add(schema);
|
|
4438
|
+
if (Array.isArray(schema)) {
|
|
4439
|
+
for (const item of schema) {
|
|
4440
|
+
this.collectSchemaSearchText(item, parts, seen);
|
|
4441
|
+
}
|
|
4442
|
+
return;
|
|
4443
|
+
}
|
|
4444
|
+
const schemaObject = schema;
|
|
4445
|
+
this.pushStringValue(schemaObject.description, parts);
|
|
4446
|
+
this.pushStringValue(schemaObject.title, parts);
|
|
4447
|
+
const properties = schemaObject.properties;
|
|
4448
|
+
if (properties && typeof properties === "object" && !Array.isArray(properties)) {
|
|
4449
|
+
for (const [propertyName, propertySchema] of Object.entries(properties)) {
|
|
4450
|
+
parts.push(propertyName);
|
|
4451
|
+
this.collectSchemaSearchText(propertySchema, parts, seen);
|
|
4452
|
+
}
|
|
4453
|
+
}
|
|
4454
|
+
const patternProperties = schemaObject.patternProperties;
|
|
4455
|
+
if (patternProperties && typeof patternProperties === "object" && !Array.isArray(patternProperties)) {
|
|
4456
|
+
for (const [propertyPattern, propertySchema] of Object.entries(patternProperties)) {
|
|
4457
|
+
parts.push(propertyPattern);
|
|
4458
|
+
this.collectSchemaSearchText(propertySchema, parts, seen);
|
|
4459
|
+
}
|
|
4460
|
+
}
|
|
4461
|
+
const dependentSchemas = schemaObject.dependentSchemas;
|
|
4462
|
+
if (dependentSchemas && typeof dependentSchemas === "object" && !Array.isArray(dependentSchemas)) {
|
|
4463
|
+
for (const [propertyName, dependentSchema] of Object.entries(dependentSchemas)) {
|
|
4464
|
+
parts.push(propertyName);
|
|
4465
|
+
this.collectSchemaSearchText(dependentSchema, parts, seen);
|
|
4466
|
+
}
|
|
4467
|
+
}
|
|
4468
|
+
for (const key of [
|
|
4469
|
+
"items",
|
|
4470
|
+
"additionalProperties",
|
|
4471
|
+
"contains",
|
|
4472
|
+
"propertyNames",
|
|
4473
|
+
"if",
|
|
4474
|
+
"then",
|
|
4475
|
+
"else",
|
|
4476
|
+
"not"
|
|
4477
|
+
]) {
|
|
4478
|
+
this.collectSchemaSearchText(schemaObject[key], parts, seen);
|
|
4479
|
+
}
|
|
4480
|
+
for (const key of ["allOf", "anyOf", "oneOf", "prefixItems"]) {
|
|
4481
|
+
this.collectSchemaSearchText(schemaObject[key], parts, seen);
|
|
4482
|
+
}
|
|
4483
|
+
for (const key of ["$defs", "definitions"]) {
|
|
4484
|
+
const definitions = schemaObject[key];
|
|
4485
|
+
if (definitions && typeof definitions === "object" && !Array.isArray(definitions)) {
|
|
4486
|
+
for (const [definitionName, definitionSchema] of Object.entries(definitions)) {
|
|
4487
|
+
parts.push(definitionName);
|
|
4488
|
+
this.collectSchemaSearchText(definitionSchema, parts, seen);
|
|
4206
4489
|
}
|
|
4207
4490
|
}
|
|
4208
4491
|
}
|
|
4209
|
-
|
|
4492
|
+
}
|
|
4493
|
+
pushStringValue(value, parts) {
|
|
4494
|
+
if (typeof value === "string" && value.trim()) {
|
|
4495
|
+
parts.push(value);
|
|
4496
|
+
}
|
|
4210
4497
|
}
|
|
4211
4498
|
getDocumentKey(tool) {
|
|
4212
4499
|
return `${tool.sessionId}::${tool.serverId}::${tool.name}`;
|
|
@@ -4227,7 +4514,7 @@ var ToolIndex = class _ToolIndex {
|
|
|
4227
4514
|
}
|
|
4228
4515
|
/** Simple whitespace + camelCase + snake_case tokenizer. */
|
|
4229
4516
|
tokenize(text) {
|
|
4230
|
-
return text.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]/g, " ").replace(/[^a-z0-9\s]/g, "").split(/\s+/).filter((t) => t.length > 1);
|
|
4517
|
+
return text.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]/g, " ").toLowerCase().replace(/[^a-z0-9\s]/g, "").split(/\s+/).filter((t) => t.length > 1);
|
|
4231
4518
|
}
|
|
4232
4519
|
/** Cosine similarity between two vectors. */
|
|
4233
4520
|
cosineSimilarity(a, b) {
|
|
@@ -4282,27 +4569,6 @@ var SchemaCompressor = class _SchemaCompressor {
|
|
|
4282
4569
|
const limited = options?.maxTools ? tools.slice(0, options.maxTools) : tools;
|
|
4283
4570
|
return limited.map((t) => _SchemaCompressor.toCompact(t));
|
|
4284
4571
|
}
|
|
4285
|
-
/**
|
|
4286
|
-
* Estimate token savings from using compact vs full tool schemas.
|
|
4287
|
-
*/
|
|
4288
|
-
static estimateSavings(tools) {
|
|
4289
|
-
let fullTokens = 0;
|
|
4290
|
-
let compactTokens = 0;
|
|
4291
|
-
for (const tool of tools) {
|
|
4292
|
-
fullTokens += ToolIndex.estimateTokens(tool);
|
|
4293
|
-
const compact = _SchemaCompressor.toCompact(tool);
|
|
4294
|
-
const text = [compact.name, compact.description ?? "", compact.parameterHint ?? ""].join(" ");
|
|
4295
|
-
compactTokens += Math.ceil(text.length / 4);
|
|
4296
|
-
}
|
|
4297
|
-
const saved = fullTokens - compactTokens;
|
|
4298
|
-
const pct = fullTokens > 0 ? (saved / fullTokens * 100).toFixed(1) : "0.0";
|
|
4299
|
-
return {
|
|
4300
|
-
fullTokens,
|
|
4301
|
-
compactTokens,
|
|
4302
|
-
savedTokens: saved,
|
|
4303
|
-
savingsPercent: `${pct}%`
|
|
4304
|
-
};
|
|
4305
|
-
}
|
|
4306
4572
|
};
|
|
4307
4573
|
|
|
4308
4574
|
// src/shared/meta-tools.ts
|
|
@@ -4498,7 +4764,7 @@ async function executeMetaTool(toolName, args, router, callToolFn) {
|
|
|
4498
4764
|
const lines = [];
|
|
4499
4765
|
if (found.length > 0) {
|
|
4500
4766
|
lines.push(...found.map(
|
|
4501
|
-
(t, i) => `${i + 1}. **${t.name}** (
|
|
4767
|
+
(t, i) => `${i + 1}. **${t.name}** (serverName: ${t.serverName}, serverId: ${t.serverId})
|
|
4502
4768
|
${t.description}`
|
|
4503
4769
|
));
|
|
4504
4770
|
}
|
|
@@ -4526,7 +4792,7 @@ async function executeMetaTool(toolName, args, router, callToolFn) {
|
|
|
4526
4792
|
serverName: query || void 0
|
|
4527
4793
|
});
|
|
4528
4794
|
const text = servers.length === 0 ? "No connected servers found." : servers.map(
|
|
4529
|
-
(server, i) => `${i + 1}. **${server.serverName}** (serverId: ${server.serverId}
|
|
4795
|
+
(server, i) => `${i + 1}. **${server.serverName}** (serverId: ${server.serverId})
|
|
4530
4796
|
Tool count: ${server.toolCount}`
|
|
4531
4797
|
).join("\n");
|
|
4532
4798
|
return {
|
|
@@ -4633,9 +4899,8 @@ async function executeMetaTool(toolName, args, router, callToolFn) {
|
|
|
4633
4899
|
}
|
|
4634
4900
|
function formatToolSummaries(tools) {
|
|
4635
4901
|
return tools.map(
|
|
4636
|
-
(t, i) => `${i + 1}. **${t.name}** (
|
|
4637
|
-
${t.description}
|
|
4638
|
-
Estimated tokens: ${t.estimatedTokens}`
|
|
4902
|
+
(t, i) => `${i + 1}. **${t.name}** (serverName: ${t.serverName}, serverId: ${t.serverId})
|
|
4903
|
+
${t.description}`
|
|
4639
4904
|
);
|
|
4640
4905
|
}
|
|
4641
4906
|
function isMetaTool(toolName) {
|
|
@@ -4778,26 +5043,6 @@ var ToolRouter = class {
|
|
|
4778
5043
|
getActiveGroups() {
|
|
4779
5044
|
return [...this.activeGroups];
|
|
4780
5045
|
}
|
|
4781
|
-
// -----------------------------------------------------------------------
|
|
4782
|
-
// Stats & Introspection
|
|
4783
|
-
// -----------------------------------------------------------------------
|
|
4784
|
-
/** Total token cost of all tools if loaded without filtering. */
|
|
4785
|
-
getTotalTokenCost() {
|
|
4786
|
-
return this.index.getTotalTokenCost();
|
|
4787
|
-
}
|
|
4788
|
-
/** Estimate token cost of the currently filtered tool set. */
|
|
4789
|
-
async getFilteredTokenCost() {
|
|
4790
|
-
const tools = await this.getFilteredTools();
|
|
4791
|
-
let total = 0;
|
|
4792
|
-
for (const tool of tools) {
|
|
4793
|
-
total += ToolIndex.estimateTokens(tool);
|
|
4794
|
-
}
|
|
4795
|
-
return total;
|
|
4796
|
-
}
|
|
4797
|
-
/** Get compression stats showing savings from current strategy. */
|
|
4798
|
-
getCompressionStats() {
|
|
4799
|
-
return SchemaCompressor.estimateSavings(this.allTools);
|
|
4800
|
-
}
|
|
4801
5046
|
/** Number of total indexed tools. */
|
|
4802
5047
|
get totalToolCount() {
|
|
4803
5048
|
return this.allTools.length;
|
|
@@ -4943,6 +5188,6 @@ var ToolRouter = class {
|
|
|
4943
5188
|
}
|
|
4944
5189
|
};
|
|
4945
5190
|
|
|
4946
|
-
export { APP_HOST_DEFAULTS, AppHost, AuthenticationError, ConfigurationError, ConnectionError, DEFAULT_CLIENT_NAME, DEFAULT_CLIENT_URI, DEFAULT_HEARTBEAT_INTERVAL_MS, DEFAULT_LOGO_URI, DEFAULT_MCP_APP_CSP, DEFAULT_POLICY_URI, DisposableStore, Emitter, InvalidStateError, MCPClient, MCP_CLIENT_NAME, MCP_CLIENT_VERSION, McpError, MultiSessionClient, NotConnectedError, REDIS_KEY_PREFIX, RpcErrorCodes, SANDBOX_PROXY_READY_METHOD, SANDBOX_RESOURCE_READY_METHOD, SESSION_TTL_SECONDS, SOFTWARE_ID, SOFTWARE_VERSION, SSEClient, SSEConnectionManager, STATE_EXPIRATION_MS, SchemaCompressor, SessionNotFoundError, SessionValidationError, StorageOAuthClientProvider, TOKEN_EXPIRY_BUFFER_MS, ToolExecutionError, ToolIndex, ToolRouter, UnauthorizedError, createExecuteToolDefinition, createGetSchemaToolDefinition, createListServersToolDefinition, createNextMcpHandler, createRegexSearchToolDefinition, createSSEHandler, createSearchToolDefinition, executeMetaTool, findToolByName, getToolUiResourceUri, isCallToolSuccess, isConnectAuthRequired, isConnectError, isConnectSuccess, isListToolsSuccess, isMetaTool, resolveMetaToolProxy, sanitizeServerLabel,
|
|
5191
|
+
export { APP_HOST_DEFAULTS, AppHost, AuthenticationError, ConfigurationError, ConnectionError, DEFAULT_CLIENT_NAME, DEFAULT_CLIENT_URI, DEFAULT_HEARTBEAT_INTERVAL_MS, DEFAULT_LOGO_URI, DEFAULT_MCP_APP_CSP, DEFAULT_POLICY_URI, DisposableStore, Emitter, InvalidStateError, MCPClient, MCP_CLIENT_NAME, MCP_CLIENT_VERSION, McpError, MultiSessionClient, NotConnectedError, REDIS_KEY_PREFIX, RpcErrorCodes, SANDBOX_PROXY_READY_METHOD, SANDBOX_RESOURCE_READY_METHOD, SESSION_TTL_SECONDS, SOFTWARE_ID, SOFTWARE_VERSION, SSEClient, SSEConnectionManager, STATE_EXPIRATION_MS, SchemaCompressor, SessionNotFoundError, SessionValidationError, StorageOAuthClientProvider, TOKEN_EXPIRY_BUFFER_MS, ToolExecutionError, ToolIndex, ToolRouter, UnauthorizedError, createExecuteToolDefinition, createGetSchemaToolDefinition, createListServersToolDefinition, createNextMcpHandler, createRegexSearchToolDefinition, createSSEHandler, createSearchToolDefinition, executeMetaTool, findToolByName, getToolUiResourceUri, isCallToolSuccess, isConnectAuthRequired, isConnectError, isConnectSuccess, isListToolsSuccess, isMetaTool, resolveMetaToolProxy, sanitizeServerLabel, sessions };
|
|
4947
5192
|
//# sourceMappingURL=index.mjs.map
|
|
4948
5193
|
//# sourceMappingURL=index.mjs.map
|