@mcp-ts/sdk 1.3.5 → 1.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,13 +1,16 @@
1
1
  {
2
2
  "name": "@mcp-ts/sdk",
3
- "version": "1.3.5",
3
+ "version": "1.3.6",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
7
- "description": "A Lightweight MCP (Model Context Protocol) client library for JavaScript applications, supporting multiple storage backends (Memory, File, Redis) and real-time SSE support.",
7
+ "description": "A Lightweight MCP (Model Context Protocol) client library for JavaScript applications, supporting multiple storage backends (Memory, File, Redis, Supabase) and real-time SSE support.",
8
8
  "main": "./dist/index.js",
9
9
  "module": "./dist/index.mjs",
10
10
  "types": "./dist/index.d.ts",
11
+ "bin": {
12
+ "mcp-ts": "./dist/bin/mcp-ts.js"
13
+ },
11
14
  "exports": {
12
15
  ".": {
13
16
  "types": "./dist/index.d.ts",
@@ -68,7 +71,9 @@
68
71
  "files": [
69
72
  "dist",
70
73
  "src",
71
- "README.md"
74
+ "supabase",
75
+ "README.md",
76
+ "LICENSE"
72
77
  ],
73
78
  "scripts": {
74
79
  "build": "tsup",
@@ -78,7 +83,10 @@
78
83
  "prepublishOnly": "npm run clean && npm run build",
79
84
  "test": "npx playwright test",
80
85
  "test:ui": "npx playwright test --ui",
81
- "test:debug": "npx playwright test --debug"
86
+ "test:debug": "npx playwright test --debug",
87
+ "supabase:push": "supabase db push",
88
+ "supabase:reset": "supabase db reset",
89
+ "supabase:status": "supabase status"
82
90
  },
83
91
  "keywords": [
84
92
  "mcp",
@@ -111,7 +119,8 @@
111
119
  "ioredis": "^5.0.0",
112
120
  "react": ">=18.0.0",
113
121
  "rxjs": ">=7.0.0",
114
- "zod": "^3.23.0"
122
+ "zod": "^3.23.0",
123
+ "@supabase/supabase-js": "^2.0.0"
115
124
  },
116
125
  "peerDependenciesMeta": {
117
126
  "react": {
@@ -137,6 +146,9 @@
137
146
  },
138
147
  "better-sqlite3": {
139
148
  "optional": true
149
+ },
150
+ "@supabase/supabase-js": {
151
+ "optional": true
140
152
  }
141
153
  },
142
154
  "dependencies": {
@@ -164,7 +176,8 @@
164
176
  "tsup": "^8.5.1",
165
177
  "typescript": "^5.9.3",
166
178
  "vue": "^3.5.27",
167
- "zod": "^3.24.1"
179
+ "zod": "^3.24.1",
180
+ "@supabase/supabase-js": "^2.48.0"
168
181
  },
169
182
  "engines": {
170
183
  "node": ">=18.0.0"
@@ -0,0 +1,102 @@
1
+ #!/usr/bin/env node
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+
5
+ /**
6
+ * MCP-TS CLI Utility
7
+ *
8
+ * Provides helper commands for users of the @mcp-ts/sdk library.
9
+ */
10
+ async function run() {
11
+ const args = process.argv.slice(2);
12
+ const command = args[0];
13
+
14
+ if (command === 'supabase-init') {
15
+ await initSupabase();
16
+ } else {
17
+ showHelp();
18
+ }
19
+ }
20
+
21
+ function showHelp() {
22
+ console.log(`
23
+ 🚀 MCP-TS CLI Utility
24
+ Usage: npx mcp-ts <command>
25
+
26
+ Commands:
27
+ supabase-init Initialize Supabase migrations in your project
28
+ `);
29
+ }
30
+
31
+ async function initSupabase() {
32
+ console.log('🚀 Initializing Supabase storage for MCP-TS...');
33
+
34
+ // When running from dist/bin/mcp-ts.js (compiled), __dirname is dist/bin.
35
+ // The supabase/ migrations are at the root of the package.
36
+ // We need to look up two levels to find 'supabase' folder in the package.
37
+ const pkgRoot = path.resolve(__dirname, '../..');
38
+ const sourceDir = path.join(pkgRoot, 'supabase', 'migrations');
39
+
40
+ if (!fs.existsSync(sourceDir)) {
41
+ console.error(`❌ Error: Could not find migration files in package at: ${sourceDir}`);
42
+ console.log('Please ensure you are running this from a project where @mcp-ts/sdk is installed.');
43
+ process.exit(1);
44
+ }
45
+
46
+ const targetDir = path.join(process.cwd(), 'supabase', 'migrations');
47
+
48
+ try {
49
+ if (!fs.existsSync(targetDir)) {
50
+ fs.mkdirSync(targetDir, { recursive: true });
51
+ console.log(`📁 Created directory: ${targetDir}`);
52
+ }
53
+
54
+ const files = fs.readdirSync(sourceDir);
55
+ let copiedCount = 0;
56
+
57
+ for (const file of files) {
58
+ if (file.endsWith('.sql')) {
59
+ const srcPath = path.join(sourceDir, file);
60
+ const destPath = path.join(targetDir, file);
61
+
62
+ if (fs.existsSync(destPath)) {
63
+ console.log(`⏭️ Skipping existing migration: ${file}`);
64
+ continue;
65
+ }
66
+
67
+ fs.copyFileSync(srcPath, destPath);
68
+ console.log(`✅ Copied: ${file}`);
69
+ copiedCount++;
70
+ }
71
+ }
72
+
73
+ if (copiedCount > 0) {
74
+ console.log('\n✨ Database migrations successfully initialized!');
75
+ console.log('\nNext steps:');
76
+ console.log('1. Link your Supabase project:');
77
+ console.log(' npx supabase link --project-ref <your-project-id>');
78
+ console.log('\n2. Push the migrations to your remote database:');
79
+ console.log(' npx supabase db push');
80
+ console.log('\n3. Add your Supabase credentials to .env:');
81
+ console.log(' SUPABASE_URL=https://<your-project-id>.supabase.co');
82
+ console.log(' SUPABASE_SERVICE_ROLE_KEY=<your-service-role-key>');
83
+ console.log('\n⚠️ Important: Use the service_role key (not the anon key) for server-side storage.');
84
+ console.log(' The service_role key bypasses RLS policies and is required for mcp-ts to work correctly.');
85
+ console.log(' Find it in: Supabase Dashboard -> Project Settings -> API -> service_role');
86
+ } else if (files.length > 0) {
87
+ console.log('\n👍 All migration files are already present in your project.');
88
+ console.log(' Ensure SUPABASE_SERVICE_ROLE_KEY (not SUPABASE_ANON_KEY) is set in your .env');
89
+ } else {
90
+ console.log('⚠️ No migration files found to copy.');
91
+ }
92
+
93
+ } catch (error: any) {
94
+ console.error(`❌ Error initializing Supabase: ${error.message}`);
95
+ process.exit(1);
96
+ }
97
+ }
98
+
99
+ run().catch(err => {
100
+ console.error(err);
101
+ process.exit(1);
102
+ });
@@ -5,10 +5,10 @@
5
5
  * - POST + JSON accepts direct RPC result response
6
6
  */
7
7
 
8
- import { SSEConnectionManager, type ClientMetadata } from './sse-handler.js';
9
- import type { McpConnectionEvent, McpObservabilityEvent } from '../../shared/events.js';
10
- import { isConnectionEvent, isRpcResponseEvent } from '../../shared/event-routing.js';
11
- import type { McpRpcResponse } from '../../shared/types.js';
8
+ import { SSEConnectionManager, type ClientMetadata } from './sse-handler.js';
9
+ import type { McpConnectionEvent, McpObservabilityEvent } from '../../shared/events.js';
10
+ import { isConnectionEvent, isRpcResponseEvent } from '../../shared/event-routing.js';
11
+ import type { McpRpcResponse } from '../../shared/types.js';
12
12
 
13
13
  export interface NextMcpHandlerOptions {
14
14
  /**
@@ -139,14 +139,14 @@ export function createNextMcpHandler(options: NextMcpHandlerOptions = {}) {
139
139
 
140
140
  const manager = new SSEConnectionManager(
141
141
  toManagerOptions(identity, resolvedClientMetadata),
142
- (event: McpConnectionEvent | McpObservabilityEvent | McpRpcResponse) => {
143
- if (isRpcResponseEvent(event)) {
144
- sendSSE('rpc-response', event);
145
- } else if (isConnectionEvent(event)) {
146
- sendSSE('connection', event);
147
- } else {
148
- sendSSE('observability', event);
149
- }
142
+ (event: McpConnectionEvent | McpObservabilityEvent | McpRpcResponse) => {
143
+ if (isRpcResponseEvent(event)) {
144
+ sendSSE('rpc-response', event);
145
+ } else if (isConnectionEvent(event)) {
146
+ sendSSE('connection', event);
147
+ } else {
148
+ sendSSE('observability', event);
149
+ }
150
150
  }
151
151
  );
152
152
 
@@ -13,8 +13,8 @@
13
13
  */
14
14
 
15
15
  import type { McpConnectionEvent, McpObservabilityEvent } from '../../shared/events.js';
16
- import type {
17
- McpRpcRequest,
16
+ import type {
17
+ McpRpcRequest,
18
18
  McpRpcResponse,
19
19
  ConnectParams,
20
20
  DisconnectParams,
@@ -32,12 +32,12 @@ import type {
32
32
  ListPromptsResult,
33
33
  ListResourcesResult,
34
34
  CallToolResult,
35
- } from '../../shared/types.js';
36
- import { RpcErrorCodes } from '../../shared/errors.js';
37
- import { UnauthorizedError } from '../../shared/errors.js';
38
- import { isConnectionEvent, isRpcResponseEvent } from '../../shared/event-routing.js';
39
- import { MCPClient } from '../mcp/oauth-client.js';
40
- import { storage } from '../storage/index.js';
35
+ } from '../../shared/types.js';
36
+ import { RpcErrorCodes } from '../../shared/errors.js';
37
+ import { UnauthorizedError } from '../../shared/errors.js';
38
+ import { isConnectionEvent, isRpcResponseEvent } from '../../shared/event-routing.js';
39
+ import { MCPClient } from '../mcp/oauth-client.js';
40
+ import { storage } from '../storage/index.js';
41
41
 
42
42
  // ============================================
43
43
  // Types & Interfaces
@@ -81,7 +81,7 @@ const DEFAULT_HEARTBEAT_INTERVAL = 30000;
81
81
  * Manages a single SSE connection and handles MCP operations.
82
82
  * Each instance corresponds to one connected browser client.
83
83
  */
84
- export class SSEConnectionManager {
84
+ export class SSEConnectionManager {
85
85
  private readonly identity: string;
86
86
  private readonly clients = new Map<string, MCPClient>();
87
87
  private heartbeatTimer?: NodeJS.Timeout;
@@ -236,7 +236,7 @@ export class SSEConnectionManager {
236
236
  /**
237
237
  * Connect to an MCP server
238
238
  */
239
- private async connect(params: ConnectParams): Promise<ConnectResult> {
239
+ private async connect(params: ConnectParams): Promise<ConnectResult> {
240
240
  const { serverName, serverUrl, callbackUrl, transportType } = params;
241
241
 
242
242
  // Normalize serverId to max 12 chars to keep tool names under 64 chars (DeepSeek/OpenAI limits)
@@ -267,21 +267,21 @@ export class SSEConnectionManager {
267
267
  // Generate session ID
268
268
  const sessionId = await storage.generateSessionId();
269
269
 
270
- try {
270
+ try {
271
271
  // Get resolved client metadata
272
272
  const clientMetadata = await this.getResolvedClientMetadata();
273
273
 
274
274
  // Create MCP client
275
- const client = new MCPClient({
276
- identity: this.identity,
277
- sessionId,
278
- serverId,
279
- serverName,
280
- serverUrl,
281
- callbackUrl,
282
- transportType,
283
- ...clientMetadata, // Spread client metadata (clientName, clientUri, logoUri, policyUri)
284
- });
275
+ const client = new MCPClient({
276
+ identity: this.identity,
277
+ sessionId,
278
+ serverId,
279
+ serverName,
280
+ serverUrl,
281
+ callbackUrl,
282
+ transportType,
283
+ ...clientMetadata, // Spread client metadata (clientName, clientUri, logoUri, policyUri)
284
+ });
285
285
 
286
286
  // Note: Session will be created by MCPClient after successful connection
287
287
  // This ensures sessions only exist for successful or OAuth-pending connections
@@ -308,19 +308,19 @@ export class SSEConnectionManager {
308
308
  sessionId,
309
309
  success: true,
310
310
  };
311
- } catch (error) {
312
- if (error instanceof UnauthorizedError) {
313
- // OAuth-required is a pending-auth state, not a failed connection.
314
- this.clients.delete(sessionId);
315
- return {
316
- sessionId,
317
- success: true,
318
- };
319
- }
320
-
321
- this.emitConnectionEvent({
322
- type: 'error',
323
- sessionId,
311
+ } catch (error) {
312
+ if (error instanceof UnauthorizedError) {
313
+ // OAuth-required is a pending-auth state, not a failed connection.
314
+ this.clients.delete(sessionId);
315
+ return {
316
+ sessionId,
317
+ success: true,
318
+ };
319
+ }
320
+
321
+ this.emitConnectionEvent({
322
+ type: 'error',
323
+ sessionId,
324
324
  serverId,
325
325
  error: error instanceof Error ? error.message : 'Connection failed',
326
326
  errorType: 'connection',
@@ -466,18 +466,18 @@ export class SSEConnectionManager {
466
466
  /**
467
467
  * Complete OAuth authorization flow
468
468
  */
469
- private async finishAuth(params: FinishAuthParams): Promise<FinishAuthResult> {
470
- const { sessionId, code } = params;
471
-
472
- const session = await storage.getSession(this.identity, sessionId);
473
- if (!session) {
474
- throw new Error('Session not found');
475
- }
476
-
477
- try {
478
- const client = new MCPClient({
479
- identity: this.identity,
480
- sessionId,
469
+ private async finishAuth(params: FinishAuthParams): Promise<FinishAuthResult> {
470
+ const { sessionId, code } = params;
471
+
472
+ const session = await storage.getSession(this.identity, sessionId);
473
+ if (!session) {
474
+ throw new Error('Session not found');
475
+ }
476
+
477
+ try {
478
+ const client = new MCPClient({
479
+ identity: this.identity,
480
+ sessionId,
481
481
  });
482
482
 
483
483
  client.onConnectionEvent((event) => this.emitConnectionEvent(event));
@@ -573,8 +573,8 @@ export class SSEConnectionManager {
573
573
  * Create an SSE endpoint handler compatible with Node.js HTTP frameworks.
574
574
  * Handles both SSE streaming (GET) and RPC requests (POST).
575
575
  */
576
- export function createSSEHandler(options: SSEHandlerOptions) {
577
- return async (req: { method?: string; on: Function }, res: { writeHead: Function; write: Function }) => {
576
+ export function createSSEHandler(options: SSEHandlerOptions) {
577
+ return async (req: { method?: string; on: Function }, res: { writeHead: Function; write: Function }) => {
578
578
  // Set SSE headers
579
579
  res.writeHead(200, {
580
580
  'Content-Type': 'text/event-stream',
@@ -586,15 +586,15 @@ export function createSSEHandler(options: SSEHandlerOptions) {
586
586
  // Send initial connection acknowledgment
587
587
  writeSSEEvent(res, 'connected', { timestamp: Date.now() });
588
588
 
589
- // Create connection manager with event routing
590
- const manager = new SSEConnectionManager(options, (event) => {
591
- if (isRpcResponseEvent(event)) {
592
- writeSSEEvent(res, 'rpc-response', event);
593
- } else if (isConnectionEvent(event)) {
594
- writeSSEEvent(res, 'connection', event);
595
- } else {
596
- writeSSEEvent(res, 'observability', event);
597
- }
589
+ // Create connection manager with event routing
590
+ const manager = new SSEConnectionManager(options, (event) => {
591
+ if (isRpcResponseEvent(event)) {
592
+ writeSSEEvent(res, 'rpc-response', event);
593
+ } else if (isConnectionEvent(event)) {
594
+ writeSSEEvent(res, 'connection', event);
595
+ } else {
596
+ writeSSEEvent(res, 'observability', event);
597
+ }
598
598
  });
599
599
 
600
600
  // Cleanup on client disconnect
@@ -625,7 +625,7 @@ export function createSSEHandler(options: SSEHandlerOptions) {
625
625
  /**
626
626
  * Write an SSE event to the response stream
627
627
  */
628
- function writeSSEEvent(res: { write: Function }, event: string, data: unknown): void {
629
- res.write(`event: ${event}\n`);
630
- res.write(`data: ${JSON.stringify(data)}\n\n`);
631
- }
628
+ function writeSSEEvent(res: { write: Function }, event: string, data: unknown): void {
629
+ res.write(`event: ${event}\n`);
630
+ res.write(`data: ${JSON.stringify(data)}\n\n`);
631
+ }
@@ -65,6 +65,7 @@ export class FileStorageBackend implements StorageBackend {
65
65
  }
66
66
 
67
67
  this.initialized = true;
68
+ console.log(`[mcp-ts][Storage] File: ✓ storage directory at ${path.dirname(this.filePath)} verified.`);
68
69
  }
69
70
 
70
71
  private async ensureInitialized() {
@@ -2,12 +2,17 @@
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 { RedisStorageBackend, MemoryStorageBackend, FileStorageBackend, SqliteStorage, SupabaseStorageBackend };
12
+
13
+ export function createSupabaseStorageBackend(client: any): SupabaseStorageBackend {
14
+ return new SupabaseStorageBackend(client);
15
+ }
11
16
 
12
17
  let storageInstance: StorageBackend | null = null;
13
18
  let storagePromise: Promise<StorageBackend> | null = null;
@@ -30,33 +35,53 @@ async function createStorage(): Promise<StorageBackend> {
30
35
  try {
31
36
  const { getRedis } = await import('./redis.js');
32
37
  const redis = await getRedis();
33
- console.log('[Storage] Using Redis storage (Explicit)');
34
- return new RedisStorageBackend(redis);
38
+ console.log('[mcp-ts][Storage] Explicit selection: "redis"');
39
+ return await initializeStorage(new RedisStorageBackend(redis));
35
40
  } 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();
41
+ console.error('[mcp-ts][Storage] Failed to initialize Redis:', error.message);
42
+ console.log('[mcp-ts][Storage] Falling back to In-Memory storage');
43
+ return await initializeStorage(new MemoryStorageBackend());
39
44
  }
40
45
  }
41
46
 
42
47
  if (type === 'file') {
43
48
  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)`);
49
+ console.log(`[mcp-ts][Storage] Explicit selection: "file" (${filePath || 'default'})`);
48
50
  return await initializeStorage(new FileStorageBackend({ path: filePath }));
49
51
  }
50
52
 
51
53
  if (type === 'sqlite') {
52
54
  const dbPath = process.env.MCP_TS_STORAGE_SQLITE_PATH;
53
- console.log(`[Storage] Using SQLite storage (${dbPath || 'default'}) (Explicit)`);
55
+ console.log(`[mcp-ts][Storage] Explicit selection: "sqlite" (${dbPath || 'default'})`);
54
56
  return await initializeStorage(new SqliteStorage({ path: dbPath }));
55
57
  }
56
58
 
59
+ if (type === 'supabase') {
60
+ const url = process.env.SUPABASE_URL;
61
+ const key = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SUPABASE_ANON_KEY;
62
+
63
+ if (!url || !key) {
64
+ console.warn('[mcp-ts][Storage] Explicit selection "supabase" requires SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY.');
65
+ } else {
66
+ if (!process.env.SUPABASE_SERVICE_ROLE_KEY) {
67
+ 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.');
68
+ }
69
+ try {
70
+ const { createClient } = await import('@supabase/supabase-js');
71
+ const client = createClient(url, key);
72
+ console.log('[mcp-ts][Storage] Explicit selection: "supabase"');
73
+ return await initializeStorage(new SupabaseStorageBackend(client as any));
74
+ } catch (error: any) {
75
+ console.error('[mcp-ts][Storage] Failed to initialize Supabase:', error.message);
76
+ console.log('[mcp-ts][Storage] Falling back to In-Memory storage');
77
+ return await initializeStorage(new MemoryStorageBackend());
78
+ }
79
+ }
80
+ }
81
+
57
82
  if (type === 'memory') {
58
- console.log('[Storage] Using In-Memory storage (Explicit)');
59
- return new MemoryStorageBackend();
83
+ console.log('[mcp-ts][Storage] Explicit selection: "memory"');
84
+ return await initializeStorage(new MemoryStorageBackend());
60
85
  }
61
86
 
62
87
  // Automatic inference (Fallback)
@@ -64,27 +89,44 @@ async function createStorage(): Promise<StorageBackend> {
64
89
  try {
65
90
  const { getRedis } = await import('./redis.js');
66
91
  const redis = await getRedis();
67
- console.log('[Storage] Auto-detected REDIS_URL. Using Redis storage.');
68
- return new RedisStorageBackend(redis);
92
+ console.log('[mcp-ts][Storage] Auto-detection: "redis" (via REDIS_URL)');
93
+ return await initializeStorage(new RedisStorageBackend(redis));
69
94
  } 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();
95
+ console.error('[mcp-ts][Storage] Redis auto-detection failed:', error.message);
96
+ console.log('[mcp-ts][Storage] Falling back to next available backend');
73
97
  }
74
98
  }
75
99
 
76
100
  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}).`);
101
+ console.log(`[mcp-ts][Storage] Auto-detection: "file" (${process.env.MCP_TS_STORAGE_FILE})`);
78
102
  return await initializeStorage(new FileStorageBackend({ path: process.env.MCP_TS_STORAGE_FILE }));
79
103
  }
80
104
 
81
105
  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}).`);
106
+ console.log(`[mcp-ts][Storage] Auto-detection: "sqlite" (${process.env.MCP_TS_STORAGE_SQLITE_PATH})`);
83
107
  return await initializeStorage(new SqliteStorage({ path: process.env.MCP_TS_STORAGE_SQLITE_PATH }));
84
108
  }
85
109
 
86
- console.log('[Storage] No storage configured. Using In-Memory storage (Default).');
87
- return new MemoryStorageBackend();
110
+ if (process.env.SUPABASE_URL && (process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SUPABASE_ANON_KEY)) {
111
+ try {
112
+ const { createClient } = await import('@supabase/supabase-js');
113
+ const url = process.env.SUPABASE_URL;
114
+ const key = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SUPABASE_ANON_KEY!;
115
+
116
+ if (!process.env.SUPABASE_SERVICE_ROLE_KEY) {
117
+ 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.');
118
+ }
119
+
120
+ const client = createClient(url, key);
121
+ console.log('[mcp-ts][Storage] Auto-detection: "supabase" (via SUPABASE_URL)');
122
+ return await initializeStorage(new SupabaseStorageBackend(client as any));
123
+ } catch (error: any) {
124
+ console.error('[mcp-ts][Storage] Supabase auto-detection failed:', error.message);
125
+ }
126
+ }
127
+
128
+ console.log('[mcp-ts][Storage] Defaulting to: "memory"');
129
+ return await initializeStorage(new MemoryStorageBackend());
88
130
  }
89
131
 
90
132
  async function getStorage(): Promise<StorageBackend> {
@@ -27,6 +27,10 @@ export class MemoryStorageBackend implements StorageBackend {
27
27
 
28
28
  constructor() { }
29
29
 
30
+ async init(): Promise<void> {
31
+ console.log('[mcp-ts][Storage] Memory: ✓ internal memory store active.');
32
+ }
33
+
30
34
  private getSessionKey(identity: string, sessionId: string): string {
31
35
  return `${identity}:${sessionId}`;
32
36
  }
@@ -26,6 +26,15 @@ export class RedisStorageBackend implements StorageBackend {
26
26
  private readonly IDENTITY_KEY_SUFFIX = ':sessions';
27
27
 
28
28
  constructor(private redis: Redis) { }
29
+
30
+ async init(): Promise<void> {
31
+ try {
32
+ await this.redis.ping();
33
+ console.log('[mcp-ts][Storage] Redis: ✓ Connected to server.');
34
+ } catch (error: any) {
35
+ throw new Error(`[RedisStorage] Failed to connect to Redis: ${error.message}`);
36
+ }
37
+ }
29
38
 
30
39
  /**
31
40
  * Generates Redis key for a specific session
@@ -44,6 +44,7 @@ export class SqliteStorage implements StorageBackend {
44
44
  `);
45
45
 
46
46
  this.initialized = true;
47
+ console.log(`[mcp-ts][Storage] SQLite: ✓ database at ${this.dbPath} verified.`);
47
48
  } catch (error: any) {
48
49
  if (error.code === 'MODULE_NOT_FOUND' || error.message?.includes('better-sqlite3')) {
49
50
  throw new Error(