@mcp-ts/sdk 1.3.4 → 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.
Files changed (70) hide show
  1. package/README.md +404 -400
  2. package/dist/adapters/agui-adapter.d.mts +1 -1
  3. package/dist/adapters/agui-adapter.d.ts +1 -1
  4. package/dist/adapters/agui-middleware.d.mts +1 -1
  5. package/dist/adapters/agui-middleware.d.ts +1 -1
  6. package/dist/adapters/ai-adapter.d.mts +1 -1
  7. package/dist/adapters/ai-adapter.d.ts +1 -1
  8. package/dist/adapters/langchain-adapter.d.mts +1 -1
  9. package/dist/adapters/langchain-adapter.d.ts +1 -1
  10. package/dist/adapters/mastra-adapter.d.mts +1 -1
  11. package/dist/adapters/mastra-adapter.d.ts +1 -1
  12. package/dist/bin/mcp-ts.d.mts +1 -0
  13. package/dist/bin/mcp-ts.d.ts +1 -0
  14. package/dist/bin/mcp-ts.js +105 -0
  15. package/dist/bin/mcp-ts.js.map +1 -0
  16. package/dist/bin/mcp-ts.mjs +82 -0
  17. package/dist/bin/mcp-ts.mjs.map +1 -0
  18. package/dist/client/index.d.mts +1 -0
  19. package/dist/client/index.d.ts +1 -0
  20. package/dist/client/index.js +14 -5
  21. package/dist/client/index.js.map +1 -1
  22. package/dist/client/index.mjs +14 -5
  23. package/dist/client/index.mjs.map +1 -1
  24. package/dist/client/react.js +15 -6
  25. package/dist/client/react.js.map +1 -1
  26. package/dist/client/react.mjs +15 -6
  27. package/dist/client/react.mjs.map +1 -1
  28. package/dist/client/vue.js +15 -6
  29. package/dist/client/vue.js.map +1 -1
  30. package/dist/client/vue.mjs +15 -6
  31. package/dist/client/vue.mjs.map +1 -1
  32. package/dist/index.d.mts +1 -1
  33. package/dist/index.d.ts +1 -1
  34. package/dist/index.js +480 -179
  35. package/dist/index.js.map +1 -1
  36. package/dist/index.mjs +418 -179
  37. package/dist/index.mjs.map +1 -1
  38. package/dist/{multi-session-client-FAFpUzZ4.d.ts → multi-session-client-BYLarghq.d.ts} +29 -19
  39. package/dist/{multi-session-client-DzjmT7FX.d.mts → multi-session-client-CzhMkE0k.d.mts} +29 -19
  40. package/dist/server/index.d.mts +1 -1
  41. package/dist/server/index.d.ts +1 -1
  42. package/dist/server/index.js +455 -172
  43. package/dist/server/index.js.map +1 -1
  44. package/dist/server/index.mjs +410 -172
  45. package/dist/server/index.mjs.map +1 -1
  46. package/dist/shared/index.d.mts +2 -2
  47. package/dist/shared/index.d.ts +2 -2
  48. package/dist/shared/index.js +2 -2
  49. package/dist/shared/index.js.map +1 -1
  50. package/dist/shared/index.mjs +2 -2
  51. package/dist/shared/index.mjs.map +1 -1
  52. package/package.json +19 -6
  53. package/src/bin/mcp-ts.ts +102 -0
  54. package/src/client/core/sse-client.ts +371 -354
  55. package/src/client/react/use-mcp.ts +31 -31
  56. package/src/client/vue/use-mcp.ts +77 -77
  57. package/src/server/handlers/nextjs-handler.ts +204 -207
  58. package/src/server/handlers/sse-handler.ts +14 -63
  59. package/src/server/mcp/oauth-client.ts +67 -79
  60. package/src/server/mcp/storage-oauth-provider.ts +71 -38
  61. package/src/server/storage/file-backend.ts +1 -0
  62. package/src/server/storage/index.ts +82 -38
  63. package/src/server/storage/memory-backend.ts +4 -0
  64. package/src/server/storage/redis-backend.ts +102 -23
  65. package/src/server/storage/sqlite-backend.ts +1 -0
  66. package/src/server/storage/supabase-backend.ts +227 -0
  67. package/src/server/storage/types.ts +12 -12
  68. package/src/shared/constants.ts +2 -2
  69. package/src/shared/event-routing.ts +28 -0
  70. package/supabase/migrations/20260330195700_install_mcp_sessions.sql +84 -0
@@ -28,15 +28,18 @@ import {
28
28
  ReadResourceResult,
29
29
  ReadResourceResultSchema,
30
30
  } from '@modelcontextprotocol/sdk/types.js';
31
- import type { OAuthClientMetadata, OAuthTokens, OAuthClientInformationFull } from '@modelcontextprotocol/sdk/shared/auth.js';
31
+ import type { OAuthTokens, OAuthClientInformationFull } from '@modelcontextprotocol/sdk/shared/auth.js';
32
32
  import { StorageOAuthClientProvider, type AgentsOAuthProvider } from './storage-oauth-provider.js';
33
33
  import { sanitizeServerLabel } from '../../shared/utils.js';
34
34
  import { Emitter, type McpConnectionEvent, type McpObservabilityEvent, type McpConnectionState } from '../../shared/events.js';
35
35
  import { UnauthorizedError } from '../../shared/errors.js';
36
36
  import { storage } from '../storage/index.js';
37
- import { SESSION_TTL_SECONDS, STATE_EXPIRATION_MS } from '../../shared/constants.js';
38
-
39
-
37
+ import {
38
+ MCP_CLIENT_NAME,
39
+ MCP_CLIENT_VERSION,
40
+ SESSION_TTL_SECONDS,
41
+ STATE_EXPIRATION_MS,
42
+ } from '../../shared/constants.js';
40
43
 
41
44
  /**
42
45
  * Supported MCP transport types
@@ -300,49 +303,34 @@ export class MCPClient {
300
303
  throw new Error('Missing required connection metadata');
301
304
  }
302
305
 
303
- const clientMetadata: OAuthClientMetadata = {
304
- client_name: this.clientName || 'MCP Assistant',
305
- redirect_uris: [this.callbackUrl],
306
- grant_types: ['authorization_code', 'refresh_token'],
307
- response_types: ['code'],
308
- token_endpoint_auth_method: this.clientSecret ? 'client_secret_basic' : 'none',
309
- client_uri: this.clientUri || 'https://mcp-assistant.in',
310
- logo_uri: this.logoUri || 'https://mcp-assistant.in/logo.png',
311
- policy_uri: this.policyUri || 'https://mcp-assistant.in/privacy',
312
- software_id: '@mcp-ts',
313
- software_version: '1.0.0-beta.4',
314
- ...(this.clientId ? { client_id: this.clientId } : {}),
315
- ...(this.clientSecret ? { client_secret: this.clientSecret } : {}),
316
- };
317
-
318
306
  if (!this.oauthProvider) {
319
307
  if (!this.serverId) {
320
308
  throw new Error('serverId required for OAuth provider initialization');
321
309
  }
322
-
323
- this.oauthProvider = new StorageOAuthClientProvider(
324
- this.identity,
325
- this.serverId,
326
- this.sessionId,
327
- clientMetadata.client_name ?? 'MCP Assistant',
328
- this.callbackUrl,
329
- (redirectUrl: string) => {
310
+ this.oauthProvider = new StorageOAuthClientProvider({
311
+ identity: this.identity,
312
+ serverId: this.serverId,
313
+ sessionId: this.sessionId,
314
+ redirectUrl: this.callbackUrl!,
315
+ clientName: this.clientName,
316
+ clientUri: this.clientUri,
317
+ logoUri: this.logoUri,
318
+ policyUri: this.policyUri,
319
+ clientId: this.clientId,
320
+ clientSecret: this.clientSecret,
321
+ onRedirect: (redirectUrl: string) => {
330
322
  if (this.onRedirect) {
331
323
  this.onRedirect(redirectUrl);
332
324
  }
333
325
  }
334
- );
335
-
336
- if (this.clientId && this.oauthProvider) {
337
- this.oauthProvider.clientId = this.clientId;
338
- }
326
+ });
339
327
  }
340
328
 
341
329
  if (!this.client) {
342
330
  this.client = new Client(
343
331
  {
344
- name: 'mcp-ts-oauth-client',
345
- version: '2.0',
332
+ name: MCP_CLIENT_NAME,
333
+ version: MCP_CLIENT_VERSION,
346
334
  },
347
335
  {
348
336
  capabilities: {
@@ -363,34 +351,34 @@ export class MCPClient {
363
351
  if (!existingSession && this.serverId && this.serverUrl && this.callbackUrl) {
364
352
  this.createdAt = Date.now();
365
353
  console.log(`[MCPClient] Creating initial session ${this.sessionId} for OAuth flow`);
366
- await storage.createSession({
367
- sessionId: this.sessionId,
368
- identity: this.identity,
369
- serverId: this.serverId,
370
- serverName: this.serverName,
371
- serverUrl: this.serverUrl,
372
- callbackUrl: this.callbackUrl,
373
- transportType: this.transportType || 'streamable_http',
374
- createdAt: this.createdAt,
375
- active: false,
376
- }, Math.floor(STATE_EXPIRATION_MS / 1000)); // Short TTL until connection succeeds
377
- }
378
- }
354
+ await storage.createSession({
355
+ sessionId: this.sessionId,
356
+ identity: this.identity,
357
+ serverId: this.serverId,
358
+ serverName: this.serverName,
359
+ serverUrl: this.serverUrl,
360
+ callbackUrl: this.callbackUrl,
361
+ transportType: this.transportType || 'streamable_http',
362
+ createdAt: this.createdAt,
363
+ active: false,
364
+ }, Math.floor(STATE_EXPIRATION_MS / 1000)); // Short TTL until connection succeeds
365
+ }
366
+ }
379
367
 
380
368
  /**
381
369
  * Saves current session state to storage
382
370
  * Creates new session if it doesn't exist, updates if it does
383
- * @param ttl - Time-to-live in seconds (defaults to 12hr for connected sessions)
384
- * @param active - Session status marker used to avoid unnecessary TTL rewrites
385
- * @private
386
- */
387
- private async saveSession(
388
- ttl: number = SESSION_TTL_SECONDS,
389
- active: boolean = true
390
- ): Promise<void> {
391
- if (!this.sessionId || !this.serverId || !this.serverUrl || !this.callbackUrl) {
392
- return;
393
- }
371
+ * @param ttl - Time-to-live in seconds (defaults to 12hr for connected sessions)
372
+ * @param active - Session status marker used to avoid unnecessary TTL rewrites
373
+ * @private
374
+ */
375
+ private async saveSession(
376
+ ttl: number = SESSION_TTL_SECONDS,
377
+ active: boolean = true
378
+ ): Promise<void> {
379
+ if (!this.sessionId || !this.serverId || !this.serverUrl || !this.callbackUrl) {
380
+ return;
381
+ }
394
382
 
395
383
  const sessionData = {
396
384
  sessionId: this.sessionId,
@@ -398,11 +386,11 @@ export class MCPClient {
398
386
  serverId: this.serverId,
399
387
  serverName: this.serverName,
400
388
  serverUrl: this.serverUrl,
401
- callbackUrl: this.callbackUrl,
402
- transportType: (this.transportType || 'streamable_http') as TransportType,
403
- createdAt: this.createdAt || Date.now(),
404
- active,
405
- };
389
+ callbackUrl: this.callbackUrl,
390
+ transportType: (this.transportType || 'streamable_http') as TransportType,
391
+ createdAt: this.createdAt || Date.now(),
392
+ active,
393
+ };
406
394
 
407
395
  // Try to update first, create if doesn't exist
408
396
  const existingSession = await storage.getSession(this.identity, this.sessionId);
@@ -512,16 +500,16 @@ export class MCPClient {
512
500
  this.emitStateChange('CONNECTED');
513
501
  this.emitProgress('Connected successfully');
514
502
 
515
- // Promote short-lived OAuth-pending session TTL to long-lived active TTL once.
516
- // Also persist when transport negotiation changed the effective transport.
517
- const existingSession = await storage.getSession(this.identity, this.sessionId);
518
- const needsTransportUpdate = !existingSession || existingSession.transportType !== this.transportType;
519
- const needsTtlPromotion = !existingSession || existingSession.active !== true;
520
-
521
- if (needsTransportUpdate || needsTtlPromotion) {
522
- console.log(`[MCPClient] Saving session ${this.sessionId} with 12hr TTL (connect success)`);
523
- await this.saveSession(SESSION_TTL_SECONDS, true);
524
- }
503
+ // Promote short-lived OAuth-pending session TTL to long-lived active TTL once.
504
+ // Also persist when transport negotiation changed the effective transport.
505
+ const existingSession = await storage.getSession(this.identity, this.sessionId);
506
+ const needsTransportUpdate = !existingSession || existingSession.transportType !== this.transportType;
507
+ const needsTtlPromotion = !existingSession || existingSession.active !== true;
508
+
509
+ if (needsTransportUpdate || needsTtlPromotion) {
510
+ console.log(`[MCPClient] Saving session ${this.sessionId} with 12hr TTL (connect success)`);
511
+ await this.saveSession(SESSION_TTL_SECONDS, true);
512
+ }
525
513
  } catch (error) {
526
514
  /** Handle Authentication Errors */
527
515
  if (
@@ -531,7 +519,7 @@ export class MCPClient {
531
519
  this.emitStateChange('AUTHENTICATING');
532
520
  // Save session with 10min TTL for OAuth pending state
533
521
  console.log(`[MCPClient] Saving session ${this.sessionId} with 10min TTL (OAuth pending)`);
534
- await this.saveSession(Math.floor(STATE_EXPIRATION_MS / 1000), false);
522
+ await this.saveSession(Math.floor(STATE_EXPIRATION_MS / 1000), false);
535
523
 
536
524
  /** Get OAuth authorization URL if available */
537
525
  let authUrl = '';
@@ -620,8 +608,8 @@ export class MCPClient {
620
608
 
621
609
  this.client = new Client(
622
610
  {
623
- name: 'mcp-ts-oauth-client',
624
- version: '2.0',
611
+ name: MCP_CLIENT_NAME,
612
+ version: MCP_CLIENT_VERSION,
625
613
  },
626
614
  {
627
615
  capabilities: {
@@ -642,7 +630,7 @@ export class MCPClient {
642
630
  this.emitStateChange('CONNECTED');
643
631
  // Update session with 12hr TTL after successful OAuth
644
632
  console.log(`[MCPClient] Updating session ${this.sessionId} to 12hr TTL (OAuth complete)`);
645
- await this.saveSession(SESSION_TTL_SECONDS, true);
633
+ await this.saveSession(SESSION_TTL_SECONDS, true);
646
634
 
647
635
  return; // Success, exit function
648
636
 
@@ -980,8 +968,8 @@ export class MCPClient {
980
968
 
981
969
  this.client = new Client(
982
970
  {
983
- name: 'mcp-ts-oauth-client',
984
- version: '2.0',
971
+ name: MCP_CLIENT_NAME,
972
+ version: MCP_CLIENT_VERSION,
985
973
  },
986
974
  { capabilities: {} }
987
975
  );
@@ -1,13 +1,20 @@
1
-
2
1
  import type { OAuthClientProvider } from "@modelcontextprotocol/sdk/client/auth.js";
3
2
  import type {
4
- OAuthClientInformation,
5
3
  OAuthClientInformationFull,
4
+ OAuthClientInformationMixed,
6
5
  OAuthClientMetadata,
7
6
  OAuthTokens
8
7
  } from "@modelcontextprotocol/sdk/shared/auth.js";
9
8
  import { storage, SessionData } from "../storage/index.js";
10
- import { TOKEN_EXPIRY_BUFFER_MS } from '../../shared/constants.js';
9
+ import {
10
+ DEFAULT_CLIENT_NAME,
11
+ DEFAULT_CLIENT_URI,
12
+ DEFAULT_LOGO_URI,
13
+ DEFAULT_POLICY_URI,
14
+ SOFTWARE_ID,
15
+ SOFTWARE_VERSION,
16
+ TOKEN_EXPIRY_BUFFER_MS,
17
+ } from '../../shared/constants.js';
11
18
 
12
19
  /**
13
20
  * Extension of OAuthClientProvider interface with additional methods
@@ -26,56 +33,74 @@ export interface AgentsOAuthProvider extends OAuthClientProvider {
26
33
  setTokenExpiresAt(expiresAt: number): void;
27
34
  }
28
35
 
36
+ export interface StorageOAuthClientProviderOptions {
37
+ identity: string;
38
+ serverId: string;
39
+ sessionId: string;
40
+ redirectUrl: string;
41
+ clientName?: string;
42
+ clientUri?: string;
43
+ logoUri?: string;
44
+ policyUri?: string;
45
+ clientId?: string;
46
+ clientSecret?: string;
47
+ onRedirect?: (url: string) => void;
48
+ }
49
+
29
50
  /**
30
51
  * Storage-backed OAuth provider implementation for MCP
31
52
  * Stores OAuth tokens, client information, and PKCE verifiers using the configured StorageBackend
32
53
  */
33
54
  export class StorageOAuthClientProvider implements AgentsOAuthProvider {
55
+ public readonly identity: string;
56
+ public readonly serverId: string;
57
+ public readonly sessionId: string;
58
+ public readonly redirectUrl: string;
59
+
60
+ private readonly clientName?: string;
61
+ private readonly clientUri?: string;
62
+ private readonly logoUri?: string;
63
+ private readonly policyUri?: string;
64
+ private readonly clientSecret?: string;
65
+
34
66
  private _authUrl: string | undefined;
35
67
  private _clientId: string | undefined;
36
68
  private onRedirectCallback?: (url: string) => void;
37
69
  private tokenExpiresAt?: number;
38
70
 
39
71
  /**
40
- * Creates a new Storage-backed OAuth provider
41
- * @param identity - User/Client identifier
42
- * @param serverId - Server identifier (for tracking which server this OAuth session belongs to)
43
- * @param sessionId - Session identifier (used as OAuth state)
44
- * @param clientName - OAuth client name
45
- * @param baseRedirectUrl - OAuth callback URL
46
- * @param onRedirect - Optional callback when redirect to authorization is needed
72
+ * Creates a new storage-backed OAuth provider
73
+ * @param options - Provider configuration
47
74
  */
48
- constructor(
49
- public identity: string,
50
- public serverId: string,
51
- public sessionId: string,
52
- public clientName: string,
53
- public baseRedirectUrl: string,
54
- onRedirect?: (url: string) => void
55
- ) {
56
- this.onRedirectCallback = onRedirect;
75
+ constructor(options: StorageOAuthClientProviderOptions) {
76
+ this.identity = options.identity;
77
+ this.serverId = options.serverId;
78
+ this.sessionId = options.sessionId;
79
+ this.redirectUrl = options.redirectUrl;
80
+ this.clientName = options.clientName;
81
+ this.clientUri = options.clientUri;
82
+ this.logoUri = options.logoUri;
83
+ this.policyUri = options.policyUri;
84
+ this._clientId = options.clientId;
85
+ this.clientSecret = options.clientSecret;
86
+ this.onRedirectCallback = options.onRedirect;
57
87
  }
58
88
 
59
89
  get clientMetadata(): OAuthClientMetadata {
60
90
  return {
61
- client_name: this.clientName,
62
- client_uri: this.clientUri,
91
+ client_name: this.clientName || DEFAULT_CLIENT_NAME,
92
+ client_uri: this.clientUri || DEFAULT_CLIENT_URI,
93
+ logo_uri: this.logoUri || DEFAULT_LOGO_URI,
94
+ policy_uri: this.policyUri || DEFAULT_POLICY_URI,
63
95
  grant_types: ["authorization_code", "refresh_token"],
64
96
  redirect_uris: [this.redirectUrl],
65
97
  response_types: ["code"],
66
- token_endpoint_auth_method: "none",
67
- ...(this._clientId ? { client_id: this._clientId } : {})
98
+ token_endpoint_auth_method: this.clientSecret ? "client_secret_basic" : "none",
99
+ software_id: SOFTWARE_ID,
100
+ software_version: SOFTWARE_VERSION,
68
101
  };
69
102
  }
70
103
 
71
- get clientUri() {
72
- return new URL(this.redirectUrl).origin;
73
- }
74
-
75
- get redirectUrl() {
76
- return this.baseRedirectUrl;
77
- }
78
-
79
104
  get clientId() {
80
105
  return this._clientId;
81
106
  }
@@ -91,7 +116,6 @@ export class StorageOAuthClientProvider implements AgentsOAuthProvider {
91
116
  private async getSessionData(): Promise<SessionData> {
92
117
  const data = await storage.getSession(this.identity, this.sessionId);
93
118
  if (!data) {
94
- // Return empty/partial object if not found
95
119
  return {} as SessionData;
96
120
  }
97
121
  return data;
@@ -110,14 +134,25 @@ export class StorageOAuthClientProvider implements AgentsOAuthProvider {
110
134
  /**
111
135
  * Retrieves stored OAuth client information
112
136
  */
113
- async clientInformation(): Promise<OAuthClientInformation | undefined> {
137
+ async clientInformation(): Promise<OAuthClientInformationMixed | undefined> {
114
138
  const data = await this.getSessionData();
115
139
 
116
140
  if (data.clientId && !this._clientId) {
117
141
  this._clientId = data.clientId;
118
142
  }
119
143
 
120
- return data.clientInformation;
144
+ if (data.clientInformation) {
145
+ return data.clientInformation;
146
+ }
147
+
148
+ if (!this._clientId) {
149
+ return undefined;
150
+ }
151
+
152
+ return {
153
+ client_id: this._clientId,
154
+ ...(this.clientSecret ? { client_secret: this.clientSecret } : {}),
155
+ };
121
156
  }
122
157
 
123
158
  /**
@@ -152,7 +187,7 @@ export class StorageOAuthClientProvider implements AgentsOAuthProvider {
152
187
  return this.sessionId;
153
188
  }
154
189
 
155
- async checkState(state: string): Promise<{ valid: boolean; serverId?: string; error?: string }> {
190
+ async checkState(_state: string): Promise<{ valid: boolean; serverId?: string; error?: string }> {
156
191
  const data = await storage.getSession(this.identity, this.sessionId);
157
192
 
158
193
  if (!data) {
@@ -162,7 +197,7 @@ export class StorageOAuthClientProvider implements AgentsOAuthProvider {
162
197
  return { valid: true, serverId: this.serverId };
163
198
  }
164
199
 
165
- async consumeState(state: string): Promise<void> {
200
+ async consumeState(_state: string): Promise<void> {
166
201
  // No-op
167
202
  }
168
203
 
@@ -179,8 +214,6 @@ export class StorageOAuthClientProvider implements AgentsOAuthProvider {
179
214
  if (scope === "all") {
180
215
  await storage.removeSession(this.identity, this.sessionId);
181
216
  } else {
182
- const data = await this.getSessionData();
183
- // Create a copy to modify
184
217
  const updates: Partial<SessionData> = {};
185
218
 
186
219
  if (scope === "client") {
@@ -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,16 +2,28 @@
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;
14
19
 
20
+ async function initializeStorage<T extends StorageBackend>(store: T): Promise<T> {
21
+ if (typeof store.init === 'function') {
22
+ await store.init();
23
+ }
24
+ return store;
25
+ }
26
+
15
27
  async function createStorage(): Promise<StorageBackend> {
16
28
  const type = process.env.MCP_TS_STORAGE_TYPE?.toLowerCase();
17
29
 
@@ -23,37 +35,53 @@ async function createStorage(): Promise<StorageBackend> {
23
35
  try {
24
36
  const { getRedis } = await import('./redis.js');
25
37
  const redis = await getRedis();
26
- console.log('[Storage] Using Redis storage (Explicit)');
27
- return new RedisStorageBackend(redis);
38
+ console.log('[mcp-ts][Storage] Explicit selection: "redis"');
39
+ return await initializeStorage(new RedisStorageBackend(redis));
28
40
  } catch (error: any) {
29
- console.error('[Storage] Failed to initialize Redis:', error.message);
30
- console.log('[Storage] Falling back to In-Memory storage');
31
- 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());
32
44
  }
33
45
  }
34
46
 
35
47
  if (type === 'file') {
36
48
  const filePath = process.env.MCP_TS_STORAGE_FILE;
37
- if (!filePath) {
38
- console.warn('[Storage] MCP_TS_STORAGE_TYPE is "file" but MCP_TS_STORAGE_FILE is missing');
39
- }
40
- console.log(`[Storage] Using File storage (${filePath}) (Explicit)`);
41
- const store = new FileStorageBackend({ path: filePath });
42
- store.init().catch(err => console.error('[Storage] Failed to initialize file storage:', err));
43
- return store;
49
+ console.log(`[mcp-ts][Storage] Explicit selection: "file" (${filePath || 'default'})`);
50
+ return await initializeStorage(new FileStorageBackend({ path: filePath }));
44
51
  }
45
52
 
46
53
  if (type === 'sqlite') {
47
54
  const dbPath = process.env.MCP_TS_STORAGE_SQLITE_PATH;
48
- console.log(`[Storage] Using SQLite storage (${dbPath || 'default'}) (Explicit)`);
49
- const store = new SqliteStorage({ path: dbPath });
50
- store.init().catch(err => console.error('[Storage] Failed to initialize SQLite storage:', err));
51
- return store;
55
+ console.log(`[mcp-ts][Storage] Explicit selection: "sqlite" (${dbPath || 'default'})`);
56
+ return await initializeStorage(new SqliteStorage({ path: dbPath }));
57
+ }
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
+ }
52
80
  }
53
81
 
54
82
  if (type === 'memory') {
55
- console.log('[Storage] Using In-Memory storage (Explicit)');
56
- return new MemoryStorageBackend();
83
+ console.log('[mcp-ts][Storage] Explicit selection: "memory"');
84
+ return await initializeStorage(new MemoryStorageBackend());
57
85
  }
58
86
 
59
87
  // Automatic inference (Fallback)
@@ -61,31 +89,44 @@ async function createStorage(): Promise<StorageBackend> {
61
89
  try {
62
90
  const { getRedis } = await import('./redis.js');
63
91
  const redis = await getRedis();
64
- console.log('[Storage] Auto-detected REDIS_URL. Using Redis storage.');
65
- return new RedisStorageBackend(redis);
92
+ console.log('[mcp-ts][Storage] Auto-detection: "redis" (via REDIS_URL)');
93
+ return await initializeStorage(new RedisStorageBackend(redis));
66
94
  } catch (error: any) {
67
- console.error('[Storage] Redis auto-detection failed:', error.message);
68
- console.log('[Storage] Falling back to In-Memory storage');
69
- 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');
70
97
  }
71
98
  }
72
99
 
73
100
  if (process.env.MCP_TS_STORAGE_FILE) {
74
- console.log(`[Storage] Auto-detected MCP_TS_STORAGE_FILE. Using File storage (${process.env.MCP_TS_STORAGE_FILE}).`);
75
- const store = new FileStorageBackend({ path: process.env.MCP_TS_STORAGE_FILE });
76
- store.init().catch(err => console.error('[Storage] Failed to initialize file storage:', err));
77
- return store;
101
+ console.log(`[mcp-ts][Storage] Auto-detection: "file" (${process.env.MCP_TS_STORAGE_FILE})`);
102
+ return await initializeStorage(new FileStorageBackend({ path: process.env.MCP_TS_STORAGE_FILE }));
78
103
  }
79
104
 
80
105
  if (process.env.MCP_TS_STORAGE_SQLITE_PATH) {
81
- console.log(`[Storage] Auto-detected MCP_TS_STORAGE_SQLITE_PATH. Using SQLite storage (${process.env.MCP_TS_STORAGE_SQLITE_PATH}).`);
82
- const store = new SqliteStorage({ path: process.env.MCP_TS_STORAGE_SQLITE_PATH });
83
- store.init().catch(err => console.error('[Storage] Failed to initialize SQLite storage:', err));
84
- return store;
106
+ console.log(`[mcp-ts][Storage] Auto-detection: "sqlite" (${process.env.MCP_TS_STORAGE_SQLITE_PATH})`);
107
+ return await initializeStorage(new SqliteStorage({ path: process.env.MCP_TS_STORAGE_SQLITE_PATH }));
108
+ }
109
+
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
+ }
85
126
  }
86
127
 
87
- console.log('[Storage] No storage configured. Using In-Memory storage (Default).');
88
- return new MemoryStorageBackend();
128
+ console.log('[mcp-ts][Storage] Defaulting to: "memory"');
129
+ return await initializeStorage(new MemoryStorageBackend());
89
130
  }
90
131
 
91
132
  async function getStorage(): Promise<StorageBackend> {
@@ -94,7 +135,10 @@ async function getStorage(): Promise<StorageBackend> {
94
135
  }
95
136
 
96
137
  if (!storagePromise) {
97
- storagePromise = createStorage();
138
+ storagePromise = createStorage().catch((error) => {
139
+ storagePromise = null;
140
+ throw error;
141
+ });
98
142
  }
99
143
 
100
144
  storageInstance = await storagePromise;
@@ -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
  }