@mcp-ts/sdk 1.6.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/README.md +12 -6
  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-adapter.js +4 -5
  5. package/dist/adapters/agui-adapter.js.map +1 -1
  6. package/dist/adapters/agui-adapter.mjs +4 -5
  7. package/dist/adapters/agui-adapter.mjs.map +1 -1
  8. package/dist/adapters/agui-middleware.d.mts +3 -3
  9. package/dist/adapters/agui-middleware.d.ts +3 -3
  10. package/dist/adapters/ai-adapter.d.mts +9 -3
  11. package/dist/adapters/ai-adapter.d.ts +9 -3
  12. package/dist/adapters/ai-adapter.js +20 -6
  13. package/dist/adapters/ai-adapter.js.map +1 -1
  14. package/dist/adapters/ai-adapter.mjs +20 -6
  15. package/dist/adapters/ai-adapter.mjs.map +1 -1
  16. package/dist/adapters/langchain-adapter.d.mts +3 -3
  17. package/dist/adapters/langchain-adapter.d.ts +3 -3
  18. package/dist/adapters/langchain-adapter.js +9 -6
  19. package/dist/adapters/langchain-adapter.js.map +1 -1
  20. package/dist/adapters/langchain-adapter.mjs +9 -6
  21. package/dist/adapters/langchain-adapter.mjs.map +1 -1
  22. package/dist/adapters/mastra-adapter.d.mts +1 -1
  23. package/dist/adapters/mastra-adapter.d.ts +1 -1
  24. package/dist/adapters/mastra-adapter.js +5 -1
  25. package/dist/adapters/mastra-adapter.js.map +1 -1
  26. package/dist/adapters/mastra-adapter.mjs +5 -1
  27. package/dist/adapters/mastra-adapter.mjs.map +1 -1
  28. package/dist/bin/mcp-ts.js +7 -1
  29. package/dist/bin/mcp-ts.js.map +1 -1
  30. package/dist/bin/mcp-ts.mjs +7 -1
  31. package/dist/bin/mcp-ts.mjs.map +1 -1
  32. package/dist/client/index.d.mts +2 -2
  33. package/dist/client/index.d.ts +2 -2
  34. package/dist/client/index.js +9 -13
  35. package/dist/client/index.js.map +1 -1
  36. package/dist/client/index.mjs +9 -13
  37. package/dist/client/index.mjs.map +1 -1
  38. package/dist/client/react.d.mts +7 -7
  39. package/dist/client/react.d.ts +7 -7
  40. package/dist/client/react.js +15 -19
  41. package/dist/client/react.js.map +1 -1
  42. package/dist/client/react.mjs +15 -19
  43. package/dist/client/react.mjs.map +1 -1
  44. package/dist/client/vue.d.mts +7 -7
  45. package/dist/client/vue.d.ts +7 -7
  46. package/dist/client/vue.js +14 -18
  47. package/dist/client/vue.js.map +1 -1
  48. package/dist/client/vue.mjs +14 -18
  49. package/dist/client/vue.mjs.map +1 -1
  50. package/dist/{index-DhA-OEAe.d.ts → index-C9gvpxy5.d.ts} +5 -5
  51. package/dist/{index-bFL4ZF2N.d.mts → index-eaH14_5u.d.mts} +5 -5
  52. package/dist/index.d.mts +6 -6
  53. package/dist/index.d.ts +6 -6
  54. package/dist/index.js +616 -370
  55. package/dist/index.js.map +1 -1
  56. package/dist/index.mjs +615 -370
  57. package/dist/index.mjs.map +1 -1
  58. package/dist/{multi-session-client-CHE8QpVE.d.ts → multi-session-client-BYtguGJm.d.ts} +22 -22
  59. package/dist/{multi-session-client-CQsRbxYI.d.mts → multi-session-client-DYNe6az3.d.mts} +22 -22
  60. package/dist/server/index.d.mts +31 -34
  61. package/dist/server/index.d.ts +31 -34
  62. package/dist/server/index.js +531 -256
  63. package/dist/server/index.js.map +1 -1
  64. package/dist/server/index.mjs +530 -256
  65. package/dist/server/index.mjs.map +1 -1
  66. package/dist/shared/index.d.mts +5 -5
  67. package/dist/shared/index.d.ts +5 -5
  68. package/dist/shared/index.js +76 -101
  69. package/dist/shared/index.js.map +1 -1
  70. package/dist/shared/index.mjs +76 -101
  71. package/dist/shared/index.mjs.map +1 -1
  72. package/dist/{tool-router-Dh2804tM.d.ts → tool-router-Ddtybmr0.d.ts} +71 -73
  73. package/dist/{tool-router-BVaV1udm.d.mts → tool-router-Dnd6IOKC.d.mts} +71 -73
  74. package/dist/{types-rIuN1CQi.d.mts → types-BCAG20P6.d.mts} +4 -4
  75. package/dist/{types-rIuN1CQi.d.ts → types-BCAG20P6.d.ts} +4 -4
  76. package/dist/{utils-0qmYrqoa.d.mts → utils-DELRKQPU.d.mts} +1 -1
  77. package/dist/{utils-0qmYrqoa.d.ts → utils-DELRKQPU.d.ts} +1 -1
  78. package/migrations/neon/20260513010000_install_mcp_sessions.sql +69 -0
  79. package/migrations/neon/20260513020000_add_session_cleanup_cron.sql +35 -0
  80. package/{supabase/migrations → migrations/supabase}/20260330195700_install_mcp_sessions.sql +7 -9
  81. package/package.json +14 -5
  82. package/src/adapters/ai-adapter.ts +30 -1
  83. package/src/adapters/langchain-adapter.ts +6 -2
  84. package/src/adapters/mastra-adapter.ts +6 -2
  85. package/src/bin/mcp-ts.ts +8 -1
  86. package/src/client/core/app-host.ts +1 -1
  87. package/src/client/core/sse-client.ts +12 -14
  88. package/src/client/core/types.ts +1 -1
  89. package/src/client/react/use-mcp-apps.tsx +1 -1
  90. package/src/client/react/use-mcp.ts +11 -11
  91. package/src/client/vue/use-mcp.ts +10 -10
  92. package/src/server/handlers/nextjs-handler.ts +18 -15
  93. package/src/server/handlers/sse-handler.ts +29 -29
  94. package/src/server/index.ts +1 -1
  95. package/src/server/mcp/multi-session-client.ts +17 -17
  96. package/src/server/mcp/oauth-client.ts +37 -37
  97. package/src/server/mcp/storage-oauth-provider.ts +17 -17
  98. package/src/server/storage/file-backend.ts +25 -25
  99. package/src/server/storage/index.ts +67 -10
  100. package/src/server/storage/memory-backend.ts +34 -34
  101. package/src/server/storage/neon-backend.ts +281 -0
  102. package/src/server/storage/redis-backend.ts +64 -64
  103. package/src/server/storage/sqlite-backend.ts +33 -33
  104. package/src/server/storage/supabase-backend.ts +23 -24
  105. package/src/server/storage/types.ts +18 -21
  106. package/src/shared/errors.ts +1 -1
  107. package/src/shared/index.ts +1 -2
  108. package/src/shared/meta-tools.ts +4 -6
  109. package/src/shared/schema-compressor.ts +2 -42
  110. package/src/shared/tool-index.ts +89 -84
  111. package/src/shared/tool-router.ts +0 -24
  112. package/src/shared/types.ts +4 -4
  113. /package/{supabase/migrations → migrations/supabase}/20260421010000_add_session_cleanup_cron.sql +0 -0
@@ -0,0 +1,281 @@
1
+ import type { SessionStore, Session } from './types.js';
2
+ import { SESSION_TTL_SECONDS } from '../../shared/constants.js';
3
+ import { generateSessionId } from '../../shared/utils.js';
4
+ import { encryptObject, decryptObject } from './crypto.js';
5
+
6
+ export interface NeonStorageOptions {
7
+ schema?: string;
8
+ table?: string;
9
+ }
10
+
11
+ type NeonSql = {
12
+ query(queryWithPlaceholders: string, params?: unknown[]): Promise<any[]>;
13
+ };
14
+
15
+ type NeonSessionRow = {
16
+ session_id: string;
17
+ server_id?: string | null;
18
+ server_name?: string | null;
19
+ server_url: string;
20
+ transport_type: 'sse' | 'streamable-http';
21
+ callback_url: string;
22
+ created_at: string | Date;
23
+ user_id: string;
24
+ headers?: unknown;
25
+ active?: boolean | null;
26
+ client_information?: unknown;
27
+ tokens?: unknown;
28
+ code_verifier?: string | null;
29
+ client_id?: string | null;
30
+ };
31
+
32
+ export class NeonStorageBackend implements SessionStore {
33
+ private readonly DEFAULT_TTL = SESSION_TTL_SECONDS;
34
+ private readonly tableName: string;
35
+
36
+ constructor(
37
+ private readonly sql: NeonSql,
38
+ options: NeonStorageOptions = {}
39
+ ) {
40
+ const schema = options.schema || 'public';
41
+ const table = options.table || 'mcp_sessions';
42
+ this.tableName = `${this.quoteIdentifier(schema)}.${this.quoteIdentifier(table)}`;
43
+ }
44
+
45
+ async init(): Promise<void> {
46
+ const [{ exists } = { exists: null }] = await this.sql.query(
47
+ 'SELECT to_regclass($1) AS exists',
48
+ [this.tableName.replace(/"/g, '')]
49
+ ) as Array<{ exists: string | null }>;
50
+
51
+ if (!exists) {
52
+ throw new Error(
53
+ '[NeonStorage] Table "mcp_sessions" not found in your database. ' +
54
+ 'Please create it using the Neon storage guide in docs/storage-backends/neon.md.'
55
+ );
56
+ }
57
+
58
+ console.log('[mcp-ts][Storage] Neon: "mcp_sessions" table verified.');
59
+ }
60
+
61
+ generateSessionId(): string {
62
+ return generateSessionId();
63
+ }
64
+
65
+ private quoteIdentifier(identifier: string): string {
66
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(identifier)) {
67
+ throw new Error(`Invalid Neon storage identifier: ${identifier}`);
68
+ }
69
+ return `"${identifier}"`;
70
+ }
71
+
72
+ private mapRowToSessionData(row: NeonSessionRow): Session {
73
+ return {
74
+ sessionId: row.session_id,
75
+ serverId: row.server_id ?? undefined,
76
+ serverName: row.server_name ?? undefined,
77
+ serverUrl: row.server_url,
78
+ transportType: row.transport_type,
79
+ callbackUrl: row.callback_url,
80
+ createdAt: new Date(row.created_at).getTime(),
81
+ userId: row.user_id,
82
+ headers: decryptObject(row.headers),
83
+ active: row.active ?? false,
84
+ clientInformation: row.client_information as Session['clientInformation'],
85
+ tokens: decryptObject(row.tokens),
86
+ codeVerifier: row.code_verifier ?? undefined,
87
+ clientId: row.client_id ?? undefined,
88
+ };
89
+ }
90
+
91
+ async create(session: Session, ttl?: number): Promise<void> {
92
+ const { sessionId, userId } = session;
93
+ if (!sessionId || !userId) throw new Error('userId and sessionId required');
94
+
95
+ const effectiveTtl = ttl ?? this.DEFAULT_TTL;
96
+ const expiresAt = new Date(Date.now() + effectiveTtl * 1000).toISOString();
97
+
98
+ try {
99
+ await this.sql.query(
100
+ `INSERT INTO ${this.tableName} (
101
+ session_id,
102
+ user_id,
103
+ server_id,
104
+ server_name,
105
+ server_url,
106
+ transport_type,
107
+ callback_url,
108
+ created_at,
109
+ headers,
110
+ active,
111
+ client_information,
112
+ tokens,
113
+ code_verifier,
114
+ client_id,
115
+ expires_at
116
+ ) VALUES (
117
+ $1, $2, $3, $4, $5, $6, $7, $8,
118
+ $9, $10, $11, $12, $13, $14, $15
119
+ )`,
120
+ [
121
+ sessionId,
122
+ userId,
123
+ session.serverId,
124
+ session.serverName,
125
+ session.serverUrl,
126
+ session.transportType,
127
+ session.callbackUrl,
128
+ new Date(session.createdAt || Date.now()).toISOString(),
129
+ encryptObject(session.headers),
130
+ session.active ?? false,
131
+ session.clientInformation,
132
+ encryptObject(session.tokens),
133
+ session.codeVerifier,
134
+ session.clientId,
135
+ expiresAt,
136
+ ]
137
+ );
138
+ } catch (error: any) {
139
+ if (error.code === '23505') {
140
+ throw new Error(`Session ${sessionId} already exists`);
141
+ }
142
+ throw new Error(`Failed to create session in Neon: ${error.message}`);
143
+ }
144
+ }
145
+
146
+ async update(userId: string, sessionId: string, data: Partial<Session>, ttl?: number): Promise<void> {
147
+ const currentSession = await this.get(userId, sessionId);
148
+ if (!currentSession) {
149
+ throw new Error(`Session ${sessionId} not found for userId ${userId}`);
150
+ }
151
+
152
+ const updatedSession = { ...currentSession, ...data };
153
+ const effectiveTtl = ttl ?? this.DEFAULT_TTL;
154
+ const expiresAt = new Date(Date.now() + effectiveTtl * 1000).toISOString();
155
+
156
+ const updatedRows = await this.sql.query(
157
+ `UPDATE ${this.tableName}
158
+ SET
159
+ server_id = $1,
160
+ server_name = $2,
161
+ server_url = $3,
162
+ transport_type = $4,
163
+ callback_url = $5,
164
+ active = $6,
165
+ headers = $7,
166
+ client_information = $8,
167
+ tokens = $9,
168
+ code_verifier = $10,
169
+ client_id = $11,
170
+ expires_at = $12,
171
+ updated_at = now()
172
+ WHERE user_id = $13 AND session_id = $14
173
+ RETURNING id`,
174
+ [
175
+ updatedSession.serverId,
176
+ updatedSession.serverName,
177
+ updatedSession.serverUrl,
178
+ updatedSession.transportType,
179
+ updatedSession.callbackUrl,
180
+ updatedSession.active ?? false,
181
+ encryptObject(updatedSession.headers),
182
+ updatedSession.clientInformation,
183
+ encryptObject(updatedSession.tokens),
184
+ updatedSession.codeVerifier,
185
+ updatedSession.clientId,
186
+ expiresAt,
187
+ userId,
188
+ sessionId,
189
+ ]
190
+ ) as Array<{ id: string }>;
191
+
192
+ if (updatedRows.length === 0) {
193
+ throw new Error(`Session ${sessionId} not found for userId ${userId}`);
194
+ }
195
+ }
196
+
197
+ async get(userId: string, sessionId: string): Promise<Session | null> {
198
+ try {
199
+ const rows = await this.sql.query(
200
+ `SELECT * FROM ${this.tableName} WHERE user_id = $1 AND session_id = $2`,
201
+ [userId, sessionId]
202
+ ) as NeonSessionRow[];
203
+ return rows[0] ? this.mapRowToSessionData(rows[0]) : null;
204
+ } catch (error) {
205
+ console.error('[NeonStorage] Failed to get session:', error);
206
+ return null;
207
+ }
208
+ }
209
+
210
+ async list(userId: string): Promise<Session[]> {
211
+ try {
212
+ const rows = await this.sql.query(
213
+ `SELECT * FROM ${this.tableName} WHERE user_id = $1`,
214
+ [userId]
215
+ ) as NeonSessionRow[];
216
+ return rows.map((row) => this.mapRowToSessionData(row));
217
+ } catch (error) {
218
+ console.error(`[NeonStorage] Failed to get session data for ${userId}:`, error);
219
+ return [];
220
+ }
221
+ }
222
+
223
+ async delete(userId: string, sessionId: string): Promise<void> {
224
+ try {
225
+ await this.sql.query(
226
+ `DELETE FROM ${this.tableName} WHERE user_id = $1 AND session_id = $2`,
227
+ [userId, sessionId]
228
+ );
229
+ } catch (error) {
230
+ console.error('[NeonStorage] Failed to remove session:', error);
231
+ }
232
+ }
233
+
234
+ async listIds(userId: string): Promise<string[]> {
235
+ try {
236
+ const rows = await this.sql.query(
237
+ `SELECT session_id FROM ${this.tableName} WHERE user_id = $1`,
238
+ [userId]
239
+ ) as Array<{ session_id: string }>;
240
+ return rows.map((row) => row.session_id);
241
+ } catch (error) {
242
+ console.error(`[NeonStorage] Failed to get sessions for ${userId}:`, error);
243
+ return [];
244
+ }
245
+ }
246
+
247
+ async listAllIds(): Promise<string[]> {
248
+ try {
249
+ const rows = await this.sql.query(
250
+ `SELECT session_id FROM ${this.tableName}`
251
+ ) as Array<{ session_id: string }>;
252
+ return rows.map((row) => row.session_id);
253
+ } catch (error) {
254
+ console.error('[NeonStorage] Failed to get all sessions:', error);
255
+ return [];
256
+ }
257
+ }
258
+
259
+ async clearAll(): Promise<void> {
260
+ try {
261
+ await this.sql.query(`DELETE FROM ${this.tableName}`);
262
+ } catch (error) {
263
+ console.error('[NeonStorage] Failed to clear sessions:', error);
264
+ }
265
+ }
266
+
267
+ async cleanupExpired(): Promise<void> {
268
+ try {
269
+ await this.sql.query(
270
+ `DELETE FROM ${this.tableName} WHERE expires_at < $1`,
271
+ [new Date().toISOString()]
272
+ );
273
+ } catch (error) {
274
+ console.error('[NeonStorage] Failed to cleanup expired sessions:', error);
275
+ }
276
+ }
277
+
278
+ async disconnect(): Promise<void> {
279
+ // Neon HTTP queries do not hold a persistent connection.
280
+ }
281
+ }
@@ -1,16 +1,16 @@
1
1
  import type { Redis } from 'ioredis';
2
- import { StorageBackend, SessionData } from './types.js';
2
+ import type { SessionStore, Session } from './types.js';
3
3
  import { SESSION_TTL_SECONDS } from '../../shared/constants.js';
4
4
  import { generateSessionId } from '../../shared/utils.js';
5
5
 
6
6
  /**
7
- * Redis implementation of StorageBackend
7
+ * Redis implementation of SessionStore
8
8
  */
9
- export class RedisStorageBackend implements StorageBackend {
9
+ export class RedisStorageBackend implements SessionStore {
10
10
  private readonly DEFAULT_TTL = SESSION_TTL_SECONDS;
11
11
  private readonly KEY_PREFIX = 'mcp:session:';
12
- private readonly IDENTITY_KEY_PREFIX = 'mcp:identity:';
13
- private readonly IDENTITY_KEY_SUFFIX = ':sessions';
12
+ private readonly USER_ID_KEY_PREFIX = 'mcp:userId:';
13
+ private readonly USER_ID_KEY_SUFFIX = ':sessions';
14
14
 
15
15
  constructor(private redis: Redis) { }
16
16
 
@@ -19,7 +19,7 @@ export class RedisStorageBackend implements StorageBackend {
19
19
  await this.redis.ping();
20
20
  console.log('[mcp-ts][Storage] Redis: ✓ Connected to server.');
21
21
  } catch (error: any) {
22
- throw new Error(`[RedisStorage] Failed to connect to Redis: ${error.message}`);
22
+ throw new Error(`[RedisStorageBackend] Failed to connect to Redis: ${error.message}`);
23
23
  }
24
24
  }
25
25
 
@@ -27,22 +27,22 @@ export class RedisStorageBackend implements StorageBackend {
27
27
  * Generates Redis key for a specific session
28
28
  * @private
29
29
  */
30
- private getSessionKey(identity: string, sessionId: string): string {
31
- return `${this.KEY_PREFIX}${identity}:${sessionId}`;
30
+ private getSessionKey(userId: string, sessionId: string): string {
31
+ return `${this.KEY_PREFIX}${userId}:${sessionId}`;
32
32
  }
33
33
 
34
34
  /**
35
- * Generates Redis key for tracking all sessions for an identity
35
+ * Generates Redis key for tracking all sessions for a user
36
36
  * @private
37
37
  */
38
- private getIdentityKey(identity: string): string {
39
- return `${this.IDENTITY_KEY_PREFIX}${identity}${this.IDENTITY_KEY_SUFFIX}`;
38
+ private getUserIdKey(userId: string): string {
39
+ return `${this.USER_ID_KEY_PREFIX}${userId}${this.USER_ID_KEY_SUFFIX}`;
40
40
  }
41
41
 
42
- private parseIdentityFromKey(identityKey: string): string {
43
- return identityKey.slice(
44
- this.IDENTITY_KEY_PREFIX.length,
45
- identityKey.length - this.IDENTITY_KEY_SUFFIX.length
42
+ private parseUserIdFromKey(userIdKey: string): string {
43
+ return userIdKey.slice(
44
+ this.USER_ID_KEY_PREFIX.length,
45
+ userIdKey.length - this.USER_ID_KEY_SUFFIX.length
46
46
  );
47
47
  }
48
48
 
@@ -67,7 +67,7 @@ export class RedisStorageBackend implements StorageBackend {
67
67
  }
68
68
  } while (cursor !== '0');
69
69
  } catch (error) {
70
- console.warn('[RedisStorage] SCAN failed, falling back to KEYS:', error);
70
+ console.warn('[RedisStorageBackend] SCAN failed, falling back to KEYS:', error);
71
71
  return await this.redis.keys(pattern);
72
72
  }
73
73
 
@@ -78,12 +78,12 @@ export class RedisStorageBackend implements StorageBackend {
78
78
  return generateSessionId();
79
79
  }
80
80
 
81
- async createSession(session: SessionData, ttl?: number): Promise<void> {
82
- const { sessionId, identity } = session;
83
- if (!sessionId || !identity) throw new Error('identity and sessionId required');
81
+ async create(session: Session, ttl?: number): Promise<void> {
82
+ const { sessionId, userId } = session;
83
+ if (!sessionId || !userId) throw new Error('userId and sessionId required');
84
84
 
85
- const sessionKey = this.getSessionKey(identity, sessionId);
86
- const identityKey = this.getIdentityKey(identity);
85
+ const sessionKey = this.getSessionKey(userId, sessionId);
86
+ const userIdKey = this.getUserIdKey(userId);
87
87
  const effectiveTtl = ttl ?? this.DEFAULT_TTL;
88
88
 
89
89
  /** ioredis syntax: set(key, val, 'EX', ttl, 'NX') */
@@ -99,10 +99,10 @@ export class RedisStorageBackend implements StorageBackend {
99
99
  throw new Error(`Session ${sessionId} already exists`);
100
100
  }
101
101
 
102
- await this.redis.sadd(identityKey, sessionId);
102
+ await this.redis.sadd(userIdKey, sessionId);
103
103
  }
104
- async updateSession(identity: string, sessionId: string, data: Partial<SessionData>, ttl?: number): Promise<void> {
105
- const sessionKey = this.getSessionKey(identity, sessionId);
104
+ async update(userId: string, sessionId: string, data: Partial<Session>, ttl?: number): Promise<void> {
105
+ const sessionKey = this.getSessionKey(userId, sessionId);
106
106
  const effectiveTtl = ttl ?? this.DEFAULT_TTL;
107
107
 
108
108
  /** Lua script for atomic parsing, merging, and saving */
@@ -132,70 +132,70 @@ export class RedisStorageBackend implements StorageBackend {
132
132
  );
133
133
 
134
134
  if (result === 0) {
135
- throw new Error(`Session ${sessionId} not found for identity ${identity}`);
135
+ throw new Error(`Session ${sessionId} not found for userId ${userId}`);
136
136
  }
137
137
  }
138
138
 
139
- async getSession(identity: string, sessionId: string): Promise<SessionData | null> {
139
+ async get(userId: string, sessionId: string): Promise<Session | null> {
140
140
  try {
141
- const sessionKey = this.getSessionKey(identity, sessionId);
141
+ const sessionKey = this.getSessionKey(userId, sessionId);
142
142
  const sessionDataStr = await this.redis.get(sessionKey);
143
143
 
144
144
  if (!sessionDataStr) {
145
145
  return null;
146
146
  }
147
147
 
148
- const sessionData: SessionData = JSON.parse(sessionDataStr);
149
- return sessionData;
148
+ const Session: Session = JSON.parse(sessionDataStr);
149
+ return Session;
150
150
  } catch (error) {
151
- console.error('[RedisStorage] Failed to get session:', error);
151
+ console.error('[RedisStorageBackend] Failed to get session:', error);
152
152
  return null;
153
153
  }
154
154
  }
155
155
 
156
- async getIdentityMcpSessions(identity: string): Promise<string[]> {
157
- const sessions = await this.getIdentitySessionsData(identity);
156
+ async listIds(userId: string): Promise<string[]> {
157
+ const sessions = await this.list(userId);
158
158
  return sessions.map((session) => session.sessionId);
159
159
  }
160
160
 
161
- async getIdentitySessionsData(identity: string): Promise<SessionData[]> {
161
+ async list(userId: string): Promise<Session[]> {
162
162
  try {
163
- const identityKey = this.getIdentityKey(identity);
164
- const sessionIds = await this.redis.smembers(identityKey);
163
+ const userIdKey = this.getUserIdKey(userId);
164
+ const sessionIds = await this.redis.smembers(userIdKey);
165
165
  if (sessionIds.length === 0) return [];
166
166
 
167
167
  const results = await Promise.all(
168
168
  sessionIds.map(async (sessionId) => {
169
- const data = await this.redis.get(this.getSessionKey(identity, sessionId));
170
- return data ? (JSON.parse(data) as SessionData) : null;
169
+ const data = await this.redis.get(this.getSessionKey(userId, sessionId));
170
+ return data ? (JSON.parse(data) as Session) : null;
171
171
  })
172
172
  );
173
173
 
174
174
  const staleSessionIds = sessionIds.filter((_, index) => results[index] === null);
175
175
  if (staleSessionIds.length > 0) {
176
- await this.redis.srem(identityKey, ...staleSessionIds);
176
+ await this.redis.srem(userIdKey, ...staleSessionIds);
177
177
  }
178
178
 
179
- return results.filter((session): session is SessionData => session !== null);
179
+ return results.filter((session): session is Session => session !== null);
180
180
  } catch (error) {
181
- console.error(`[RedisStorage] Failed to get session data for ${identity}:`, error);
181
+ console.error(`[RedisStorageBackend] Failed to get session data for ${userId}:`, error);
182
182
  return [];
183
183
  }
184
184
  }
185
185
 
186
- async removeSession(identity: string, sessionId: string): Promise<void> {
186
+ async delete(userId: string, sessionId: string): Promise<void> {
187
187
  try {
188
- const sessionKey = this.getSessionKey(identity, sessionId);
189
- const identityKey = this.getIdentityKey(identity);
188
+ const sessionKey = this.getSessionKey(userId, sessionId);
189
+ const userIdKey = this.getUserIdKey(userId);
190
190
 
191
- await this.redis.srem(identityKey, sessionId);
191
+ await this.redis.srem(userIdKey, sessionId);
192
192
  await this.redis.del(sessionKey);
193
193
  } catch (error) {
194
- console.error('[RedisStorage] Failed to remove session:', error);
194
+ console.error('[RedisStorageBackend] Failed to remove session:', error);
195
195
  }
196
196
  }
197
197
 
198
- async getAllSessionIds(): Promise<string[]> {
198
+ async listAllIds(): Promise<string[]> {
199
199
  try {
200
200
  const keys = await this.scanKeys(`${this.KEY_PREFIX}*`);
201
201
  const sessions = await Promise.all(
@@ -206,9 +206,9 @@ export class RedisStorageBackend implements StorageBackend {
206
206
  }
207
207
 
208
208
  try {
209
- return (JSON.parse(data) as SessionData).sessionId;
209
+ return (JSON.parse(data) as Session).sessionId;
210
210
  } catch (error) {
211
- console.error('[RedisStorage] Failed to parse session while listing all session IDs:', error);
211
+ console.error('[RedisStorageBackend] Failed to parse session while listing all session IDs:', error);
212
212
  return null;
213
213
  }
214
214
  })
@@ -216,7 +216,7 @@ export class RedisStorageBackend implements StorageBackend {
216
216
 
217
217
  return sessions.filter((sessionId): sessionId is string => sessionId !== null);
218
218
  } catch (error) {
219
- console.error('[RedisStorage] Failed to get all sessions:', error);
219
+ console.error('[RedisStorageBackend] Failed to get all sessions:', error);
220
220
  return [];
221
221
  }
222
222
  }
@@ -224,45 +224,45 @@ export class RedisStorageBackend implements StorageBackend {
224
224
  async clearAll(): Promise<void> {
225
225
  try {
226
226
  const keys = await this.scanKeys(`${this.KEY_PREFIX}*`);
227
- const identityKeys = await this.scanKeys(`${this.IDENTITY_KEY_PREFIX}*${this.IDENTITY_KEY_SUFFIX}`);
228
- const allKeys = [...keys, ...identityKeys];
227
+ const userIdKeys = await this.scanKeys(`${this.USER_ID_KEY_PREFIX}*${this.USER_ID_KEY_SUFFIX}`);
228
+ const allKeys = [...keys, ...userIdKeys];
229
229
  if (allKeys.length > 0) {
230
230
  await this.redis.del(...allKeys);
231
231
  }
232
232
  } catch (error) {
233
- console.error('[RedisStorage] Failed to clear sessions:', error);
233
+ console.error('[RedisStorageBackend] Failed to clear sessions:', error);
234
234
  }
235
235
  }
236
236
 
237
- async cleanupExpiredSessions(): Promise<void> {
237
+ async cleanupExpired(): Promise<void> {
238
238
  try {
239
- const identityKeys = await this.scanKeys(`${this.IDENTITY_KEY_PREFIX}*${this.IDENTITY_KEY_SUFFIX}`);
239
+ const userIdKeys = await this.scanKeys(`${this.USER_ID_KEY_PREFIX}*${this.USER_ID_KEY_SUFFIX}`);
240
240
 
241
- for (const identityKey of identityKeys) {
242
- const identity = this.parseIdentityFromKey(identityKey);
243
- const sessionIds = await this.redis.smembers(identityKey);
241
+ for (const userIdKey of userIdKeys) {
242
+ const userId = this.parseUserIdFromKey(userIdKey);
243
+ const sessionIds = await this.redis.smembers(userIdKey);
244
244
 
245
245
  if (sessionIds.length === 0) {
246
- await this.redis.del(identityKey);
246
+ await this.redis.del(userIdKey);
247
247
  continue;
248
248
  }
249
249
 
250
250
  const existenceChecks = await Promise.all(
251
- sessionIds.map((sessionId) => this.redis.exists(this.getSessionKey(identity, sessionId)))
251
+ sessionIds.map((sessionId) => this.redis.exists(this.getSessionKey(userId, sessionId)))
252
252
  );
253
253
 
254
254
  const staleSessionIds = sessionIds.filter((_, index) => existenceChecks[index] === 0);
255
255
  if (staleSessionIds.length > 0) {
256
- await this.redis.srem(identityKey, ...staleSessionIds);
256
+ await this.redis.srem(userIdKey, ...staleSessionIds);
257
257
  }
258
258
 
259
- const remainingCount = await this.redis.scard(identityKey);
259
+ const remainingCount = await this.redis.scard(userIdKey);
260
260
  if (remainingCount === 0) {
261
- await this.redis.del(identityKey);
261
+ await this.redis.del(userIdKey);
262
262
  }
263
263
  }
264
264
  } catch (error) {
265
- console.error('[RedisStorage] Failed to cleanup expired sessions:', error);
265
+ console.error('[RedisStorageBackend] Failed to cleanup expired sessions:', error);
266
266
  }
267
267
  }
268
268
 
@@ -270,7 +270,7 @@ export class RedisStorageBackend implements StorageBackend {
270
270
  try {
271
271
  await this.redis.quit();
272
272
  } catch (error) {
273
- console.error('[RedisStorage] Failed to disconnect:', error);
273
+ console.error('[RedisStorageBackend] Failed to disconnect:', error);
274
274
  }
275
275
  }
276
276
  }