@mcp-ts/sdk 2.3.3 → 2.4.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 +113 -30
- package/dist/adapters/agui-adapter.d.mts +3 -3
- package/dist/adapters/agui-adapter.d.ts +3 -3
- 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 +3 -3
- package/dist/adapters/ai-adapter.d.ts +3 -3
- package/dist/adapters/langchain-adapter.d.mts +3 -3
- package/dist/adapters/langchain-adapter.d.ts +3 -3
- package/dist/adapters/mastra-adapter.d.mts +1 -1
- package/dist/adapters/mastra-adapter.d.ts +1 -1
- package/dist/client/index.d.mts +2 -2
- package/dist/client/index.d.ts +2 -2
- package/dist/client/index.js +2 -2
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +2 -2
- package/dist/client/index.mjs.map +1 -1
- package/dist/client/react.d.mts +9 -8
- package/dist/client/react.d.ts +9 -8
- package/dist/client/react.js +66 -26
- package/dist/client/react.js.map +1 -1
- package/dist/client/react.mjs +67 -27
- package/dist/client/react.mjs.map +1 -1
- package/dist/client/vue.d.mts +6 -5
- package/dist/client/vue.d.ts +6 -5
- package/dist/client/vue.js +27 -17
- package/dist/client/vue.js.map +1 -1
- package/dist/client/vue.mjs +27 -17
- package/dist/client/vue.mjs.map +1 -1
- package/dist/{index-Cfjsme-a.d.mts → index-ByIjEReo.d.mts} +2 -2
- package/dist/{index-CmjMd2ac.d.ts → index-CtXvKl8N.d.ts} +2 -2
- package/dist/index.d.mts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +729 -397
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +724 -396
- package/dist/index.mjs.map +1 -1
- package/dist/{multi-session-client-DYNe6az3.d.mts → multi-session-client-CIMUGF8S.d.mts} +15 -41
- package/dist/{multi-session-client-BYtguGJm.d.ts → multi-session-client-CnvZEGPY.d.ts} +15 -41
- package/dist/server/index.d.mts +45 -17
- package/dist/server/index.d.ts +45 -17
- package/dist/server/index.js +721 -391
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +718 -391
- package/dist/server/index.mjs.map +1 -1
- package/dist/shared/index.d.mts +9 -8
- package/dist/shared/index.d.ts +9 -8
- package/dist/shared/index.js +7 -5
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/index.mjs +5 -4
- package/dist/shared/index.mjs.map +1 -1
- package/dist/{tool-router-BMzhoNYt.d.ts → tool-router-CZMrOG8J.d.ts} +1 -1
- package/dist/{tool-router-BhHsvBfP.d.mts → tool-router-CuApsDiV.d.mts} +1 -1
- package/dist/{types-CxFaaZrM.d.mts → types-DCk_IF4L.d.mts} +5 -3
- package/dist/{types-CxFaaZrM.d.ts → types-DCk_IF4L.d.ts} +5 -3
- package/migrations/neon/20260513010000_install_mcp_sessions.sql +40 -3
- package/migrations/neon/20260513020000_add_session_cleanup_cron.sql +4 -4
- package/migrations/supabase/20260330195700_install_mcp_sessions.sql +67 -3
- package/migrations/supabase/20260421010000_add_session_cleanup_cron.sql +6 -6
- package/package.json +13 -3
- package/src/client/core/sse-client.ts +2 -2
- package/src/client/react/index.ts +1 -1
- package/src/client/react/oauth-popup.tsx +34 -13
- package/src/client/react/use-mcp.ts +19 -45
- package/src/client/utils/session-state.ts +43 -0
- package/src/client/vue/use-mcp.ts +18 -47
- package/src/server/handlers/sse-handler.ts +9 -4
- package/src/server/mcp/multi-session-client.ts +2 -6
- package/src/server/mcp/oauth-client.ts +73 -220
- package/src/server/mcp/storage-oauth-provider.ts +79 -50
- package/src/server/storage/file-backend.ts +74 -18
- package/src/server/storage/index.ts +2 -4
- package/src/server/storage/memory-backend.ts +49 -13
- package/src/server/storage/neon-backend.ts +201 -68
- package/src/server/storage/redis-backend.ts +81 -23
- package/src/server/storage/session-lifecycle.ts +78 -0
- package/src/server/storage/sqlite-backend.ts +89 -15
- package/src/server/storage/supabase-backend.ts +188 -63
- package/src/server/storage/types.ts +49 -16
- package/src/shared/constants.ts +5 -6
- package/src/shared/types.ts +5 -3
- package/src/shared/utils.ts +26 -0
package/dist/index.js
CHANGED
|
@@ -160,24 +160,9 @@ init_cjs_shims();
|
|
|
160
160
|
// src/server/storage/redis-backend.ts
|
|
161
161
|
init_cjs_shims();
|
|
162
162
|
|
|
163
|
-
// src/shared/constants.ts
|
|
164
|
-
init_cjs_shims();
|
|
165
|
-
var SESSION_TTL_SECONDS = 43200;
|
|
166
|
-
var STATE_EXPIRATION_MS = 10 * 60 * 1e3;
|
|
167
|
-
var DEFAULT_HEARTBEAT_INTERVAL_MS = 3e4;
|
|
168
|
-
var REDIS_KEY_PREFIX = "mcp:session:";
|
|
169
|
-
var TOKEN_EXPIRY_BUFFER_MS = 5 * 60 * 1e3;
|
|
170
|
-
var DEFAULT_CLIENT_NAME = "MCP Assistant";
|
|
171
|
-
var DEFAULT_CLIENT_URI = "https://mcp-assistant.in";
|
|
172
|
-
var DEFAULT_LOGO_URI = "https://mcp-assistant.in/logo.svg";
|
|
173
|
-
var DEFAULT_POLICY_URI = "https://mcp-assistant.in/privacy";
|
|
174
|
-
var SOFTWARE_ID = "@mcp-ts";
|
|
175
|
-
var SOFTWARE_VERSION = "1.3.4";
|
|
176
|
-
var MCP_CLIENT_NAME = "mcp-ts-oauth-client";
|
|
177
|
-
var MCP_CLIENT_VERSION = "2.0";
|
|
178
|
-
|
|
179
163
|
// src/shared/utils.ts
|
|
180
164
|
init_cjs_shims();
|
|
165
|
+
var OAUTH_STATE_SEPARATOR = ".";
|
|
181
166
|
var firstChar = nanoid.customAlphabet(
|
|
182
167
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
|
|
183
168
|
1
|
|
@@ -196,13 +181,102 @@ function sanitizeServerLabel(name) {
|
|
|
196
181
|
function generateSessionId() {
|
|
197
182
|
return firstChar() + rest();
|
|
198
183
|
}
|
|
184
|
+
function formatOAuthState(nonce, sessionId) {
|
|
185
|
+
return `${nonce}${OAUTH_STATE_SEPARATOR}${sessionId}`;
|
|
186
|
+
}
|
|
187
|
+
function parseOAuthState(state) {
|
|
188
|
+
const separatorIndex = state.indexOf(OAUTH_STATE_SEPARATOR);
|
|
189
|
+
if (separatorIndex <= 0 || separatorIndex === state.length - 1) {
|
|
190
|
+
return void 0;
|
|
191
|
+
}
|
|
192
|
+
const nonce = state.slice(0, separatorIndex);
|
|
193
|
+
const sessionId = state.slice(separatorIndex + 1);
|
|
194
|
+
if (!nonce || !sessionId || sessionId.includes(OAUTH_STATE_SEPARATOR)) {
|
|
195
|
+
return void 0;
|
|
196
|
+
}
|
|
197
|
+
return { nonce, sessionId };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// src/server/storage/session-lifecycle.ts
|
|
201
|
+
init_cjs_shims();
|
|
202
|
+
|
|
203
|
+
// src/shared/constants.ts
|
|
204
|
+
init_cjs_shims();
|
|
205
|
+
var STATE_EXPIRATION_MS = 10 * 60 * 1e3;
|
|
206
|
+
var PENDING_SESSION_EXPIRATION_SECONDS = Math.floor(STATE_EXPIRATION_MS / 1e3);
|
|
207
|
+
var DORMANT_SESSION_EXPIRATION_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
208
|
+
var DORMANT_SESSION_EXPIRATION_SECONDS = Math.floor(DORMANT_SESSION_EXPIRATION_MS / 1e3);
|
|
209
|
+
var DEFAULT_HEARTBEAT_INTERVAL_MS = 3e4;
|
|
210
|
+
var REDIS_KEY_PREFIX = "mcp:session:";
|
|
211
|
+
var DEFAULT_CLIENT_NAME = "MCP Assistant";
|
|
212
|
+
var DEFAULT_CLIENT_URI = "https://mcp-assistant.in";
|
|
213
|
+
var DEFAULT_LOGO_URI = "https://mcp-assistant.in/logo.svg";
|
|
214
|
+
var DEFAULT_POLICY_URI = "https://mcp-assistant.in/privacy";
|
|
215
|
+
var SOFTWARE_ID = "@mcp-ts";
|
|
216
|
+
var SOFTWARE_VERSION = "2.3.4";
|
|
217
|
+
var MCP_CLIENT_NAME = "mcp-ts-oauth-client";
|
|
218
|
+
var MCP_CLIENT_VERSION = "2.0";
|
|
219
|
+
|
|
220
|
+
// src/server/storage/session-lifecycle.ts
|
|
221
|
+
function resolveSessionExpiresAt(status = "pending", referenceTime = Date.now()) {
|
|
222
|
+
return status === "active" ? null : referenceTime + STATE_EXPIRATION_MS;
|
|
223
|
+
}
|
|
224
|
+
function resolveSessionRedisTtlSeconds(session) {
|
|
225
|
+
return session.status === "active" ? DORMANT_SESSION_EXPIRATION_SECONDS : Math.floor(STATE_EXPIRATION_MS / 1e3);
|
|
226
|
+
}
|
|
227
|
+
function normalizeNewSession(session, now = Date.now()) {
|
|
228
|
+
const createdAt = session.createdAt || now;
|
|
229
|
+
const updatedAt = session.updatedAt ?? createdAt;
|
|
230
|
+
const status = session.status ?? "pending";
|
|
231
|
+
return {
|
|
232
|
+
...session,
|
|
233
|
+
status,
|
|
234
|
+
createdAt,
|
|
235
|
+
updatedAt,
|
|
236
|
+
expiresAt: resolveSessionExpiresAt(status, status === "active" ? updatedAt : createdAt)
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
function mergeSessionUpdate(current, data, now = Date.now()) {
|
|
240
|
+
const updatedAt = data.updatedAt ?? now;
|
|
241
|
+
const updated = {
|
|
242
|
+
...current,
|
|
243
|
+
...data,
|
|
244
|
+
updatedAt
|
|
245
|
+
};
|
|
246
|
+
const status = updated.status ?? "pending";
|
|
247
|
+
return {
|
|
248
|
+
...updated,
|
|
249
|
+
status,
|
|
250
|
+
expiresAt: resolveSessionExpiresAt(status, updatedAt)
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
function normalizeStoredSession(session) {
|
|
254
|
+
const createdAt = session.createdAt || Date.now();
|
|
255
|
+
const updatedAt = session.updatedAt ?? createdAt;
|
|
256
|
+
const status = session.status ?? "pending";
|
|
257
|
+
const expiresAt = status === "active" ? null : session.expiresAt ?? resolveSessionExpiresAt(status, createdAt);
|
|
258
|
+
return {
|
|
259
|
+
...session,
|
|
260
|
+
status,
|
|
261
|
+
createdAt,
|
|
262
|
+
updatedAt,
|
|
263
|
+
expiresAt
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
function isSessionExpired(session, now = Date.now()) {
|
|
267
|
+
const hydrated = normalizeStoredSession(session);
|
|
268
|
+
if (hydrated.status === "active") {
|
|
269
|
+
return hydrated.updatedAt !== void 0 && hydrated.updatedAt < now - DORMANT_SESSION_EXPIRATION_MS;
|
|
270
|
+
}
|
|
271
|
+
return typeof hydrated.expiresAt === "number" && hydrated.expiresAt < now;
|
|
272
|
+
}
|
|
199
273
|
|
|
200
274
|
// src/server/storage/redis-backend.ts
|
|
201
275
|
var RedisStorageBackend = class {
|
|
202
276
|
constructor(redis2) {
|
|
203
277
|
this.redis = redis2;
|
|
204
|
-
__publicField(this, "DEFAULT_TTL", SESSION_TTL_SECONDS);
|
|
205
278
|
__publicField(this, "KEY_PREFIX", "mcp:session:");
|
|
279
|
+
__publicField(this, "CREDENTIALS_KEY_PREFIX", "mcp:credentials:");
|
|
206
280
|
__publicField(this, "USER_ID_KEY_PREFIX", "mcp:userId:");
|
|
207
281
|
__publicField(this, "USER_ID_KEY_SUFFIX", ":sessions");
|
|
208
282
|
}
|
|
@@ -221,6 +295,9 @@ var RedisStorageBackend = class {
|
|
|
221
295
|
getSessionKey(userId, sessionId) {
|
|
222
296
|
return `${this.KEY_PREFIX}${userId}:${sessionId}`;
|
|
223
297
|
}
|
|
298
|
+
getCredentialsKey(userId, sessionId) {
|
|
299
|
+
return `${this.CREDENTIALS_KEY_PREFIX}${userId}:${sessionId}`;
|
|
300
|
+
}
|
|
224
301
|
/**
|
|
225
302
|
* Generates Redis key for tracking all sessions for a user
|
|
226
303
|
* @private
|
|
@@ -258,15 +335,16 @@ var RedisStorageBackend = class {
|
|
|
258
335
|
generateSessionId() {
|
|
259
336
|
return generateSessionId();
|
|
260
337
|
}
|
|
261
|
-
async create(session
|
|
338
|
+
async create(session) {
|
|
262
339
|
const { sessionId, userId } = session;
|
|
263
340
|
if (!sessionId || !userId) throw new Error("userId and sessionId required");
|
|
264
341
|
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
265
342
|
const userIdKey = this.getUserIdKey(userId);
|
|
266
|
-
const
|
|
343
|
+
const sessionWithLifecycle = normalizeNewSession(session);
|
|
344
|
+
const effectiveTtl = resolveSessionRedisTtlSeconds(sessionWithLifecycle);
|
|
267
345
|
const result = await this.redis.set(
|
|
268
346
|
sessionKey,
|
|
269
|
-
JSON.stringify(
|
|
347
|
+
JSON.stringify(sessionWithLifecycle),
|
|
270
348
|
"EX",
|
|
271
349
|
effectiveTtl,
|
|
272
350
|
"NX"
|
|
@@ -276,9 +354,8 @@ var RedisStorageBackend = class {
|
|
|
276
354
|
}
|
|
277
355
|
await this.redis.sadd(userIdKey, sessionId);
|
|
278
356
|
}
|
|
279
|
-
async update(userId, sessionId, data
|
|
357
|
+
async update(userId, sessionId, data) {
|
|
280
358
|
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
281
|
-
const effectiveTtl = ttl ?? this.DEFAULT_TTL;
|
|
282
359
|
const script = `
|
|
283
360
|
local currentStr = redis.call("GET", KEYS[1])
|
|
284
361
|
if not currentStr then
|
|
@@ -286,26 +363,47 @@ var RedisStorageBackend = class {
|
|
|
286
363
|
end
|
|
287
364
|
|
|
288
365
|
local current = cjson.decode(currentStr)
|
|
289
|
-
local
|
|
290
|
-
|
|
291
|
-
for k,v in pairs(updates) do
|
|
292
|
-
current[k] = v
|
|
293
|
-
end
|
|
366
|
+
local updated = cjson.decode(ARGV[1])
|
|
294
367
|
|
|
295
|
-
redis.call("SET", KEYS[1], cjson.encode(
|
|
368
|
+
redis.call("SET", KEYS[1], cjson.encode(updated), "EX", ARGV[2])
|
|
296
369
|
return 1
|
|
297
370
|
`;
|
|
371
|
+
const current = await this.get(userId, sessionId);
|
|
372
|
+
if (!current) {
|
|
373
|
+
throw new Error(`Session ${sessionId} not found for userId ${userId}`);
|
|
374
|
+
}
|
|
375
|
+
const updated = mergeSessionUpdate(current, data);
|
|
376
|
+
const effectiveTtl = resolveSessionRedisTtlSeconds(updated);
|
|
298
377
|
const result = await this.redis.eval(
|
|
299
378
|
script,
|
|
300
379
|
1,
|
|
301
380
|
sessionKey,
|
|
302
|
-
JSON.stringify(
|
|
381
|
+
JSON.stringify(updated),
|
|
303
382
|
effectiveTtl
|
|
304
383
|
);
|
|
305
384
|
if (result === 0) {
|
|
306
385
|
throw new Error(`Session ${sessionId} not found for userId ${userId}`);
|
|
307
386
|
}
|
|
308
387
|
}
|
|
388
|
+
async patchCredentials(userId, sessionId, data) {
|
|
389
|
+
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
390
|
+
const credentialsKey = this.getCredentialsKey(userId, sessionId);
|
|
391
|
+
const sessionExists = await this.redis.exists(sessionKey);
|
|
392
|
+
if (!sessionExists) {
|
|
393
|
+
throw new Error(`Session ${sessionId} not found for userId ${userId}`);
|
|
394
|
+
}
|
|
395
|
+
const session = await this.get(userId, sessionId);
|
|
396
|
+
const currentTtl = await this.redis.ttl(sessionKey);
|
|
397
|
+
const effectiveTtl = currentTtl > 0 && session ? currentTtl : resolveSessionRedisTtlSeconds(session ?? { status: "pending" });
|
|
398
|
+
const currentStr = await this.redis.get(credentialsKey);
|
|
399
|
+
const current = currentStr ? JSON.parse(currentStr) : { sessionId, userId };
|
|
400
|
+
const credentials = { ...current, ...data, sessionId, userId };
|
|
401
|
+
if (effectiveTtl > 0) {
|
|
402
|
+
await this.redis.set(credentialsKey, JSON.stringify(credentials), "EX", effectiveTtl);
|
|
403
|
+
} else {
|
|
404
|
+
await this.redis.set(credentialsKey, JSON.stringify(credentials));
|
|
405
|
+
}
|
|
406
|
+
}
|
|
309
407
|
async get(userId, sessionId) {
|
|
310
408
|
try {
|
|
311
409
|
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
@@ -313,13 +411,28 @@ var RedisStorageBackend = class {
|
|
|
313
411
|
if (!sessionDataStr) {
|
|
314
412
|
return null;
|
|
315
413
|
}
|
|
316
|
-
const
|
|
317
|
-
return
|
|
414
|
+
const session = JSON.parse(sessionDataStr);
|
|
415
|
+
return normalizeStoredSession(session);
|
|
318
416
|
} catch (error) {
|
|
319
417
|
console.error("[RedisStorageBackend] Failed to get session:", error);
|
|
320
418
|
return null;
|
|
321
419
|
}
|
|
322
420
|
}
|
|
421
|
+
async getCredentials(userId, sessionId) {
|
|
422
|
+
const session = await this.get(userId, sessionId);
|
|
423
|
+
if (!session) return null;
|
|
424
|
+
const credentialsStr = await this.redis.get(this.getCredentialsKey(userId, sessionId));
|
|
425
|
+
return credentialsStr ? JSON.parse(credentialsStr) : { sessionId, userId };
|
|
426
|
+
}
|
|
427
|
+
async clearCredentials(userId, sessionId) {
|
|
428
|
+
await this.patchCredentials(userId, sessionId, {
|
|
429
|
+
clientInformation: null,
|
|
430
|
+
tokens: null,
|
|
431
|
+
codeVerifier: null,
|
|
432
|
+
clientId: null,
|
|
433
|
+
oauthState: null
|
|
434
|
+
});
|
|
435
|
+
}
|
|
323
436
|
async listIds(userId) {
|
|
324
437
|
const sessions2 = await this.list(userId);
|
|
325
438
|
return sessions2.map((session) => session.sessionId);
|
|
@@ -339,7 +452,7 @@ var RedisStorageBackend = class {
|
|
|
339
452
|
if (staleSessionIds.length > 0) {
|
|
340
453
|
await this.redis.srem(userIdKey, ...staleSessionIds);
|
|
341
454
|
}
|
|
342
|
-
return results.filter((session) => session !== null);
|
|
455
|
+
return results.filter((session) => session !== null).map((session) => normalizeStoredSession(session));
|
|
343
456
|
} catch (error) {
|
|
344
457
|
console.error(`[RedisStorageBackend] Failed to get session data for ${userId}:`, error);
|
|
345
458
|
return [];
|
|
@@ -348,9 +461,10 @@ var RedisStorageBackend = class {
|
|
|
348
461
|
async delete(userId, sessionId) {
|
|
349
462
|
try {
|
|
350
463
|
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
464
|
+
const credentialsKey = this.getCredentialsKey(userId, sessionId);
|
|
351
465
|
const userIdKey = this.getUserIdKey(userId);
|
|
352
466
|
await this.redis.srem(userIdKey, sessionId);
|
|
353
|
-
await this.redis.del(sessionKey);
|
|
467
|
+
await this.redis.del(sessionKey, credentialsKey);
|
|
354
468
|
} catch (error) {
|
|
355
469
|
console.error("[RedisStorageBackend] Failed to remove session:", error);
|
|
356
470
|
}
|
|
@@ -365,7 +479,7 @@ var RedisStorageBackend = class {
|
|
|
365
479
|
return null;
|
|
366
480
|
}
|
|
367
481
|
try {
|
|
368
|
-
return JSON.parse(data).sessionId;
|
|
482
|
+
return normalizeStoredSession(JSON.parse(data)).sessionId;
|
|
369
483
|
} catch (error) {
|
|
370
484
|
console.error("[RedisStorageBackend] Failed to parse session while listing all session IDs:", error);
|
|
371
485
|
return null;
|
|
@@ -381,8 +495,9 @@ var RedisStorageBackend = class {
|
|
|
381
495
|
async clearAll() {
|
|
382
496
|
try {
|
|
383
497
|
const keys = await this.scanKeys(`${this.KEY_PREFIX}*`);
|
|
498
|
+
const credentialKeys = await this.scanKeys(`${this.CREDENTIALS_KEY_PREFIX}*`);
|
|
384
499
|
const userIdKeys = await this.scanKeys(`${this.USER_ID_KEY_PREFIX}*${this.USER_ID_KEY_SUFFIX}`);
|
|
385
|
-
const allKeys = [...keys, ...userIdKeys];
|
|
500
|
+
const allKeys = [...keys, ...credentialKeys, ...userIdKeys];
|
|
386
501
|
if (allKeys.length > 0) {
|
|
387
502
|
await this.redis.del(...allKeys);
|
|
388
503
|
}
|
|
@@ -406,6 +521,7 @@ var RedisStorageBackend = class {
|
|
|
406
521
|
const staleSessionIds = sessionIds.filter((_, index) => existenceChecks[index] === 0);
|
|
407
522
|
if (staleSessionIds.length > 0) {
|
|
408
523
|
await this.redis.srem(userIdKey, ...staleSessionIds);
|
|
524
|
+
await this.redis.del(...staleSessionIds.map((sessionId) => this.getCredentialsKey(userId, sessionId)));
|
|
409
525
|
}
|
|
410
526
|
const remainingCount = await this.redis.scard(userIdKey);
|
|
411
527
|
if (remainingCount === 0) {
|
|
@@ -431,6 +547,7 @@ var MemoryStorageBackend = class {
|
|
|
431
547
|
constructor() {
|
|
432
548
|
// Map<userId:sessionId, Session>
|
|
433
549
|
__publicField(this, "sessions", /* @__PURE__ */ new Map());
|
|
550
|
+
__publicField(this, "credentials", /* @__PURE__ */ new Map());
|
|
434
551
|
// Map<userId, Set<sessionId>>
|
|
435
552
|
__publicField(this, "userIdSessions", /* @__PURE__ */ new Map());
|
|
436
553
|
}
|
|
@@ -443,36 +560,55 @@ var MemoryStorageBackend = class {
|
|
|
443
560
|
generateSessionId() {
|
|
444
561
|
return generateSessionId();
|
|
445
562
|
}
|
|
446
|
-
async create(session
|
|
563
|
+
async create(session) {
|
|
447
564
|
const { sessionId, userId } = session;
|
|
448
565
|
if (!sessionId || !userId) throw new Error("userId and sessionId required");
|
|
449
566
|
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
450
567
|
if (this.sessions.has(sessionKey)) {
|
|
451
568
|
throw new Error(`Session ${sessionId} already exists`);
|
|
452
569
|
}
|
|
453
|
-
this.sessions.set(sessionKey, session);
|
|
570
|
+
this.sessions.set(sessionKey, normalizeNewSession(session));
|
|
454
571
|
if (!this.userIdSessions.has(userId)) {
|
|
455
572
|
this.userIdSessions.set(userId, /* @__PURE__ */ new Set());
|
|
456
573
|
}
|
|
457
574
|
this.userIdSessions.get(userId).add(sessionId);
|
|
458
575
|
}
|
|
459
|
-
async update(userId, sessionId, data
|
|
576
|
+
async update(userId, sessionId, data) {
|
|
460
577
|
if (!userId || !sessionId) throw new Error("userId and sessionId required");
|
|
461
578
|
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
462
579
|
const current = this.sessions.get(sessionKey);
|
|
463
580
|
if (!current) {
|
|
464
581
|
throw new Error(`Session ${sessionId} not found`);
|
|
465
582
|
}
|
|
466
|
-
const updated =
|
|
467
|
-
...current,
|
|
468
|
-
...data
|
|
469
|
-
};
|
|
583
|
+
const updated = mergeSessionUpdate(current, data);
|
|
470
584
|
this.sessions.set(sessionKey, updated);
|
|
471
585
|
}
|
|
586
|
+
async patchCredentials(userId, sessionId, data) {
|
|
587
|
+
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
588
|
+
if (!this.sessions.has(sessionKey)) {
|
|
589
|
+
throw new Error(`Session ${sessionId} not found`);
|
|
590
|
+
}
|
|
591
|
+
const current = this.credentials.get(sessionKey) ?? { sessionId, userId };
|
|
592
|
+
this.credentials.set(sessionKey, { ...current, ...data, sessionId, userId });
|
|
593
|
+
}
|
|
472
594
|
async get(userId, sessionId) {
|
|
473
595
|
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
474
596
|
return this.sessions.get(sessionKey) || null;
|
|
475
597
|
}
|
|
598
|
+
async getCredentials(userId, sessionId) {
|
|
599
|
+
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
600
|
+
if (!this.sessions.has(sessionKey)) return null;
|
|
601
|
+
return this.credentials.get(sessionKey) ?? { sessionId, userId };
|
|
602
|
+
}
|
|
603
|
+
async clearCredentials(userId, sessionId) {
|
|
604
|
+
await this.patchCredentials(userId, sessionId, {
|
|
605
|
+
clientInformation: null,
|
|
606
|
+
tokens: null,
|
|
607
|
+
codeVerifier: null,
|
|
608
|
+
clientId: null,
|
|
609
|
+
oauthState: null
|
|
610
|
+
});
|
|
611
|
+
}
|
|
476
612
|
async listIds(userId) {
|
|
477
613
|
const set = this.userIdSessions.get(userId);
|
|
478
614
|
return set ? Array.from(set) : [];
|
|
@@ -492,6 +628,7 @@ var MemoryStorageBackend = class {
|
|
|
492
628
|
async delete(userId, sessionId) {
|
|
493
629
|
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
494
630
|
this.sessions.delete(sessionKey);
|
|
631
|
+
this.credentials.delete(sessionKey);
|
|
495
632
|
const set = this.userIdSessions.get(userId);
|
|
496
633
|
if (set) {
|
|
497
634
|
set.delete(sessionId);
|
|
@@ -505,9 +642,22 @@ var MemoryStorageBackend = class {
|
|
|
505
642
|
}
|
|
506
643
|
async clearAll() {
|
|
507
644
|
this.sessions.clear();
|
|
645
|
+
this.credentials.clear();
|
|
508
646
|
this.userIdSessions.clear();
|
|
509
647
|
}
|
|
510
648
|
async cleanupExpired() {
|
|
649
|
+
for (const [key, session] of this.sessions.entries()) {
|
|
650
|
+
if (!isSessionExpired(session)) continue;
|
|
651
|
+
this.sessions.delete(key);
|
|
652
|
+
this.credentials.delete(key);
|
|
653
|
+
const set = this.userIdSessions.get(session.userId);
|
|
654
|
+
if (set) {
|
|
655
|
+
set.delete(session.sessionId);
|
|
656
|
+
if (set.size === 0) {
|
|
657
|
+
this.userIdSessions.delete(session.userId);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
511
661
|
}
|
|
512
662
|
async disconnect() {
|
|
513
663
|
}
|
|
@@ -522,6 +672,7 @@ var FileStorageBackend = class {
|
|
|
522
672
|
constructor(options = {}) {
|
|
523
673
|
__publicField(this, "filePath");
|
|
524
674
|
__publicField(this, "memoryCache", null);
|
|
675
|
+
__publicField(this, "credentialsCache", null);
|
|
525
676
|
__publicField(this, "initialized", false);
|
|
526
677
|
this.filePath = options.path || "./sessions.json";
|
|
527
678
|
}
|
|
@@ -536,14 +687,22 @@ var FileStorageBackend = class {
|
|
|
536
687
|
const data = await fs2.promises.readFile(this.filePath, "utf-8");
|
|
537
688
|
const json = JSON.parse(data);
|
|
538
689
|
this.memoryCache = /* @__PURE__ */ new Map();
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
690
|
+
this.credentialsCache = /* @__PURE__ */ new Map();
|
|
691
|
+
if (Array.isArray(json.sessions)) {
|
|
692
|
+
json.sessions.forEach((s) => {
|
|
693
|
+
const session = normalizeStoredSession(s);
|
|
694
|
+
this.memoryCache.set(this.getSessionKey(session.userId || "unknown", session.sessionId), session);
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
if (Array.isArray(json.credentials)) {
|
|
698
|
+
json.credentials.forEach((c) => {
|
|
699
|
+
this.credentialsCache.set(this.getSessionKey(c.userId, c.sessionId), c);
|
|
542
700
|
});
|
|
543
701
|
}
|
|
544
702
|
} catch (error) {
|
|
545
703
|
if (error.code === "ENOENT") {
|
|
546
704
|
this.memoryCache = /* @__PURE__ */ new Map();
|
|
705
|
+
this.credentialsCache = /* @__PURE__ */ new Map();
|
|
547
706
|
await this.flush();
|
|
548
707
|
} else {
|
|
549
708
|
console.error("[FileStorage] Failed to load sessions:", error);
|
|
@@ -557,9 +716,11 @@ var FileStorageBackend = class {
|
|
|
557
716
|
if (!this.initialized) await this.init();
|
|
558
717
|
}
|
|
559
718
|
async flush() {
|
|
560
|
-
if (!this.memoryCache) return;
|
|
561
|
-
|
|
562
|
-
|
|
719
|
+
if (!this.memoryCache || !this.credentialsCache) return;
|
|
720
|
+
await fs2.promises.writeFile(this.filePath, JSON.stringify({
|
|
721
|
+
sessions: Array.from(this.memoryCache.values()),
|
|
722
|
+
credentials: Array.from(this.credentialsCache.values())
|
|
723
|
+
}, null, 2), "utf-8");
|
|
563
724
|
}
|
|
564
725
|
getSessionKey(userId, sessionId) {
|
|
565
726
|
return `${userId}:${sessionId}`;
|
|
@@ -567,7 +728,7 @@ var FileStorageBackend = class {
|
|
|
567
728
|
generateSessionId() {
|
|
568
729
|
return generateSessionId();
|
|
569
730
|
}
|
|
570
|
-
async create(session
|
|
731
|
+
async create(session) {
|
|
571
732
|
await this.ensureInitialized();
|
|
572
733
|
const { sessionId, userId } = session;
|
|
573
734
|
if (!sessionId || !userId) throw new Error("userId and sessionId required");
|
|
@@ -575,10 +736,10 @@ var FileStorageBackend = class {
|
|
|
575
736
|
if (this.memoryCache.has(sessionKey)) {
|
|
576
737
|
throw new Error(`Session ${sessionId} already exists`);
|
|
577
738
|
}
|
|
578
|
-
this.memoryCache.set(sessionKey, session);
|
|
739
|
+
this.memoryCache.set(sessionKey, normalizeNewSession(session));
|
|
579
740
|
await this.flush();
|
|
580
741
|
}
|
|
581
|
-
async update(userId, sessionId, data
|
|
742
|
+
async update(userId, sessionId, data) {
|
|
582
743
|
await this.ensureInitialized();
|
|
583
744
|
if (!userId || !sessionId) throw new Error("userId and sessionId required");
|
|
584
745
|
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
@@ -586,18 +747,40 @@ var FileStorageBackend = class {
|
|
|
586
747
|
if (!current) {
|
|
587
748
|
throw new Error(`Session ${sessionId} not found`);
|
|
588
749
|
}
|
|
589
|
-
const updated =
|
|
590
|
-
...current,
|
|
591
|
-
...data
|
|
592
|
-
};
|
|
750
|
+
const updated = mergeSessionUpdate(current, data);
|
|
593
751
|
this.memoryCache.set(sessionKey, updated);
|
|
594
752
|
await this.flush();
|
|
595
753
|
}
|
|
754
|
+
async patchCredentials(userId, sessionId, data) {
|
|
755
|
+
await this.ensureInitialized();
|
|
756
|
+
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
757
|
+
if (!this.memoryCache.has(sessionKey)) {
|
|
758
|
+
throw new Error(`Session ${sessionId} not found`);
|
|
759
|
+
}
|
|
760
|
+
const current = this.credentialsCache.get(sessionKey) ?? { sessionId, userId };
|
|
761
|
+
this.credentialsCache.set(sessionKey, { ...current, ...data, sessionId, userId });
|
|
762
|
+
await this.flush();
|
|
763
|
+
}
|
|
596
764
|
async get(userId, sessionId) {
|
|
597
765
|
await this.ensureInitialized();
|
|
598
766
|
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
599
767
|
return this.memoryCache.get(sessionKey) || null;
|
|
600
768
|
}
|
|
769
|
+
async getCredentials(userId, sessionId) {
|
|
770
|
+
await this.ensureInitialized();
|
|
771
|
+
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
772
|
+
if (!this.memoryCache.has(sessionKey)) return null;
|
|
773
|
+
return this.credentialsCache.get(sessionKey) ?? { sessionId, userId };
|
|
774
|
+
}
|
|
775
|
+
async clearCredentials(userId, sessionId) {
|
|
776
|
+
await this.patchCredentials(userId, sessionId, {
|
|
777
|
+
clientInformation: null,
|
|
778
|
+
tokens: null,
|
|
779
|
+
codeVerifier: null,
|
|
780
|
+
clientId: null,
|
|
781
|
+
oauthState: null
|
|
782
|
+
});
|
|
783
|
+
}
|
|
601
784
|
async list(userId) {
|
|
602
785
|
await this.ensureInitialized();
|
|
603
786
|
return Array.from(this.memoryCache.values()).filter((s) => s.userId === userId);
|
|
@@ -609,7 +792,9 @@ var FileStorageBackend = class {
|
|
|
609
792
|
async delete(userId, sessionId) {
|
|
610
793
|
await this.ensureInitialized();
|
|
611
794
|
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
612
|
-
|
|
795
|
+
const deleted = this.memoryCache.delete(sessionKey);
|
|
796
|
+
this.credentialsCache.delete(sessionKey);
|
|
797
|
+
if (deleted) {
|
|
613
798
|
await this.flush();
|
|
614
799
|
}
|
|
615
800
|
}
|
|
@@ -620,10 +805,21 @@ var FileStorageBackend = class {
|
|
|
620
805
|
async clearAll() {
|
|
621
806
|
await this.ensureInitialized();
|
|
622
807
|
this.memoryCache.clear();
|
|
808
|
+
this.credentialsCache.clear();
|
|
623
809
|
await this.flush();
|
|
624
810
|
}
|
|
625
811
|
async cleanupExpired() {
|
|
626
812
|
await this.ensureInitialized();
|
|
813
|
+
let changed = false;
|
|
814
|
+
for (const [key, session] of this.memoryCache.entries()) {
|
|
815
|
+
if (!isSessionExpired(session)) continue;
|
|
816
|
+
this.memoryCache.delete(key);
|
|
817
|
+
this.credentialsCache.delete(key);
|
|
818
|
+
changed = true;
|
|
819
|
+
}
|
|
820
|
+
if (changed) {
|
|
821
|
+
await this.flush();
|
|
822
|
+
}
|
|
627
823
|
}
|
|
628
824
|
async disconnect() {
|
|
629
825
|
}
|
|
@@ -635,10 +831,12 @@ var SqliteStorage = class {
|
|
|
635
831
|
constructor(options = {}) {
|
|
636
832
|
__publicField(this, "db", null);
|
|
637
833
|
__publicField(this, "table");
|
|
834
|
+
__publicField(this, "credentialsTable");
|
|
638
835
|
__publicField(this, "initialized", false);
|
|
639
836
|
__publicField(this, "dbPath");
|
|
640
837
|
this.dbPath = options.path || "./sessions.db";
|
|
641
838
|
this.table = options.table || "mcp_sessions";
|
|
839
|
+
this.credentialsTable = `${this.table}_credentials`;
|
|
642
840
|
}
|
|
643
841
|
async init() {
|
|
644
842
|
if (this.initialized) return;
|
|
@@ -649,6 +847,7 @@ var SqliteStorage = class {
|
|
|
649
847
|
fs2__namespace.mkdirSync(dir, { recursive: true });
|
|
650
848
|
}
|
|
651
849
|
this.db = new DatabaseConstructor(this.dbPath);
|
|
850
|
+
this.db.pragma("foreign_keys = ON");
|
|
652
851
|
this.db.exec(`
|
|
653
852
|
CREATE TABLE IF NOT EXISTS ${this.table} (
|
|
654
853
|
sessionId TEXT PRIMARY KEY,
|
|
@@ -657,6 +856,13 @@ var SqliteStorage = class {
|
|
|
657
856
|
expiresAt INTEGER
|
|
658
857
|
);
|
|
659
858
|
CREATE INDEX IF NOT EXISTS idx_${this.table}_userId ON ${this.table}(userId);
|
|
859
|
+
CREATE TABLE IF NOT EXISTS ${this.credentialsTable} (
|
|
860
|
+
sessionId TEXT NOT NULL,
|
|
861
|
+
userId TEXT NOT NULL,
|
|
862
|
+
data TEXT NOT NULL,
|
|
863
|
+
PRIMARY KEY (userId, sessionId),
|
|
864
|
+
FOREIGN KEY (sessionId) REFERENCES ${this.table}(sessionId) ON DELETE CASCADE
|
|
865
|
+
);
|
|
660
866
|
`);
|
|
661
867
|
this.initialized = true;
|
|
662
868
|
console.log(`[mcp-ts][Storage] SQLite: \u2713 database at ${this.dbPath} verified.`);
|
|
@@ -677,18 +883,18 @@ var SqliteStorage = class {
|
|
|
677
883
|
generateSessionId() {
|
|
678
884
|
return generateSessionId();
|
|
679
885
|
}
|
|
680
|
-
async create(session
|
|
886
|
+
async create(session) {
|
|
681
887
|
this.ensureInitialized();
|
|
682
888
|
const { sessionId, userId } = session;
|
|
683
889
|
if (!sessionId || !userId) {
|
|
684
890
|
throw new Error("userId and sessionId required");
|
|
685
891
|
}
|
|
686
|
-
const
|
|
892
|
+
const sessionWithLifecycle = normalizeNewSession(session);
|
|
687
893
|
try {
|
|
688
894
|
const stmt = this.db.prepare(
|
|
689
895
|
`INSERT INTO ${this.table} (sessionId, userId, data, expiresAt) VALUES (?, ?, ?, ?)`
|
|
690
896
|
);
|
|
691
|
-
stmt.run(sessionId, userId, JSON.stringify(
|
|
897
|
+
stmt.run(sessionId, userId, JSON.stringify(sessionWithLifecycle), sessionWithLifecycle.expiresAt ?? null);
|
|
692
898
|
} catch (error) {
|
|
693
899
|
if (error.code === "SQLITE_CONSTRAINT_PRIMARYKEY") {
|
|
694
900
|
throw new Error(`Session ${sessionId} already exists`);
|
|
@@ -696,7 +902,7 @@ var SqliteStorage = class {
|
|
|
696
902
|
throw error;
|
|
697
903
|
}
|
|
698
904
|
}
|
|
699
|
-
async update(userId, sessionId, data
|
|
905
|
+
async update(userId, sessionId, data) {
|
|
700
906
|
this.ensureInitialized();
|
|
701
907
|
if (!sessionId || !userId) {
|
|
702
908
|
throw new Error("userId and sessionId required");
|
|
@@ -705,12 +911,25 @@ var SqliteStorage = class {
|
|
|
705
911
|
if (!currentSession) {
|
|
706
912
|
throw new Error(`Session ${sessionId} not found for userId ${userId}`);
|
|
707
913
|
}
|
|
708
|
-
const updatedSession =
|
|
709
|
-
const expiresAt = ttl ? Date.now() + ttl * 1e3 : null;
|
|
914
|
+
const updatedSession = mergeSessionUpdate(currentSession, data);
|
|
710
915
|
const stmt = this.db.prepare(
|
|
711
916
|
`UPDATE ${this.table} SET data = ?, expiresAt = ? WHERE sessionId = ? AND userId = ?`
|
|
712
917
|
);
|
|
713
|
-
stmt.run(JSON.stringify(updatedSession), expiresAt, sessionId, userId);
|
|
918
|
+
stmt.run(JSON.stringify(updatedSession), updatedSession.expiresAt ?? null, sessionId, userId);
|
|
919
|
+
}
|
|
920
|
+
async patchCredentials(userId, sessionId, data) {
|
|
921
|
+
this.ensureInitialized();
|
|
922
|
+
if (!await this.get(userId, sessionId)) {
|
|
923
|
+
throw new Error(`Session ${sessionId} not found for userId ${userId}`);
|
|
924
|
+
}
|
|
925
|
+
const current = await this.getCredentials(userId, sessionId) ?? { sessionId, userId };
|
|
926
|
+
const credentials = { ...current, ...data, sessionId, userId };
|
|
927
|
+
const stmt = this.db.prepare(
|
|
928
|
+
`INSERT INTO ${this.credentialsTable} (sessionId, userId, data)
|
|
929
|
+
VALUES (?, ?, ?)
|
|
930
|
+
ON CONFLICT(userId, sessionId) DO UPDATE SET data = excluded.data`
|
|
931
|
+
);
|
|
932
|
+
stmt.run(sessionId, userId, JSON.stringify(credentials));
|
|
714
933
|
}
|
|
715
934
|
async get(userId, sessionId) {
|
|
716
935
|
this.ensureInitialized();
|
|
@@ -719,7 +938,25 @@ var SqliteStorage = class {
|
|
|
719
938
|
);
|
|
720
939
|
const row = stmt.get(sessionId, userId);
|
|
721
940
|
if (!row) return null;
|
|
722
|
-
return JSON.parse(row.data);
|
|
941
|
+
return normalizeStoredSession(JSON.parse(row.data));
|
|
942
|
+
}
|
|
943
|
+
async getCredentials(userId, sessionId) {
|
|
944
|
+
this.ensureInitialized();
|
|
945
|
+
if (!await this.get(userId, sessionId)) return null;
|
|
946
|
+
const stmt = this.db.prepare(
|
|
947
|
+
`SELECT data FROM ${this.credentialsTable} WHERE sessionId = ? AND userId = ?`
|
|
948
|
+
);
|
|
949
|
+
const row = stmt.get(sessionId, userId);
|
|
950
|
+
return row ? JSON.parse(row.data) : { sessionId, userId };
|
|
951
|
+
}
|
|
952
|
+
async clearCredentials(userId, sessionId) {
|
|
953
|
+
await this.patchCredentials(userId, sessionId, {
|
|
954
|
+
clientInformation: null,
|
|
955
|
+
tokens: null,
|
|
956
|
+
codeVerifier: null,
|
|
957
|
+
clientId: null,
|
|
958
|
+
oauthState: null
|
|
959
|
+
});
|
|
723
960
|
}
|
|
724
961
|
async list(userId) {
|
|
725
962
|
this.ensureInitialized();
|
|
@@ -727,7 +964,7 @@ var SqliteStorage = class {
|
|
|
727
964
|
`SELECT data FROM ${this.table} WHERE userId = ?`
|
|
728
965
|
);
|
|
729
966
|
const rows = stmt.all(userId);
|
|
730
|
-
return rows.map((row) => JSON.parse(row.data));
|
|
967
|
+
return rows.map((row) => normalizeStoredSession(JSON.parse(row.data)));
|
|
731
968
|
}
|
|
732
969
|
async listIds(userId) {
|
|
733
970
|
this.ensureInitialized();
|
|
@@ -742,6 +979,9 @@ var SqliteStorage = class {
|
|
|
742
979
|
const stmt = this.db.prepare(
|
|
743
980
|
`DELETE FROM ${this.table} WHERE sessionId = ? AND userId = ?`
|
|
744
981
|
);
|
|
982
|
+
this.db.prepare(
|
|
983
|
+
`DELETE FROM ${this.credentialsTable} WHERE sessionId = ? AND userId = ?`
|
|
984
|
+
).run(sessionId, userId);
|
|
745
985
|
stmt.run(sessionId, userId);
|
|
746
986
|
}
|
|
747
987
|
async listAllIds() {
|
|
@@ -752,16 +992,28 @@ var SqliteStorage = class {
|
|
|
752
992
|
}
|
|
753
993
|
async clearAll() {
|
|
754
994
|
this.ensureInitialized();
|
|
995
|
+
this.db.prepare(`DELETE FROM ${this.credentialsTable}`).run();
|
|
755
996
|
const stmt = this.db.prepare(`DELETE FROM ${this.table}`);
|
|
756
997
|
stmt.run();
|
|
757
998
|
}
|
|
758
999
|
async cleanupExpired() {
|
|
759
1000
|
this.ensureInitialized();
|
|
760
|
-
const
|
|
761
|
-
const
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
1001
|
+
const rows = this.db.prepare(`SELECT sessionId, userId, data FROM ${this.table}`).all();
|
|
1002
|
+
const deleteStmt = this.db.prepare(`DELETE FROM ${this.table} WHERE sessionId = ? AND userId = ?`);
|
|
1003
|
+
for (const row of rows) {
|
|
1004
|
+
const session = normalizeStoredSession(JSON.parse(row.data));
|
|
1005
|
+
if (isSessionExpired(session)) {
|
|
1006
|
+
deleteStmt.run(row.sessionId, row.userId);
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
this.db.prepare(
|
|
1010
|
+
`DELETE FROM ${this.credentialsTable}
|
|
1011
|
+
WHERE NOT EXISTS (
|
|
1012
|
+
SELECT 1 FROM ${this.table}
|
|
1013
|
+
WHERE ${this.table}.sessionId = ${this.credentialsTable}.sessionId
|
|
1014
|
+
AND ${this.table}.userId = ${this.credentialsTable}.userId
|
|
1015
|
+
)`
|
|
1016
|
+
).run();
|
|
765
1017
|
}
|
|
766
1018
|
async disconnect() {
|
|
767
1019
|
if (this.db) {
|
|
@@ -846,19 +1098,21 @@ function decryptObject(data) {
|
|
|
846
1098
|
var SupabaseStorageBackend = class {
|
|
847
1099
|
constructor(supabase) {
|
|
848
1100
|
this.supabase = supabase;
|
|
849
|
-
__publicField(this, "DEFAULT_TTL", SESSION_TTL_SECONDS);
|
|
850
1101
|
}
|
|
851
1102
|
async init() {
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
1103
|
+
await this.assertTable("mcp_sessions", "session_id");
|
|
1104
|
+
await this.assertTable("mcp_credentials", "session_id");
|
|
1105
|
+
console.log("[mcp-ts][Storage] Supabase: storage tables verified.");
|
|
1106
|
+
}
|
|
1107
|
+
async assertTable(table, column) {
|
|
1108
|
+
const { error } = await this.supabase.from(table).select(column).limit(0);
|
|
1109
|
+
if (!error) return;
|
|
1110
|
+
if (error.code === "42P01") {
|
|
1111
|
+
throw new Error(
|
|
1112
|
+
`[SupabaseStorage] Table "${table}" not found in your database. Please run "npx mcp-ts supabase-init" to set up the required storage schema.`
|
|
1113
|
+
);
|
|
860
1114
|
}
|
|
861
|
-
|
|
1115
|
+
throw new Error(`[SupabaseStorage] Initialization check failed for "${table}": ${error.message}`);
|
|
862
1116
|
}
|
|
863
1117
|
generateSessionId() {
|
|
864
1118
|
return generateSessionId();
|
|
@@ -872,37 +1126,49 @@ var SupabaseStorageBackend = class {
|
|
|
872
1126
|
transportType: row.transport_type,
|
|
873
1127
|
callbackUrl: row.callback_url,
|
|
874
1128
|
createdAt: new Date(row.created_at).getTime(),
|
|
1129
|
+
updatedAt: new Date(row.updated_at ?? row.created_at).getTime(),
|
|
1130
|
+
expiresAt: row.expires_at ? new Date(row.expires_at).getTime() : null,
|
|
875
1131
|
userId: row.user_id,
|
|
876
1132
|
headers: decryptObject(row.headers),
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
tokens: decryptObject(row.tokens),
|
|
880
|
-
codeVerifier: row.code_verifier,
|
|
881
|
-
clientId: row.client_id
|
|
1133
|
+
authUrl: row.auth_url,
|
|
1134
|
+
status: row.status ?? "pending"
|
|
882
1135
|
};
|
|
883
1136
|
}
|
|
884
|
-
|
|
1137
|
+
mapRowToCredentials(row, userId, sessionId) {
|
|
1138
|
+
return {
|
|
1139
|
+
sessionId,
|
|
1140
|
+
userId,
|
|
1141
|
+
clientInformation: decryptObject(row?.client_information),
|
|
1142
|
+
tokens: decryptObject(row?.tokens),
|
|
1143
|
+
codeVerifier: decryptObject(row?.code_verifier),
|
|
1144
|
+
clientId: row?.client_id,
|
|
1145
|
+
oauthState: row?.oauth_state
|
|
1146
|
+
};
|
|
1147
|
+
}
|
|
1148
|
+
hasCredentialData(data) {
|
|
1149
|
+
return "clientInformation" in data || "tokens" in data || "codeVerifier" in data || "clientId" in data || "oauthState" in data;
|
|
1150
|
+
}
|
|
1151
|
+
async create(session) {
|
|
885
1152
|
const { sessionId, userId } = session;
|
|
886
1153
|
if (!sessionId || !userId) throw new Error("userId and sessionId required");
|
|
887
|
-
const
|
|
888
|
-
const
|
|
1154
|
+
const status = session.status ?? "pending";
|
|
1155
|
+
const createdAt = new Date(session.createdAt || Date.now()).toISOString();
|
|
1156
|
+
const updatedAt = new Date(session.updatedAt ?? session.createdAt ?? Date.now()).toISOString();
|
|
1157
|
+
const expiresAt = resolveSessionExpiresAt(status, new Date(createdAt).getTime());
|
|
889
1158
|
const { error } = await this.supabase.from("mcp_sessions").insert({
|
|
890
1159
|
session_id: sessionId,
|
|
891
1160
|
user_id: userId,
|
|
892
|
-
// Maps user_id to userId to support RLS using auth.uid()
|
|
893
1161
|
server_id: session.serverId,
|
|
894
1162
|
server_name: session.serverName,
|
|
895
1163
|
server_url: session.serverUrl,
|
|
896
1164
|
transport_type: session.transportType,
|
|
897
1165
|
callback_url: session.callbackUrl,
|
|
898
|
-
created_at:
|
|
1166
|
+
created_at: createdAt,
|
|
1167
|
+
updated_at: updatedAt,
|
|
899
1168
|
headers: encryptObject(session.headers),
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
code_verifier: session.codeVerifier,
|
|
904
|
-
client_id: session.clientId,
|
|
905
|
-
expires_at: expiresAt
|
|
1169
|
+
auth_url: session.authUrl ?? null,
|
|
1170
|
+
status,
|
|
1171
|
+
expires_at: expiresAt === null ? null : new Date(expiresAt).toISOString()
|
|
906
1172
|
});
|
|
907
1173
|
if (error) {
|
|
908
1174
|
if (error.code === "23505") {
|
|
@@ -911,11 +1177,8 @@ var SupabaseStorageBackend = class {
|
|
|
911
1177
|
throw new Error(`Failed to create session in Supabase: ${error.message}`);
|
|
912
1178
|
}
|
|
913
1179
|
}
|
|
914
|
-
async update(userId, sessionId, data
|
|
915
|
-
const effectiveTtl = ttl ?? this.DEFAULT_TTL;
|
|
916
|
-
const expiresAt = new Date(Date.now() + effectiveTtl * 1e3).toISOString();
|
|
1180
|
+
async update(userId, sessionId, data) {
|
|
917
1181
|
const updateData = {
|
|
918
|
-
expires_at: expiresAt,
|
|
919
1182
|
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
920
1183
|
};
|
|
921
1184
|
if ("serverId" in data) updateData.server_id = data.serverId;
|
|
@@ -923,20 +1186,50 @@ var SupabaseStorageBackend = class {
|
|
|
923
1186
|
if ("serverUrl" in data) updateData.server_url = data.serverUrl;
|
|
924
1187
|
if ("transportType" in data) updateData.transport_type = data.transportType;
|
|
925
1188
|
if ("callbackUrl" in data) updateData.callback_url = data.callbackUrl;
|
|
926
|
-
if ("
|
|
1189
|
+
if ("status" in data) {
|
|
1190
|
+
const status = data.status ?? "pending";
|
|
1191
|
+
const expiresAt = resolveSessionExpiresAt(status);
|
|
1192
|
+
updateData.status = status;
|
|
1193
|
+
updateData.expires_at = expiresAt === null ? null : new Date(expiresAt).toISOString();
|
|
1194
|
+
}
|
|
927
1195
|
if ("headers" in data) updateData.headers = encryptObject(data.headers);
|
|
928
|
-
if ("
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
if (
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
1196
|
+
if ("authUrl" in data) updateData.auth_url = data.authUrl ?? null;
|
|
1197
|
+
const shouldUpdateSession = Object.keys(updateData).some((key) => key !== "updated_at");
|
|
1198
|
+
let updatedRows = null;
|
|
1199
|
+
if (shouldUpdateSession) {
|
|
1200
|
+
const result = await this.supabase.from("mcp_sessions").update(updateData).eq("user_id", userId).eq("session_id", sessionId).select("id");
|
|
1201
|
+
if (result.error) {
|
|
1202
|
+
throw new Error(`Failed to update session: ${result.error.message}`);
|
|
1203
|
+
}
|
|
1204
|
+
updatedRows = result.data;
|
|
1205
|
+
} else {
|
|
1206
|
+
const result = await this.supabase.from("mcp_sessions").select("id").eq("user_id", userId).eq("session_id", sessionId);
|
|
1207
|
+
if (result.error) {
|
|
1208
|
+
throw new Error(`Failed to update session: ${result.error.message}`);
|
|
1209
|
+
}
|
|
1210
|
+
updatedRows = result.data;
|
|
935
1211
|
}
|
|
936
1212
|
if (!updatedRows || updatedRows.length === 0) {
|
|
937
1213
|
throw new Error(`Session ${sessionId} not found for userId ${userId}`);
|
|
938
1214
|
}
|
|
939
1215
|
}
|
|
1216
|
+
async patchCredentials(userId, sessionId, data) {
|
|
1217
|
+
if (!this.hasCredentialData(data)) return;
|
|
1218
|
+
const row = {
|
|
1219
|
+
user_id: userId,
|
|
1220
|
+
session_id: sessionId,
|
|
1221
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1222
|
+
};
|
|
1223
|
+
if ("clientInformation" in data) row.client_information = data.clientInformation == null ? null : encryptObject(data.clientInformation);
|
|
1224
|
+
if ("tokens" in data) row.tokens = data.tokens == null ? null : encryptObject(data.tokens);
|
|
1225
|
+
if ("codeVerifier" in data) row.code_verifier = data.codeVerifier == null ? null : encryptObject(data.codeVerifier);
|
|
1226
|
+
if ("clientId" in data) row.client_id = data.clientId ?? null;
|
|
1227
|
+
if ("oauthState" in data) row.oauth_state = data.oauthState ?? null;
|
|
1228
|
+
const { error } = await this.supabase.from("mcp_credentials").upsert(row, { onConflict: "user_id,session_id" });
|
|
1229
|
+
if (error) {
|
|
1230
|
+
throw new Error(`Failed to update credentials: ${error.message}`);
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
940
1233
|
async get(userId, sessionId) {
|
|
941
1234
|
const { data, error } = await this.supabase.from("mcp_sessions").select("*").eq("user_id", userId).eq("session_id", sessionId).maybeSingle();
|
|
942
1235
|
if (error) {
|
|
@@ -946,6 +1239,21 @@ var SupabaseStorageBackend = class {
|
|
|
946
1239
|
if (!data) return null;
|
|
947
1240
|
return this.mapRowToSessionData(data);
|
|
948
1241
|
}
|
|
1242
|
+
async getCredentials(userId, sessionId) {
|
|
1243
|
+
const { data, error } = await this.supabase.from("mcp_credentials").select("*").eq("user_id", userId).eq("session_id", sessionId).maybeSingle();
|
|
1244
|
+
if (error) {
|
|
1245
|
+
console.error("[SupabaseStorage] Failed to get credentials:", error);
|
|
1246
|
+
return null;
|
|
1247
|
+
}
|
|
1248
|
+
if (data) {
|
|
1249
|
+
return this.mapRowToCredentials(data, userId, sessionId);
|
|
1250
|
+
}
|
|
1251
|
+
const { data: sessionRows, error: sessionError } = await this.supabase.from("mcp_sessions").select("id").eq("user_id", userId).eq("session_id", sessionId);
|
|
1252
|
+
if (sessionError || !sessionRows || sessionRows.length === 0) {
|
|
1253
|
+
return null;
|
|
1254
|
+
}
|
|
1255
|
+
return { sessionId, userId };
|
|
1256
|
+
}
|
|
949
1257
|
async list(userId) {
|
|
950
1258
|
const { data, error } = await this.supabase.from("mcp_sessions").select("*").eq("user_id", userId);
|
|
951
1259
|
if (error) {
|
|
@@ -954,6 +1262,12 @@ var SupabaseStorageBackend = class {
|
|
|
954
1262
|
}
|
|
955
1263
|
return data.map((row) => this.mapRowToSessionData(row));
|
|
956
1264
|
}
|
|
1265
|
+
async clearCredentials(userId, sessionId) {
|
|
1266
|
+
const { error } = await this.supabase.from("mcp_credentials").delete().eq("user_id", userId).eq("session_id", sessionId);
|
|
1267
|
+
if (error) {
|
|
1268
|
+
throw new Error(`Failed to clear credentials: ${error.message}`);
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
957
1271
|
async delete(userId, sessionId) {
|
|
958
1272
|
const { error } = await this.supabase.from("mcp_sessions").delete().eq("user_id", userId).eq("session_id", sessionId);
|
|
959
1273
|
if (error) {
|
|
@@ -977,15 +1291,24 @@ var SupabaseStorageBackend = class {
|
|
|
977
1291
|
return data.map((row) => row.session_id);
|
|
978
1292
|
}
|
|
979
1293
|
async clearAll() {
|
|
1294
|
+
const { error: credentialsError } = await this.supabase.from("mcp_credentials").delete().neq("session_id", "");
|
|
1295
|
+
if (credentialsError) {
|
|
1296
|
+
console.error("[SupabaseStorage] Failed to clear credentials:", credentialsError);
|
|
1297
|
+
}
|
|
980
1298
|
const { error } = await this.supabase.from("mcp_sessions").delete().neq("session_id", "");
|
|
981
1299
|
if (error) {
|
|
982
1300
|
console.error("[SupabaseStorage] Failed to clear sessions:", error);
|
|
983
1301
|
}
|
|
984
1302
|
}
|
|
985
1303
|
async cleanupExpired() {
|
|
986
|
-
const { error } = await this.supabase.from("mcp_sessions").delete().lt("expires_at", (/* @__PURE__ */ new Date()).toISOString());
|
|
987
|
-
if (
|
|
988
|
-
console.error("[SupabaseStorage] Failed to cleanup expired sessions:",
|
|
1304
|
+
const { error: transientError } = await this.supabase.from("mcp_sessions").delete().not("expires_at", "is", null).neq("status", "active").lt("expires_at", (/* @__PURE__ */ new Date()).toISOString());
|
|
1305
|
+
if (transientError) {
|
|
1306
|
+
console.error("[SupabaseStorage] Failed to cleanup expired inactive sessions:", transientError);
|
|
1307
|
+
}
|
|
1308
|
+
const dormantCutoff = new Date(Date.now() - DORMANT_SESSION_EXPIRATION_MS).toISOString();
|
|
1309
|
+
const { error: dormantError } = await this.supabase.from("mcp_sessions").delete().eq("status", "active").lt("updated_at", dormantCutoff);
|
|
1310
|
+
if (dormantError) {
|
|
1311
|
+
console.error("[SupabaseStorage] Failed to cleanup dormant active sessions:", dormantError);
|
|
989
1312
|
}
|
|
990
1313
|
}
|
|
991
1314
|
async disconnect() {
|
|
@@ -997,23 +1320,29 @@ init_cjs_shims();
|
|
|
997
1320
|
var NeonStorageBackend = class {
|
|
998
1321
|
constructor(sql, options = {}) {
|
|
999
1322
|
this.sql = sql;
|
|
1000
|
-
__publicField(this, "DEFAULT_TTL", SESSION_TTL_SECONDS);
|
|
1001
1323
|
__publicField(this, "tableName");
|
|
1324
|
+
__publicField(this, "credentialsTableName");
|
|
1002
1325
|
const schema = options.schema || "public";
|
|
1003
1326
|
const table = options.table || "mcp_sessions";
|
|
1327
|
+
const credentialsTable = options.credentialsTable || "mcp_credentials";
|
|
1004
1328
|
this.tableName = `${this.quoteIdentifier(schema)}.${this.quoteIdentifier(table)}`;
|
|
1329
|
+
this.credentialsTableName = `${this.quoteIdentifier(schema)}.${this.quoteIdentifier(credentialsTable)}`;
|
|
1005
1330
|
}
|
|
1006
1331
|
async init() {
|
|
1332
|
+
await this.assertTable(this.tableName, "mcp_sessions");
|
|
1333
|
+
await this.assertTable(this.credentialsTableName, "mcp_credentials");
|
|
1334
|
+
console.log("[mcp-ts][Storage] Neon: storage tables verified.");
|
|
1335
|
+
}
|
|
1336
|
+
async assertTable(qualifiedName, displayName) {
|
|
1007
1337
|
const [{ exists } = { exists: null }] = await this.sql.query(
|
|
1008
1338
|
"SELECT to_regclass($1) AS exists",
|
|
1009
|
-
[
|
|
1339
|
+
[qualifiedName.replace(/"/g, "")]
|
|
1010
1340
|
);
|
|
1011
1341
|
if (!exists) {
|
|
1012
1342
|
throw new Error(
|
|
1013
|
-
|
|
1343
|
+
`[NeonStorage] Table "${displayName}" not found in your database. Please create it using the Neon storage guide in docs/storage-backends/neon.md.`
|
|
1014
1344
|
);
|
|
1015
1345
|
}
|
|
1016
|
-
console.log('[mcp-ts][Storage] Neon: "mcp_sessions" table verified.');
|
|
1017
1346
|
}
|
|
1018
1347
|
generateSessionId() {
|
|
1019
1348
|
return generateSessionId();
|
|
@@ -1033,20 +1362,35 @@ var NeonStorageBackend = class {
|
|
|
1033
1362
|
transportType: row.transport_type,
|
|
1034
1363
|
callbackUrl: row.callback_url,
|
|
1035
1364
|
createdAt: new Date(row.created_at).getTime(),
|
|
1365
|
+
updatedAt: new Date(row.updated_at ?? row.created_at).getTime(),
|
|
1366
|
+
expiresAt: row.expires_at ? new Date(row.expires_at).getTime() : null,
|
|
1036
1367
|
userId: row.user_id,
|
|
1037
1368
|
headers: decryptObject(row.headers),
|
|
1038
|
-
|
|
1039
|
-
|
|
1369
|
+
authUrl: row.auth_url ?? void 0,
|
|
1370
|
+
status: row.status ?? "pending"
|
|
1371
|
+
};
|
|
1372
|
+
}
|
|
1373
|
+
mapRowToCredentials(row, userId, sessionId) {
|
|
1374
|
+
return {
|
|
1375
|
+
sessionId,
|
|
1376
|
+
userId,
|
|
1377
|
+
clientInformation: decryptObject(row.client_information),
|
|
1040
1378
|
tokens: decryptObject(row.tokens),
|
|
1041
|
-
codeVerifier: row.code_verifier
|
|
1042
|
-
clientId: row.client_id ?? void 0
|
|
1379
|
+
codeVerifier: decryptObject(row.code_verifier),
|
|
1380
|
+
clientId: row.client_id ?? void 0,
|
|
1381
|
+
oauthState: row.oauth_state
|
|
1043
1382
|
};
|
|
1044
1383
|
}
|
|
1045
|
-
|
|
1384
|
+
hasCredentialData(data) {
|
|
1385
|
+
return "clientInformation" in data || "tokens" in data || "codeVerifier" in data || "clientId" in data || "oauthState" in data;
|
|
1386
|
+
}
|
|
1387
|
+
async create(session) {
|
|
1046
1388
|
const { sessionId, userId } = session;
|
|
1047
1389
|
if (!sessionId || !userId) throw new Error("userId and sessionId required");
|
|
1048
|
-
const
|
|
1049
|
-
const
|
|
1390
|
+
const status = session.status ?? "pending";
|
|
1391
|
+
const createdAt = new Date(session.createdAt || Date.now()).toISOString();
|
|
1392
|
+
const updatedAt = new Date(session.updatedAt ?? session.createdAt ?? Date.now()).toISOString();
|
|
1393
|
+
const expiresAt = resolveSessionExpiresAt(status, new Date(createdAt).getTime());
|
|
1050
1394
|
try {
|
|
1051
1395
|
await this.sql.query(
|
|
1052
1396
|
`INSERT INTO ${this.tableName} (
|
|
@@ -1058,16 +1402,14 @@ var NeonStorageBackend = class {
|
|
|
1058
1402
|
transport_type,
|
|
1059
1403
|
callback_url,
|
|
1060
1404
|
created_at,
|
|
1405
|
+
updated_at,
|
|
1061
1406
|
headers,
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
tokens,
|
|
1065
|
-
code_verifier,
|
|
1066
|
-
client_id,
|
|
1407
|
+
auth_url,
|
|
1408
|
+
status,
|
|
1067
1409
|
expires_at
|
|
1068
1410
|
) VALUES (
|
|
1069
1411
|
$1, $2, $3, $4, $5, $6, $7, $8,
|
|
1070
|
-
$9, $10, $11, $12, $13
|
|
1412
|
+
$9, $10, $11, $12, $13
|
|
1071
1413
|
)`,
|
|
1072
1414
|
[
|
|
1073
1415
|
sessionId,
|
|
@@ -1077,14 +1419,12 @@ var NeonStorageBackend = class {
|
|
|
1077
1419
|
session.serverUrl,
|
|
1078
1420
|
session.transportType,
|
|
1079
1421
|
session.callbackUrl,
|
|
1080
|
-
|
|
1422
|
+
createdAt,
|
|
1423
|
+
updatedAt,
|
|
1081
1424
|
encryptObject(session.headers),
|
|
1082
|
-
session.
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
session.codeVerifier,
|
|
1086
|
-
session.clientId,
|
|
1087
|
-
expiresAt
|
|
1425
|
+
session.authUrl ?? null,
|
|
1426
|
+
status,
|
|
1427
|
+
expiresAt === null ? null : new Date(expiresAt).toISOString()
|
|
1088
1428
|
]
|
|
1089
1429
|
);
|
|
1090
1430
|
} catch (error) {
|
|
@@ -1094,52 +1434,86 @@ var NeonStorageBackend = class {
|
|
|
1094
1434
|
throw new Error(`Failed to create session in Neon: ${error.message}`);
|
|
1095
1435
|
}
|
|
1096
1436
|
}
|
|
1097
|
-
async update(userId, sessionId, data
|
|
1437
|
+
async update(userId, sessionId, data) {
|
|
1098
1438
|
const currentSession = await this.get(userId, sessionId);
|
|
1099
1439
|
if (!currentSession) {
|
|
1100
1440
|
throw new Error(`Session ${sessionId} not found for userId ${userId}`);
|
|
1101
1441
|
}
|
|
1102
1442
|
const updatedSession = { ...currentSession, ...data };
|
|
1103
|
-
const
|
|
1104
|
-
const expiresAt =
|
|
1105
|
-
const
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1443
|
+
const status = updatedSession.status ?? "pending";
|
|
1444
|
+
const expiresAt = resolveSessionExpiresAt(status);
|
|
1445
|
+
const shouldUpdateSession = "serverId" in data || "serverName" in data || "serverUrl" in data || "transportType" in data || "callbackUrl" in data || "status" in data || "headers" in data || "authUrl" in data;
|
|
1446
|
+
if (shouldUpdateSession) {
|
|
1447
|
+
const updatedRows = await this.sql.query(
|
|
1448
|
+
`UPDATE ${this.tableName}
|
|
1449
|
+
SET
|
|
1450
|
+
server_id = $1,
|
|
1451
|
+
server_name = $2,
|
|
1452
|
+
server_url = $3,
|
|
1453
|
+
transport_type = $4,
|
|
1454
|
+
callback_url = $5,
|
|
1455
|
+
status = $6,
|
|
1456
|
+
headers = $7,
|
|
1457
|
+
auth_url = $8,
|
|
1458
|
+
expires_at = $9,
|
|
1459
|
+
updated_at = now()
|
|
1460
|
+
WHERE user_id = $10 AND session_id = $11
|
|
1461
|
+
RETURNING id`,
|
|
1462
|
+
[
|
|
1463
|
+
updatedSession.serverId,
|
|
1464
|
+
updatedSession.serverName,
|
|
1465
|
+
updatedSession.serverUrl,
|
|
1466
|
+
updatedSession.transportType,
|
|
1467
|
+
updatedSession.callbackUrl,
|
|
1468
|
+
status,
|
|
1469
|
+
encryptObject(updatedSession.headers),
|
|
1470
|
+
updatedSession.authUrl ?? null,
|
|
1471
|
+
expiresAt === null ? null : new Date(expiresAt).toISOString(),
|
|
1472
|
+
userId,
|
|
1473
|
+
sessionId
|
|
1474
|
+
]
|
|
1475
|
+
);
|
|
1476
|
+
if (updatedRows.length === 0) {
|
|
1477
|
+
throw new Error(`Session ${sessionId} not found for userId ${userId}`);
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
async patchCredentials(userId, sessionId, data) {
|
|
1482
|
+
if (!this.hasCredentialData(data)) return;
|
|
1483
|
+
await this.sql.query(
|
|
1484
|
+
`INSERT INTO ${this.credentialsTableName} (
|
|
1485
|
+
user_id,
|
|
1486
|
+
session_id,
|
|
1487
|
+
client_information,
|
|
1488
|
+
tokens,
|
|
1489
|
+
code_verifier,
|
|
1490
|
+
client_id,
|
|
1491
|
+
oauth_state,
|
|
1492
|
+
updated_at
|
|
1493
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7, now())
|
|
1494
|
+
ON CONFLICT (user_id, session_id)
|
|
1495
|
+
DO UPDATE SET
|
|
1496
|
+
client_information = CASE WHEN $8 THEN EXCLUDED.client_information ELSE ${this.credentialsTableName}.client_information END,
|
|
1497
|
+
tokens = CASE WHEN $9 THEN EXCLUDED.tokens ELSE ${this.credentialsTableName}.tokens END,
|
|
1498
|
+
code_verifier = CASE WHEN $10 THEN EXCLUDED.code_verifier ELSE ${this.credentialsTableName}.code_verifier END,
|
|
1499
|
+
client_id = CASE WHEN $11 THEN EXCLUDED.client_id ELSE ${this.credentialsTableName}.client_id END,
|
|
1500
|
+
oauth_state = CASE WHEN $12 THEN EXCLUDED.oauth_state ELSE ${this.credentialsTableName}.oauth_state END,
|
|
1501
|
+
updated_at = now()`,
|
|
1123
1502
|
[
|
|
1124
|
-
updatedSession.serverId,
|
|
1125
|
-
updatedSession.serverName,
|
|
1126
|
-
updatedSession.serverUrl,
|
|
1127
|
-
updatedSession.transportType,
|
|
1128
|
-
updatedSession.callbackUrl,
|
|
1129
|
-
updatedSession.active ?? false,
|
|
1130
|
-
encryptObject(updatedSession.headers),
|
|
1131
|
-
updatedSession.clientInformation,
|
|
1132
|
-
encryptObject(updatedSession.tokens),
|
|
1133
|
-
updatedSession.codeVerifier,
|
|
1134
|
-
updatedSession.clientId,
|
|
1135
|
-
expiresAt,
|
|
1136
1503
|
userId,
|
|
1137
|
-
sessionId
|
|
1504
|
+
sessionId,
|
|
1505
|
+
"clientInformation" in data ? data.clientInformation == null ? null : encryptObject(data.clientInformation) : null,
|
|
1506
|
+
"tokens" in data ? data.tokens == null ? null : encryptObject(data.tokens) : null,
|
|
1507
|
+
"codeVerifier" in data ? data.codeVerifier == null ? null : encryptObject(data.codeVerifier) : null,
|
|
1508
|
+
"clientId" in data ? data.clientId ?? null : null,
|
|
1509
|
+
"oauthState" in data ? data.oauthState ?? null : null,
|
|
1510
|
+
"clientInformation" in data,
|
|
1511
|
+
"tokens" in data,
|
|
1512
|
+
"codeVerifier" in data,
|
|
1513
|
+
"clientId" in data,
|
|
1514
|
+
"oauthState" in data
|
|
1138
1515
|
]
|
|
1139
1516
|
);
|
|
1140
|
-
if (updatedRows.length === 0) {
|
|
1141
|
-
throw new Error(`Session ${sessionId} not found for userId ${userId}`);
|
|
1142
|
-
}
|
|
1143
1517
|
}
|
|
1144
1518
|
async get(userId, sessionId) {
|
|
1145
1519
|
try {
|
|
@@ -1147,12 +1521,32 @@ var NeonStorageBackend = class {
|
|
|
1147
1521
|
`SELECT * FROM ${this.tableName} WHERE user_id = $1 AND session_id = $2`,
|
|
1148
1522
|
[userId, sessionId]
|
|
1149
1523
|
);
|
|
1150
|
-
|
|
1524
|
+
if (!rows[0]) return null;
|
|
1525
|
+
return this.mapRowToSessionData(rows[0]);
|
|
1151
1526
|
} catch (error) {
|
|
1152
1527
|
console.error("[NeonStorage] Failed to get session:", error);
|
|
1153
1528
|
return null;
|
|
1154
1529
|
}
|
|
1155
1530
|
}
|
|
1531
|
+
async getCredentials(userId, sessionId) {
|
|
1532
|
+
try {
|
|
1533
|
+
const credentialRows = await this.sql.query(
|
|
1534
|
+
`SELECT * FROM ${this.credentialsTableName} WHERE user_id = $1 AND session_id = $2`,
|
|
1535
|
+
[userId, sessionId]
|
|
1536
|
+
);
|
|
1537
|
+
if (credentialRows[0]) {
|
|
1538
|
+
return this.mapRowToCredentials(credentialRows[0], userId, sessionId);
|
|
1539
|
+
}
|
|
1540
|
+
const sessionRows = await this.sql.query(
|
|
1541
|
+
`SELECT id FROM ${this.tableName} WHERE user_id = $1 AND session_id = $2`,
|
|
1542
|
+
[userId, sessionId]
|
|
1543
|
+
);
|
|
1544
|
+
return sessionRows[0] ? { sessionId, userId } : null;
|
|
1545
|
+
} catch (error) {
|
|
1546
|
+
console.error("[NeonStorage] Failed to get credentials:", error);
|
|
1547
|
+
return null;
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1156
1550
|
async list(userId) {
|
|
1157
1551
|
try {
|
|
1158
1552
|
const rows = await this.sql.query(
|
|
@@ -1165,6 +1559,16 @@ var NeonStorageBackend = class {
|
|
|
1165
1559
|
return [];
|
|
1166
1560
|
}
|
|
1167
1561
|
}
|
|
1562
|
+
async clearCredentials(userId, sessionId) {
|
|
1563
|
+
try {
|
|
1564
|
+
await this.sql.query(
|
|
1565
|
+
`DELETE FROM ${this.credentialsTableName} WHERE user_id = $1 AND session_id = $2`,
|
|
1566
|
+
[userId, sessionId]
|
|
1567
|
+
);
|
|
1568
|
+
} catch (error) {
|
|
1569
|
+
console.error("[NeonStorage] Failed to clear credentials:", error);
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1168
1572
|
async delete(userId, sessionId) {
|
|
1169
1573
|
try {
|
|
1170
1574
|
await this.sql.query(
|
|
@@ -1200,6 +1604,7 @@ var NeonStorageBackend = class {
|
|
|
1200
1604
|
}
|
|
1201
1605
|
async clearAll() {
|
|
1202
1606
|
try {
|
|
1607
|
+
await this.sql.query(`DELETE FROM ${this.credentialsTableName}`);
|
|
1203
1608
|
await this.sql.query(`DELETE FROM ${this.tableName}`);
|
|
1204
1609
|
} catch (error) {
|
|
1205
1610
|
console.error("[NeonStorage] Failed to clear sessions:", error);
|
|
@@ -1208,9 +1613,17 @@ var NeonStorageBackend = class {
|
|
|
1208
1613
|
async cleanupExpired() {
|
|
1209
1614
|
try {
|
|
1210
1615
|
await this.sql.query(
|
|
1211
|
-
`DELETE FROM ${this.tableName}
|
|
1616
|
+
`DELETE FROM ${this.tableName}
|
|
1617
|
+
WHERE expires_at IS NOT NULL
|
|
1618
|
+
AND expires_at < $1
|
|
1619
|
+
AND status <> 'active'`,
|
|
1212
1620
|
[(/* @__PURE__ */ new Date()).toISOString()]
|
|
1213
1621
|
);
|
|
1622
|
+
await this.sql.query(
|
|
1623
|
+
`DELETE FROM ${this.tableName}
|
|
1624
|
+
WHERE status = 'active' AND updated_at < $1`,
|
|
1625
|
+
[new Date(Date.now() - DORMANT_SESSION_EXPIRATION_MS).toISOString()]
|
|
1626
|
+
);
|
|
1214
1627
|
} catch (error) {
|
|
1215
1628
|
console.error("[NeonStorage] Failed to cleanup expired sessions:", error);
|
|
1216
1629
|
}
|
|
@@ -1260,26 +1673,24 @@ function emitSessionMutation(event) {
|
|
|
1260
1673
|
function createSessionMutationEvent(prop, args) {
|
|
1261
1674
|
const timestamp = Date.now();
|
|
1262
1675
|
if (prop === "create") {
|
|
1263
|
-
const [session
|
|
1676
|
+
const [session] = args;
|
|
1264
1677
|
if (!session?.userId || !session?.sessionId) return null;
|
|
1265
1678
|
return {
|
|
1266
1679
|
type: "create",
|
|
1267
1680
|
userId: session.userId,
|
|
1268
1681
|
sessionId: session.sessionId,
|
|
1269
1682
|
session,
|
|
1270
|
-
ttl,
|
|
1271
1683
|
timestamp
|
|
1272
1684
|
};
|
|
1273
1685
|
}
|
|
1274
1686
|
if (prop === "update") {
|
|
1275
|
-
const [userId, sessionId, patch
|
|
1687
|
+
const [userId, sessionId, patch] = args;
|
|
1276
1688
|
if (!userId || !sessionId) return null;
|
|
1277
1689
|
return {
|
|
1278
1690
|
type: "update",
|
|
1279
1691
|
userId,
|
|
1280
1692
|
sessionId,
|
|
1281
1693
|
patch,
|
|
1282
|
-
ttl,
|
|
1283
1694
|
timestamp
|
|
1284
1695
|
};
|
|
1285
1696
|
}
|
|
@@ -1475,7 +1886,6 @@ var StorageOAuthClientProvider = class {
|
|
|
1475
1886
|
__publicField(this, "_authUrl");
|
|
1476
1887
|
__publicField(this, "_clientId");
|
|
1477
1888
|
__publicField(this, "onRedirectCallback");
|
|
1478
|
-
__publicField(this, "tokenExpiresAt");
|
|
1479
1889
|
this.userId = options.userId;
|
|
1480
1890
|
this.serverId = options.serverId;
|
|
1481
1891
|
this.sessionId = options.sessionId;
|
|
@@ -1509,30 +1919,30 @@ var StorageOAuthClientProvider = class {
|
|
|
1509
1919
|
this._clientId = clientId_;
|
|
1510
1920
|
}
|
|
1511
1921
|
/**
|
|
1512
|
-
* Loads OAuth
|
|
1922
|
+
* Loads OAuth credentials from the session store
|
|
1513
1923
|
* @private
|
|
1514
1924
|
*/
|
|
1515
|
-
async
|
|
1516
|
-
const data = await sessions.
|
|
1925
|
+
async getCredentials() {
|
|
1926
|
+
const data = await sessions.getCredentials(this.userId, this.sessionId);
|
|
1517
1927
|
if (!data) {
|
|
1518
|
-
return {};
|
|
1928
|
+
return { userId: this.userId, sessionId: this.sessionId };
|
|
1519
1929
|
}
|
|
1520
1930
|
return data;
|
|
1521
1931
|
}
|
|
1522
1932
|
/**
|
|
1523
|
-
* Saves OAuth
|
|
1524
|
-
* @param data - Partial OAuth
|
|
1933
|
+
* Saves OAuth credentials to the session store
|
|
1934
|
+
* @param data - Partial OAuth credentials to save
|
|
1525
1935
|
* @private
|
|
1526
1936
|
* @throws Error if session doesn't exist (session must be created by controller layer)
|
|
1527
1937
|
*/
|
|
1528
|
-
async
|
|
1529
|
-
await sessions.
|
|
1938
|
+
async patchCredentials(data) {
|
|
1939
|
+
await sessions.patchCredentials(this.userId, this.sessionId, data);
|
|
1530
1940
|
}
|
|
1531
1941
|
/**
|
|
1532
1942
|
* Retrieves stored OAuth client information
|
|
1533
1943
|
*/
|
|
1534
1944
|
async clientInformation() {
|
|
1535
|
-
const data = await this.
|
|
1945
|
+
const data = await this.getCredentials();
|
|
1536
1946
|
if (data.clientId && !this._clientId) {
|
|
1537
1947
|
this._clientId = data.clientId;
|
|
1538
1948
|
}
|
|
@@ -1551,7 +1961,7 @@ var StorageOAuthClientProvider = class {
|
|
|
1551
1961
|
* Stores OAuth client information
|
|
1552
1962
|
*/
|
|
1553
1963
|
async saveClientInformation(clientInformation) {
|
|
1554
|
-
await this.
|
|
1964
|
+
await this.patchCredentials({
|
|
1555
1965
|
clientInformation,
|
|
1556
1966
|
clientId: clientInformation.client_id
|
|
1557
1967
|
});
|
|
@@ -1561,29 +1971,58 @@ var StorageOAuthClientProvider = class {
|
|
|
1561
1971
|
* Stores OAuth tokens
|
|
1562
1972
|
*/
|
|
1563
1973
|
async saveTokens(tokens) {
|
|
1564
|
-
|
|
1565
|
-
if (tokens.expires_in) {
|
|
1566
|
-
this.tokenExpiresAt = Date.now() + tokens.expires_in * 1e3 - TOKEN_EXPIRY_BUFFER_MS;
|
|
1567
|
-
}
|
|
1568
|
-
await this.saveSessionData(data);
|
|
1974
|
+
await this.patchCredentials({ tokens });
|
|
1569
1975
|
}
|
|
1570
1976
|
get authUrl() {
|
|
1571
1977
|
return this._authUrl;
|
|
1572
1978
|
}
|
|
1573
1979
|
async state() {
|
|
1574
|
-
|
|
1980
|
+
const nonce = nanoid.nanoid(32);
|
|
1981
|
+
await this.patchCredentials({
|
|
1982
|
+
oauthState: {
|
|
1983
|
+
nonce,
|
|
1984
|
+
sessionId: this.sessionId,
|
|
1985
|
+
serverId: this.serverId,
|
|
1986
|
+
createdAt: Date.now()
|
|
1987
|
+
},
|
|
1988
|
+
codeVerifier: null
|
|
1989
|
+
});
|
|
1990
|
+
return formatOAuthState(nonce, this.sessionId);
|
|
1575
1991
|
}
|
|
1576
|
-
async checkState(
|
|
1577
|
-
const
|
|
1992
|
+
async checkState(state) {
|
|
1993
|
+
const parsed = parseOAuthState(state);
|
|
1994
|
+
if (!parsed) {
|
|
1995
|
+
return { valid: false, error: "Invalid OAuth state" };
|
|
1996
|
+
}
|
|
1997
|
+
if (parsed.sessionId !== this.sessionId) {
|
|
1998
|
+
return { valid: false, error: "OAuth state mismatch" };
|
|
1999
|
+
}
|
|
2000
|
+
const data = await sessions.getCredentials(this.userId, parsed.sessionId);
|
|
1578
2001
|
if (!data) {
|
|
1579
2002
|
return { valid: false, error: "Session not found" };
|
|
1580
2003
|
}
|
|
1581
|
-
|
|
2004
|
+
const oauthState = data.oauthState;
|
|
2005
|
+
if (!oauthState) {
|
|
2006
|
+
return { valid: false, error: "OAuth state not found" };
|
|
2007
|
+
}
|
|
2008
|
+
if (oauthState.nonce !== parsed.nonce || oauthState.sessionId !== parsed.sessionId || oauthState.serverId !== this.serverId) {
|
|
2009
|
+
return { valid: false, error: "OAuth state mismatch" };
|
|
2010
|
+
}
|
|
2011
|
+
if (Date.now() - oauthState.createdAt > STATE_EXPIRATION_MS) {
|
|
2012
|
+
return { valid: false, error: "OAuth state expired" };
|
|
2013
|
+
}
|
|
2014
|
+
return { valid: true, serverId: oauthState.serverId };
|
|
1582
2015
|
}
|
|
1583
|
-
async consumeState(
|
|
2016
|
+
async consumeState(state) {
|
|
2017
|
+
const result = await this.checkState(state);
|
|
2018
|
+
if (!result.valid) {
|
|
2019
|
+
throw new Error(result.error || "Invalid OAuth state");
|
|
2020
|
+
}
|
|
2021
|
+
await this.patchCredentials({ oauthState: null });
|
|
1584
2022
|
}
|
|
1585
2023
|
async redirectToAuthorization(authUrl) {
|
|
1586
2024
|
this._authUrl = authUrl.toString();
|
|
2025
|
+
await sessions.update(this.userId, this.sessionId, { authUrl: this._authUrl });
|
|
1587
2026
|
if (this.onRedirectCallback) {
|
|
1588
2027
|
this.onRedirectCallback(authUrl.toString());
|
|
1589
2028
|
}
|
|
@@ -1594,21 +2033,25 @@ var StorageOAuthClientProvider = class {
|
|
|
1594
2033
|
} else {
|
|
1595
2034
|
const updates = {};
|
|
1596
2035
|
if (scope === "client") {
|
|
1597
|
-
updates.clientInformation =
|
|
1598
|
-
updates.clientId =
|
|
2036
|
+
updates.clientInformation = null;
|
|
2037
|
+
updates.clientId = null;
|
|
1599
2038
|
} else if (scope === "tokens") {
|
|
1600
|
-
updates.tokens =
|
|
2039
|
+
updates.tokens = null;
|
|
1601
2040
|
} else if (scope === "verifier") {
|
|
1602
|
-
updates.codeVerifier =
|
|
2041
|
+
updates.codeVerifier = null;
|
|
1603
2042
|
}
|
|
1604
|
-
await this.
|
|
2043
|
+
await this.patchCredentials(updates);
|
|
1605
2044
|
}
|
|
1606
2045
|
}
|
|
1607
2046
|
async saveCodeVerifier(verifier) {
|
|
1608
|
-
await this.
|
|
2047
|
+
const data = await this.getCredentials();
|
|
2048
|
+
if (data.codeVerifier) {
|
|
2049
|
+
return;
|
|
2050
|
+
}
|
|
2051
|
+
await this.patchCredentials({ codeVerifier: verifier });
|
|
1609
2052
|
}
|
|
1610
2053
|
async codeVerifier() {
|
|
1611
|
-
const data = await this.
|
|
2054
|
+
const data = await this.getCredentials();
|
|
1612
2055
|
if (data.clientId && !this._clientId) {
|
|
1613
2056
|
this._clientId = data.clientId;
|
|
1614
2057
|
}
|
|
@@ -1618,23 +2061,14 @@ var StorageOAuthClientProvider = class {
|
|
|
1618
2061
|
return data.codeVerifier;
|
|
1619
2062
|
}
|
|
1620
2063
|
async deleteCodeVerifier() {
|
|
1621
|
-
await this.
|
|
2064
|
+
await this.patchCredentials({ codeVerifier: null });
|
|
1622
2065
|
}
|
|
1623
2066
|
async tokens() {
|
|
1624
|
-
const data = await this.
|
|
2067
|
+
const data = await this.getCredentials();
|
|
1625
2068
|
if (data.clientId && !this._clientId) {
|
|
1626
2069
|
this._clientId = data.clientId;
|
|
1627
2070
|
}
|
|
1628
|
-
return data.tokens;
|
|
1629
|
-
}
|
|
1630
|
-
isTokenExpired() {
|
|
1631
|
-
if (!this.tokenExpiresAt) {
|
|
1632
|
-
return false;
|
|
1633
|
-
}
|
|
1634
|
-
return Date.now() >= this.tokenExpiresAt;
|
|
1635
|
-
}
|
|
1636
|
-
setTokenExpiresAt(expiresAt) {
|
|
1637
|
-
this.tokenExpiresAt = expiresAt;
|
|
2071
|
+
return data.tokens ?? void 0;
|
|
1638
2072
|
}
|
|
1639
2073
|
};
|
|
1640
2074
|
|
|
@@ -1783,14 +2217,7 @@ var RpcErrorCodes = {
|
|
|
1783
2217
|
};
|
|
1784
2218
|
|
|
1785
2219
|
// src/server/mcp/oauth-client.ts
|
|
1786
|
-
|
|
1787
|
-
if (!(error instanceof Error)) {
|
|
1788
|
-
return false;
|
|
1789
|
-
}
|
|
1790
|
-
const text = `${error.name} ${error.message}`.toLowerCase();
|
|
1791
|
-
return text.includes("invalidgrant") || text.includes("invalid_grant") || text.includes("invalid refresh token") || /refresh\s+token\s+(?:is\s+)?(?:invalid|expired|revoked)/i.test(text);
|
|
1792
|
-
}
|
|
1793
|
-
var MCPClient = class _MCPClient {
|
|
2220
|
+
var MCPClient = class {
|
|
1794
2221
|
/**
|
|
1795
2222
|
* Creates a new MCP client instance
|
|
1796
2223
|
* Can be initialized with minimal options (userId + sessionId) for session restoration
|
|
@@ -1965,17 +2392,18 @@ var MCPClient = class _MCPClient {
|
|
|
1965
2392
|
}
|
|
1966
2393
|
this.emitStateChange("INITIALIZING");
|
|
1967
2394
|
this.emitProgress("Loading session configuration...");
|
|
2395
|
+
let existingSession = null;
|
|
1968
2396
|
if (!this.serverUrl || !this.callbackUrl || !this.serverId) {
|
|
1969
|
-
|
|
1970
|
-
if (!
|
|
2397
|
+
existingSession = await sessions.get(this.userId, this.sessionId);
|
|
2398
|
+
if (!existingSession) {
|
|
1971
2399
|
throw new Error(`Session not found: ${this.sessionId}`);
|
|
1972
2400
|
}
|
|
1973
|
-
this.serverUrl = this.serverUrl ||
|
|
1974
|
-
this.callbackUrl = this.callbackUrl ||
|
|
1975
|
-
this.serverName = this.serverName ||
|
|
1976
|
-
this.serverId = this.serverId ||
|
|
1977
|
-
this.headers = this.headers ||
|
|
1978
|
-
this.createdAt =
|
|
2401
|
+
this.serverUrl = this.serverUrl || existingSession.serverUrl;
|
|
2402
|
+
this.callbackUrl = this.callbackUrl || existingSession.callbackUrl;
|
|
2403
|
+
this.serverName = this.serverName || existingSession.serverName;
|
|
2404
|
+
this.serverId = this.serverId || existingSession.serverId || "unknown";
|
|
2405
|
+
this.headers = this.headers || existingSession.headers;
|
|
2406
|
+
this.createdAt = existingSession.createdAt;
|
|
1979
2407
|
}
|
|
1980
2408
|
if (!this.serverUrl || !this.callbackUrl || !this.serverId) {
|
|
1981
2409
|
throw new Error("Missing required connection metadata");
|
|
@@ -2019,10 +2447,13 @@ var MCPClient = class _MCPClient {
|
|
|
2019
2447
|
}
|
|
2020
2448
|
);
|
|
2021
2449
|
}
|
|
2022
|
-
|
|
2450
|
+
if (existingSession === null) {
|
|
2451
|
+
existingSession = await sessions.get(this.userId, this.sessionId);
|
|
2452
|
+
}
|
|
2023
2453
|
if (!existingSession && this.serverId && this.serverUrl && this.callbackUrl) {
|
|
2024
2454
|
this.createdAt = Date.now();
|
|
2025
|
-
|
|
2455
|
+
const updatedAt = this.createdAt;
|
|
2456
|
+
console.log(`[MCPClient] Creating pending session ${this.sessionId} for connection setup`);
|
|
2026
2457
|
await sessions.create({
|
|
2027
2458
|
sessionId: this.sessionId,
|
|
2028
2459
|
userId: this.userId,
|
|
@@ -2033,18 +2464,18 @@ var MCPClient = class _MCPClient {
|
|
|
2033
2464
|
transportType: this.transportType || "streamable-http",
|
|
2034
2465
|
headers: this.headers,
|
|
2035
2466
|
createdAt: this.createdAt,
|
|
2036
|
-
|
|
2037
|
-
|
|
2467
|
+
updatedAt,
|
|
2468
|
+
status: "pending"
|
|
2469
|
+
});
|
|
2038
2470
|
}
|
|
2039
2471
|
}
|
|
2040
2472
|
/**
|
|
2041
2473
|
* Saves current session state to the session store
|
|
2042
2474
|
* Creates new session if it doesn't exist, updates if it does
|
|
2043
|
-
* @param
|
|
2044
|
-
* @param active - Session status marker used to avoid unnecessary TTL rewrites
|
|
2475
|
+
* @param status - Session lifecycle status used by storage cleanup
|
|
2045
2476
|
* @private
|
|
2046
2477
|
*/
|
|
2047
|
-
async saveSession(
|
|
2478
|
+
async saveSession(status = "active", existingSession) {
|
|
2048
2479
|
if (!this.sessionId || !this.serverId || !this.serverUrl || !this.callbackUrl) {
|
|
2049
2480
|
return;
|
|
2050
2481
|
}
|
|
@@ -2058,13 +2489,29 @@ var MCPClient = class _MCPClient {
|
|
|
2058
2489
|
transportType: this.transportType || "streamable-http",
|
|
2059
2490
|
headers: this.headers,
|
|
2060
2491
|
createdAt: this.createdAt || Date.now(),
|
|
2061
|
-
|
|
2492
|
+
updatedAt: Date.now(),
|
|
2493
|
+
status
|
|
2062
2494
|
};
|
|
2063
|
-
|
|
2495
|
+
if (status === "active") {
|
|
2496
|
+
sessionData.authUrl = null;
|
|
2497
|
+
}
|
|
2498
|
+
if (existingSession === void 0) {
|
|
2499
|
+
existingSession = await sessions.get(this.userId, this.sessionId);
|
|
2500
|
+
}
|
|
2064
2501
|
if (existingSession) {
|
|
2065
|
-
await sessions.update(this.userId, this.sessionId, sessionData
|
|
2502
|
+
await sessions.update(this.userId, this.sessionId, sessionData);
|
|
2066
2503
|
} else {
|
|
2067
|
-
await sessions.create(sessionData
|
|
2504
|
+
await sessions.create(sessionData);
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
/**
|
|
2508
|
+
* Removes transient setup/auth sessions without masking the original error.
|
|
2509
|
+
* @private
|
|
2510
|
+
*/
|
|
2511
|
+
async deleteTransientSession() {
|
|
2512
|
+
try {
|
|
2513
|
+
await sessions.delete(this.userId, this.sessionId);
|
|
2514
|
+
} catch {
|
|
2068
2515
|
}
|
|
2069
2516
|
}
|
|
2070
2517
|
/**
|
|
@@ -2124,15 +2571,13 @@ var MCPClient = class _MCPClient {
|
|
|
2124
2571
|
throw new Error(error);
|
|
2125
2572
|
}
|
|
2126
2573
|
try {
|
|
2127
|
-
this.emitProgress("Validating OAuth tokens...");
|
|
2128
|
-
await this.getValidTokens();
|
|
2129
2574
|
this.emitStateChange("CONNECTING");
|
|
2130
2575
|
const { transportType } = await this.tryConnect();
|
|
2131
2576
|
this.transportType = transportType;
|
|
2132
2577
|
this.emitStateChange("CONNECTED");
|
|
2133
2578
|
this.emitProgress("Connected successfully");
|
|
2134
|
-
console.log(`[MCPClient] Saving session ${this.sessionId}
|
|
2135
|
-
await this.saveSession(
|
|
2579
|
+
console.log(`[MCPClient] Saving active session ${this.sessionId} (connect success)`);
|
|
2580
|
+
await this.saveSession("active");
|
|
2136
2581
|
} catch (error) {
|
|
2137
2582
|
if (error instanceof auth_js.UnauthorizedError || error instanceof Error && error.message.toLowerCase().includes("unauthorized")) {
|
|
2138
2583
|
let authUrl = "";
|
|
@@ -2144,15 +2589,12 @@ var MCPClient = class _MCPClient {
|
|
|
2144
2589
|
const message = detail.toLowerCase() === "unauthorized" ? "OAuth authorization URL not available" : `OAuth authorization URL not available: ${detail}`;
|
|
2145
2590
|
this.emitError(message, "auth");
|
|
2146
2591
|
this.emitStateChange("FAILED");
|
|
2147
|
-
|
|
2148
|
-
await sessions.delete(this.userId, this.sessionId);
|
|
2149
|
-
} catch {
|
|
2150
|
-
}
|
|
2592
|
+
await this.deleteTransientSession();
|
|
2151
2593
|
throw new Error(message);
|
|
2152
2594
|
}
|
|
2153
2595
|
this.emitStateChange("AUTHENTICATING");
|
|
2154
|
-
console.log(`[MCPClient] Saving session ${this.sessionId}
|
|
2155
|
-
await this.saveSession(
|
|
2596
|
+
console.log(`[MCPClient] Saving pending OAuth session ${this.sessionId}`);
|
|
2597
|
+
await this.saveSession("pending");
|
|
2156
2598
|
if (this.serverId) {
|
|
2157
2599
|
this._onConnectionEvent.fire({
|
|
2158
2600
|
type: "auth_required",
|
|
@@ -2172,7 +2614,7 @@ var MCPClient = class _MCPClient {
|
|
|
2172
2614
|
this.emitStateChange("FAILED");
|
|
2173
2615
|
try {
|
|
2174
2616
|
const existingSession = await sessions.get(this.userId, this.sessionId);
|
|
2175
|
-
if (!existingSession || existingSession.
|
|
2617
|
+
if (!existingSession || existingSession.status !== "active") {
|
|
2176
2618
|
await sessions.delete(this.userId, this.sessionId);
|
|
2177
2619
|
}
|
|
2178
2620
|
} catch {
|
|
@@ -2187,7 +2629,7 @@ var MCPClient = class _MCPClient {
|
|
|
2187
2629
|
* @param authCode - Authorization code received from OAuth callback
|
|
2188
2630
|
*/
|
|
2189
2631
|
// TODO: needs to be optimized
|
|
2190
|
-
async finishAuth(authCode) {
|
|
2632
|
+
async finishAuth(authCode, state) {
|
|
2191
2633
|
this.emitStateChange("AUTHENTICATING");
|
|
2192
2634
|
this.emitProgress("Exchanging authorization code for tokens...");
|
|
2193
2635
|
await this.initialize();
|
|
@@ -2197,6 +2639,16 @@ var MCPClient = class _MCPClient {
|
|
|
2197
2639
|
this.emitStateChange("FAILED");
|
|
2198
2640
|
throw new Error(error);
|
|
2199
2641
|
}
|
|
2642
|
+
if (state) {
|
|
2643
|
+
const stateCheck = await this.oauthProvider.checkState(state);
|
|
2644
|
+
if (!stateCheck.valid) {
|
|
2645
|
+
const error = stateCheck.error || "Invalid OAuth state";
|
|
2646
|
+
this.emitError(error, "auth");
|
|
2647
|
+
this.emitStateChange("FAILED");
|
|
2648
|
+
throw new Error(error);
|
|
2649
|
+
}
|
|
2650
|
+
await this.oauthProvider.consumeState(state);
|
|
2651
|
+
}
|
|
2200
2652
|
const transportsToTry = this.transportType ? [this.transportType] : ["streamable-http", "sse"];
|
|
2201
2653
|
let lastError;
|
|
2202
2654
|
let tokensExchanged = false;
|
|
@@ -2236,8 +2688,8 @@ var MCPClient = class _MCPClient {
|
|
|
2236
2688
|
await this.client.connect(this.transport);
|
|
2237
2689
|
this.transportType = currentType;
|
|
2238
2690
|
this.emitStateChange("CONNECTED");
|
|
2239
|
-
console.log(`[MCPClient]
|
|
2240
|
-
await this.saveSession(
|
|
2691
|
+
console.log(`[MCPClient] Saving active session ${this.sessionId} (OAuth complete)`);
|
|
2692
|
+
await this.saveSession("active");
|
|
2241
2693
|
return;
|
|
2242
2694
|
} catch (error) {
|
|
2243
2695
|
lastError = error;
|
|
@@ -2250,12 +2702,14 @@ var MCPClient = class _MCPClient {
|
|
|
2250
2702
|
const msg = error instanceof Error ? error.message : "Authentication failed";
|
|
2251
2703
|
this.emitError(msg, "auth");
|
|
2252
2704
|
this.emitStateChange("FAILED");
|
|
2705
|
+
await this.deleteTransientSession();
|
|
2253
2706
|
throw error;
|
|
2254
2707
|
}
|
|
2255
2708
|
if (isLastAttempt) {
|
|
2256
2709
|
const msg = error instanceof Error ? error.message : "Authentication failed";
|
|
2257
2710
|
this.emitError(msg, "auth");
|
|
2258
2711
|
this.emitStateChange("FAILED");
|
|
2712
|
+
await this.deleteTransientSession();
|
|
2259
2713
|
throw error;
|
|
2260
2714
|
}
|
|
2261
2715
|
this.emitProgress(`Auth attempt with ${currentType} failed: ${errorMessage}. Retrying...`);
|
|
@@ -2265,6 +2719,7 @@ var MCPClient = class _MCPClient {
|
|
|
2265
2719
|
const errorMessage = lastError instanceof Error ? lastError.message : "Authentication failed";
|
|
2266
2720
|
this.emitError(errorMessage, "auth");
|
|
2267
2721
|
this.emitStateChange("FAILED");
|
|
2722
|
+
await this.deleteTransientSession();
|
|
2268
2723
|
throw lastError;
|
|
2269
2724
|
}
|
|
2270
2725
|
}
|
|
@@ -2450,67 +2905,6 @@ var MCPClient = class _MCPClient {
|
|
|
2450
2905
|
};
|
|
2451
2906
|
return await this.client.request(request, types_js.ReadResourceResultSchema);
|
|
2452
2907
|
}
|
|
2453
|
-
/**
|
|
2454
|
-
* Refreshes the OAuth access token using the refresh token
|
|
2455
|
-
* Discovers OAuth metadata from server and exchanges refresh token for new access token
|
|
2456
|
-
* @returns True if refresh was successful, false otherwise
|
|
2457
|
-
*/
|
|
2458
|
-
async refreshToken() {
|
|
2459
|
-
await this.initialize();
|
|
2460
|
-
if (!this.oauthProvider) {
|
|
2461
|
-
return false;
|
|
2462
|
-
}
|
|
2463
|
-
const tokens = await this.oauthProvider.tokens();
|
|
2464
|
-
if (!tokens || !tokens.refresh_token) {
|
|
2465
|
-
return false;
|
|
2466
|
-
}
|
|
2467
|
-
const clientInformation = await this.oauthProvider.clientInformation();
|
|
2468
|
-
if (!clientInformation) {
|
|
2469
|
-
return false;
|
|
2470
|
-
}
|
|
2471
|
-
try {
|
|
2472
|
-
const resourceMetadata = await auth_js.discoverOAuthProtectedResourceMetadata(this.serverUrl);
|
|
2473
|
-
const authServerUrl = resourceMetadata?.authorization_servers?.[0] || this.serverUrl;
|
|
2474
|
-
const authMetadata = await auth_js.discoverAuthorizationServerMetadata(authServerUrl);
|
|
2475
|
-
const newTokens = await auth_js.refreshAuthorization(authServerUrl, {
|
|
2476
|
-
metadata: authMetadata,
|
|
2477
|
-
clientInformation,
|
|
2478
|
-
refreshToken: tokens.refresh_token
|
|
2479
|
-
});
|
|
2480
|
-
await this.oauthProvider.saveTokens(newTokens);
|
|
2481
|
-
return true;
|
|
2482
|
-
} catch (error) {
|
|
2483
|
-
console.error("[OAuth] Token refresh failed:", error);
|
|
2484
|
-
if (isInvalidRefreshTokenError(error)) {
|
|
2485
|
-
try {
|
|
2486
|
-
await this.oauthProvider.invalidateCredentials?.("tokens");
|
|
2487
|
-
this.emitProgress("OAuth refresh token is invalid; requesting reauthorization...");
|
|
2488
|
-
} catch (invalidateError) {
|
|
2489
|
-
console.warn("[OAuth] Failed to invalidate stale refresh token credentials:", invalidateError);
|
|
2490
|
-
}
|
|
2491
|
-
}
|
|
2492
|
-
return false;
|
|
2493
|
-
}
|
|
2494
|
-
}
|
|
2495
|
-
/**
|
|
2496
|
-
* Ensures OAuth tokens are valid, refreshing them if expired
|
|
2497
|
-
* Called automatically by connect() - rarely needs to be called manually
|
|
2498
|
-
* @returns True if valid tokens are available, false otherwise
|
|
2499
|
-
*/
|
|
2500
|
-
async getValidTokens() {
|
|
2501
|
-
await this.initialize();
|
|
2502
|
-
if (!this.oauthProvider) {
|
|
2503
|
-
return false;
|
|
2504
|
-
}
|
|
2505
|
-
const tokens = await this.oauthProvider.tokens();
|
|
2506
|
-
if (!tokens) {
|
|
2507
|
-
return false;
|
|
2508
|
-
}
|
|
2509
|
-
if (this.oauthProvider.isTokenExpired()) {
|
|
2510
|
-
return await this.refreshToken();
|
|
2511
|
-
}
|
|
2512
|
-
return true;
|
|
2513
|
-
}
|
|
2514
2908
|
/**
|
|
2515
2909
|
* Reconnects to MCP server using existing OAuth provider from Redis
|
|
2516
2910
|
* Used for session restoration in serverless environments
|
|
@@ -2640,68 +3034,6 @@ var MCPClient = class _MCPClient {
|
|
|
2640
3034
|
getSessionId() {
|
|
2641
3035
|
return this.sessionId;
|
|
2642
3036
|
}
|
|
2643
|
-
/**
|
|
2644
|
-
* Gets MCP server configuration for all active user sessions
|
|
2645
|
-
* Loads sessions from Redis, validates OAuth tokens, refreshes if expired
|
|
2646
|
-
* Returns ready-to-use configuration with valid auth headers
|
|
2647
|
-
* @param userId - User ID to fetch sessions for
|
|
2648
|
-
* @returns Object keyed by sanitized server labels containing transport, url, headers, etc.
|
|
2649
|
-
* @static
|
|
2650
|
-
*/
|
|
2651
|
-
static async getMcpServerConfig(userId) {
|
|
2652
|
-
const mcpConfig = {};
|
|
2653
|
-
const sessionList = await sessions.list(userId);
|
|
2654
|
-
await Promise.all(
|
|
2655
|
-
sessionList.map(async (sessionData) => {
|
|
2656
|
-
const { sessionId } = sessionData;
|
|
2657
|
-
try {
|
|
2658
|
-
if (!sessionData.serverId || !sessionData.transportType || !sessionData.serverUrl || !sessionData.callbackUrl) {
|
|
2659
|
-
await sessions.delete(userId, sessionId);
|
|
2660
|
-
return;
|
|
2661
|
-
}
|
|
2662
|
-
let headers;
|
|
2663
|
-
try {
|
|
2664
|
-
const client = new _MCPClient({
|
|
2665
|
-
userId,
|
|
2666
|
-
sessionId,
|
|
2667
|
-
serverId: sessionData.serverId,
|
|
2668
|
-
serverUrl: sessionData.serverUrl,
|
|
2669
|
-
callbackUrl: sessionData.callbackUrl,
|
|
2670
|
-
serverName: sessionData.serverName,
|
|
2671
|
-
transportType: sessionData.transportType,
|
|
2672
|
-
headers: sessionData.headers
|
|
2673
|
-
});
|
|
2674
|
-
await client.initialize();
|
|
2675
|
-
const hasValidTokens = await client.getValidTokens();
|
|
2676
|
-
if (hasValidTokens && client.oauthProvider) {
|
|
2677
|
-
const tokens = await client.oauthProvider.tokens();
|
|
2678
|
-
if (tokens?.access_token) {
|
|
2679
|
-
headers = { Authorization: `Bearer ${tokens.access_token}` };
|
|
2680
|
-
}
|
|
2681
|
-
}
|
|
2682
|
-
} catch (error) {
|
|
2683
|
-
console.warn(`[MCP] Failed to get OAuth tokens for ${sessionId}:`, error);
|
|
2684
|
-
}
|
|
2685
|
-
const label = sanitizeServerLabel(
|
|
2686
|
-
sessionData.serverName || sessionData.serverId || "server"
|
|
2687
|
-
);
|
|
2688
|
-
mcpConfig[label] = {
|
|
2689
|
-
transport: sessionData.transportType,
|
|
2690
|
-
url: sessionData.serverUrl,
|
|
2691
|
-
...sessionData.serverName && {
|
|
2692
|
-
serverName: sessionData.serverName,
|
|
2693
|
-
serverLabel: label
|
|
2694
|
-
},
|
|
2695
|
-
...headers && { headers }
|
|
2696
|
-
};
|
|
2697
|
-
} catch (error) {
|
|
2698
|
-
await sessions.delete(userId, sessionId);
|
|
2699
|
-
console.warn(`[MCP] Failed to process session ${sessionId}:`, error);
|
|
2700
|
-
}
|
|
2701
|
-
})
|
|
2702
|
-
);
|
|
2703
|
-
return mcpConfig;
|
|
2704
|
-
}
|
|
2705
3037
|
};
|
|
2706
3038
|
|
|
2707
3039
|
// src/server/mcp/multi-session-client.ts
|
|
@@ -2737,18 +3069,13 @@ var MultiSessionClient = class {
|
|
|
2737
3069
|
*
|
|
2738
3070
|
* A session is considered connectable when:
|
|
2739
3071
|
* - It has a `serverId`, `serverUrl`, and `callbackUrl` (i.e. it was fully initialized)
|
|
2740
|
-
* - Its
|
|
2741
|
-
* either mid-OAuth flow, auth-pending, or previously failed. We skip those here
|
|
3072
|
+
* - Its status is `active`. Pending sessions are skipped here
|
|
2742
3073
|
* and let the OAuth flow complete separately before we try to reconnect them.
|
|
2743
|
-
*
|
|
2744
|
-
* Note: Sessions where `active` is `undefined` (legacy records) are included
|
|
2745
|
-
* for backwards compatibility.
|
|
2746
3074
|
*/
|
|
2747
3075
|
async getActiveSessions() {
|
|
2748
3076
|
const sessionList = await sessions.list(this.userId);
|
|
2749
3077
|
const valid = sessionList.filter(
|
|
2750
|
-
(s) => s.serverId && s.serverUrl && s.callbackUrl && s.
|
|
2751
|
-
// exclude OAuth-pending / failed sessions
|
|
3078
|
+
(s) => s.serverId && s.serverUrl && s.callbackUrl && s.status === "active"
|
|
2752
3079
|
);
|
|
2753
3080
|
return valid;
|
|
2754
3081
|
}
|
|
@@ -3023,7 +3350,8 @@ var SSEConnectionManager = class {
|
|
|
3023
3350
|
serverUrl: s.serverUrl,
|
|
3024
3351
|
transport: s.transportType,
|
|
3025
3352
|
createdAt: s.createdAt,
|
|
3026
|
-
|
|
3353
|
+
updatedAt: s.updatedAt ?? s.createdAt,
|
|
3354
|
+
status: s.status ?? "pending"
|
|
3027
3355
|
}))
|
|
3028
3356
|
};
|
|
3029
3357
|
}
|
|
@@ -3039,7 +3367,7 @@ var SSEConnectionManager = class {
|
|
|
3039
3367
|
(s) => s.serverId === serverId || s.serverUrl === serverUrl
|
|
3040
3368
|
);
|
|
3041
3369
|
if (duplicate) {
|
|
3042
|
-
if (duplicate.
|
|
3370
|
+
if (duplicate.status === "pending") {
|
|
3043
3371
|
await this.getSession({ sessionId: duplicate.sessionId });
|
|
3044
3372
|
return {
|
|
3045
3373
|
sessionId: duplicate.sessionId,
|
|
@@ -3226,7 +3554,10 @@ var SSEConnectionManager = class {
|
|
|
3226
3554
|
* Complete OAuth authorization flow
|
|
3227
3555
|
*/
|
|
3228
3556
|
async finishAuth(params) {
|
|
3229
|
-
const {
|
|
3557
|
+
const { code } = params;
|
|
3558
|
+
const oauthState = params.state;
|
|
3559
|
+
const parsedState = parseOAuthState(oauthState);
|
|
3560
|
+
const sessionId = parsedState?.sessionId || oauthState;
|
|
3230
3561
|
const session = await sessions.get(this.userId, sessionId);
|
|
3231
3562
|
if (!session) {
|
|
3232
3563
|
throw new Error("Session not found");
|
|
@@ -3251,7 +3582,7 @@ var SSEConnectionManager = class {
|
|
|
3251
3582
|
headers: session.headers
|
|
3252
3583
|
});
|
|
3253
3584
|
client.onConnectionEvent((event) => this.emitConnectionEvent(event));
|
|
3254
|
-
await client.finishAuth(code);
|
|
3585
|
+
await client.finishAuth(code, oauthState);
|
|
3255
3586
|
this.clients.set(sessionId, client);
|
|
3256
3587
|
const tools = await client.listTools();
|
|
3257
3588
|
return { success: true, toolCount: tools.tools.length };
|
|
@@ -3563,8 +3894,8 @@ var SSEClient = class {
|
|
|
3563
3894
|
async getSession(sessionId) {
|
|
3564
3895
|
return this.sendRequest("getSession", { sessionId });
|
|
3565
3896
|
}
|
|
3566
|
-
async finishAuth(
|
|
3567
|
-
return this.sendRequest("finishAuth", {
|
|
3897
|
+
async finishAuth(state, code) {
|
|
3898
|
+
return this.sendRequest("finishAuth", { state, code });
|
|
3568
3899
|
}
|
|
3569
3900
|
async listPrompts(sessionId) {
|
|
3570
3901
|
return this.sendRequest("listPrompts", { sessionId });
|
|
@@ -5447,6 +5778,8 @@ exports.DEFAULT_HEARTBEAT_INTERVAL_MS = DEFAULT_HEARTBEAT_INTERVAL_MS;
|
|
|
5447
5778
|
exports.DEFAULT_LOGO_URI = DEFAULT_LOGO_URI;
|
|
5448
5779
|
exports.DEFAULT_MCP_APP_CSP = DEFAULT_MCP_APP_CSP;
|
|
5449
5780
|
exports.DEFAULT_POLICY_URI = DEFAULT_POLICY_URI;
|
|
5781
|
+
exports.DORMANT_SESSION_EXPIRATION_MS = DORMANT_SESSION_EXPIRATION_MS;
|
|
5782
|
+
exports.DORMANT_SESSION_EXPIRATION_SECONDS = DORMANT_SESSION_EXPIRATION_SECONDS;
|
|
5450
5783
|
exports.DisposableStore = DisposableStore;
|
|
5451
5784
|
exports.Emitter = Emitter;
|
|
5452
5785
|
exports.InvalidStateError = InvalidStateError;
|
|
@@ -5456,11 +5789,11 @@ exports.MCP_CLIENT_VERSION = MCP_CLIENT_VERSION;
|
|
|
5456
5789
|
exports.McpError = McpError;
|
|
5457
5790
|
exports.MultiSessionClient = MultiSessionClient;
|
|
5458
5791
|
exports.NotConnectedError = NotConnectedError;
|
|
5792
|
+
exports.PENDING_SESSION_EXPIRATION_SECONDS = PENDING_SESSION_EXPIRATION_SECONDS;
|
|
5459
5793
|
exports.REDIS_KEY_PREFIX = REDIS_KEY_PREFIX;
|
|
5460
5794
|
exports.RpcErrorCodes = RpcErrorCodes;
|
|
5461
5795
|
exports.SANDBOX_PROXY_READY_METHOD = SANDBOX_PROXY_READY_METHOD;
|
|
5462
5796
|
exports.SANDBOX_RESOURCE_READY_METHOD = SANDBOX_RESOURCE_READY_METHOD;
|
|
5463
|
-
exports.SESSION_TTL_SECONDS = SESSION_TTL_SECONDS;
|
|
5464
5797
|
exports.SOFTWARE_ID = SOFTWARE_ID;
|
|
5465
5798
|
exports.SOFTWARE_VERSION = SOFTWARE_VERSION;
|
|
5466
5799
|
exports.SSEClient = SSEClient;
|
|
@@ -5470,7 +5803,6 @@ exports.SchemaCompressor = SchemaCompressor;
|
|
|
5470
5803
|
exports.SessionNotFoundError = SessionNotFoundError;
|
|
5471
5804
|
exports.SessionValidationError = SessionValidationError;
|
|
5472
5805
|
exports.StorageOAuthClientProvider = StorageOAuthClientProvider;
|
|
5473
|
-
exports.TOKEN_EXPIRY_BUFFER_MS = TOKEN_EXPIRY_BUFFER_MS;
|
|
5474
5806
|
exports.ToolExecutionError = ToolExecutionError;
|
|
5475
5807
|
exports.ToolIndex = ToolIndex;
|
|
5476
5808
|
exports.ToolRouter = ToolRouter;
|