@mcp-ts/sdk 1.3.7 → 1.3.9

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 (61) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +398 -404
  3. package/dist/adapters/agui-middleware.js.map +1 -1
  4. package/dist/adapters/agui-middleware.mjs.map +1 -1
  5. package/dist/bin/mcp-ts.js +0 -0
  6. package/dist/bin/mcp-ts.js.map +1 -1
  7. package/dist/bin/mcp-ts.mjs +0 -0
  8. package/dist/bin/mcp-ts.mjs.map +1 -1
  9. package/dist/client/index.js.map +1 -1
  10. package/dist/client/index.mjs.map +1 -1
  11. package/dist/client/react.d.mts +2 -2
  12. package/dist/client/react.d.ts +2 -2
  13. package/dist/client/react.js +25 -2
  14. package/dist/client/react.js.map +1 -1
  15. package/dist/client/react.mjs +26 -3
  16. package/dist/client/react.mjs.map +1 -1
  17. package/dist/client/vue.js.map +1 -1
  18. package/dist/client/vue.mjs.map +1 -1
  19. package/dist/index.js.map +1 -1
  20. package/dist/index.mjs.map +1 -1
  21. package/dist/server/index.js.map +1 -1
  22. package/dist/server/index.mjs.map +1 -1
  23. package/dist/shared/index.js.map +1 -1
  24. package/dist/shared/index.mjs.map +1 -1
  25. package/package.json +185 -185
  26. package/src/adapters/agui-middleware.ts +382 -382
  27. package/src/bin/mcp-ts.ts +102 -102
  28. package/src/client/core/app-host.ts +417 -417
  29. package/src/client/core/sse-client.ts +371 -371
  30. package/src/client/core/types.ts +31 -31
  31. package/src/client/index.ts +27 -27
  32. package/src/client/react/index.ts +16 -16
  33. package/src/client/react/use-app-host.ts +73 -73
  34. package/src/client/react/use-mcp-apps.tsx +247 -214
  35. package/src/client/react/use-mcp.ts +641 -641
  36. package/src/client/vue/index.ts +10 -10
  37. package/src/client/vue/use-mcp.ts +617 -617
  38. package/src/index.ts +11 -11
  39. package/src/server/handlers/nextjs-handler.ts +204 -204
  40. package/src/server/handlers/sse-handler.ts +631 -631
  41. package/src/server/index.ts +57 -57
  42. package/src/server/mcp/multi-session-client.ts +228 -228
  43. package/src/server/mcp/oauth-client.ts +1188 -1188
  44. package/src/server/mcp/storage-oauth-provider.ts +272 -272
  45. package/src/server/storage/file-backend.ts +157 -157
  46. package/src/server/storage/index.ts +176 -176
  47. package/src/server/storage/memory-backend.ts +123 -123
  48. package/src/server/storage/redis-backend.ts +276 -276
  49. package/src/server/storage/redis.ts +160 -160
  50. package/src/server/storage/sqlite-backend.ts +182 -182
  51. package/src/server/storage/supabase-backend.ts +228 -228
  52. package/src/server/storage/types.ts +116 -116
  53. package/src/shared/constants.ts +29 -29
  54. package/src/shared/errors.ts +133 -133
  55. package/src/shared/event-routing.ts +28 -28
  56. package/src/shared/events.ts +180 -180
  57. package/src/shared/index.ts +75 -75
  58. package/src/shared/tool-utils.ts +61 -61
  59. package/src/shared/types.ts +282 -282
  60. package/src/shared/utils.ts +38 -38
  61. package/supabase/migrations/20260330195700_install_mcp_sessions.sql +84 -84
@@ -1,176 +1,176 @@
1
-
2
- import { RedisStorageBackend } from './redis-backend';
3
- import { MemoryStorageBackend } from './memory-backend';
4
- import { FileStorageBackend } from './file-backend';
5
- import { SqliteStorage } from './sqlite-backend.js';
6
- import { SupabaseStorageBackend } from './supabase-backend.js';
7
- import type { StorageBackend } from './types.js';
8
-
9
- // Re-export types
10
- export * from './types.js';
11
- export { generateSessionId } from '../../shared/utils.js';
12
- export { RedisStorageBackend, MemoryStorageBackend, FileStorageBackend, SqliteStorage, SupabaseStorageBackend };
13
-
14
- export function createSupabaseStorageBackend(client: any): SupabaseStorageBackend {
15
- return new SupabaseStorageBackend(client);
16
- }
17
-
18
- let storageInstance: StorageBackend | null = null;
19
- let storagePromise: Promise<StorageBackend> | null = null;
20
-
21
- async function initializeStorage<T extends StorageBackend>(store: T): Promise<T> {
22
- if (typeof store.init === 'function') {
23
- await store.init();
24
- }
25
- return store;
26
- }
27
-
28
- async function createStorage(): Promise<StorageBackend> {
29
- const type = process.env.MCP_TS_STORAGE_TYPE?.toLowerCase();
30
-
31
- // Explicit selection
32
- if (type === 'redis') {
33
- if (!process.env.REDIS_URL) {
34
- console.warn('[Storage] MCP_TS_STORAGE_TYPE is "redis" but REDIS_URL is missing');
35
- }
36
- try {
37
- const { getRedis } = await import('./redis.js');
38
- const redis = await getRedis();
39
- console.log('[mcp-ts][Storage] Explicit selection: "redis"');
40
- return await initializeStorage(new RedisStorageBackend(redis));
41
- } catch (error: any) {
42
- console.error('[mcp-ts][Storage] Failed to initialize Redis:', error.message);
43
- console.log('[mcp-ts][Storage] Falling back to In-Memory storage');
44
- return await initializeStorage(new MemoryStorageBackend());
45
- }
46
- }
47
-
48
- if (type === 'file') {
49
- const filePath = process.env.MCP_TS_STORAGE_FILE;
50
- console.log(`[mcp-ts][Storage] Explicit selection: "file" (${filePath || 'default'})`);
51
- return await initializeStorage(new FileStorageBackend({ path: filePath }));
52
- }
53
-
54
- if (type === 'sqlite') {
55
- const dbPath = process.env.MCP_TS_STORAGE_SQLITE_PATH;
56
- console.log(`[mcp-ts][Storage] Explicit selection: "sqlite" (${dbPath || 'default'})`);
57
- return await initializeStorage(new SqliteStorage({ path: dbPath }));
58
- }
59
-
60
- if (type === 'supabase') {
61
- const url = process.env.SUPABASE_URL;
62
- const key = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SUPABASE_ANON_KEY;
63
-
64
- if (!url || !key) {
65
- console.warn('[mcp-ts][Storage] Explicit selection "supabase" requires SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY.');
66
- } else {
67
- if (!process.env.SUPABASE_SERVICE_ROLE_KEY) {
68
- console.warn('[mcp-ts][Storage] ⚠️ Warning: Using "SUPABASE_ANON_KEY" for server-side storage. You may encounter RLS policy violations. "SUPABASE_SERVICE_ROLE_KEY" is recommended.');
69
- }
70
- try {
71
- const { createClient } = await import('@supabase/supabase-js');
72
- const client = createClient(url, key);
73
- console.log('[mcp-ts][Storage] Explicit selection: "supabase"');
74
- return await initializeStorage(new SupabaseStorageBackend(client as any));
75
- } catch (error: any) {
76
- console.error('[mcp-ts][Storage] Failed to initialize Supabase:', error.message);
77
- console.log('[mcp-ts][Storage] Falling back to In-Memory storage');
78
- return await initializeStorage(new MemoryStorageBackend());
79
- }
80
- }
81
- }
82
-
83
- if (type === 'memory') {
84
- console.log('[mcp-ts][Storage] Explicit selection: "memory"');
85
- return await initializeStorage(new MemoryStorageBackend());
86
- }
87
-
88
- // Automatic inference (Fallback)
89
- if (process.env.REDIS_URL) {
90
- try {
91
- const { getRedis } = await import('./redis.js');
92
- const redis = await getRedis();
93
- console.log('[mcp-ts][Storage] Auto-detection: "redis" (via REDIS_URL)');
94
- return await initializeStorage(new RedisStorageBackend(redis));
95
- } catch (error: any) {
96
- console.error('[mcp-ts][Storage] Redis auto-detection failed:', error.message);
97
- console.log('[mcp-ts][Storage] Falling back to next available backend');
98
- }
99
- }
100
-
101
- if (process.env.MCP_TS_STORAGE_FILE) {
102
- console.log(`[mcp-ts][Storage] Auto-detection: "file" (${process.env.MCP_TS_STORAGE_FILE})`);
103
- return await initializeStorage(new FileStorageBackend({ path: process.env.MCP_TS_STORAGE_FILE }));
104
- }
105
-
106
- if (process.env.MCP_TS_STORAGE_SQLITE_PATH) {
107
- console.log(`[mcp-ts][Storage] Auto-detection: "sqlite" (${process.env.MCP_TS_STORAGE_SQLITE_PATH})`);
108
- return await initializeStorage(new SqliteStorage({ path: process.env.MCP_TS_STORAGE_SQLITE_PATH }));
109
- }
110
-
111
- if (process.env.SUPABASE_URL && (process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SUPABASE_ANON_KEY)) {
112
- try {
113
- const { createClient } = await import('@supabase/supabase-js');
114
- const url = process.env.SUPABASE_URL;
115
- const key = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SUPABASE_ANON_KEY!;
116
-
117
- if (!process.env.SUPABASE_SERVICE_ROLE_KEY) {
118
- console.warn('[mcp-ts][Storage] ⚠️ Warning: Using "SUPABASE_ANON_KEY" for server-side storage. You may encounter RLS policy violations. "SUPABASE_SERVICE_ROLE_KEY" is recommended.');
119
- }
120
-
121
- const client = createClient(url, key);
122
- console.log('[mcp-ts][Storage] Auto-detection: "supabase" (via SUPABASE_URL)');
123
- return await initializeStorage(new SupabaseStorageBackend(client as any));
124
- } catch (error: any) {
125
- console.error('[mcp-ts][Storage] Supabase auto-detection failed:', error.message);
126
- }
127
- }
128
-
129
- console.log('[mcp-ts][Storage] Defaulting to: "memory"');
130
- return await initializeStorage(new MemoryStorageBackend());
131
- }
132
-
133
- async function getStorage(): Promise<StorageBackend> {
134
- if (storageInstance) {
135
- return storageInstance;
136
- }
137
-
138
- if (!storagePromise) {
139
- storagePromise = createStorage().catch((error) => {
140
- storagePromise = null;
141
- throw error;
142
- });
143
- }
144
-
145
- storageInstance = await storagePromise;
146
- return storageInstance;
147
- }
148
-
149
- /**
150
- * Set the storage instance (for testing)
151
- * @internal
152
- * @param instance - StorageBackend instance or null to reset
153
- */
154
- export function _setStorageInstanceForTesting(instance: StorageBackend | null): void {
155
- storageInstance = instance;
156
- if (!instance) {
157
- storagePromise = null;
158
- }
159
- }
160
-
161
- /**
162
- * Global session store instance
163
- * Uses lazy initialization with a Proxy to handle async setup transparently
164
- */
165
- export const storage: StorageBackend = new Proxy({} as StorageBackend, {
166
- get(_target, prop) {
167
- return async (...args: any[]) => {
168
- const instance = await getStorage();
169
- const value = (instance as any)[prop];
170
- if (typeof value === 'function') {
171
- return value.apply(instance, args);
172
- }
173
- return value;
174
- };
175
- },
176
- });
1
+
2
+ import { RedisStorageBackend } from './redis-backend';
3
+ import { MemoryStorageBackend } from './memory-backend';
4
+ import { FileStorageBackend } from './file-backend';
5
+ import { SqliteStorage } from './sqlite-backend.js';
6
+ import { SupabaseStorageBackend } from './supabase-backend.js';
7
+ import type { StorageBackend } from './types.js';
8
+
9
+ // Re-export types
10
+ export * from './types.js';
11
+ export { generateSessionId } from '../../shared/utils.js';
12
+ export { RedisStorageBackend, MemoryStorageBackend, FileStorageBackend, SqliteStorage, SupabaseStorageBackend };
13
+
14
+ export function createSupabaseStorageBackend(client: any): SupabaseStorageBackend {
15
+ return new SupabaseStorageBackend(client);
16
+ }
17
+
18
+ let storageInstance: StorageBackend | null = null;
19
+ let storagePromise: Promise<StorageBackend> | null = null;
20
+
21
+ async function initializeStorage<T extends StorageBackend>(store: T): Promise<T> {
22
+ if (typeof store.init === 'function') {
23
+ await store.init();
24
+ }
25
+ return store;
26
+ }
27
+
28
+ async function createStorage(): Promise<StorageBackend> {
29
+ const type = process.env.MCP_TS_STORAGE_TYPE?.toLowerCase();
30
+
31
+ // Explicit selection
32
+ if (type === 'redis') {
33
+ if (!process.env.REDIS_URL) {
34
+ console.warn('[Storage] MCP_TS_STORAGE_TYPE is "redis" but REDIS_URL is missing');
35
+ }
36
+ try {
37
+ const { getRedis } = await import('./redis.js');
38
+ const redis = await getRedis();
39
+ console.log('[mcp-ts][Storage] Explicit selection: "redis"');
40
+ return await initializeStorage(new RedisStorageBackend(redis));
41
+ } catch (error: any) {
42
+ console.error('[mcp-ts][Storage] Failed to initialize Redis:', error.message);
43
+ console.log('[mcp-ts][Storage] Falling back to In-Memory storage');
44
+ return await initializeStorage(new MemoryStorageBackend());
45
+ }
46
+ }
47
+
48
+ if (type === 'file') {
49
+ const filePath = process.env.MCP_TS_STORAGE_FILE;
50
+ console.log(`[mcp-ts][Storage] Explicit selection: "file" (${filePath || 'default'})`);
51
+ return await initializeStorage(new FileStorageBackend({ path: filePath }));
52
+ }
53
+
54
+ if (type === 'sqlite') {
55
+ const dbPath = process.env.MCP_TS_STORAGE_SQLITE_PATH;
56
+ console.log(`[mcp-ts][Storage] Explicit selection: "sqlite" (${dbPath || 'default'})`);
57
+ return await initializeStorage(new SqliteStorage({ path: dbPath }));
58
+ }
59
+
60
+ if (type === 'supabase') {
61
+ const url = process.env.SUPABASE_URL;
62
+ const key = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SUPABASE_ANON_KEY;
63
+
64
+ if (!url || !key) {
65
+ console.warn('[mcp-ts][Storage] Explicit selection "supabase" requires SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY.');
66
+ } else {
67
+ if (!process.env.SUPABASE_SERVICE_ROLE_KEY) {
68
+ console.warn('[mcp-ts][Storage] ⚠️ Warning: Using "SUPABASE_ANON_KEY" for server-side storage. You may encounter RLS policy violations. "SUPABASE_SERVICE_ROLE_KEY" is recommended.');
69
+ }
70
+ try {
71
+ const { createClient } = await import('@supabase/supabase-js');
72
+ const client = createClient(url, key);
73
+ console.log('[mcp-ts][Storage] Explicit selection: "supabase"');
74
+ return await initializeStorage(new SupabaseStorageBackend(client as any));
75
+ } catch (error: any) {
76
+ console.error('[mcp-ts][Storage] Failed to initialize Supabase:', error.message);
77
+ console.log('[mcp-ts][Storage] Falling back to In-Memory storage');
78
+ return await initializeStorage(new MemoryStorageBackend());
79
+ }
80
+ }
81
+ }
82
+
83
+ if (type === 'memory') {
84
+ console.log('[mcp-ts][Storage] Explicit selection: "memory"');
85
+ return await initializeStorage(new MemoryStorageBackend());
86
+ }
87
+
88
+ // Automatic inference (Fallback)
89
+ if (process.env.REDIS_URL) {
90
+ try {
91
+ const { getRedis } = await import('./redis.js');
92
+ const redis = await getRedis();
93
+ console.log('[mcp-ts][Storage] Auto-detection: "redis" (via REDIS_URL)');
94
+ return await initializeStorage(new RedisStorageBackend(redis));
95
+ } catch (error: any) {
96
+ console.error('[mcp-ts][Storage] Redis auto-detection failed:', error.message);
97
+ console.log('[mcp-ts][Storage] Falling back to next available backend');
98
+ }
99
+ }
100
+
101
+ if (process.env.MCP_TS_STORAGE_FILE) {
102
+ console.log(`[mcp-ts][Storage] Auto-detection: "file" (${process.env.MCP_TS_STORAGE_FILE})`);
103
+ return await initializeStorage(new FileStorageBackend({ path: process.env.MCP_TS_STORAGE_FILE }));
104
+ }
105
+
106
+ if (process.env.MCP_TS_STORAGE_SQLITE_PATH) {
107
+ console.log(`[mcp-ts][Storage] Auto-detection: "sqlite" (${process.env.MCP_TS_STORAGE_SQLITE_PATH})`);
108
+ return await initializeStorage(new SqliteStorage({ path: process.env.MCP_TS_STORAGE_SQLITE_PATH }));
109
+ }
110
+
111
+ if (process.env.SUPABASE_URL && (process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SUPABASE_ANON_KEY)) {
112
+ try {
113
+ const { createClient } = await import('@supabase/supabase-js');
114
+ const url = process.env.SUPABASE_URL;
115
+ const key = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SUPABASE_ANON_KEY!;
116
+
117
+ if (!process.env.SUPABASE_SERVICE_ROLE_KEY) {
118
+ console.warn('[mcp-ts][Storage] ⚠️ Warning: Using "SUPABASE_ANON_KEY" for server-side storage. You may encounter RLS policy violations. "SUPABASE_SERVICE_ROLE_KEY" is recommended.');
119
+ }
120
+
121
+ const client = createClient(url, key);
122
+ console.log('[mcp-ts][Storage] Auto-detection: "supabase" (via SUPABASE_URL)');
123
+ return await initializeStorage(new SupabaseStorageBackend(client as any));
124
+ } catch (error: any) {
125
+ console.error('[mcp-ts][Storage] Supabase auto-detection failed:', error.message);
126
+ }
127
+ }
128
+
129
+ console.log('[mcp-ts][Storage] Defaulting to: "memory"');
130
+ return await initializeStorage(new MemoryStorageBackend());
131
+ }
132
+
133
+ async function getStorage(): Promise<StorageBackend> {
134
+ if (storageInstance) {
135
+ return storageInstance;
136
+ }
137
+
138
+ if (!storagePromise) {
139
+ storagePromise = createStorage().catch((error) => {
140
+ storagePromise = null;
141
+ throw error;
142
+ });
143
+ }
144
+
145
+ storageInstance = await storagePromise;
146
+ return storageInstance;
147
+ }
148
+
149
+ /**
150
+ * Set the storage instance (for testing)
151
+ * @internal
152
+ * @param instance - StorageBackend instance or null to reset
153
+ */
154
+ export function _setStorageInstanceForTesting(instance: StorageBackend | null): void {
155
+ storageInstance = instance;
156
+ if (!instance) {
157
+ storagePromise = null;
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Global session store instance
163
+ * Uses lazy initialization with a Proxy to handle async setup transparently
164
+ */
165
+ export const storage: StorageBackend = new Proxy({} as StorageBackend, {
166
+ get(_target, prop) {
167
+ return async (...args: any[]) => {
168
+ const instance = await getStorage();
169
+ const value = (instance as any)[prop];
170
+ if (typeof value === 'function') {
171
+ return value.apply(instance, args);
172
+ }
173
+ return value;
174
+ };
175
+ },
176
+ });
@@ -1,123 +1,123 @@
1
- import { StorageBackend, SessionData, SetClientOptions } from './types.js';
2
- import { generateSessionId } from '../../shared/utils.js';
3
-
4
- /**
5
- * In-memory implementation of StorageBackend
6
- * Useful for local development or testing
7
- */
8
- export class MemoryStorageBackend implements StorageBackend {
9
- // Map<identity:sessionId, SessionData>
10
- private sessions = new Map<string, SessionData>();
11
-
12
- // Map<identity, Set<sessionId>>
13
- private identitySessions = new Map<string, Set<string>>();
14
-
15
- constructor() { }
16
-
17
- async init(): Promise<void> {
18
- console.log('[mcp-ts][Storage] Memory: ✓ internal memory store active.');
19
- }
20
-
21
- private getSessionKey(identity: string, sessionId: string): string {
22
- return `${identity}:${sessionId}`;
23
- }
24
-
25
- generateSessionId(): string {
26
- return generateSessionId();
27
- }
28
-
29
- async createSession(session: SessionData, ttl?: number): Promise<void> {
30
- const { sessionId, identity } = session;
31
- if (!sessionId || !identity) throw new Error('identity and sessionId required');
32
-
33
- const sessionKey = this.getSessionKey(identity, sessionId);
34
- if (this.sessions.has(sessionKey)) {
35
- throw new Error(`Session ${sessionId} already exists`);
36
- }
37
-
38
- this.sessions.set(sessionKey, session);
39
-
40
- // Update index
41
- if (!this.identitySessions.has(identity)) {
42
- this.identitySessions.set(identity, new Set());
43
- }
44
- this.identitySessions.get(identity)!.add(sessionId);
45
- // Note: TTL is ignored in memory backend - sessions don't auto-expire
46
- }
47
-
48
- async updateSession(identity: string, sessionId: string, data: Partial<SessionData>, ttl?: number): Promise<void> {
49
- if (!identity || !sessionId) throw new Error('identity and sessionId required');
50
-
51
- const sessionKey = this.getSessionKey(identity, sessionId);
52
- const current = this.sessions.get(sessionKey);
53
-
54
- if (!current) {
55
- throw new Error(`Session ${sessionId} not found`);
56
- }
57
-
58
- const updated = {
59
- ...current,
60
- ...data
61
- };
62
-
63
- this.sessions.set(sessionKey, updated);
64
- // Note: TTL is ignored in memory backend - sessions don't auto-expire
65
- }
66
-
67
-
68
- async getSession(identity: string, sessionId: string): Promise<SessionData | null> {
69
- const sessionKey = this.getSessionKey(identity, sessionId);
70
- return this.sessions.get(sessionKey) || null;
71
- }
72
-
73
- async getIdentityMcpSessions(identity: string): Promise<string[]> {
74
- const set = this.identitySessions.get(identity);
75
- return set ? Array.from(set) : [];
76
- }
77
-
78
- async getIdentitySessionsData(identity: string): Promise<SessionData[]> {
79
- const set = this.identitySessions.get(identity);
80
- if (!set) return [];
81
-
82
- const results: SessionData[] = [];
83
- for (const sessionId of set) {
84
- const session = this.sessions.get(this.getSessionKey(identity, sessionId));
85
- if (session) {
86
- results.push(session);
87
- }
88
- }
89
- return results;
90
- }
91
-
92
- async removeSession(identity: string, sessionId: string): Promise<void> {
93
- const sessionKey = this.getSessionKey(identity, sessionId);
94
- this.sessions.delete(sessionKey);
95
-
96
- const set = this.identitySessions.get(identity);
97
- if (set) {
98
- set.delete(sessionId);
99
- if (set.size === 0) {
100
- this.identitySessions.delete(identity);
101
- }
102
- }
103
- }
104
-
105
- async getAllSessionIds(): Promise<string[]> {
106
- return Array.from(this.sessions.values()).map(s => s.sessionId);
107
- }
108
-
109
- async clearAll(): Promise<void> {
110
- this.sessions.clear();
111
- this.identitySessions.clear();
112
- }
113
-
114
- async cleanupExpiredSessions(): Promise<void> {
115
- // In-memory doesn't implement TTL automatically,
116
- // but we could check createdAt + TTL here if needed.
117
- // For now, no-op.
118
- }
119
-
120
- async disconnect(): Promise<void> {
121
- // No-op for memory
122
- }
123
- }
1
+ import { StorageBackend, SessionData, SetClientOptions } from './types.js';
2
+ import { generateSessionId } from '../../shared/utils.js';
3
+
4
+ /**
5
+ * In-memory implementation of StorageBackend
6
+ * Useful for local development or testing
7
+ */
8
+ export class MemoryStorageBackend implements StorageBackend {
9
+ // Map<identity:sessionId, SessionData>
10
+ private sessions = new Map<string, SessionData>();
11
+
12
+ // Map<identity, Set<sessionId>>
13
+ private identitySessions = new Map<string, Set<string>>();
14
+
15
+ constructor() { }
16
+
17
+ async init(): Promise<void> {
18
+ console.log('[mcp-ts][Storage] Memory: ✓ internal memory store active.');
19
+ }
20
+
21
+ private getSessionKey(identity: string, sessionId: string): string {
22
+ return `${identity}:${sessionId}`;
23
+ }
24
+
25
+ generateSessionId(): string {
26
+ return generateSessionId();
27
+ }
28
+
29
+ async createSession(session: SessionData, ttl?: number): Promise<void> {
30
+ const { sessionId, identity } = session;
31
+ if (!sessionId || !identity) throw new Error('identity and sessionId required');
32
+
33
+ const sessionKey = this.getSessionKey(identity, sessionId);
34
+ if (this.sessions.has(sessionKey)) {
35
+ throw new Error(`Session ${sessionId} already exists`);
36
+ }
37
+
38
+ this.sessions.set(sessionKey, session);
39
+
40
+ // Update index
41
+ if (!this.identitySessions.has(identity)) {
42
+ this.identitySessions.set(identity, new Set());
43
+ }
44
+ this.identitySessions.get(identity)!.add(sessionId);
45
+ // Note: TTL is ignored in memory backend - sessions don't auto-expire
46
+ }
47
+
48
+ async updateSession(identity: string, sessionId: string, data: Partial<SessionData>, ttl?: number): Promise<void> {
49
+ if (!identity || !sessionId) throw new Error('identity and sessionId required');
50
+
51
+ const sessionKey = this.getSessionKey(identity, sessionId);
52
+ const current = this.sessions.get(sessionKey);
53
+
54
+ if (!current) {
55
+ throw new Error(`Session ${sessionId} not found`);
56
+ }
57
+
58
+ const updated = {
59
+ ...current,
60
+ ...data
61
+ };
62
+
63
+ this.sessions.set(sessionKey, updated);
64
+ // Note: TTL is ignored in memory backend - sessions don't auto-expire
65
+ }
66
+
67
+
68
+ async getSession(identity: string, sessionId: string): Promise<SessionData | null> {
69
+ const sessionKey = this.getSessionKey(identity, sessionId);
70
+ return this.sessions.get(sessionKey) || null;
71
+ }
72
+
73
+ async getIdentityMcpSessions(identity: string): Promise<string[]> {
74
+ const set = this.identitySessions.get(identity);
75
+ return set ? Array.from(set) : [];
76
+ }
77
+
78
+ async getIdentitySessionsData(identity: string): Promise<SessionData[]> {
79
+ const set = this.identitySessions.get(identity);
80
+ if (!set) return [];
81
+
82
+ const results: SessionData[] = [];
83
+ for (const sessionId of set) {
84
+ const session = this.sessions.get(this.getSessionKey(identity, sessionId));
85
+ if (session) {
86
+ results.push(session);
87
+ }
88
+ }
89
+ return results;
90
+ }
91
+
92
+ async removeSession(identity: string, sessionId: string): Promise<void> {
93
+ const sessionKey = this.getSessionKey(identity, sessionId);
94
+ this.sessions.delete(sessionKey);
95
+
96
+ const set = this.identitySessions.get(identity);
97
+ if (set) {
98
+ set.delete(sessionId);
99
+ if (set.size === 0) {
100
+ this.identitySessions.delete(identity);
101
+ }
102
+ }
103
+ }
104
+
105
+ async getAllSessionIds(): Promise<string[]> {
106
+ return Array.from(this.sessions.values()).map(s => s.sessionId);
107
+ }
108
+
109
+ async clearAll(): Promise<void> {
110
+ this.sessions.clear();
111
+ this.identitySessions.clear();
112
+ }
113
+
114
+ async cleanupExpiredSessions(): Promise<void> {
115
+ // In-memory doesn't implement TTL automatically,
116
+ // but we could check createdAt + TTL here if needed.
117
+ // For now, no-op.
118
+ }
119
+
120
+ async disconnect(): Promise<void> {
121
+ // No-op for memory
122
+ }
123
+ }