@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.
Files changed (82) hide show
  1. package/README.md +113 -30
  2. package/dist/adapters/agui-adapter.d.mts +3 -3
  3. package/dist/adapters/agui-adapter.d.ts +3 -3
  4. package/dist/adapters/agui-middleware.d.mts +3 -3
  5. package/dist/adapters/agui-middleware.d.ts +3 -3
  6. package/dist/adapters/ai-adapter.d.mts +3 -3
  7. package/dist/adapters/ai-adapter.d.ts +3 -3
  8. package/dist/adapters/langchain-adapter.d.mts +3 -3
  9. package/dist/adapters/langchain-adapter.d.ts +3 -3
  10. package/dist/adapters/mastra-adapter.d.mts +1 -1
  11. package/dist/adapters/mastra-adapter.d.ts +1 -1
  12. package/dist/client/index.d.mts +2 -2
  13. package/dist/client/index.d.ts +2 -2
  14. package/dist/client/index.js +2 -2
  15. package/dist/client/index.js.map +1 -1
  16. package/dist/client/index.mjs +2 -2
  17. package/dist/client/index.mjs.map +1 -1
  18. package/dist/client/react.d.mts +9 -8
  19. package/dist/client/react.d.ts +9 -8
  20. package/dist/client/react.js +66 -26
  21. package/dist/client/react.js.map +1 -1
  22. package/dist/client/react.mjs +67 -27
  23. package/dist/client/react.mjs.map +1 -1
  24. package/dist/client/vue.d.mts +6 -5
  25. package/dist/client/vue.d.ts +6 -5
  26. package/dist/client/vue.js +27 -17
  27. package/dist/client/vue.js.map +1 -1
  28. package/dist/client/vue.mjs +27 -17
  29. package/dist/client/vue.mjs.map +1 -1
  30. package/dist/{index-Cfjsme-a.d.mts → index-ByIjEReo.d.mts} +2 -2
  31. package/dist/{index-CmjMd2ac.d.ts → index-CtXvKl8N.d.ts} +2 -2
  32. package/dist/index.d.mts +5 -5
  33. package/dist/index.d.ts +5 -5
  34. package/dist/index.js +729 -397
  35. package/dist/index.js.map +1 -1
  36. package/dist/index.mjs +724 -396
  37. package/dist/index.mjs.map +1 -1
  38. package/dist/{multi-session-client-DYNe6az3.d.mts → multi-session-client-CIMUGF8S.d.mts} +15 -41
  39. package/dist/{multi-session-client-BYtguGJm.d.ts → multi-session-client-CnvZEGPY.d.ts} +15 -41
  40. package/dist/server/index.d.mts +45 -17
  41. package/dist/server/index.d.ts +45 -17
  42. package/dist/server/index.js +721 -391
  43. package/dist/server/index.js.map +1 -1
  44. package/dist/server/index.mjs +718 -391
  45. package/dist/server/index.mjs.map +1 -1
  46. package/dist/shared/index.d.mts +9 -8
  47. package/dist/shared/index.d.ts +9 -8
  48. package/dist/shared/index.js +7 -5
  49. package/dist/shared/index.js.map +1 -1
  50. package/dist/shared/index.mjs +5 -4
  51. package/dist/shared/index.mjs.map +1 -1
  52. package/dist/{tool-router-BMzhoNYt.d.ts → tool-router-CZMrOG8J.d.ts} +1 -1
  53. package/dist/{tool-router-BhHsvBfP.d.mts → tool-router-CuApsDiV.d.mts} +1 -1
  54. package/dist/{types-CxFaaZrM.d.mts → types-DCk_IF4L.d.mts} +5 -3
  55. package/dist/{types-CxFaaZrM.d.ts → types-DCk_IF4L.d.ts} +5 -3
  56. package/migrations/neon/20260513010000_install_mcp_sessions.sql +40 -3
  57. package/migrations/neon/20260513020000_add_session_cleanup_cron.sql +4 -4
  58. package/migrations/supabase/20260330195700_install_mcp_sessions.sql +67 -3
  59. package/migrations/supabase/20260421010000_add_session_cleanup_cron.sql +6 -6
  60. package/package.json +13 -3
  61. package/src/client/core/sse-client.ts +2 -2
  62. package/src/client/react/index.ts +1 -1
  63. package/src/client/react/oauth-popup.tsx +34 -13
  64. package/src/client/react/use-mcp.ts +19 -45
  65. package/src/client/utils/session-state.ts +43 -0
  66. package/src/client/vue/use-mcp.ts +18 -47
  67. package/src/server/handlers/sse-handler.ts +9 -4
  68. package/src/server/mcp/multi-session-client.ts +2 -6
  69. package/src/server/mcp/oauth-client.ts +73 -220
  70. package/src/server/mcp/storage-oauth-provider.ts +79 -50
  71. package/src/server/storage/file-backend.ts +74 -18
  72. package/src/server/storage/index.ts +2 -4
  73. package/src/server/storage/memory-backend.ts +49 -13
  74. package/src/server/storage/neon-backend.ts +201 -68
  75. package/src/server/storage/redis-backend.ts +81 -23
  76. package/src/server/storage/session-lifecycle.ts +78 -0
  77. package/src/server/storage/sqlite-backend.ts +89 -15
  78. package/src/server/storage/supabase-backend.ts +188 -63
  79. package/src/server/storage/types.ts +49 -16
  80. package/src/shared/constants.ts +5 -6
  81. package/src/shared/types.ts +5 -3
  82. package/src/shared/utils.ts +26 -0
@@ -2,7 +2,7 @@ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
2
2
  import { customAlphabet, nanoid } from 'nanoid';
3
3
  import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
4
4
  import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
5
- import { UnauthorizedError as UnauthorizedError$1, discoverOAuthProtectedResourceMetadata, discoverAuthorizationServerMetadata, refreshAuthorization } from '@modelcontextprotocol/sdk/client/auth.js';
5
+ import { UnauthorizedError as UnauthorizedError$1 } from '@modelcontextprotocol/sdk/client/auth.js';
6
6
  import { ListToolsResultSchema, CallToolResultSchema, ListPromptsResultSchema, GetPromptResultSchema, ListResourcesResultSchema, ReadResourceResultSchema } from '@modelcontextprotocol/sdk/types.js';
7
7
  import * as fs2 from 'fs';
8
8
  import { promises } from 'fs';
@@ -111,19 +111,7 @@ var init_redis = __esm({
111
111
  });
112
112
  }
113
113
  });
114
-
115
- // src/shared/constants.ts
116
- var SESSION_TTL_SECONDS = 43200;
117
- var STATE_EXPIRATION_MS = 10 * 60 * 1e3;
118
- var TOKEN_EXPIRY_BUFFER_MS = 5 * 60 * 1e3;
119
- var DEFAULT_CLIENT_NAME = "MCP Assistant";
120
- var DEFAULT_CLIENT_URI = "https://mcp-assistant.in";
121
- var DEFAULT_LOGO_URI = "https://mcp-assistant.in/logo.svg";
122
- var DEFAULT_POLICY_URI = "https://mcp-assistant.in/privacy";
123
- var SOFTWARE_ID = "@mcp-ts";
124
- var SOFTWARE_VERSION = "1.3.4";
125
- var MCP_CLIENT_NAME = "mcp-ts-oauth-client";
126
- var MCP_CLIENT_VERSION = "2.0";
114
+ var OAUTH_STATE_SEPARATOR = ".";
127
115
  var firstChar = customAlphabet(
128
116
  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
129
117
  1
@@ -142,13 +130,95 @@ function sanitizeServerLabel(name) {
142
130
  function generateSessionId() {
143
131
  return firstChar() + rest();
144
132
  }
133
+ function formatOAuthState(nonce, sessionId) {
134
+ return `${nonce}${OAUTH_STATE_SEPARATOR}${sessionId}`;
135
+ }
136
+ function parseOAuthState(state) {
137
+ const separatorIndex = state.indexOf(OAUTH_STATE_SEPARATOR);
138
+ if (separatorIndex <= 0 || separatorIndex === state.length - 1) {
139
+ return void 0;
140
+ }
141
+ const nonce = state.slice(0, separatorIndex);
142
+ const sessionId = state.slice(separatorIndex + 1);
143
+ if (!nonce || !sessionId || sessionId.includes(OAUTH_STATE_SEPARATOR)) {
144
+ return void 0;
145
+ }
146
+ return { nonce, sessionId };
147
+ }
148
+
149
+ // src/shared/constants.ts
150
+ var STATE_EXPIRATION_MS = 10 * 60 * 1e3;
151
+ var DORMANT_SESSION_EXPIRATION_MS = 30 * 24 * 60 * 60 * 1e3;
152
+ var DORMANT_SESSION_EXPIRATION_SECONDS = Math.floor(DORMANT_SESSION_EXPIRATION_MS / 1e3);
153
+ var DEFAULT_CLIENT_NAME = "MCP Assistant";
154
+ var DEFAULT_CLIENT_URI = "https://mcp-assistant.in";
155
+ var DEFAULT_LOGO_URI = "https://mcp-assistant.in/logo.svg";
156
+ var DEFAULT_POLICY_URI = "https://mcp-assistant.in/privacy";
157
+ var SOFTWARE_ID = "@mcp-ts";
158
+ var SOFTWARE_VERSION = "2.3.4";
159
+ var MCP_CLIENT_NAME = "mcp-ts-oauth-client";
160
+ var MCP_CLIENT_VERSION = "2.0";
161
+
162
+ // src/server/storage/session-lifecycle.ts
163
+ function resolveSessionExpiresAt(status = "pending", referenceTime = Date.now()) {
164
+ return status === "active" ? null : referenceTime + STATE_EXPIRATION_MS;
165
+ }
166
+ function resolveSessionRedisTtlSeconds(session) {
167
+ return session.status === "active" ? DORMANT_SESSION_EXPIRATION_SECONDS : Math.floor(STATE_EXPIRATION_MS / 1e3);
168
+ }
169
+ function normalizeNewSession(session, now = Date.now()) {
170
+ const createdAt = session.createdAt || now;
171
+ const updatedAt = session.updatedAt ?? createdAt;
172
+ const status = session.status ?? "pending";
173
+ return {
174
+ ...session,
175
+ status,
176
+ createdAt,
177
+ updatedAt,
178
+ expiresAt: resolveSessionExpiresAt(status, status === "active" ? updatedAt : createdAt)
179
+ };
180
+ }
181
+ function mergeSessionUpdate(current, data, now = Date.now()) {
182
+ const updatedAt = data.updatedAt ?? now;
183
+ const updated = {
184
+ ...current,
185
+ ...data,
186
+ updatedAt
187
+ };
188
+ const status = updated.status ?? "pending";
189
+ return {
190
+ ...updated,
191
+ status,
192
+ expiresAt: resolveSessionExpiresAt(status, updatedAt)
193
+ };
194
+ }
195
+ function normalizeStoredSession(session) {
196
+ const createdAt = session.createdAt || Date.now();
197
+ const updatedAt = session.updatedAt ?? createdAt;
198
+ const status = session.status ?? "pending";
199
+ const expiresAt = status === "active" ? null : session.expiresAt ?? resolveSessionExpiresAt(status, createdAt);
200
+ return {
201
+ ...session,
202
+ status,
203
+ createdAt,
204
+ updatedAt,
205
+ expiresAt
206
+ };
207
+ }
208
+ function isSessionExpired(session, now = Date.now()) {
209
+ const hydrated = normalizeStoredSession(session);
210
+ if (hydrated.status === "active") {
211
+ return hydrated.updatedAt !== void 0 && hydrated.updatedAt < now - DORMANT_SESSION_EXPIRATION_MS;
212
+ }
213
+ return typeof hydrated.expiresAt === "number" && hydrated.expiresAt < now;
214
+ }
145
215
 
146
216
  // src/server/storage/redis-backend.ts
147
217
  var RedisStorageBackend = class {
148
218
  constructor(redis2) {
149
219
  this.redis = redis2;
150
- __publicField(this, "DEFAULT_TTL", SESSION_TTL_SECONDS);
151
220
  __publicField(this, "KEY_PREFIX", "mcp:session:");
221
+ __publicField(this, "CREDENTIALS_KEY_PREFIX", "mcp:credentials:");
152
222
  __publicField(this, "USER_ID_KEY_PREFIX", "mcp:userId:");
153
223
  __publicField(this, "USER_ID_KEY_SUFFIX", ":sessions");
154
224
  }
@@ -167,6 +237,9 @@ var RedisStorageBackend = class {
167
237
  getSessionKey(userId, sessionId) {
168
238
  return `${this.KEY_PREFIX}${userId}:${sessionId}`;
169
239
  }
240
+ getCredentialsKey(userId, sessionId) {
241
+ return `${this.CREDENTIALS_KEY_PREFIX}${userId}:${sessionId}`;
242
+ }
170
243
  /**
171
244
  * Generates Redis key for tracking all sessions for a user
172
245
  * @private
@@ -204,15 +277,16 @@ var RedisStorageBackend = class {
204
277
  generateSessionId() {
205
278
  return generateSessionId();
206
279
  }
207
- async create(session, ttl) {
280
+ async create(session) {
208
281
  const { sessionId, userId } = session;
209
282
  if (!sessionId || !userId) throw new Error("userId and sessionId required");
210
283
  const sessionKey = this.getSessionKey(userId, sessionId);
211
284
  const userIdKey = this.getUserIdKey(userId);
212
- const effectiveTtl = ttl ?? this.DEFAULT_TTL;
285
+ const sessionWithLifecycle = normalizeNewSession(session);
286
+ const effectiveTtl = resolveSessionRedisTtlSeconds(sessionWithLifecycle);
213
287
  const result = await this.redis.set(
214
288
  sessionKey,
215
- JSON.stringify(session),
289
+ JSON.stringify(sessionWithLifecycle),
216
290
  "EX",
217
291
  effectiveTtl,
218
292
  "NX"
@@ -222,9 +296,8 @@ var RedisStorageBackend = class {
222
296
  }
223
297
  await this.redis.sadd(userIdKey, sessionId);
224
298
  }
225
- async update(userId, sessionId, data, ttl) {
299
+ async update(userId, sessionId, data) {
226
300
  const sessionKey = this.getSessionKey(userId, sessionId);
227
- const effectiveTtl = ttl ?? this.DEFAULT_TTL;
228
301
  const script = `
229
302
  local currentStr = redis.call("GET", KEYS[1])
230
303
  if not currentStr then
@@ -232,26 +305,47 @@ var RedisStorageBackend = class {
232
305
  end
233
306
 
234
307
  local current = cjson.decode(currentStr)
235
- local updates = cjson.decode(ARGV[1])
236
-
237
- for k,v in pairs(updates) do
238
- current[k] = v
239
- end
308
+ local updated = cjson.decode(ARGV[1])
240
309
 
241
- redis.call("SET", KEYS[1], cjson.encode(current), "EX", ARGV[2])
310
+ redis.call("SET", KEYS[1], cjson.encode(updated), "EX", ARGV[2])
242
311
  return 1
243
312
  `;
313
+ const current = await this.get(userId, sessionId);
314
+ if (!current) {
315
+ throw new Error(`Session ${sessionId} not found for userId ${userId}`);
316
+ }
317
+ const updated = mergeSessionUpdate(current, data);
318
+ const effectiveTtl = resolveSessionRedisTtlSeconds(updated);
244
319
  const result = await this.redis.eval(
245
320
  script,
246
321
  1,
247
322
  sessionKey,
248
- JSON.stringify(data),
323
+ JSON.stringify(updated),
249
324
  effectiveTtl
250
325
  );
251
326
  if (result === 0) {
252
327
  throw new Error(`Session ${sessionId} not found for userId ${userId}`);
253
328
  }
254
329
  }
330
+ async patchCredentials(userId, sessionId, data) {
331
+ const sessionKey = this.getSessionKey(userId, sessionId);
332
+ const credentialsKey = this.getCredentialsKey(userId, sessionId);
333
+ const sessionExists = await this.redis.exists(sessionKey);
334
+ if (!sessionExists) {
335
+ throw new Error(`Session ${sessionId} not found for userId ${userId}`);
336
+ }
337
+ const session = await this.get(userId, sessionId);
338
+ const currentTtl = await this.redis.ttl(sessionKey);
339
+ const effectiveTtl = currentTtl > 0 && session ? currentTtl : resolveSessionRedisTtlSeconds(session ?? { status: "pending" });
340
+ const currentStr = await this.redis.get(credentialsKey);
341
+ const current = currentStr ? JSON.parse(currentStr) : { sessionId, userId };
342
+ const credentials = { ...current, ...data, sessionId, userId };
343
+ if (effectiveTtl > 0) {
344
+ await this.redis.set(credentialsKey, JSON.stringify(credentials), "EX", effectiveTtl);
345
+ } else {
346
+ await this.redis.set(credentialsKey, JSON.stringify(credentials));
347
+ }
348
+ }
255
349
  async get(userId, sessionId) {
256
350
  try {
257
351
  const sessionKey = this.getSessionKey(userId, sessionId);
@@ -259,13 +353,28 @@ var RedisStorageBackend = class {
259
353
  if (!sessionDataStr) {
260
354
  return null;
261
355
  }
262
- const Session = JSON.parse(sessionDataStr);
263
- return Session;
356
+ const session = JSON.parse(sessionDataStr);
357
+ return normalizeStoredSession(session);
264
358
  } catch (error) {
265
359
  console.error("[RedisStorageBackend] Failed to get session:", error);
266
360
  return null;
267
361
  }
268
362
  }
363
+ async getCredentials(userId, sessionId) {
364
+ const session = await this.get(userId, sessionId);
365
+ if (!session) return null;
366
+ const credentialsStr = await this.redis.get(this.getCredentialsKey(userId, sessionId));
367
+ return credentialsStr ? JSON.parse(credentialsStr) : { sessionId, userId };
368
+ }
369
+ async clearCredentials(userId, sessionId) {
370
+ await this.patchCredentials(userId, sessionId, {
371
+ clientInformation: null,
372
+ tokens: null,
373
+ codeVerifier: null,
374
+ clientId: null,
375
+ oauthState: null
376
+ });
377
+ }
269
378
  async listIds(userId) {
270
379
  const sessions2 = await this.list(userId);
271
380
  return sessions2.map((session) => session.sessionId);
@@ -285,7 +394,7 @@ var RedisStorageBackend = class {
285
394
  if (staleSessionIds.length > 0) {
286
395
  await this.redis.srem(userIdKey, ...staleSessionIds);
287
396
  }
288
- return results.filter((session) => session !== null);
397
+ return results.filter((session) => session !== null).map((session) => normalizeStoredSession(session));
289
398
  } catch (error) {
290
399
  console.error(`[RedisStorageBackend] Failed to get session data for ${userId}:`, error);
291
400
  return [];
@@ -294,9 +403,10 @@ var RedisStorageBackend = class {
294
403
  async delete(userId, sessionId) {
295
404
  try {
296
405
  const sessionKey = this.getSessionKey(userId, sessionId);
406
+ const credentialsKey = this.getCredentialsKey(userId, sessionId);
297
407
  const userIdKey = this.getUserIdKey(userId);
298
408
  await this.redis.srem(userIdKey, sessionId);
299
- await this.redis.del(sessionKey);
409
+ await this.redis.del(sessionKey, credentialsKey);
300
410
  } catch (error) {
301
411
  console.error("[RedisStorageBackend] Failed to remove session:", error);
302
412
  }
@@ -311,7 +421,7 @@ var RedisStorageBackend = class {
311
421
  return null;
312
422
  }
313
423
  try {
314
- return JSON.parse(data).sessionId;
424
+ return normalizeStoredSession(JSON.parse(data)).sessionId;
315
425
  } catch (error) {
316
426
  console.error("[RedisStorageBackend] Failed to parse session while listing all session IDs:", error);
317
427
  return null;
@@ -327,8 +437,9 @@ var RedisStorageBackend = class {
327
437
  async clearAll() {
328
438
  try {
329
439
  const keys = await this.scanKeys(`${this.KEY_PREFIX}*`);
440
+ const credentialKeys = await this.scanKeys(`${this.CREDENTIALS_KEY_PREFIX}*`);
330
441
  const userIdKeys = await this.scanKeys(`${this.USER_ID_KEY_PREFIX}*${this.USER_ID_KEY_SUFFIX}`);
331
- const allKeys = [...keys, ...userIdKeys];
442
+ const allKeys = [...keys, ...credentialKeys, ...userIdKeys];
332
443
  if (allKeys.length > 0) {
333
444
  await this.redis.del(...allKeys);
334
445
  }
@@ -352,6 +463,7 @@ var RedisStorageBackend = class {
352
463
  const staleSessionIds = sessionIds.filter((_, index) => existenceChecks[index] === 0);
353
464
  if (staleSessionIds.length > 0) {
354
465
  await this.redis.srem(userIdKey, ...staleSessionIds);
466
+ await this.redis.del(...staleSessionIds.map((sessionId) => this.getCredentialsKey(userId, sessionId)));
355
467
  }
356
468
  const remainingCount = await this.redis.scard(userIdKey);
357
469
  if (remainingCount === 0) {
@@ -376,6 +488,7 @@ var MemoryStorageBackend = class {
376
488
  constructor() {
377
489
  // Map<userId:sessionId, Session>
378
490
  __publicField(this, "sessions", /* @__PURE__ */ new Map());
491
+ __publicField(this, "credentials", /* @__PURE__ */ new Map());
379
492
  // Map<userId, Set<sessionId>>
380
493
  __publicField(this, "userIdSessions", /* @__PURE__ */ new Map());
381
494
  }
@@ -388,36 +501,55 @@ var MemoryStorageBackend = class {
388
501
  generateSessionId() {
389
502
  return generateSessionId();
390
503
  }
391
- async create(session, ttl) {
504
+ async create(session) {
392
505
  const { sessionId, userId } = session;
393
506
  if (!sessionId || !userId) throw new Error("userId and sessionId required");
394
507
  const sessionKey = this.getSessionKey(userId, sessionId);
395
508
  if (this.sessions.has(sessionKey)) {
396
509
  throw new Error(`Session ${sessionId} already exists`);
397
510
  }
398
- this.sessions.set(sessionKey, session);
511
+ this.sessions.set(sessionKey, normalizeNewSession(session));
399
512
  if (!this.userIdSessions.has(userId)) {
400
513
  this.userIdSessions.set(userId, /* @__PURE__ */ new Set());
401
514
  }
402
515
  this.userIdSessions.get(userId).add(sessionId);
403
516
  }
404
- async update(userId, sessionId, data, ttl) {
517
+ async update(userId, sessionId, data) {
405
518
  if (!userId || !sessionId) throw new Error("userId and sessionId required");
406
519
  const sessionKey = this.getSessionKey(userId, sessionId);
407
520
  const current = this.sessions.get(sessionKey);
408
521
  if (!current) {
409
522
  throw new Error(`Session ${sessionId} not found`);
410
523
  }
411
- const updated = {
412
- ...current,
413
- ...data
414
- };
524
+ const updated = mergeSessionUpdate(current, data);
415
525
  this.sessions.set(sessionKey, updated);
416
526
  }
527
+ async patchCredentials(userId, sessionId, data) {
528
+ const sessionKey = this.getSessionKey(userId, sessionId);
529
+ if (!this.sessions.has(sessionKey)) {
530
+ throw new Error(`Session ${sessionId} not found`);
531
+ }
532
+ const current = this.credentials.get(sessionKey) ?? { sessionId, userId };
533
+ this.credentials.set(sessionKey, { ...current, ...data, sessionId, userId });
534
+ }
417
535
  async get(userId, sessionId) {
418
536
  const sessionKey = this.getSessionKey(userId, sessionId);
419
537
  return this.sessions.get(sessionKey) || null;
420
538
  }
539
+ async getCredentials(userId, sessionId) {
540
+ const sessionKey = this.getSessionKey(userId, sessionId);
541
+ if (!this.sessions.has(sessionKey)) return null;
542
+ return this.credentials.get(sessionKey) ?? { sessionId, userId };
543
+ }
544
+ async clearCredentials(userId, sessionId) {
545
+ await this.patchCredentials(userId, sessionId, {
546
+ clientInformation: null,
547
+ tokens: null,
548
+ codeVerifier: null,
549
+ clientId: null,
550
+ oauthState: null
551
+ });
552
+ }
421
553
  async listIds(userId) {
422
554
  const set = this.userIdSessions.get(userId);
423
555
  return set ? Array.from(set) : [];
@@ -437,6 +569,7 @@ var MemoryStorageBackend = class {
437
569
  async delete(userId, sessionId) {
438
570
  const sessionKey = this.getSessionKey(userId, sessionId);
439
571
  this.sessions.delete(sessionKey);
572
+ this.credentials.delete(sessionKey);
440
573
  const set = this.userIdSessions.get(userId);
441
574
  if (set) {
442
575
  set.delete(sessionId);
@@ -450,9 +583,22 @@ var MemoryStorageBackend = class {
450
583
  }
451
584
  async clearAll() {
452
585
  this.sessions.clear();
586
+ this.credentials.clear();
453
587
  this.userIdSessions.clear();
454
588
  }
455
589
  async cleanupExpired() {
590
+ for (const [key, session] of this.sessions.entries()) {
591
+ if (!isSessionExpired(session)) continue;
592
+ this.sessions.delete(key);
593
+ this.credentials.delete(key);
594
+ const set = this.userIdSessions.get(session.userId);
595
+ if (set) {
596
+ set.delete(session.sessionId);
597
+ if (set.size === 0) {
598
+ this.userIdSessions.delete(session.userId);
599
+ }
600
+ }
601
+ }
456
602
  }
457
603
  async disconnect() {
458
604
  }
@@ -464,6 +610,7 @@ var FileStorageBackend = class {
464
610
  constructor(options = {}) {
465
611
  __publicField(this, "filePath");
466
612
  __publicField(this, "memoryCache", null);
613
+ __publicField(this, "credentialsCache", null);
467
614
  __publicField(this, "initialized", false);
468
615
  this.filePath = options.path || "./sessions.json";
469
616
  }
@@ -478,14 +625,22 @@ var FileStorageBackend = class {
478
625
  const data = await promises.readFile(this.filePath, "utf-8");
479
626
  const json = JSON.parse(data);
480
627
  this.memoryCache = /* @__PURE__ */ new Map();
481
- if (Array.isArray(json)) {
482
- json.forEach((s) => {
483
- this.memoryCache.set(this.getSessionKey(s.userId || "unknown", s.sessionId), s);
628
+ this.credentialsCache = /* @__PURE__ */ new Map();
629
+ if (Array.isArray(json.sessions)) {
630
+ json.sessions.forEach((s) => {
631
+ const session = normalizeStoredSession(s);
632
+ this.memoryCache.set(this.getSessionKey(session.userId || "unknown", session.sessionId), session);
633
+ });
634
+ }
635
+ if (Array.isArray(json.credentials)) {
636
+ json.credentials.forEach((c) => {
637
+ this.credentialsCache.set(this.getSessionKey(c.userId, c.sessionId), c);
484
638
  });
485
639
  }
486
640
  } catch (error) {
487
641
  if (error.code === "ENOENT") {
488
642
  this.memoryCache = /* @__PURE__ */ new Map();
643
+ this.credentialsCache = /* @__PURE__ */ new Map();
489
644
  await this.flush();
490
645
  } else {
491
646
  console.error("[FileStorage] Failed to load sessions:", error);
@@ -499,9 +654,11 @@ var FileStorageBackend = class {
499
654
  if (!this.initialized) await this.init();
500
655
  }
501
656
  async flush() {
502
- if (!this.memoryCache) return;
503
- const sessions2 = Array.from(this.memoryCache.values());
504
- await promises.writeFile(this.filePath, JSON.stringify(sessions2, null, 2), "utf-8");
657
+ if (!this.memoryCache || !this.credentialsCache) return;
658
+ await promises.writeFile(this.filePath, JSON.stringify({
659
+ sessions: Array.from(this.memoryCache.values()),
660
+ credentials: Array.from(this.credentialsCache.values())
661
+ }, null, 2), "utf-8");
505
662
  }
506
663
  getSessionKey(userId, sessionId) {
507
664
  return `${userId}:${sessionId}`;
@@ -509,7 +666,7 @@ var FileStorageBackend = class {
509
666
  generateSessionId() {
510
667
  return generateSessionId();
511
668
  }
512
- async create(session, ttl) {
669
+ async create(session) {
513
670
  await this.ensureInitialized();
514
671
  const { sessionId, userId } = session;
515
672
  if (!sessionId || !userId) throw new Error("userId and sessionId required");
@@ -517,10 +674,10 @@ var FileStorageBackend = class {
517
674
  if (this.memoryCache.has(sessionKey)) {
518
675
  throw new Error(`Session ${sessionId} already exists`);
519
676
  }
520
- this.memoryCache.set(sessionKey, session);
677
+ this.memoryCache.set(sessionKey, normalizeNewSession(session));
521
678
  await this.flush();
522
679
  }
523
- async update(userId, sessionId, data, ttl) {
680
+ async update(userId, sessionId, data) {
524
681
  await this.ensureInitialized();
525
682
  if (!userId || !sessionId) throw new Error("userId and sessionId required");
526
683
  const sessionKey = this.getSessionKey(userId, sessionId);
@@ -528,18 +685,40 @@ var FileStorageBackend = class {
528
685
  if (!current) {
529
686
  throw new Error(`Session ${sessionId} not found`);
530
687
  }
531
- const updated = {
532
- ...current,
533
- ...data
534
- };
688
+ const updated = mergeSessionUpdate(current, data);
535
689
  this.memoryCache.set(sessionKey, updated);
536
690
  await this.flush();
537
691
  }
692
+ async patchCredentials(userId, sessionId, data) {
693
+ await this.ensureInitialized();
694
+ const sessionKey = this.getSessionKey(userId, sessionId);
695
+ if (!this.memoryCache.has(sessionKey)) {
696
+ throw new Error(`Session ${sessionId} not found`);
697
+ }
698
+ const current = this.credentialsCache.get(sessionKey) ?? { sessionId, userId };
699
+ this.credentialsCache.set(sessionKey, { ...current, ...data, sessionId, userId });
700
+ await this.flush();
701
+ }
538
702
  async get(userId, sessionId) {
539
703
  await this.ensureInitialized();
540
704
  const sessionKey = this.getSessionKey(userId, sessionId);
541
705
  return this.memoryCache.get(sessionKey) || null;
542
706
  }
707
+ async getCredentials(userId, sessionId) {
708
+ await this.ensureInitialized();
709
+ const sessionKey = this.getSessionKey(userId, sessionId);
710
+ if (!this.memoryCache.has(sessionKey)) return null;
711
+ return this.credentialsCache.get(sessionKey) ?? { sessionId, userId };
712
+ }
713
+ async clearCredentials(userId, sessionId) {
714
+ await this.patchCredentials(userId, sessionId, {
715
+ clientInformation: null,
716
+ tokens: null,
717
+ codeVerifier: null,
718
+ clientId: null,
719
+ oauthState: null
720
+ });
721
+ }
543
722
  async list(userId) {
544
723
  await this.ensureInitialized();
545
724
  return Array.from(this.memoryCache.values()).filter((s) => s.userId === userId);
@@ -551,7 +730,9 @@ var FileStorageBackend = class {
551
730
  async delete(userId, sessionId) {
552
731
  await this.ensureInitialized();
553
732
  const sessionKey = this.getSessionKey(userId, sessionId);
554
- if (this.memoryCache.delete(sessionKey)) {
733
+ const deleted = this.memoryCache.delete(sessionKey);
734
+ this.credentialsCache.delete(sessionKey);
735
+ if (deleted) {
555
736
  await this.flush();
556
737
  }
557
738
  }
@@ -562,10 +743,21 @@ var FileStorageBackend = class {
562
743
  async clearAll() {
563
744
  await this.ensureInitialized();
564
745
  this.memoryCache.clear();
746
+ this.credentialsCache.clear();
565
747
  await this.flush();
566
748
  }
567
749
  async cleanupExpired() {
568
750
  await this.ensureInitialized();
751
+ let changed = false;
752
+ for (const [key, session] of this.memoryCache.entries()) {
753
+ if (!isSessionExpired(session)) continue;
754
+ this.memoryCache.delete(key);
755
+ this.credentialsCache.delete(key);
756
+ changed = true;
757
+ }
758
+ if (changed) {
759
+ await this.flush();
760
+ }
569
761
  }
570
762
  async disconnect() {
571
763
  }
@@ -574,10 +766,12 @@ var SqliteStorage = class {
574
766
  constructor(options = {}) {
575
767
  __publicField(this, "db", null);
576
768
  __publicField(this, "table");
769
+ __publicField(this, "credentialsTable");
577
770
  __publicField(this, "initialized", false);
578
771
  __publicField(this, "dbPath");
579
772
  this.dbPath = options.path || "./sessions.db";
580
773
  this.table = options.table || "mcp_sessions";
774
+ this.credentialsTable = `${this.table}_credentials`;
581
775
  }
582
776
  async init() {
583
777
  if (this.initialized) return;
@@ -588,6 +782,7 @@ var SqliteStorage = class {
588
782
  fs2.mkdirSync(dir, { recursive: true });
589
783
  }
590
784
  this.db = new DatabaseConstructor(this.dbPath);
785
+ this.db.pragma("foreign_keys = ON");
591
786
  this.db.exec(`
592
787
  CREATE TABLE IF NOT EXISTS ${this.table} (
593
788
  sessionId TEXT PRIMARY KEY,
@@ -596,6 +791,13 @@ var SqliteStorage = class {
596
791
  expiresAt INTEGER
597
792
  );
598
793
  CREATE INDEX IF NOT EXISTS idx_${this.table}_userId ON ${this.table}(userId);
794
+ CREATE TABLE IF NOT EXISTS ${this.credentialsTable} (
795
+ sessionId TEXT NOT NULL,
796
+ userId TEXT NOT NULL,
797
+ data TEXT NOT NULL,
798
+ PRIMARY KEY (userId, sessionId),
799
+ FOREIGN KEY (sessionId) REFERENCES ${this.table}(sessionId) ON DELETE CASCADE
800
+ );
599
801
  `);
600
802
  this.initialized = true;
601
803
  console.log(`[mcp-ts][Storage] SQLite: \u2713 database at ${this.dbPath} verified.`);
@@ -616,18 +818,18 @@ var SqliteStorage = class {
616
818
  generateSessionId() {
617
819
  return generateSessionId();
618
820
  }
619
- async create(session, ttl) {
821
+ async create(session) {
620
822
  this.ensureInitialized();
621
823
  const { sessionId, userId } = session;
622
824
  if (!sessionId || !userId) {
623
825
  throw new Error("userId and sessionId required");
624
826
  }
625
- const expiresAt = ttl ? Date.now() + ttl * 1e3 : null;
827
+ const sessionWithLifecycle = normalizeNewSession(session);
626
828
  try {
627
829
  const stmt = this.db.prepare(
628
830
  `INSERT INTO ${this.table} (sessionId, userId, data, expiresAt) VALUES (?, ?, ?, ?)`
629
831
  );
630
- stmt.run(sessionId, userId, JSON.stringify(session), expiresAt);
832
+ stmt.run(sessionId, userId, JSON.stringify(sessionWithLifecycle), sessionWithLifecycle.expiresAt ?? null);
631
833
  } catch (error) {
632
834
  if (error.code === "SQLITE_CONSTRAINT_PRIMARYKEY") {
633
835
  throw new Error(`Session ${sessionId} already exists`);
@@ -635,7 +837,7 @@ var SqliteStorage = class {
635
837
  throw error;
636
838
  }
637
839
  }
638
- async update(userId, sessionId, data, ttl) {
840
+ async update(userId, sessionId, data) {
639
841
  this.ensureInitialized();
640
842
  if (!sessionId || !userId) {
641
843
  throw new Error("userId and sessionId required");
@@ -644,12 +846,25 @@ var SqliteStorage = class {
644
846
  if (!currentSession) {
645
847
  throw new Error(`Session ${sessionId} not found for userId ${userId}`);
646
848
  }
647
- const updatedSession = { ...currentSession, ...data };
648
- const expiresAt = ttl ? Date.now() + ttl * 1e3 : null;
849
+ const updatedSession = mergeSessionUpdate(currentSession, data);
649
850
  const stmt = this.db.prepare(
650
851
  `UPDATE ${this.table} SET data = ?, expiresAt = ? WHERE sessionId = ? AND userId = ?`
651
852
  );
652
- stmt.run(JSON.stringify(updatedSession), expiresAt, sessionId, userId);
853
+ stmt.run(JSON.stringify(updatedSession), updatedSession.expiresAt ?? null, sessionId, userId);
854
+ }
855
+ async patchCredentials(userId, sessionId, data) {
856
+ this.ensureInitialized();
857
+ if (!await this.get(userId, sessionId)) {
858
+ throw new Error(`Session ${sessionId} not found for userId ${userId}`);
859
+ }
860
+ const current = await this.getCredentials(userId, sessionId) ?? { sessionId, userId };
861
+ const credentials = { ...current, ...data, sessionId, userId };
862
+ const stmt = this.db.prepare(
863
+ `INSERT INTO ${this.credentialsTable} (sessionId, userId, data)
864
+ VALUES (?, ?, ?)
865
+ ON CONFLICT(userId, sessionId) DO UPDATE SET data = excluded.data`
866
+ );
867
+ stmt.run(sessionId, userId, JSON.stringify(credentials));
653
868
  }
654
869
  async get(userId, sessionId) {
655
870
  this.ensureInitialized();
@@ -658,7 +873,25 @@ var SqliteStorage = class {
658
873
  );
659
874
  const row = stmt.get(sessionId, userId);
660
875
  if (!row) return null;
661
- return JSON.parse(row.data);
876
+ return normalizeStoredSession(JSON.parse(row.data));
877
+ }
878
+ async getCredentials(userId, sessionId) {
879
+ this.ensureInitialized();
880
+ if (!await this.get(userId, sessionId)) return null;
881
+ const stmt = this.db.prepare(
882
+ `SELECT data FROM ${this.credentialsTable} WHERE sessionId = ? AND userId = ?`
883
+ );
884
+ const row = stmt.get(sessionId, userId);
885
+ return row ? JSON.parse(row.data) : { sessionId, userId };
886
+ }
887
+ async clearCredentials(userId, sessionId) {
888
+ await this.patchCredentials(userId, sessionId, {
889
+ clientInformation: null,
890
+ tokens: null,
891
+ codeVerifier: null,
892
+ clientId: null,
893
+ oauthState: null
894
+ });
662
895
  }
663
896
  async list(userId) {
664
897
  this.ensureInitialized();
@@ -666,7 +899,7 @@ var SqliteStorage = class {
666
899
  `SELECT data FROM ${this.table} WHERE userId = ?`
667
900
  );
668
901
  const rows = stmt.all(userId);
669
- return rows.map((row) => JSON.parse(row.data));
902
+ return rows.map((row) => normalizeStoredSession(JSON.parse(row.data)));
670
903
  }
671
904
  async listIds(userId) {
672
905
  this.ensureInitialized();
@@ -681,6 +914,9 @@ var SqliteStorage = class {
681
914
  const stmt = this.db.prepare(
682
915
  `DELETE FROM ${this.table} WHERE sessionId = ? AND userId = ?`
683
916
  );
917
+ this.db.prepare(
918
+ `DELETE FROM ${this.credentialsTable} WHERE sessionId = ? AND userId = ?`
919
+ ).run(sessionId, userId);
684
920
  stmt.run(sessionId, userId);
685
921
  }
686
922
  async listAllIds() {
@@ -691,16 +927,28 @@ var SqliteStorage = class {
691
927
  }
692
928
  async clearAll() {
693
929
  this.ensureInitialized();
930
+ this.db.prepare(`DELETE FROM ${this.credentialsTable}`).run();
694
931
  const stmt = this.db.prepare(`DELETE FROM ${this.table}`);
695
932
  stmt.run();
696
933
  }
697
934
  async cleanupExpired() {
698
935
  this.ensureInitialized();
699
- const now = Date.now();
700
- const stmt = this.db.prepare(
701
- `DELETE FROM ${this.table} WHERE expiresAt IS NOT NULL AND expiresAt < ?`
702
- );
703
- stmt.run(now);
936
+ const rows = this.db.prepare(`SELECT sessionId, userId, data FROM ${this.table}`).all();
937
+ const deleteStmt = this.db.prepare(`DELETE FROM ${this.table} WHERE sessionId = ? AND userId = ?`);
938
+ for (const row of rows) {
939
+ const session = normalizeStoredSession(JSON.parse(row.data));
940
+ if (isSessionExpired(session)) {
941
+ deleteStmt.run(row.sessionId, row.userId);
942
+ }
943
+ }
944
+ this.db.prepare(
945
+ `DELETE FROM ${this.credentialsTable}
946
+ WHERE NOT EXISTS (
947
+ SELECT 1 FROM ${this.table}
948
+ WHERE ${this.table}.sessionId = ${this.credentialsTable}.sessionId
949
+ AND ${this.table}.userId = ${this.credentialsTable}.userId
950
+ )`
951
+ ).run();
704
952
  }
705
953
  async disconnect() {
706
954
  if (this.db) {
@@ -779,19 +1027,21 @@ function decryptObject(data) {
779
1027
  var SupabaseStorageBackend = class {
780
1028
  constructor(supabase) {
781
1029
  this.supabase = supabase;
782
- __publicField(this, "DEFAULT_TTL", SESSION_TTL_SECONDS);
783
1030
  }
784
1031
  async init() {
785
- const { error } = await this.supabase.from("mcp_sessions").select("session_id").limit(0);
786
- if (error) {
787
- if (error.code === "42P01") {
788
- throw new Error(
789
- '[SupabaseStorage] Table "mcp_sessions" not found in your database. Please run "npx mcp-ts supabase-init" in your project to set up the required table and RLS policies.'
790
- );
791
- }
792
- throw new Error(`[SupabaseStorage] Initialization check failed: ${error.message}`);
1032
+ await this.assertTable("mcp_sessions", "session_id");
1033
+ await this.assertTable("mcp_credentials", "session_id");
1034
+ console.log("[mcp-ts][Storage] Supabase: storage tables verified.");
1035
+ }
1036
+ async assertTable(table, column) {
1037
+ const { error } = await this.supabase.from(table).select(column).limit(0);
1038
+ if (!error) return;
1039
+ if (error.code === "42P01") {
1040
+ throw new Error(
1041
+ `[SupabaseStorage] Table "${table}" not found in your database. Please run "npx mcp-ts supabase-init" to set up the required storage schema.`
1042
+ );
793
1043
  }
794
- console.log('[mcp-ts][Storage] Supabase: \u2713 "mcp_sessions" table verified.');
1044
+ throw new Error(`[SupabaseStorage] Initialization check failed for "${table}": ${error.message}`);
795
1045
  }
796
1046
  generateSessionId() {
797
1047
  return generateSessionId();
@@ -805,37 +1055,49 @@ var SupabaseStorageBackend = class {
805
1055
  transportType: row.transport_type,
806
1056
  callbackUrl: row.callback_url,
807
1057
  createdAt: new Date(row.created_at).getTime(),
1058
+ updatedAt: new Date(row.updated_at ?? row.created_at).getTime(),
1059
+ expiresAt: row.expires_at ? new Date(row.expires_at).getTime() : null,
808
1060
  userId: row.user_id,
809
1061
  headers: decryptObject(row.headers),
810
- active: row.active,
811
- clientInformation: row.client_information,
812
- tokens: decryptObject(row.tokens),
813
- codeVerifier: row.code_verifier,
814
- clientId: row.client_id
1062
+ authUrl: row.auth_url,
1063
+ status: row.status ?? "pending"
1064
+ };
1065
+ }
1066
+ mapRowToCredentials(row, userId, sessionId) {
1067
+ return {
1068
+ sessionId,
1069
+ userId,
1070
+ clientInformation: decryptObject(row?.client_information),
1071
+ tokens: decryptObject(row?.tokens),
1072
+ codeVerifier: decryptObject(row?.code_verifier),
1073
+ clientId: row?.client_id,
1074
+ oauthState: row?.oauth_state
815
1075
  };
816
1076
  }
817
- async create(session, ttl) {
1077
+ hasCredentialData(data) {
1078
+ return "clientInformation" in data || "tokens" in data || "codeVerifier" in data || "clientId" in data || "oauthState" in data;
1079
+ }
1080
+ async create(session) {
818
1081
  const { sessionId, userId } = session;
819
1082
  if (!sessionId || !userId) throw new Error("userId and sessionId required");
820
- const effectiveTtl = ttl ?? this.DEFAULT_TTL;
821
- const expiresAt = new Date(Date.now() + effectiveTtl * 1e3).toISOString();
1083
+ const status = session.status ?? "pending";
1084
+ const createdAt = new Date(session.createdAt || Date.now()).toISOString();
1085
+ const updatedAt = new Date(session.updatedAt ?? session.createdAt ?? Date.now()).toISOString();
1086
+ const expiresAt = resolveSessionExpiresAt(status, new Date(createdAt).getTime());
822
1087
  const { error } = await this.supabase.from("mcp_sessions").insert({
823
1088
  session_id: sessionId,
824
1089
  user_id: userId,
825
- // Maps user_id to userId to support RLS using auth.uid()
826
1090
  server_id: session.serverId,
827
1091
  server_name: session.serverName,
828
1092
  server_url: session.serverUrl,
829
1093
  transport_type: session.transportType,
830
1094
  callback_url: session.callbackUrl,
831
- created_at: new Date(session.createdAt || Date.now()).toISOString(),
1095
+ created_at: createdAt,
1096
+ updated_at: updatedAt,
832
1097
  headers: encryptObject(session.headers),
833
- active: session.active ?? false,
834
- client_information: session.clientInformation,
835
- tokens: encryptObject(session.tokens),
836
- code_verifier: session.codeVerifier,
837
- client_id: session.clientId,
838
- expires_at: expiresAt
1098
+ auth_url: session.authUrl ?? null,
1099
+ status,
1100
+ expires_at: expiresAt === null ? null : new Date(expiresAt).toISOString()
839
1101
  });
840
1102
  if (error) {
841
1103
  if (error.code === "23505") {
@@ -844,11 +1106,8 @@ var SupabaseStorageBackend = class {
844
1106
  throw new Error(`Failed to create session in Supabase: ${error.message}`);
845
1107
  }
846
1108
  }
847
- async update(userId, sessionId, data, ttl) {
848
- const effectiveTtl = ttl ?? this.DEFAULT_TTL;
849
- const expiresAt = new Date(Date.now() + effectiveTtl * 1e3).toISOString();
1109
+ async update(userId, sessionId, data) {
850
1110
  const updateData = {
851
- expires_at: expiresAt,
852
1111
  updated_at: (/* @__PURE__ */ new Date()).toISOString()
853
1112
  };
854
1113
  if ("serverId" in data) updateData.server_id = data.serverId;
@@ -856,20 +1115,50 @@ var SupabaseStorageBackend = class {
856
1115
  if ("serverUrl" in data) updateData.server_url = data.serverUrl;
857
1116
  if ("transportType" in data) updateData.transport_type = data.transportType;
858
1117
  if ("callbackUrl" in data) updateData.callback_url = data.callbackUrl;
859
- if ("active" in data) updateData.active = data.active;
1118
+ if ("status" in data) {
1119
+ const status = data.status ?? "pending";
1120
+ const expiresAt = resolveSessionExpiresAt(status);
1121
+ updateData.status = status;
1122
+ updateData.expires_at = expiresAt === null ? null : new Date(expiresAt).toISOString();
1123
+ }
860
1124
  if ("headers" in data) updateData.headers = encryptObject(data.headers);
861
- if ("clientInformation" in data) updateData.client_information = data.clientInformation;
862
- if ("tokens" in data) updateData.tokens = encryptObject(data.tokens);
863
- if ("codeVerifier" in data) updateData.code_verifier = data.codeVerifier;
864
- if ("clientId" in data) updateData.client_id = data.clientId;
865
- const { data: updatedRows, error } = await this.supabase.from("mcp_sessions").update(updateData).eq("user_id", userId).eq("session_id", sessionId).select("id");
866
- if (error) {
867
- throw new Error(`Failed to update session: ${error.message}`);
1125
+ if ("authUrl" in data) updateData.auth_url = data.authUrl ?? null;
1126
+ const shouldUpdateSession = Object.keys(updateData).some((key) => key !== "updated_at");
1127
+ let updatedRows = null;
1128
+ if (shouldUpdateSession) {
1129
+ const result = await this.supabase.from("mcp_sessions").update(updateData).eq("user_id", userId).eq("session_id", sessionId).select("id");
1130
+ if (result.error) {
1131
+ throw new Error(`Failed to update session: ${result.error.message}`);
1132
+ }
1133
+ updatedRows = result.data;
1134
+ } else {
1135
+ const result = await this.supabase.from("mcp_sessions").select("id").eq("user_id", userId).eq("session_id", sessionId);
1136
+ if (result.error) {
1137
+ throw new Error(`Failed to update session: ${result.error.message}`);
1138
+ }
1139
+ updatedRows = result.data;
868
1140
  }
869
1141
  if (!updatedRows || updatedRows.length === 0) {
870
1142
  throw new Error(`Session ${sessionId} not found for userId ${userId}`);
871
1143
  }
872
1144
  }
1145
+ async patchCredentials(userId, sessionId, data) {
1146
+ if (!this.hasCredentialData(data)) return;
1147
+ const row = {
1148
+ user_id: userId,
1149
+ session_id: sessionId,
1150
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
1151
+ };
1152
+ if ("clientInformation" in data) row.client_information = data.clientInformation == null ? null : encryptObject(data.clientInformation);
1153
+ if ("tokens" in data) row.tokens = data.tokens == null ? null : encryptObject(data.tokens);
1154
+ if ("codeVerifier" in data) row.code_verifier = data.codeVerifier == null ? null : encryptObject(data.codeVerifier);
1155
+ if ("clientId" in data) row.client_id = data.clientId ?? null;
1156
+ if ("oauthState" in data) row.oauth_state = data.oauthState ?? null;
1157
+ const { error } = await this.supabase.from("mcp_credentials").upsert(row, { onConflict: "user_id,session_id" });
1158
+ if (error) {
1159
+ throw new Error(`Failed to update credentials: ${error.message}`);
1160
+ }
1161
+ }
873
1162
  async get(userId, sessionId) {
874
1163
  const { data, error } = await this.supabase.from("mcp_sessions").select("*").eq("user_id", userId).eq("session_id", sessionId).maybeSingle();
875
1164
  if (error) {
@@ -879,6 +1168,21 @@ var SupabaseStorageBackend = class {
879
1168
  if (!data) return null;
880
1169
  return this.mapRowToSessionData(data);
881
1170
  }
1171
+ async getCredentials(userId, sessionId) {
1172
+ const { data, error } = await this.supabase.from("mcp_credentials").select("*").eq("user_id", userId).eq("session_id", sessionId).maybeSingle();
1173
+ if (error) {
1174
+ console.error("[SupabaseStorage] Failed to get credentials:", error);
1175
+ return null;
1176
+ }
1177
+ if (data) {
1178
+ return this.mapRowToCredentials(data, userId, sessionId);
1179
+ }
1180
+ const { data: sessionRows, error: sessionError } = await this.supabase.from("mcp_sessions").select("id").eq("user_id", userId).eq("session_id", sessionId);
1181
+ if (sessionError || !sessionRows || sessionRows.length === 0) {
1182
+ return null;
1183
+ }
1184
+ return { sessionId, userId };
1185
+ }
882
1186
  async list(userId) {
883
1187
  const { data, error } = await this.supabase.from("mcp_sessions").select("*").eq("user_id", userId);
884
1188
  if (error) {
@@ -887,6 +1191,12 @@ var SupabaseStorageBackend = class {
887
1191
  }
888
1192
  return data.map((row) => this.mapRowToSessionData(row));
889
1193
  }
1194
+ async clearCredentials(userId, sessionId) {
1195
+ const { error } = await this.supabase.from("mcp_credentials").delete().eq("user_id", userId).eq("session_id", sessionId);
1196
+ if (error) {
1197
+ throw new Error(`Failed to clear credentials: ${error.message}`);
1198
+ }
1199
+ }
890
1200
  async delete(userId, sessionId) {
891
1201
  const { error } = await this.supabase.from("mcp_sessions").delete().eq("user_id", userId).eq("session_id", sessionId);
892
1202
  if (error) {
@@ -910,15 +1220,24 @@ var SupabaseStorageBackend = class {
910
1220
  return data.map((row) => row.session_id);
911
1221
  }
912
1222
  async clearAll() {
1223
+ const { error: credentialsError } = await this.supabase.from("mcp_credentials").delete().neq("session_id", "");
1224
+ if (credentialsError) {
1225
+ console.error("[SupabaseStorage] Failed to clear credentials:", credentialsError);
1226
+ }
913
1227
  const { error } = await this.supabase.from("mcp_sessions").delete().neq("session_id", "");
914
1228
  if (error) {
915
1229
  console.error("[SupabaseStorage] Failed to clear sessions:", error);
916
1230
  }
917
1231
  }
918
1232
  async cleanupExpired() {
919
- const { error } = await this.supabase.from("mcp_sessions").delete().lt("expires_at", (/* @__PURE__ */ new Date()).toISOString());
920
- if (error) {
921
- console.error("[SupabaseStorage] Failed to cleanup expired sessions:", error);
1233
+ 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());
1234
+ if (transientError) {
1235
+ console.error("[SupabaseStorage] Failed to cleanup expired inactive sessions:", transientError);
1236
+ }
1237
+ const dormantCutoff = new Date(Date.now() - DORMANT_SESSION_EXPIRATION_MS).toISOString();
1238
+ const { error: dormantError } = await this.supabase.from("mcp_sessions").delete().eq("status", "active").lt("updated_at", dormantCutoff);
1239
+ if (dormantError) {
1240
+ console.error("[SupabaseStorage] Failed to cleanup dormant active sessions:", dormantError);
922
1241
  }
923
1242
  }
924
1243
  async disconnect() {
@@ -929,23 +1248,29 @@ var SupabaseStorageBackend = class {
929
1248
  var NeonStorageBackend = class {
930
1249
  constructor(sql, options = {}) {
931
1250
  this.sql = sql;
932
- __publicField(this, "DEFAULT_TTL", SESSION_TTL_SECONDS);
933
1251
  __publicField(this, "tableName");
1252
+ __publicField(this, "credentialsTableName");
934
1253
  const schema = options.schema || "public";
935
1254
  const table = options.table || "mcp_sessions";
1255
+ const credentialsTable = options.credentialsTable || "mcp_credentials";
936
1256
  this.tableName = `${this.quoteIdentifier(schema)}.${this.quoteIdentifier(table)}`;
1257
+ this.credentialsTableName = `${this.quoteIdentifier(schema)}.${this.quoteIdentifier(credentialsTable)}`;
937
1258
  }
938
1259
  async init() {
1260
+ await this.assertTable(this.tableName, "mcp_sessions");
1261
+ await this.assertTable(this.credentialsTableName, "mcp_credentials");
1262
+ console.log("[mcp-ts][Storage] Neon: storage tables verified.");
1263
+ }
1264
+ async assertTable(qualifiedName, displayName) {
939
1265
  const [{ exists } = { exists: null }] = await this.sql.query(
940
1266
  "SELECT to_regclass($1) AS exists",
941
- [this.tableName.replace(/"/g, "")]
1267
+ [qualifiedName.replace(/"/g, "")]
942
1268
  );
943
1269
  if (!exists) {
944
1270
  throw new Error(
945
- '[NeonStorage] Table "mcp_sessions" not found in your database. Please create it using the Neon storage guide in docs/storage-backends/neon.md.'
1271
+ `[NeonStorage] Table "${displayName}" not found in your database. Please create it using the Neon storage guide in docs/storage-backends/neon.md.`
946
1272
  );
947
1273
  }
948
- console.log('[mcp-ts][Storage] Neon: "mcp_sessions" table verified.');
949
1274
  }
950
1275
  generateSessionId() {
951
1276
  return generateSessionId();
@@ -965,20 +1290,35 @@ var NeonStorageBackend = class {
965
1290
  transportType: row.transport_type,
966
1291
  callbackUrl: row.callback_url,
967
1292
  createdAt: new Date(row.created_at).getTime(),
1293
+ updatedAt: new Date(row.updated_at ?? row.created_at).getTime(),
1294
+ expiresAt: row.expires_at ? new Date(row.expires_at).getTime() : null,
968
1295
  userId: row.user_id,
969
1296
  headers: decryptObject(row.headers),
970
- active: row.active ?? false,
971
- clientInformation: row.client_information,
1297
+ authUrl: row.auth_url ?? void 0,
1298
+ status: row.status ?? "pending"
1299
+ };
1300
+ }
1301
+ mapRowToCredentials(row, userId, sessionId) {
1302
+ return {
1303
+ sessionId,
1304
+ userId,
1305
+ clientInformation: decryptObject(row.client_information),
972
1306
  tokens: decryptObject(row.tokens),
973
- codeVerifier: row.code_verifier ?? void 0,
974
- clientId: row.client_id ?? void 0
1307
+ codeVerifier: decryptObject(row.code_verifier),
1308
+ clientId: row.client_id ?? void 0,
1309
+ oauthState: row.oauth_state
975
1310
  };
976
1311
  }
977
- async create(session, ttl) {
1312
+ hasCredentialData(data) {
1313
+ return "clientInformation" in data || "tokens" in data || "codeVerifier" in data || "clientId" in data || "oauthState" in data;
1314
+ }
1315
+ async create(session) {
978
1316
  const { sessionId, userId } = session;
979
1317
  if (!sessionId || !userId) throw new Error("userId and sessionId required");
980
- const effectiveTtl = ttl ?? this.DEFAULT_TTL;
981
- const expiresAt = new Date(Date.now() + effectiveTtl * 1e3).toISOString();
1318
+ const status = session.status ?? "pending";
1319
+ const createdAt = new Date(session.createdAt || Date.now()).toISOString();
1320
+ const updatedAt = new Date(session.updatedAt ?? session.createdAt ?? Date.now()).toISOString();
1321
+ const expiresAt = resolveSessionExpiresAt(status, new Date(createdAt).getTime());
982
1322
  try {
983
1323
  await this.sql.query(
984
1324
  `INSERT INTO ${this.tableName} (
@@ -990,16 +1330,14 @@ var NeonStorageBackend = class {
990
1330
  transport_type,
991
1331
  callback_url,
992
1332
  created_at,
1333
+ updated_at,
993
1334
  headers,
994
- active,
995
- client_information,
996
- tokens,
997
- code_verifier,
998
- client_id,
1335
+ auth_url,
1336
+ status,
999
1337
  expires_at
1000
1338
  ) VALUES (
1001
1339
  $1, $2, $3, $4, $5, $6, $7, $8,
1002
- $9, $10, $11, $12, $13, $14, $15
1340
+ $9, $10, $11, $12, $13
1003
1341
  )`,
1004
1342
  [
1005
1343
  sessionId,
@@ -1009,14 +1347,12 @@ var NeonStorageBackend = class {
1009
1347
  session.serverUrl,
1010
1348
  session.transportType,
1011
1349
  session.callbackUrl,
1012
- new Date(session.createdAt || Date.now()).toISOString(),
1350
+ createdAt,
1351
+ updatedAt,
1013
1352
  encryptObject(session.headers),
1014
- session.active ?? false,
1015
- session.clientInformation,
1016
- encryptObject(session.tokens),
1017
- session.codeVerifier,
1018
- session.clientId,
1019
- expiresAt
1353
+ session.authUrl ?? null,
1354
+ status,
1355
+ expiresAt === null ? null : new Date(expiresAt).toISOString()
1020
1356
  ]
1021
1357
  );
1022
1358
  } catch (error) {
@@ -1026,52 +1362,86 @@ var NeonStorageBackend = class {
1026
1362
  throw new Error(`Failed to create session in Neon: ${error.message}`);
1027
1363
  }
1028
1364
  }
1029
- async update(userId, sessionId, data, ttl) {
1365
+ async update(userId, sessionId, data) {
1030
1366
  const currentSession = await this.get(userId, sessionId);
1031
1367
  if (!currentSession) {
1032
1368
  throw new Error(`Session ${sessionId} not found for userId ${userId}`);
1033
1369
  }
1034
1370
  const updatedSession = { ...currentSession, ...data };
1035
- const effectiveTtl = ttl ?? this.DEFAULT_TTL;
1036
- const expiresAt = new Date(Date.now() + effectiveTtl * 1e3).toISOString();
1037
- const updatedRows = await this.sql.query(
1038
- `UPDATE ${this.tableName}
1039
- SET
1040
- server_id = $1,
1041
- server_name = $2,
1042
- server_url = $3,
1043
- transport_type = $4,
1044
- callback_url = $5,
1045
- active = $6,
1046
- headers = $7,
1047
- client_information = $8,
1048
- tokens = $9,
1049
- code_verifier = $10,
1050
- client_id = $11,
1051
- expires_at = $12,
1052
- updated_at = now()
1053
- WHERE user_id = $13 AND session_id = $14
1054
- RETURNING id`,
1371
+ const status = updatedSession.status ?? "pending";
1372
+ const expiresAt = resolveSessionExpiresAt(status);
1373
+ 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;
1374
+ if (shouldUpdateSession) {
1375
+ const updatedRows = await this.sql.query(
1376
+ `UPDATE ${this.tableName}
1377
+ SET
1378
+ server_id = $1,
1379
+ server_name = $2,
1380
+ server_url = $3,
1381
+ transport_type = $4,
1382
+ callback_url = $5,
1383
+ status = $6,
1384
+ headers = $7,
1385
+ auth_url = $8,
1386
+ expires_at = $9,
1387
+ updated_at = now()
1388
+ WHERE user_id = $10 AND session_id = $11
1389
+ RETURNING id`,
1390
+ [
1391
+ updatedSession.serverId,
1392
+ updatedSession.serverName,
1393
+ updatedSession.serverUrl,
1394
+ updatedSession.transportType,
1395
+ updatedSession.callbackUrl,
1396
+ status,
1397
+ encryptObject(updatedSession.headers),
1398
+ updatedSession.authUrl ?? null,
1399
+ expiresAt === null ? null : new Date(expiresAt).toISOString(),
1400
+ userId,
1401
+ sessionId
1402
+ ]
1403
+ );
1404
+ if (updatedRows.length === 0) {
1405
+ throw new Error(`Session ${sessionId} not found for userId ${userId}`);
1406
+ }
1407
+ }
1408
+ }
1409
+ async patchCredentials(userId, sessionId, data) {
1410
+ if (!this.hasCredentialData(data)) return;
1411
+ await this.sql.query(
1412
+ `INSERT INTO ${this.credentialsTableName} (
1413
+ user_id,
1414
+ session_id,
1415
+ client_information,
1416
+ tokens,
1417
+ code_verifier,
1418
+ client_id,
1419
+ oauth_state,
1420
+ updated_at
1421
+ ) VALUES ($1, $2, $3, $4, $5, $6, $7, now())
1422
+ ON CONFLICT (user_id, session_id)
1423
+ DO UPDATE SET
1424
+ client_information = CASE WHEN $8 THEN EXCLUDED.client_information ELSE ${this.credentialsTableName}.client_information END,
1425
+ tokens = CASE WHEN $9 THEN EXCLUDED.tokens ELSE ${this.credentialsTableName}.tokens END,
1426
+ code_verifier = CASE WHEN $10 THEN EXCLUDED.code_verifier ELSE ${this.credentialsTableName}.code_verifier END,
1427
+ client_id = CASE WHEN $11 THEN EXCLUDED.client_id ELSE ${this.credentialsTableName}.client_id END,
1428
+ oauth_state = CASE WHEN $12 THEN EXCLUDED.oauth_state ELSE ${this.credentialsTableName}.oauth_state END,
1429
+ updated_at = now()`,
1055
1430
  [
1056
- updatedSession.serverId,
1057
- updatedSession.serverName,
1058
- updatedSession.serverUrl,
1059
- updatedSession.transportType,
1060
- updatedSession.callbackUrl,
1061
- updatedSession.active ?? false,
1062
- encryptObject(updatedSession.headers),
1063
- updatedSession.clientInformation,
1064
- encryptObject(updatedSession.tokens),
1065
- updatedSession.codeVerifier,
1066
- updatedSession.clientId,
1067
- expiresAt,
1068
1431
  userId,
1069
- sessionId
1432
+ sessionId,
1433
+ "clientInformation" in data ? data.clientInformation == null ? null : encryptObject(data.clientInformation) : null,
1434
+ "tokens" in data ? data.tokens == null ? null : encryptObject(data.tokens) : null,
1435
+ "codeVerifier" in data ? data.codeVerifier == null ? null : encryptObject(data.codeVerifier) : null,
1436
+ "clientId" in data ? data.clientId ?? null : null,
1437
+ "oauthState" in data ? data.oauthState ?? null : null,
1438
+ "clientInformation" in data,
1439
+ "tokens" in data,
1440
+ "codeVerifier" in data,
1441
+ "clientId" in data,
1442
+ "oauthState" in data
1070
1443
  ]
1071
1444
  );
1072
- if (updatedRows.length === 0) {
1073
- throw new Error(`Session ${sessionId} not found for userId ${userId}`);
1074
- }
1075
1445
  }
1076
1446
  async get(userId, sessionId) {
1077
1447
  try {
@@ -1079,12 +1449,32 @@ var NeonStorageBackend = class {
1079
1449
  `SELECT * FROM ${this.tableName} WHERE user_id = $1 AND session_id = $2`,
1080
1450
  [userId, sessionId]
1081
1451
  );
1082
- return rows[0] ? this.mapRowToSessionData(rows[0]) : null;
1452
+ if (!rows[0]) return null;
1453
+ return this.mapRowToSessionData(rows[0]);
1083
1454
  } catch (error) {
1084
1455
  console.error("[NeonStorage] Failed to get session:", error);
1085
1456
  return null;
1086
1457
  }
1087
1458
  }
1459
+ async getCredentials(userId, sessionId) {
1460
+ try {
1461
+ const credentialRows = await this.sql.query(
1462
+ `SELECT * FROM ${this.credentialsTableName} WHERE user_id = $1 AND session_id = $2`,
1463
+ [userId, sessionId]
1464
+ );
1465
+ if (credentialRows[0]) {
1466
+ return this.mapRowToCredentials(credentialRows[0], userId, sessionId);
1467
+ }
1468
+ const sessionRows = await this.sql.query(
1469
+ `SELECT id FROM ${this.tableName} WHERE user_id = $1 AND session_id = $2`,
1470
+ [userId, sessionId]
1471
+ );
1472
+ return sessionRows[0] ? { sessionId, userId } : null;
1473
+ } catch (error) {
1474
+ console.error("[NeonStorage] Failed to get credentials:", error);
1475
+ return null;
1476
+ }
1477
+ }
1088
1478
  async list(userId) {
1089
1479
  try {
1090
1480
  const rows = await this.sql.query(
@@ -1097,6 +1487,16 @@ var NeonStorageBackend = class {
1097
1487
  return [];
1098
1488
  }
1099
1489
  }
1490
+ async clearCredentials(userId, sessionId) {
1491
+ try {
1492
+ await this.sql.query(
1493
+ `DELETE FROM ${this.credentialsTableName} WHERE user_id = $1 AND session_id = $2`,
1494
+ [userId, sessionId]
1495
+ );
1496
+ } catch (error) {
1497
+ console.error("[NeonStorage] Failed to clear credentials:", error);
1498
+ }
1499
+ }
1100
1500
  async delete(userId, sessionId) {
1101
1501
  try {
1102
1502
  await this.sql.query(
@@ -1132,6 +1532,7 @@ var NeonStorageBackend = class {
1132
1532
  }
1133
1533
  async clearAll() {
1134
1534
  try {
1535
+ await this.sql.query(`DELETE FROM ${this.credentialsTableName}`);
1135
1536
  await this.sql.query(`DELETE FROM ${this.tableName}`);
1136
1537
  } catch (error) {
1137
1538
  console.error("[NeonStorage] Failed to clear sessions:", error);
@@ -1140,9 +1541,17 @@ var NeonStorageBackend = class {
1140
1541
  async cleanupExpired() {
1141
1542
  try {
1142
1543
  await this.sql.query(
1143
- `DELETE FROM ${this.tableName} WHERE expires_at < $1`,
1544
+ `DELETE FROM ${this.tableName}
1545
+ WHERE expires_at IS NOT NULL
1546
+ AND expires_at < $1
1547
+ AND status <> 'active'`,
1144
1548
  [(/* @__PURE__ */ new Date()).toISOString()]
1145
1549
  );
1550
+ await this.sql.query(
1551
+ `DELETE FROM ${this.tableName}
1552
+ WHERE status = 'active' AND updated_at < $1`,
1553
+ [new Date(Date.now() - DORMANT_SESSION_EXPIRATION_MS).toISOString()]
1554
+ );
1146
1555
  } catch (error) {
1147
1556
  console.error("[NeonStorage] Failed to cleanup expired sessions:", error);
1148
1557
  }
@@ -1189,26 +1598,24 @@ function emitSessionMutation(event) {
1189
1598
  function createSessionMutationEvent(prop, args) {
1190
1599
  const timestamp = Date.now();
1191
1600
  if (prop === "create") {
1192
- const [session, ttl] = args;
1601
+ const [session] = args;
1193
1602
  if (!session?.userId || !session?.sessionId) return null;
1194
1603
  return {
1195
1604
  type: "create",
1196
1605
  userId: session.userId,
1197
1606
  sessionId: session.sessionId,
1198
1607
  session,
1199
- ttl,
1200
1608
  timestamp
1201
1609
  };
1202
1610
  }
1203
1611
  if (prop === "update") {
1204
- const [userId, sessionId, patch, ttl] = args;
1612
+ const [userId, sessionId, patch] = args;
1205
1613
  if (!userId || !sessionId) return null;
1206
1614
  return {
1207
1615
  type: "update",
1208
1616
  userId,
1209
1617
  sessionId,
1210
1618
  patch,
1211
- ttl,
1212
1619
  timestamp
1213
1620
  };
1214
1621
  }
@@ -1404,7 +1811,6 @@ var StorageOAuthClientProvider = class {
1404
1811
  __publicField(this, "_authUrl");
1405
1812
  __publicField(this, "_clientId");
1406
1813
  __publicField(this, "onRedirectCallback");
1407
- __publicField(this, "tokenExpiresAt");
1408
1814
  this.userId = options.userId;
1409
1815
  this.serverId = options.serverId;
1410
1816
  this.sessionId = options.sessionId;
@@ -1438,30 +1844,30 @@ var StorageOAuthClientProvider = class {
1438
1844
  this._clientId = clientId_;
1439
1845
  }
1440
1846
  /**
1441
- * Loads OAuth data from the session store
1847
+ * Loads OAuth credentials from the session store
1442
1848
  * @private
1443
1849
  */
1444
- async getSessionData() {
1445
- const data = await sessions.get(this.userId, this.sessionId);
1850
+ async getCredentials() {
1851
+ const data = await sessions.getCredentials(this.userId, this.sessionId);
1446
1852
  if (!data) {
1447
- return {};
1853
+ return { userId: this.userId, sessionId: this.sessionId };
1448
1854
  }
1449
1855
  return data;
1450
1856
  }
1451
1857
  /**
1452
- * Saves OAuth data to the session store
1453
- * @param data - Partial OAuth data to save
1858
+ * Saves OAuth credentials to the session store
1859
+ * @param data - Partial OAuth credentials to save
1454
1860
  * @private
1455
1861
  * @throws Error if session doesn't exist (session must be created by controller layer)
1456
1862
  */
1457
- async saveSessionData(data) {
1458
- await sessions.update(this.userId, this.sessionId, data);
1863
+ async patchCredentials(data) {
1864
+ await sessions.patchCredentials(this.userId, this.sessionId, data);
1459
1865
  }
1460
1866
  /**
1461
1867
  * Retrieves stored OAuth client information
1462
1868
  */
1463
1869
  async clientInformation() {
1464
- const data = await this.getSessionData();
1870
+ const data = await this.getCredentials();
1465
1871
  if (data.clientId && !this._clientId) {
1466
1872
  this._clientId = data.clientId;
1467
1873
  }
@@ -1480,7 +1886,7 @@ var StorageOAuthClientProvider = class {
1480
1886
  * Stores OAuth client information
1481
1887
  */
1482
1888
  async saveClientInformation(clientInformation) {
1483
- await this.saveSessionData({
1889
+ await this.patchCredentials({
1484
1890
  clientInformation,
1485
1891
  clientId: clientInformation.client_id
1486
1892
  });
@@ -1490,29 +1896,58 @@ var StorageOAuthClientProvider = class {
1490
1896
  * Stores OAuth tokens
1491
1897
  */
1492
1898
  async saveTokens(tokens) {
1493
- const data = { tokens };
1494
- if (tokens.expires_in) {
1495
- this.tokenExpiresAt = Date.now() + tokens.expires_in * 1e3 - TOKEN_EXPIRY_BUFFER_MS;
1496
- }
1497
- await this.saveSessionData(data);
1899
+ await this.patchCredentials({ tokens });
1498
1900
  }
1499
1901
  get authUrl() {
1500
1902
  return this._authUrl;
1501
1903
  }
1502
1904
  async state() {
1503
- return this.sessionId;
1905
+ const nonce = nanoid(32);
1906
+ await this.patchCredentials({
1907
+ oauthState: {
1908
+ nonce,
1909
+ sessionId: this.sessionId,
1910
+ serverId: this.serverId,
1911
+ createdAt: Date.now()
1912
+ },
1913
+ codeVerifier: null
1914
+ });
1915
+ return formatOAuthState(nonce, this.sessionId);
1504
1916
  }
1505
- async checkState(_state) {
1506
- const data = await sessions.get(this.userId, this.sessionId);
1917
+ async checkState(state) {
1918
+ const parsed = parseOAuthState(state);
1919
+ if (!parsed) {
1920
+ return { valid: false, error: "Invalid OAuth state" };
1921
+ }
1922
+ if (parsed.sessionId !== this.sessionId) {
1923
+ return { valid: false, error: "OAuth state mismatch" };
1924
+ }
1925
+ const data = await sessions.getCredentials(this.userId, parsed.sessionId);
1507
1926
  if (!data) {
1508
1927
  return { valid: false, error: "Session not found" };
1509
1928
  }
1510
- return { valid: true, serverId: this.serverId };
1929
+ const oauthState = data.oauthState;
1930
+ if (!oauthState) {
1931
+ return { valid: false, error: "OAuth state not found" };
1932
+ }
1933
+ if (oauthState.nonce !== parsed.nonce || oauthState.sessionId !== parsed.sessionId || oauthState.serverId !== this.serverId) {
1934
+ return { valid: false, error: "OAuth state mismatch" };
1935
+ }
1936
+ if (Date.now() - oauthState.createdAt > STATE_EXPIRATION_MS) {
1937
+ return { valid: false, error: "OAuth state expired" };
1938
+ }
1939
+ return { valid: true, serverId: oauthState.serverId };
1511
1940
  }
1512
- async consumeState(_state) {
1941
+ async consumeState(state) {
1942
+ const result = await this.checkState(state);
1943
+ if (!result.valid) {
1944
+ throw new Error(result.error || "Invalid OAuth state");
1945
+ }
1946
+ await this.patchCredentials({ oauthState: null });
1513
1947
  }
1514
1948
  async redirectToAuthorization(authUrl) {
1515
1949
  this._authUrl = authUrl.toString();
1950
+ await sessions.update(this.userId, this.sessionId, { authUrl: this._authUrl });
1516
1951
  if (this.onRedirectCallback) {
1517
1952
  this.onRedirectCallback(authUrl.toString());
1518
1953
  }
@@ -1523,21 +1958,25 @@ var StorageOAuthClientProvider = class {
1523
1958
  } else {
1524
1959
  const updates = {};
1525
1960
  if (scope === "client") {
1526
- updates.clientInformation = void 0;
1527
- updates.clientId = void 0;
1961
+ updates.clientInformation = null;
1962
+ updates.clientId = null;
1528
1963
  } else if (scope === "tokens") {
1529
- updates.tokens = void 0;
1964
+ updates.tokens = null;
1530
1965
  } else if (scope === "verifier") {
1531
- updates.codeVerifier = void 0;
1966
+ updates.codeVerifier = null;
1532
1967
  }
1533
- await this.saveSessionData(updates);
1968
+ await this.patchCredentials(updates);
1534
1969
  }
1535
1970
  }
1536
1971
  async saveCodeVerifier(verifier) {
1537
- await this.saveSessionData({ codeVerifier: verifier });
1972
+ const data = await this.getCredentials();
1973
+ if (data.codeVerifier) {
1974
+ return;
1975
+ }
1976
+ await this.patchCredentials({ codeVerifier: verifier });
1538
1977
  }
1539
1978
  async codeVerifier() {
1540
- const data = await this.getSessionData();
1979
+ const data = await this.getCredentials();
1541
1980
  if (data.clientId && !this._clientId) {
1542
1981
  this._clientId = data.clientId;
1543
1982
  }
@@ -1547,23 +1986,14 @@ var StorageOAuthClientProvider = class {
1547
1986
  return data.codeVerifier;
1548
1987
  }
1549
1988
  async deleteCodeVerifier() {
1550
- await this.saveSessionData({ codeVerifier: void 0 });
1989
+ await this.patchCredentials({ codeVerifier: null });
1551
1990
  }
1552
1991
  async tokens() {
1553
- const data = await this.getSessionData();
1992
+ const data = await this.getCredentials();
1554
1993
  if (data.clientId && !this._clientId) {
1555
1994
  this._clientId = data.clientId;
1556
1995
  }
1557
- return data.tokens;
1558
- }
1559
- isTokenExpired() {
1560
- if (!this.tokenExpiresAt) {
1561
- return false;
1562
- }
1563
- return Date.now() >= this.tokenExpiresAt;
1564
- }
1565
- setTokenExpiresAt(expiresAt) {
1566
- this.tokenExpiresAt = expiresAt;
1996
+ return data.tokens ?? void 0;
1567
1997
  }
1568
1998
  };
1569
1999
 
@@ -1642,14 +2072,7 @@ var RpcErrorCodes = {
1642
2072
  EXECUTION_ERROR: "EXECUTION_ERROR"};
1643
2073
 
1644
2074
  // src/server/mcp/oauth-client.ts
1645
- function isInvalidRefreshTokenError(error) {
1646
- if (!(error instanceof Error)) {
1647
- return false;
1648
- }
1649
- const text = `${error.name} ${error.message}`.toLowerCase();
1650
- 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);
1651
- }
1652
- var MCPClient = class _MCPClient {
2075
+ var MCPClient = class {
1653
2076
  /**
1654
2077
  * Creates a new MCP client instance
1655
2078
  * Can be initialized with minimal options (userId + sessionId) for session restoration
@@ -1824,17 +2247,18 @@ var MCPClient = class _MCPClient {
1824
2247
  }
1825
2248
  this.emitStateChange("INITIALIZING");
1826
2249
  this.emitProgress("Loading session configuration...");
2250
+ let existingSession = null;
1827
2251
  if (!this.serverUrl || !this.callbackUrl || !this.serverId) {
1828
- const sessionData = await sessions.get(this.userId, this.sessionId);
1829
- if (!sessionData) {
2252
+ existingSession = await sessions.get(this.userId, this.sessionId);
2253
+ if (!existingSession) {
1830
2254
  throw new Error(`Session not found: ${this.sessionId}`);
1831
2255
  }
1832
- this.serverUrl = this.serverUrl || sessionData.serverUrl;
1833
- this.callbackUrl = this.callbackUrl || sessionData.callbackUrl;
1834
- this.serverName = this.serverName || sessionData.serverName;
1835
- this.serverId = this.serverId || sessionData.serverId || "unknown";
1836
- this.headers = this.headers || sessionData.headers;
1837
- this.createdAt = sessionData.createdAt;
2256
+ this.serverUrl = this.serverUrl || existingSession.serverUrl;
2257
+ this.callbackUrl = this.callbackUrl || existingSession.callbackUrl;
2258
+ this.serverName = this.serverName || existingSession.serverName;
2259
+ this.serverId = this.serverId || existingSession.serverId || "unknown";
2260
+ this.headers = this.headers || existingSession.headers;
2261
+ this.createdAt = existingSession.createdAt;
1838
2262
  }
1839
2263
  if (!this.serverUrl || !this.callbackUrl || !this.serverId) {
1840
2264
  throw new Error("Missing required connection metadata");
@@ -1878,10 +2302,13 @@ var MCPClient = class _MCPClient {
1878
2302
  }
1879
2303
  );
1880
2304
  }
1881
- const existingSession = await sessions.get(this.userId, this.sessionId);
2305
+ if (existingSession === null) {
2306
+ existingSession = await sessions.get(this.userId, this.sessionId);
2307
+ }
1882
2308
  if (!existingSession && this.serverId && this.serverUrl && this.callbackUrl) {
1883
2309
  this.createdAt = Date.now();
1884
- console.log(`[MCPClient] Creating initial session ${this.sessionId} for OAuth flow`);
2310
+ const updatedAt = this.createdAt;
2311
+ console.log(`[MCPClient] Creating pending session ${this.sessionId} for connection setup`);
1885
2312
  await sessions.create({
1886
2313
  sessionId: this.sessionId,
1887
2314
  userId: this.userId,
@@ -1892,18 +2319,18 @@ var MCPClient = class _MCPClient {
1892
2319
  transportType: this.transportType || "streamable-http",
1893
2320
  headers: this.headers,
1894
2321
  createdAt: this.createdAt,
1895
- active: false
1896
- }, Math.floor(STATE_EXPIRATION_MS / 1e3));
2322
+ updatedAt,
2323
+ status: "pending"
2324
+ });
1897
2325
  }
1898
2326
  }
1899
2327
  /**
1900
2328
  * Saves current session state to the session store
1901
2329
  * Creates new session if it doesn't exist, updates if it does
1902
- * @param ttl - Time-to-live in seconds (defaults to 12hr for connected sessions)
1903
- * @param active - Session status marker used to avoid unnecessary TTL rewrites
2330
+ * @param status - Session lifecycle status used by storage cleanup
1904
2331
  * @private
1905
2332
  */
1906
- async saveSession(ttl = SESSION_TTL_SECONDS, active = true) {
2333
+ async saveSession(status = "active", existingSession) {
1907
2334
  if (!this.sessionId || !this.serverId || !this.serverUrl || !this.callbackUrl) {
1908
2335
  return;
1909
2336
  }
@@ -1917,13 +2344,29 @@ var MCPClient = class _MCPClient {
1917
2344
  transportType: this.transportType || "streamable-http",
1918
2345
  headers: this.headers,
1919
2346
  createdAt: this.createdAt || Date.now(),
1920
- active
2347
+ updatedAt: Date.now(),
2348
+ status
1921
2349
  };
1922
- const existingSession = await sessions.get(this.userId, this.sessionId);
2350
+ if (status === "active") {
2351
+ sessionData.authUrl = null;
2352
+ }
2353
+ if (existingSession === void 0) {
2354
+ existingSession = await sessions.get(this.userId, this.sessionId);
2355
+ }
1923
2356
  if (existingSession) {
1924
- await sessions.update(this.userId, this.sessionId, sessionData, ttl);
2357
+ await sessions.update(this.userId, this.sessionId, sessionData);
1925
2358
  } else {
1926
- await sessions.create(sessionData, ttl);
2359
+ await sessions.create(sessionData);
2360
+ }
2361
+ }
2362
+ /**
2363
+ * Removes transient setup/auth sessions without masking the original error.
2364
+ * @private
2365
+ */
2366
+ async deleteTransientSession() {
2367
+ try {
2368
+ await sessions.delete(this.userId, this.sessionId);
2369
+ } catch {
1927
2370
  }
1928
2371
  }
1929
2372
  /**
@@ -1983,15 +2426,13 @@ var MCPClient = class _MCPClient {
1983
2426
  throw new Error(error);
1984
2427
  }
1985
2428
  try {
1986
- this.emitProgress("Validating OAuth tokens...");
1987
- await this.getValidTokens();
1988
2429
  this.emitStateChange("CONNECTING");
1989
2430
  const { transportType } = await this.tryConnect();
1990
2431
  this.transportType = transportType;
1991
2432
  this.emitStateChange("CONNECTED");
1992
2433
  this.emitProgress("Connected successfully");
1993
- console.log(`[MCPClient] Saving session ${this.sessionId} with 12hr TTL (connect success)`);
1994
- await this.saveSession(SESSION_TTL_SECONDS, true);
2434
+ console.log(`[MCPClient] Saving active session ${this.sessionId} (connect success)`);
2435
+ await this.saveSession("active");
1995
2436
  } catch (error) {
1996
2437
  if (error instanceof UnauthorizedError$1 || error instanceof Error && error.message.toLowerCase().includes("unauthorized")) {
1997
2438
  let authUrl = "";
@@ -2003,15 +2444,12 @@ var MCPClient = class _MCPClient {
2003
2444
  const message = detail.toLowerCase() === "unauthorized" ? "OAuth authorization URL not available" : `OAuth authorization URL not available: ${detail}`;
2004
2445
  this.emitError(message, "auth");
2005
2446
  this.emitStateChange("FAILED");
2006
- try {
2007
- await sessions.delete(this.userId, this.sessionId);
2008
- } catch {
2009
- }
2447
+ await this.deleteTransientSession();
2010
2448
  throw new Error(message);
2011
2449
  }
2012
2450
  this.emitStateChange("AUTHENTICATING");
2013
- console.log(`[MCPClient] Saving session ${this.sessionId} with 10min TTL (OAuth pending)`);
2014
- await this.saveSession(Math.floor(STATE_EXPIRATION_MS / 1e3), false);
2451
+ console.log(`[MCPClient] Saving pending OAuth session ${this.sessionId}`);
2452
+ await this.saveSession("pending");
2015
2453
  if (this.serverId) {
2016
2454
  this._onConnectionEvent.fire({
2017
2455
  type: "auth_required",
@@ -2031,7 +2469,7 @@ var MCPClient = class _MCPClient {
2031
2469
  this.emitStateChange("FAILED");
2032
2470
  try {
2033
2471
  const existingSession = await sessions.get(this.userId, this.sessionId);
2034
- if (!existingSession || existingSession.active !== true) {
2472
+ if (!existingSession || existingSession.status !== "active") {
2035
2473
  await sessions.delete(this.userId, this.sessionId);
2036
2474
  }
2037
2475
  } catch {
@@ -2046,7 +2484,7 @@ var MCPClient = class _MCPClient {
2046
2484
  * @param authCode - Authorization code received from OAuth callback
2047
2485
  */
2048
2486
  // TODO: needs to be optimized
2049
- async finishAuth(authCode) {
2487
+ async finishAuth(authCode, state) {
2050
2488
  this.emitStateChange("AUTHENTICATING");
2051
2489
  this.emitProgress("Exchanging authorization code for tokens...");
2052
2490
  await this.initialize();
@@ -2056,6 +2494,16 @@ var MCPClient = class _MCPClient {
2056
2494
  this.emitStateChange("FAILED");
2057
2495
  throw new Error(error);
2058
2496
  }
2497
+ if (state) {
2498
+ const stateCheck = await this.oauthProvider.checkState(state);
2499
+ if (!stateCheck.valid) {
2500
+ const error = stateCheck.error || "Invalid OAuth state";
2501
+ this.emitError(error, "auth");
2502
+ this.emitStateChange("FAILED");
2503
+ throw new Error(error);
2504
+ }
2505
+ await this.oauthProvider.consumeState(state);
2506
+ }
2059
2507
  const transportsToTry = this.transportType ? [this.transportType] : ["streamable-http", "sse"];
2060
2508
  let lastError;
2061
2509
  let tokensExchanged = false;
@@ -2095,8 +2543,8 @@ var MCPClient = class _MCPClient {
2095
2543
  await this.client.connect(this.transport);
2096
2544
  this.transportType = currentType;
2097
2545
  this.emitStateChange("CONNECTED");
2098
- console.log(`[MCPClient] Updating session ${this.sessionId} to 12hr TTL (OAuth complete)`);
2099
- await this.saveSession(SESSION_TTL_SECONDS, true);
2546
+ console.log(`[MCPClient] Saving active session ${this.sessionId} (OAuth complete)`);
2547
+ await this.saveSession("active");
2100
2548
  return;
2101
2549
  } catch (error) {
2102
2550
  lastError = error;
@@ -2109,12 +2557,14 @@ var MCPClient = class _MCPClient {
2109
2557
  const msg = error instanceof Error ? error.message : "Authentication failed";
2110
2558
  this.emitError(msg, "auth");
2111
2559
  this.emitStateChange("FAILED");
2560
+ await this.deleteTransientSession();
2112
2561
  throw error;
2113
2562
  }
2114
2563
  if (isLastAttempt) {
2115
2564
  const msg = error instanceof Error ? error.message : "Authentication failed";
2116
2565
  this.emitError(msg, "auth");
2117
2566
  this.emitStateChange("FAILED");
2567
+ await this.deleteTransientSession();
2118
2568
  throw error;
2119
2569
  }
2120
2570
  this.emitProgress(`Auth attempt with ${currentType} failed: ${errorMessage}. Retrying...`);
@@ -2124,6 +2574,7 @@ var MCPClient = class _MCPClient {
2124
2574
  const errorMessage = lastError instanceof Error ? lastError.message : "Authentication failed";
2125
2575
  this.emitError(errorMessage, "auth");
2126
2576
  this.emitStateChange("FAILED");
2577
+ await this.deleteTransientSession();
2127
2578
  throw lastError;
2128
2579
  }
2129
2580
  }
@@ -2309,67 +2760,6 @@ var MCPClient = class _MCPClient {
2309
2760
  };
2310
2761
  return await this.client.request(request, ReadResourceResultSchema);
2311
2762
  }
2312
- /**
2313
- * Refreshes the OAuth access token using the refresh token
2314
- * Discovers OAuth metadata from server and exchanges refresh token for new access token
2315
- * @returns True if refresh was successful, false otherwise
2316
- */
2317
- async refreshToken() {
2318
- await this.initialize();
2319
- if (!this.oauthProvider) {
2320
- return false;
2321
- }
2322
- const tokens = await this.oauthProvider.tokens();
2323
- if (!tokens || !tokens.refresh_token) {
2324
- return false;
2325
- }
2326
- const clientInformation = await this.oauthProvider.clientInformation();
2327
- if (!clientInformation) {
2328
- return false;
2329
- }
2330
- try {
2331
- const resourceMetadata = await discoverOAuthProtectedResourceMetadata(this.serverUrl);
2332
- const authServerUrl = resourceMetadata?.authorization_servers?.[0] || this.serverUrl;
2333
- const authMetadata = await discoverAuthorizationServerMetadata(authServerUrl);
2334
- const newTokens = await refreshAuthorization(authServerUrl, {
2335
- metadata: authMetadata,
2336
- clientInformation,
2337
- refreshToken: tokens.refresh_token
2338
- });
2339
- await this.oauthProvider.saveTokens(newTokens);
2340
- return true;
2341
- } catch (error) {
2342
- console.error("[OAuth] Token refresh failed:", error);
2343
- if (isInvalidRefreshTokenError(error)) {
2344
- try {
2345
- await this.oauthProvider.invalidateCredentials?.("tokens");
2346
- this.emitProgress("OAuth refresh token is invalid; requesting reauthorization...");
2347
- } catch (invalidateError) {
2348
- console.warn("[OAuth] Failed to invalidate stale refresh token credentials:", invalidateError);
2349
- }
2350
- }
2351
- return false;
2352
- }
2353
- }
2354
- /**
2355
- * Ensures OAuth tokens are valid, refreshing them if expired
2356
- * Called automatically by connect() - rarely needs to be called manually
2357
- * @returns True if valid tokens are available, false otherwise
2358
- */
2359
- async getValidTokens() {
2360
- await this.initialize();
2361
- if (!this.oauthProvider) {
2362
- return false;
2363
- }
2364
- const tokens = await this.oauthProvider.tokens();
2365
- if (!tokens) {
2366
- return false;
2367
- }
2368
- if (this.oauthProvider.isTokenExpired()) {
2369
- return await this.refreshToken();
2370
- }
2371
- return true;
2372
- }
2373
2763
  /**
2374
2764
  * Reconnects to MCP server using existing OAuth provider from Redis
2375
2765
  * Used for session restoration in serverless environments
@@ -2499,68 +2889,6 @@ var MCPClient = class _MCPClient {
2499
2889
  getSessionId() {
2500
2890
  return this.sessionId;
2501
2891
  }
2502
- /**
2503
- * Gets MCP server configuration for all active user sessions
2504
- * Loads sessions from Redis, validates OAuth tokens, refreshes if expired
2505
- * Returns ready-to-use configuration with valid auth headers
2506
- * @param userId - User ID to fetch sessions for
2507
- * @returns Object keyed by sanitized server labels containing transport, url, headers, etc.
2508
- * @static
2509
- */
2510
- static async getMcpServerConfig(userId) {
2511
- const mcpConfig = {};
2512
- const sessionList = await sessions.list(userId);
2513
- await Promise.all(
2514
- sessionList.map(async (sessionData) => {
2515
- const { sessionId } = sessionData;
2516
- try {
2517
- if (!sessionData.serverId || !sessionData.transportType || !sessionData.serverUrl || !sessionData.callbackUrl) {
2518
- await sessions.delete(userId, sessionId);
2519
- return;
2520
- }
2521
- let headers;
2522
- try {
2523
- const client = new _MCPClient({
2524
- userId,
2525
- sessionId,
2526
- serverId: sessionData.serverId,
2527
- serverUrl: sessionData.serverUrl,
2528
- callbackUrl: sessionData.callbackUrl,
2529
- serverName: sessionData.serverName,
2530
- transportType: sessionData.transportType,
2531
- headers: sessionData.headers
2532
- });
2533
- await client.initialize();
2534
- const hasValidTokens = await client.getValidTokens();
2535
- if (hasValidTokens && client.oauthProvider) {
2536
- const tokens = await client.oauthProvider.tokens();
2537
- if (tokens?.access_token) {
2538
- headers = { Authorization: `Bearer ${tokens.access_token}` };
2539
- }
2540
- }
2541
- } catch (error) {
2542
- console.warn(`[MCP] Failed to get OAuth tokens for ${sessionId}:`, error);
2543
- }
2544
- const label = sanitizeServerLabel(
2545
- sessionData.serverName || sessionData.serverId || "server"
2546
- );
2547
- mcpConfig[label] = {
2548
- transport: sessionData.transportType,
2549
- url: sessionData.serverUrl,
2550
- ...sessionData.serverName && {
2551
- serverName: sessionData.serverName,
2552
- serverLabel: label
2553
- },
2554
- ...headers && { headers }
2555
- };
2556
- } catch (error) {
2557
- await sessions.delete(userId, sessionId);
2558
- console.warn(`[MCP] Failed to process session ${sessionId}:`, error);
2559
- }
2560
- })
2561
- );
2562
- return mcpConfig;
2563
- }
2564
2892
  };
2565
2893
 
2566
2894
  // src/server/mcp/multi-session-client.ts
@@ -2595,18 +2923,13 @@ var MultiSessionClient = class {
2595
2923
  *
2596
2924
  * A session is considered connectable when:
2597
2925
  * - It has a `serverId`, `serverUrl`, and `callbackUrl` (i.e. it was fully initialized)
2598
- * - Its `active` flag is not explicitly `false` sessions with `active: false` are
2599
- * either mid-OAuth flow, auth-pending, or previously failed. We skip those here
2926
+ * - Its status is `active`. Pending sessions are skipped here
2600
2927
  * and let the OAuth flow complete separately before we try to reconnect them.
2601
- *
2602
- * Note: Sessions where `active` is `undefined` (legacy records) are included
2603
- * for backwards compatibility.
2604
2928
  */
2605
2929
  async getActiveSessions() {
2606
2930
  const sessionList = await sessions.list(this.userId);
2607
2931
  const valid = sessionList.filter(
2608
- (s) => s.serverId && s.serverUrl && s.callbackUrl && s.active !== false
2609
- // exclude OAuth-pending / failed sessions
2932
+ (s) => s.serverId && s.serverUrl && s.callbackUrl && s.status === "active"
2610
2933
  );
2611
2934
  return valid;
2612
2935
  }
@@ -2877,7 +3200,8 @@ var SSEConnectionManager = class {
2877
3200
  serverUrl: s.serverUrl,
2878
3201
  transport: s.transportType,
2879
3202
  createdAt: s.createdAt,
2880
- active: s.active !== false
3203
+ updatedAt: s.updatedAt ?? s.createdAt,
3204
+ status: s.status ?? "pending"
2881
3205
  }))
2882
3206
  };
2883
3207
  }
@@ -2893,7 +3217,7 @@ var SSEConnectionManager = class {
2893
3217
  (s) => s.serverId === serverId || s.serverUrl === serverUrl
2894
3218
  );
2895
3219
  if (duplicate) {
2896
- if (duplicate.active === false) {
3220
+ if (duplicate.status === "pending") {
2897
3221
  await this.getSession({ sessionId: duplicate.sessionId });
2898
3222
  return {
2899
3223
  sessionId: duplicate.sessionId,
@@ -3080,7 +3404,10 @@ var SSEConnectionManager = class {
3080
3404
  * Complete OAuth authorization flow
3081
3405
  */
3082
3406
  async finishAuth(params) {
3083
- const { sessionId, code } = params;
3407
+ const { code } = params;
3408
+ const oauthState = params.state;
3409
+ const parsedState = parseOAuthState(oauthState);
3410
+ const sessionId = parsedState?.sessionId || oauthState;
3084
3411
  const session = await sessions.get(this.userId, sessionId);
3085
3412
  if (!session) {
3086
3413
  throw new Error("Session not found");
@@ -3105,7 +3432,7 @@ var SSEConnectionManager = class {
3105
3432
  headers: session.headers
3106
3433
  });
3107
3434
  client.onConnectionEvent((event) => this.emitConnectionEvent(event));
3108
- await client.finishAuth(code);
3435
+ await client.finishAuth(code, oauthState);
3109
3436
  this.clients.set(sessionId, client);
3110
3437
  const tools = await client.listTools();
3111
3438
  return { success: true, toolCount: tools.tools.length };