@mcp-ts/sdk 1.3.5 → 1.3.7

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 (70) hide show
  1. package/dist/adapters/agui-adapter.d.mts +1 -1
  2. package/dist/adapters/agui-adapter.d.ts +1 -1
  3. package/dist/adapters/agui-adapter.js +2 -2
  4. package/dist/adapters/agui-adapter.js.map +1 -1
  5. package/dist/adapters/agui-adapter.mjs +2 -2
  6. package/dist/adapters/agui-adapter.mjs.map +1 -1
  7. package/dist/adapters/agui-middleware.d.mts +1 -1
  8. package/dist/adapters/agui-middleware.d.ts +1 -1
  9. package/dist/adapters/agui-middleware.js.map +1 -1
  10. package/dist/adapters/agui-middleware.mjs.map +1 -1
  11. package/dist/adapters/ai-adapter.d.mts +1 -1
  12. package/dist/adapters/ai-adapter.d.ts +1 -1
  13. package/dist/adapters/ai-adapter.js +1 -1
  14. package/dist/adapters/ai-adapter.js.map +1 -1
  15. package/dist/adapters/ai-adapter.mjs +1 -1
  16. package/dist/adapters/ai-adapter.mjs.map +1 -1
  17. package/dist/adapters/langchain-adapter.d.mts +1 -1
  18. package/dist/adapters/langchain-adapter.d.ts +1 -1
  19. package/dist/adapters/langchain-adapter.js +1 -1
  20. package/dist/adapters/langchain-adapter.js.map +1 -1
  21. package/dist/adapters/langchain-adapter.mjs +1 -1
  22. package/dist/adapters/langchain-adapter.mjs.map +1 -1
  23. package/dist/adapters/mastra-adapter.d.mts +1 -1
  24. package/dist/adapters/mastra-adapter.d.ts +1 -1
  25. package/dist/adapters/mastra-adapter.js +1 -1
  26. package/dist/adapters/mastra-adapter.js.map +1 -1
  27. package/dist/adapters/mastra-adapter.mjs +1 -1
  28. package/dist/adapters/mastra-adapter.mjs.map +1 -1
  29. package/dist/bin/mcp-ts.d.mts +1 -0
  30. package/dist/bin/mcp-ts.d.ts +1 -0
  31. package/dist/bin/mcp-ts.js +105 -0
  32. package/dist/bin/mcp-ts.js.map +1 -0
  33. package/dist/bin/mcp-ts.mjs +82 -0
  34. package/dist/bin/mcp-ts.mjs.map +1 -0
  35. package/dist/index.d.mts +1 -1
  36. package/dist/index.d.ts +1 -1
  37. package/dist/index.js +411 -90
  38. package/dist/index.js.map +1 -1
  39. package/dist/index.mjs +350 -91
  40. package/dist/index.mjs.map +1 -1
  41. package/dist/{multi-session-client-BYLarghq.d.ts → multi-session-client-CHE8QpVE.d.ts} +75 -5
  42. package/dist/{multi-session-client-CzhMkE0k.d.mts → multi-session-client-CQsRbxYI.d.mts} +75 -5
  43. package/dist/server/index.d.mts +1 -1
  44. package/dist/server/index.d.ts +1 -1
  45. package/dist/server/index.js +394 -90
  46. package/dist/server/index.js.map +1 -1
  47. package/dist/server/index.mjs +350 -91
  48. package/dist/server/index.mjs.map +1 -1
  49. package/dist/shared/index.js +10 -2
  50. package/dist/shared/index.js.map +1 -1
  51. package/dist/shared/index.mjs +10 -2
  52. package/dist/shared/index.mjs.map +1 -1
  53. package/package.json +19 -6
  54. package/src/adapters/agui-adapter.ts +222 -222
  55. package/src/adapters/ai-adapter.ts +115 -115
  56. package/src/adapters/langchain-adapter.ts +127 -127
  57. package/src/adapters/mastra-adapter.ts +126 -126
  58. package/src/bin/mcp-ts.ts +102 -0
  59. package/src/server/handlers/nextjs-handler.ts +12 -12
  60. package/src/server/handlers/sse-handler.ts +61 -61
  61. package/src/server/mcp/multi-session-client.ts +135 -39
  62. package/src/server/storage/file-backend.ts +4 -16
  63. package/src/server/storage/index.ts +68 -25
  64. package/src/server/storage/memory-backend.ts +7 -16
  65. package/src/server/storage/redis-backend.ts +12 -16
  66. package/src/server/storage/sqlite-backend.ts +3 -6
  67. package/src/server/storage/supabase-backend.ts +228 -0
  68. package/src/shared/event-routing.ts +28 -28
  69. package/src/shared/utils.ts +22 -0
  70. package/supabase/migrations/20260330195700_install_mcp_sessions.sql +84 -0
@@ -3,6 +3,11 @@
3
3
  import { MCPClient } from './oauth-client.js';
4
4
  import { storage, type SessionData } from '../storage/index.js';
5
5
 
6
+ const DEFAULT_TIMEOUT_MS = 15000;
7
+ const DEFAULT_MAX_RETRIES = 2;
8
+ const DEFAULT_RETRY_DELAY_MS = 1000;
9
+ const CONNECTION_BATCH_SIZE = 5;
10
+
6
11
  /**
7
12
  * Manages multiple MCP connections for a single user identity.
8
13
  * Allows aggregating tools from all connected servers.
@@ -26,57 +31,155 @@ export interface MultiSessionOptions {
26
31
  }
27
32
 
28
33
  /**
29
- * Manages multiple MCP connections for a single user identity.
30
- * Allows aggregating tools from all connected servers.
34
+ * Manages multiple MCP client connections for a single user identity.
35
+ *
36
+ * On a traditional long-running server, you can cache this instance per user
37
+ * so the connections stay alive between requests. On serverless, a new instance
38
+ * will be created per invocation, but the underlying session data is always
39
+ * read from the storage backend so nothing is lost between calls.
31
40
  */
32
41
  export class MultiSessionClient {
33
42
  private clients: MCPClient[] = [];
34
43
  private identity: string;
35
44
  private options: MultiSessionOptions;
36
45
 
46
+ /**
47
+ * Creates a new MultiSessionClient for the given user identity.
48
+ *
49
+ * @param identity - A unique string identifying the user (e.g. user ID or email).
50
+ * @param options - Optional tuning for connection timeout, retry count, and retry delay.
51
+ * Falls back to sensible defaults if not provided.
52
+ */
37
53
  constructor(identity: string, options: MultiSessionOptions = {}) {
38
54
  this.identity = identity;
39
55
  this.options = {
40
- timeout: 15000,
41
- maxRetries: 2,
42
- retryDelay: 1000,
56
+ timeout: DEFAULT_TIMEOUT_MS,
57
+ maxRetries: DEFAULT_MAX_RETRIES,
58
+ retryDelay: DEFAULT_RETRY_DELAY_MS,
43
59
  ...options
44
60
  };
45
61
  }
46
62
 
63
+ /**
64
+ * Fetches all sessions for this identity from storage and returns only the
65
+ * ones that are ready to connect.
66
+ *
67
+ * A session is considered connectable when:
68
+ * - It has a `serverId`, `serverUrl`, and `callbackUrl` (i.e. it was fully initialized)
69
+ * - Its `active` flag is not explicitly `false` — sessions with `active: false` are
70
+ * either mid-OAuth flow, auth-pending, or previously failed. We skip those here
71
+ * and let the OAuth flow complete separately before we try to reconnect them.
72
+ *
73
+ * Note: Sessions where `active` is `undefined` (legacy records) are included
74
+ * for backwards compatibility.
75
+ */
47
76
  private async getActiveSessions(): Promise<SessionData[]> {
48
77
  const sessions = await storage.getIdentitySessionsData(this.identity);
49
- console.log(`[MultiSessionClient] All sessions for ${this.identity}:`,
50
- sessions.map(s => ({ sessionId: s.sessionId, serverId: s.serverId }))
78
+ const valid = sessions.filter(s =>
79
+ s.serverId &&
80
+ s.serverUrl &&
81
+ s.callbackUrl &&
82
+ s.active !== false // exclude OAuth-pending / failed sessions
51
83
  );
52
- const valid = sessions.filter(s => s.serverId && s.serverUrl && s.callbackUrl);
53
- console.log(`[MultiSessionClient] Filtered valid sessions:`, valid.length);
54
84
  return valid;
55
85
  }
56
86
 
87
+ /**
88
+ * Connects to a list of sessions in controlled batches of `CONNECTION_BATCH_SIZE`.
89
+ *
90
+ * Batching prevents overwhelming the event loop or external servers when a user
91
+ * has many active MCP sessions (e.g. 20+ servers). Within each batch, sessions
92
+ * are connected concurrently using `Promise.all` for speed.
93
+ */
57
94
  private async connectInBatches(sessions: SessionData[]): Promise<void> {
58
- const BATCH_SIZE = 5;
59
- for (let i = 0; i < sessions.length; i += BATCH_SIZE) {
60
- const batch = sessions.slice(i, i + BATCH_SIZE);
95
+ for (let i = 0; i < sessions.length; i += CONNECTION_BATCH_SIZE) {
96
+ const batch = sessions.slice(i, i + CONNECTION_BATCH_SIZE);
61
97
  await Promise.all(batch.map(session => this.connectSession(session)));
62
98
  }
63
99
  }
64
100
 
101
+ private connectionPromises = new Map<string, Promise<void>>();
102
+
103
+ /**
104
+ * Connects a single session, with built-in deduplication to prevent race conditions.
105
+ *
106
+ * - If a client for this session already exists and is connected, returns immediately.
107
+ * - If a connection attempt for this session is already in-flight (e.g. from a
108
+ * concurrent call), it joins the existing promise instead of starting a new one.
109
+ * This is the key concurrency lock — the `connectionPromises` map acts as a
110
+ * per-session mutex so we never spin up two physical connections for the same session.
111
+ * - On completion (success or failure), the promise is cleaned up from the map.
112
+ */
65
113
  private async connectSession(session: SessionData): Promise<void> {
66
114
  const existingClient = this.clients.find(c => c.getSessionId() === session.sessionId);
67
115
  if (existingClient?.isConnected()) {
68
116
  return;
69
117
  }
70
118
 
71
- const maxRetries = this.options.maxRetries ?? 2;
72
- const retryDelay = this.options.retryDelay ?? 1000;
119
+ // Avoid concurrent connection attempts for the same session
120
+ if (this.connectionPromises.has(session.sessionId)) {
121
+ return this.connectionPromises.get(session.sessionId)!;
122
+ }
123
+
124
+ const connectPromise = this.establishConnectionWithRetries(session);
125
+
126
+ this.connectionPromises.set(session.sessionId, connectPromise);
127
+
128
+ try {
129
+ await connectPromise;
130
+ } finally {
131
+ this.connectionPromises.delete(session.sessionId);
132
+ }
133
+ }
134
+
135
+ /**
136
+ * The core connection loop for a single session.
137
+ *
138
+ * Attempts to establish a physical MCP connection, retrying up to `maxRetries` times
139
+ * if the connection fails. Each attempt:
140
+ * 1. Creates a fresh `MCPClient` instance from the session data.
141
+ * 2. Races the connect call against a timeout promise — if the server doesn't respond
142
+ * within `timeoutMs`, the attempt is aborted and counted as a failure.
143
+ * 3. On success, replaces any stale client entry for this session in the `clients` array.
144
+ * 4. On failure, waits `retryDelay` ms before the next attempt.
145
+ *
146
+ * If all attempts are exhausted, logs an error and returns silently (does not throw),
147
+ * so a single bad server doesn't block the rest of the batch from connecting.
148
+ */
149
+ private async establishConnectionWithRetries(session: SessionData): Promise<void> {
150
+ const maxRetries = this.options.maxRetries ?? DEFAULT_MAX_RETRIES;
151
+ const retryDelay = this.options.retryDelay ?? DEFAULT_RETRY_DELAY_MS;
73
152
  let lastError: unknown;
74
153
 
75
154
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
76
155
  try {
77
- const client = await this.createAndConnectClient(session);
156
+ const client = new MCPClient({
157
+ identity: this.identity,
158
+ sessionId: session.sessionId,
159
+ serverId: session.serverId,
160
+ serverUrl: session.serverUrl,
161
+ callbackUrl: session.callbackUrl,
162
+ serverName: session.serverName,
163
+ transportType: session.transportType,
164
+ headers: session.headers,
165
+ });
166
+
167
+ const timeoutMs = this.options.timeout ?? DEFAULT_TIMEOUT_MS;
168
+ let timeoutTimer: ReturnType<typeof setTimeout>;
169
+ const timeoutPromise = new Promise<never>((_, reject) => {
170
+ timeoutTimer = setTimeout(() => reject(new Error(`Connection timed out after ${timeoutMs}ms`)), timeoutMs);
171
+ });
172
+
173
+ try {
174
+ await Promise.race([client.connect(), timeoutPromise]);
175
+ } finally {
176
+ clearTimeout(timeoutTimer!);
177
+ }
178
+
179
+ // Always replace the disconnected client entry
180
+ this.clients = this.clients.filter(c => c.getSessionId() !== session.sessionId);
78
181
  this.clients.push(client);
79
- return;
182
+ return; // successfully connected
80
183
  } catch (error) {
81
184
  lastError = error;
82
185
  if (attempt < maxRetries) {
@@ -88,41 +191,34 @@ export class MultiSessionClient {
88
191
  console.error(`[MultiSessionClient] Failed to connect to session ${session.sessionId} after ${maxRetries + 1} attempts:`, lastError);
89
192
  }
90
193
 
91
- private async createAndConnectClient(session: SessionData): Promise<MCPClient> {
92
- const client = new MCPClient({
93
- identity: this.identity,
94
- sessionId: session.sessionId,
95
- serverId: session.serverId,
96
- serverUrl: session.serverUrl,
97
- callbackUrl: session.callbackUrl,
98
- serverName: session.serverName,
99
- transportType: session.transportType,
100
- headers: session.headers,
101
- });
102
-
103
- const timeoutMs = this.options.timeout ?? 15000;
104
- const timeoutPromise = new Promise<never>((_, reject) => {
105
- setTimeout(() => reject(new Error(`Connection timed out after ${timeoutMs}ms`)), timeoutMs);
106
- });
107
-
108
- await Promise.race([client.connect(), timeoutPromise]);
109
- return client;
110
- }
111
-
194
+ /**
195
+ * The main entry point. Fetches all active sessions for this identity from
196
+ * storage and establishes connections to all of them in batches.
197
+ *
198
+ * Call this once after creating the client. On traditional servers, you can
199
+ * cache the `MultiSessionClient` instance after calling `connect()` to avoid
200
+ * re-fetching and re-connecting on every request.
201
+ */
112
202
  async connect(): Promise<void> {
113
203
  const sessions = await this.getActiveSessions();
114
204
  await this.connectInBatches(sessions);
115
205
  }
116
206
 
117
207
  /**
118
- * Returns the array of currently connected clients.
208
+ * Returns all currently connected `MCPClient` instances.
209
+ *
210
+ * Use this to enumerate available tools across all connected servers,
211
+ * or to route a tool call to the right client by `serverId`.
119
212
  */
120
213
  getClients(): MCPClient[] {
121
214
  return this.clients;
122
215
  }
123
216
 
124
217
  /**
125
- * Disconnects all clients.
218
+ * Gracefully disconnects all active MCP clients and clears the internal client list.
219
+ *
220
+ * Call this during server shutdown or when a user logs out to free up
221
+ * underlying transport resources (SSE streams, HTTP connections, etc.).
126
222
  */
127
223
  disconnect(): void {
128
224
  this.clients.forEach((client) => client.disconnect());
@@ -1,20 +1,7 @@
1
-
2
1
  import { promises as fs } from 'fs';
3
2
  import * as path from 'path';
4
- import { customAlphabet } from 'nanoid';
5
- import { StorageBackend, SessionData, SetClientOptions } from './types';
6
-
7
- // first char: letters only (required by OpenAI)
8
- const firstChar = customAlphabet(
9
- 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
10
- 1
11
- );
12
-
13
- // remaining chars: alphanumeric
14
- const rest = customAlphabet(
15
- 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',
16
- 11
17
- );
3
+ import { StorageBackend, SessionData, SetClientOptions } from './types.js';
4
+ import { generateSessionId } from '../../shared/utils.js';
18
5
 
19
6
  /**
20
7
  * File system implementation of StorageBackend
@@ -65,6 +52,7 @@ export class FileStorageBackend implements StorageBackend {
65
52
  }
66
53
 
67
54
  this.initialized = true;
55
+ console.log(`[mcp-ts][Storage] File: ✓ storage directory at ${path.dirname(this.filePath)} verified.`);
68
56
  }
69
57
 
70
58
  private async ensureInitialized() {
@@ -82,7 +70,7 @@ export class FileStorageBackend implements StorageBackend {
82
70
  }
83
71
 
84
72
  generateSessionId(): string {
85
- return firstChar() + rest();
73
+ return generateSessionId();
86
74
  }
87
75
 
88
76
  async createSession(session: SessionData, ttl?: number): Promise<void> {
@@ -2,12 +2,18 @@
2
2
  import { RedisStorageBackend } from './redis-backend';
3
3
  import { MemoryStorageBackend } from './memory-backend';
4
4
  import { FileStorageBackend } from './file-backend';
5
- import { SqliteStorage } from './sqlite-backend';
6
- import type { StorageBackend } from './types';
5
+ import { SqliteStorage } from './sqlite-backend.js';
6
+ import { SupabaseStorageBackend } from './supabase-backend.js';
7
+ import type { StorageBackend } from './types.js';
7
8
 
8
9
  // Re-export types
9
- export * from './types';
10
- export { RedisStorageBackend, MemoryStorageBackend, FileStorageBackend, SqliteStorage };
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
+ }
11
17
 
12
18
  let storageInstance: StorageBackend | null = null;
13
19
  let storagePromise: Promise<StorageBackend> | null = null;
@@ -30,33 +36,53 @@ async function createStorage(): Promise<StorageBackend> {
30
36
  try {
31
37
  const { getRedis } = await import('./redis.js');
32
38
  const redis = await getRedis();
33
- console.log('[Storage] Using Redis storage (Explicit)');
34
- return new RedisStorageBackend(redis);
39
+ console.log('[mcp-ts][Storage] Explicit selection: "redis"');
40
+ return await initializeStorage(new RedisStorageBackend(redis));
35
41
  } catch (error: any) {
36
- console.error('[Storage] Failed to initialize Redis:', error.message);
37
- console.log('[Storage] Falling back to In-Memory storage');
38
- return new MemoryStorageBackend();
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());
39
45
  }
40
46
  }
41
47
 
42
48
  if (type === 'file') {
43
49
  const filePath = process.env.MCP_TS_STORAGE_FILE;
44
- if (!filePath) {
45
- console.warn('[Storage] MCP_TS_STORAGE_TYPE is "file" but MCP_TS_STORAGE_FILE is missing');
46
- }
47
- console.log(`[Storage] Using File storage (${filePath}) (Explicit)`);
50
+ console.log(`[mcp-ts][Storage] Explicit selection: "file" (${filePath || 'default'})`);
48
51
  return await initializeStorage(new FileStorageBackend({ path: filePath }));
49
52
  }
50
53
 
51
54
  if (type === 'sqlite') {
52
55
  const dbPath = process.env.MCP_TS_STORAGE_SQLITE_PATH;
53
- console.log(`[Storage] Using SQLite storage (${dbPath || 'default'}) (Explicit)`);
56
+ console.log(`[mcp-ts][Storage] Explicit selection: "sqlite" (${dbPath || 'default'})`);
54
57
  return await initializeStorage(new SqliteStorage({ path: dbPath }));
55
58
  }
56
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
+
57
83
  if (type === 'memory') {
58
- console.log('[Storage] Using In-Memory storage (Explicit)');
59
- return new MemoryStorageBackend();
84
+ console.log('[mcp-ts][Storage] Explicit selection: "memory"');
85
+ return await initializeStorage(new MemoryStorageBackend());
60
86
  }
61
87
 
62
88
  // Automatic inference (Fallback)
@@ -64,27 +90,44 @@ async function createStorage(): Promise<StorageBackend> {
64
90
  try {
65
91
  const { getRedis } = await import('./redis.js');
66
92
  const redis = await getRedis();
67
- console.log('[Storage] Auto-detected REDIS_URL. Using Redis storage.');
68
- return new RedisStorageBackend(redis);
93
+ console.log('[mcp-ts][Storage] Auto-detection: "redis" (via REDIS_URL)');
94
+ return await initializeStorage(new RedisStorageBackend(redis));
69
95
  } catch (error: any) {
70
- console.error('[Storage] Redis auto-detection failed:', error.message);
71
- console.log('[Storage] Falling back to In-Memory storage');
72
- return new MemoryStorageBackend();
96
+ console.error('[mcp-ts][Storage] Redis auto-detection failed:', error.message);
97
+ console.log('[mcp-ts][Storage] Falling back to next available backend');
73
98
  }
74
99
  }
75
100
 
76
101
  if (process.env.MCP_TS_STORAGE_FILE) {
77
- console.log(`[Storage] Auto-detected MCP_TS_STORAGE_FILE. Using File storage (${process.env.MCP_TS_STORAGE_FILE}).`);
102
+ console.log(`[mcp-ts][Storage] Auto-detection: "file" (${process.env.MCP_TS_STORAGE_FILE})`);
78
103
  return await initializeStorage(new FileStorageBackend({ path: process.env.MCP_TS_STORAGE_FILE }));
79
104
  }
80
105
 
81
106
  if (process.env.MCP_TS_STORAGE_SQLITE_PATH) {
82
- console.log(`[Storage] Auto-detected MCP_TS_STORAGE_SQLITE_PATH. Using SQLite storage (${process.env.MCP_TS_STORAGE_SQLITE_PATH}).`);
107
+ console.log(`[mcp-ts][Storage] Auto-detection: "sqlite" (${process.env.MCP_TS_STORAGE_SQLITE_PATH})`);
83
108
  return await initializeStorage(new SqliteStorage({ path: process.env.MCP_TS_STORAGE_SQLITE_PATH }));
84
109
  }
85
110
 
86
- console.log('[Storage] No storage configured. Using In-Memory storage (Default).');
87
- return new MemoryStorageBackend();
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());
88
131
  }
89
132
 
90
133
  async function getStorage(): Promise<StorageBackend> {
@@ -1,18 +1,5 @@
1
-
2
- import { customAlphabet } from 'nanoid';
3
- import { StorageBackend, SessionData, SetClientOptions } from './types';
4
-
5
- // first char: letters only (required by OpenAI)
6
- const firstChar = customAlphabet(
7
- 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
8
- 1
9
- );
10
-
11
- // remaining chars: alphanumeric
12
- const rest = customAlphabet(
13
- 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',
14
- 11
15
- );
1
+ import { StorageBackend, SessionData, SetClientOptions } from './types.js';
2
+ import { generateSessionId } from '../../shared/utils.js';
16
3
 
17
4
  /**
18
5
  * In-memory implementation of StorageBackend
@@ -27,12 +14,16 @@ export class MemoryStorageBackend implements StorageBackend {
27
14
 
28
15
  constructor() { }
29
16
 
17
+ async init(): Promise<void> {
18
+ console.log('[mcp-ts][Storage] Memory: ✓ internal memory store active.');
19
+ }
20
+
30
21
  private getSessionKey(identity: string, sessionId: string): string {
31
22
  return `${identity}:${sessionId}`;
32
23
  }
33
24
 
34
25
  generateSessionId(): string {
35
- return firstChar() + rest();
26
+ return generateSessionId();
36
27
  }
37
28
 
38
29
  async createSession(session: SessionData, ttl?: number): Promise<void> {
@@ -1,20 +1,7 @@
1
-
2
1
  import type { Redis } from 'ioredis';
3
- import { customAlphabet } from 'nanoid';
4
- import { StorageBackend, SessionData } from './types';
2
+ import { StorageBackend, SessionData } from './types.js';
5
3
  import { SESSION_TTL_SECONDS } from '../../shared/constants.js';
6
-
7
- /** first char: letters only (required by OpenAI) */
8
- const firstChar = customAlphabet(
9
- 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
10
- 1
11
- );
12
-
13
- /** remaining chars: alphanumeric */
14
- const rest = customAlphabet(
15
- 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',
16
- 11
17
- );
4
+ import { generateSessionId } from '../../shared/utils.js';
18
5
 
19
6
  /**
20
7
  * Redis implementation of StorageBackend
@@ -26,6 +13,15 @@ export class RedisStorageBackend implements StorageBackend {
26
13
  private readonly IDENTITY_KEY_SUFFIX = ':sessions';
27
14
 
28
15
  constructor(private redis: Redis) { }
16
+
17
+ async init(): Promise<void> {
18
+ try {
19
+ await this.redis.ping();
20
+ console.log('[mcp-ts][Storage] Redis: ✓ Connected to server.');
21
+ } catch (error: any) {
22
+ throw new Error(`[RedisStorage] Failed to connect to Redis: ${error.message}`);
23
+ }
24
+ }
29
25
 
30
26
  /**
31
27
  * Generates Redis key for a specific session
@@ -79,7 +75,7 @@ export class RedisStorageBackend implements StorageBackend {
79
75
  }
80
76
 
81
77
  generateSessionId(): string {
82
- return firstChar() + rest();
78
+ return generateSessionId();
83
79
  }
84
80
 
85
81
  async createSession(session: SessionData, ttl?: number): Promise<void> {
@@ -2,6 +2,7 @@ import type { Database } from 'better-sqlite3';
2
2
  import { StorageBackend, SessionData } from './types.js'; // Ensure .js extension
3
3
  import * as fs from 'fs';
4
4
  import * as path from 'path';
5
+ import { generateSessionId } from '../../shared/utils.js';
5
6
 
6
7
  export interface SqliteStorageOptions {
7
8
  path?: string;
@@ -44,6 +45,7 @@ export class SqliteStorage implements StorageBackend {
44
45
  `);
45
46
 
46
47
  this.initialized = true;
48
+ console.log(`[mcp-ts][Storage] SQLite: ✓ database at ${this.dbPath} verified.`);
47
49
  } catch (error: any) {
48
50
  if (error.code === 'MODULE_NOT_FOUND' || error.message?.includes('better-sqlite3')) {
49
51
  throw new Error(
@@ -61,12 +63,7 @@ export class SqliteStorage implements StorageBackend {
61
63
  }
62
64
 
63
65
  generateSessionId(): string {
64
- const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
65
- let result = '';
66
- for (let i = 0; i < 32; i++) {
67
- result += chars.charAt(Math.floor(Math.random() * chars.length));
68
- }
69
- return result;
66
+ return generateSessionId();
70
67
  }
71
68
 
72
69
  async createSession(session: SessionData, ttl?: number): Promise<void> {