@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
@@ -42,6 +42,12 @@ var __export = (target, all) => {
42
42
  };
43
43
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
44
44
 
45
+ // node_modules/tsup/assets/cjs_shims.js
46
+ var init_cjs_shims = __esm({
47
+ "node_modules/tsup/assets/cjs_shims.js"() {
48
+ }
49
+ });
50
+
45
51
  // src/server/storage/redis.ts
46
52
  var redis_exports = {};
47
53
  __export(redis_exports, {
@@ -117,6 +123,7 @@ async function closeRedis() {
117
123
  var redisInstance, redis;
118
124
  var init_redis = __esm({
119
125
  "src/server/storage/redis.ts"() {
126
+ init_cjs_shims();
120
127
  redisInstance = null;
121
128
  redis = new Proxy({}, {
122
129
  get(_target, prop) {
@@ -133,7 +140,23 @@ var init_redis = __esm({
133
140
  }
134
141
  });
135
142
 
143
+ // src/server/index.ts
144
+ init_cjs_shims();
145
+
146
+ // src/server/mcp/oauth-client.ts
147
+ init_cjs_shims();
148
+
149
+ // src/server/mcp/storage-oauth-provider.ts
150
+ init_cjs_shims();
151
+
152
+ // src/server/storage/index.ts
153
+ init_cjs_shims();
154
+
155
+ // src/server/storage/redis-backend.ts
156
+ init_cjs_shims();
157
+
136
158
  // src/shared/constants.ts
159
+ init_cjs_shims();
137
160
  var SESSION_TTL_SECONDS = 43200;
138
161
  var STATE_EXPIRATION_MS = 10 * 60 * 1e3;
139
162
  var TOKEN_EXPIRY_BUFFER_MS = 5 * 60 * 1e3;
@@ -146,7 +169,8 @@ var SOFTWARE_VERSION = "1.3.4";
146
169
  var MCP_CLIENT_NAME = "mcp-ts-oauth-client";
147
170
  var MCP_CLIENT_VERSION = "2.0";
148
171
 
149
- // src/server/storage/redis-backend.ts
172
+ // src/shared/utils.ts
173
+ init_cjs_shims();
150
174
  var firstChar = nanoid.customAlphabet(
151
175
  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
152
176
  1
@@ -155,6 +179,18 @@ var rest = nanoid.customAlphabet(
155
179
  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
156
180
  11
157
181
  );
182
+ function sanitizeServerLabel(name) {
183
+ let sanitized = name.replace(/[^a-zA-Z0-9-_]/g, "_").replace(/_{2,}/g, "_").toLowerCase();
184
+ if (!/^[a-zA-Z]/.test(sanitized)) {
185
+ sanitized = "s_" + sanitized;
186
+ }
187
+ return sanitized;
188
+ }
189
+ function generateSessionId() {
190
+ return firstChar() + rest();
191
+ }
192
+
193
+ // src/server/storage/redis-backend.ts
158
194
  var RedisStorageBackend = class {
159
195
  constructor(redis2) {
160
196
  this.redis = redis2;
@@ -163,6 +199,14 @@ var RedisStorageBackend = class {
163
199
  __publicField(this, "IDENTITY_KEY_PREFIX", "mcp:identity:");
164
200
  __publicField(this, "IDENTITY_KEY_SUFFIX", ":sessions");
165
201
  }
202
+ async init() {
203
+ try {
204
+ await this.redis.ping();
205
+ console.log("[mcp-ts][Storage] Redis: \u2713 Connected to server.");
206
+ } catch (error) {
207
+ throw new Error(`[RedisStorage] Failed to connect to Redis: ${error.message}`);
208
+ }
209
+ }
166
210
  /**
167
211
  * Generates Redis key for a specific session
168
212
  * @private
@@ -205,7 +249,7 @@ var RedisStorageBackend = class {
205
249
  return Array.from(keys);
206
250
  }
207
251
  generateSessionId() {
208
- return firstChar() + rest();
252
+ return generateSessionId();
209
253
  }
210
254
  async createSession(session, ttl) {
211
255
  const { sessionId, identity } = session;
@@ -373,14 +417,9 @@ var RedisStorageBackend = class {
373
417
  }
374
418
  }
375
419
  };
376
- var firstChar2 = nanoid.customAlphabet(
377
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
378
- 1
379
- );
380
- var rest2 = nanoid.customAlphabet(
381
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
382
- 11
383
- );
420
+
421
+ // src/server/storage/memory-backend.ts
422
+ init_cjs_shims();
384
423
  var MemoryStorageBackend = class {
385
424
  constructor() {
386
425
  // Map<identity:sessionId, SessionData>
@@ -388,11 +427,14 @@ var MemoryStorageBackend = class {
388
427
  // Map<identity, Set<sessionId>>
389
428
  __publicField(this, "identitySessions", /* @__PURE__ */ new Map());
390
429
  }
430
+ async init() {
431
+ console.log("[mcp-ts][Storage] Memory: \u2713 internal memory store active.");
432
+ }
391
433
  getSessionKey(identity, sessionId) {
392
434
  return `${identity}:${sessionId}`;
393
435
  }
394
436
  generateSessionId() {
395
- return firstChar2() + rest2();
437
+ return generateSessionId();
396
438
  }
397
439
  async createSession(session, ttl) {
398
440
  const { sessionId, identity } = session;
@@ -463,14 +505,9 @@ var MemoryStorageBackend = class {
463
505
  async disconnect() {
464
506
  }
465
507
  };
466
- var firstChar3 = nanoid.customAlphabet(
467
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
468
- 1
469
- );
470
- var rest3 = nanoid.customAlphabet(
471
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
472
- 11
473
- );
508
+
509
+ // src/server/storage/file-backend.ts
510
+ init_cjs_shims();
474
511
  var FileStorageBackend = class {
475
512
  /**
476
513
  * @param options.path Path to the JSON file storage (default: ./sessions.json)
@@ -507,6 +544,7 @@ var FileStorageBackend = class {
507
544
  }
508
545
  }
509
546
  this.initialized = true;
547
+ console.log(`[mcp-ts][Storage] File: \u2713 storage directory at ${path__namespace.dirname(this.filePath)} verified.`);
510
548
  }
511
549
  async ensureInitialized() {
512
550
  if (!this.initialized) await this.init();
@@ -520,7 +558,7 @@ var FileStorageBackend = class {
520
558
  return `${identity}:${sessionId}`;
521
559
  }
522
560
  generateSessionId() {
523
- return firstChar3() + rest3();
561
+ return generateSessionId();
524
562
  }
525
563
  async createSession(session, ttl) {
526
564
  await this.ensureInitialized();
@@ -583,6 +621,9 @@ var FileStorageBackend = class {
583
621
  async disconnect() {
584
622
  }
585
623
  };
624
+
625
+ // src/server/storage/sqlite-backend.ts
626
+ init_cjs_shims();
586
627
  var SqliteStorage = class {
587
628
  constructor(options = {}) {
588
629
  __publicField(this, "db", null);
@@ -611,6 +652,7 @@ var SqliteStorage = class {
611
652
  CREATE INDEX IF NOT EXISTS idx_${this.table}_identity ON ${this.table}(identity);
612
653
  `);
613
654
  this.initialized = true;
655
+ console.log(`[mcp-ts][Storage] SQLite: \u2713 database at ${this.dbPath} verified.`);
614
656
  } catch (error) {
615
657
  if (error.code === "MODULE_NOT_FOUND" || error.message?.includes("better-sqlite3")) {
616
658
  throw new Error(
@@ -626,12 +668,7 @@ var SqliteStorage = class {
626
668
  }
627
669
  }
628
670
  generateSessionId() {
629
- const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
630
- let result = "";
631
- for (let i = 0; i < 32; i++) {
632
- result += chars.charAt(Math.floor(Math.random() * chars.length));
633
- }
634
- return result;
671
+ return generateSessionId();
635
672
  }
636
673
  async createSession(session, ttl) {
637
674
  this.ensureInitialized();
@@ -726,6 +763,161 @@ var SqliteStorage = class {
726
763
  }
727
764
  };
728
765
 
766
+ // src/server/storage/supabase-backend.ts
767
+ init_cjs_shims();
768
+ var SupabaseStorageBackend = class {
769
+ constructor(supabase) {
770
+ this.supabase = supabase;
771
+ __publicField(this, "DEFAULT_TTL", SESSION_TTL_SECONDS);
772
+ }
773
+ async init() {
774
+ const { error } = await this.supabase.from("mcp_sessions").select("session_id").limit(0);
775
+ if (error) {
776
+ if (error.code === "42P01") {
777
+ throw new Error(
778
+ '[SupabaseStorage] Table "mcp_sessions" not found in your database. Please run "npx mcp-ts supabase-init" in your project to set up the required table and RLS policies.'
779
+ );
780
+ }
781
+ throw new Error(`[SupabaseStorage] Initialization check failed: ${error.message}`);
782
+ }
783
+ console.log('[mcp-ts][Storage] Supabase: \u2713 "mcp_sessions" table verified.');
784
+ }
785
+ generateSessionId() {
786
+ return generateSessionId();
787
+ }
788
+ mapRowToSessionData(row) {
789
+ return {
790
+ sessionId: row.session_id,
791
+ serverId: row.server_id,
792
+ serverName: row.server_name,
793
+ serverUrl: row.server_url,
794
+ transportType: row.transport_type,
795
+ callbackUrl: row.callback_url,
796
+ createdAt: new Date(row.created_at).getTime(),
797
+ identity: row.identity,
798
+ headers: row.headers,
799
+ active: row.active,
800
+ clientInformation: row.client_information,
801
+ tokens: row.tokens,
802
+ codeVerifier: row.code_verifier,
803
+ clientId: row.client_id
804
+ };
805
+ }
806
+ async createSession(session, ttl) {
807
+ const { sessionId, identity } = session;
808
+ if (!sessionId || !identity) throw new Error("identity and sessionId required");
809
+ const effectiveTtl = ttl ?? this.DEFAULT_TTL;
810
+ const expiresAt = new Date(Date.now() + effectiveTtl * 1e3).toISOString();
811
+ const { error } = await this.supabase.from("mcp_sessions").insert({
812
+ session_id: sessionId,
813
+ user_id: identity,
814
+ // Maps user_id to identity to support RLS using auth.uid()
815
+ server_id: session.serverId,
816
+ server_name: session.serverName,
817
+ server_url: session.serverUrl,
818
+ transport_type: session.transportType,
819
+ callback_url: session.callbackUrl,
820
+ created_at: new Date(session.createdAt || Date.now()).toISOString(),
821
+ identity,
822
+ headers: session.headers,
823
+ active: session.active ?? false,
824
+ client_information: session.clientInformation,
825
+ tokens: session.tokens,
826
+ code_verifier: session.codeVerifier,
827
+ client_id: session.clientId,
828
+ expires_at: expiresAt
829
+ });
830
+ if (error) {
831
+ if (error.code === "23505") {
832
+ throw new Error(`Session ${sessionId} already exists`);
833
+ }
834
+ throw new Error(`Failed to create session in Supabase: ${error.message}`);
835
+ }
836
+ }
837
+ async updateSession(identity, sessionId, data, ttl) {
838
+ const effectiveTtl = ttl ?? this.DEFAULT_TTL;
839
+ const expiresAt = new Date(Date.now() + effectiveTtl * 1e3).toISOString();
840
+ const updateData = {
841
+ expires_at: expiresAt,
842
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
843
+ };
844
+ if ("serverId" in data) updateData.server_id = data.serverId;
845
+ if ("serverName" in data) updateData.server_name = data.serverName;
846
+ if ("serverUrl" in data) updateData.server_url = data.serverUrl;
847
+ if ("transportType" in data) updateData.transport_type = data.transportType;
848
+ if ("callbackUrl" in data) updateData.callback_url = data.callbackUrl;
849
+ if ("active" in data) updateData.active = data.active;
850
+ if ("headers" in data) updateData.headers = data.headers;
851
+ if ("clientInformation" in data) updateData.client_information = data.clientInformation;
852
+ if ("tokens" in data) updateData.tokens = data.tokens;
853
+ if ("codeVerifier" in data) updateData.code_verifier = data.codeVerifier;
854
+ if ("clientId" in data) updateData.client_id = data.clientId;
855
+ const { data: updatedRows, error } = await this.supabase.from("mcp_sessions").update(updateData).eq("identity", identity).eq("session_id", sessionId).select("id");
856
+ if (error) {
857
+ throw new Error(`Failed to update session: ${error.message}`);
858
+ }
859
+ if (!updatedRows || updatedRows.length === 0) {
860
+ throw new Error(`Session ${sessionId} not found for identity ${identity}`);
861
+ }
862
+ }
863
+ async getSession(identity, sessionId) {
864
+ const { data, error } = await this.supabase.from("mcp_sessions").select("*").eq("identity", identity).eq("session_id", sessionId).maybeSingle();
865
+ if (error) {
866
+ console.error("[SupabaseStorage] Failed to get session:", error);
867
+ return null;
868
+ }
869
+ if (!data) return null;
870
+ return this.mapRowToSessionData(data);
871
+ }
872
+ async getIdentitySessionsData(identity) {
873
+ const { data, error } = await this.supabase.from("mcp_sessions").select("*").eq("identity", identity);
874
+ if (error) {
875
+ console.error(`[SupabaseStorage] Failed to get session data for ${identity}:`, error);
876
+ return [];
877
+ }
878
+ return data.map((row) => this.mapRowToSessionData(row));
879
+ }
880
+ async removeSession(identity, sessionId) {
881
+ const { error } = await this.supabase.from("mcp_sessions").delete().eq("identity", identity).eq("session_id", sessionId);
882
+ if (error) {
883
+ console.error("[SupabaseStorage] Failed to remove session:", error);
884
+ }
885
+ }
886
+ async getIdentityMcpSessions(identity) {
887
+ const { data, error } = await this.supabase.from("mcp_sessions").select("session_id").eq("identity", identity);
888
+ if (error) {
889
+ console.error(`[SupabaseStorage] Failed to get sessions for ${identity}:`, error);
890
+ return [];
891
+ }
892
+ return data.map((row) => row.session_id);
893
+ }
894
+ async getAllSessionIds() {
895
+ const { data, error } = await this.supabase.from("mcp_sessions").select("session_id");
896
+ if (error) {
897
+ console.error("[SupabaseStorage] Failed to get all sessions:", error);
898
+ return [];
899
+ }
900
+ return data.map((row) => row.session_id);
901
+ }
902
+ async clearAll() {
903
+ const { error } = await this.supabase.from("mcp_sessions").delete().neq("session_id", "");
904
+ if (error) {
905
+ console.error("[SupabaseStorage] Failed to clear sessions:", error);
906
+ }
907
+ }
908
+ async cleanupExpiredSessions() {
909
+ const { error } = await this.supabase.from("mcp_sessions").delete().lt("expires_at", (/* @__PURE__ */ new Date()).toISOString());
910
+ if (error) {
911
+ console.error("[SupabaseStorage] Failed to cleanup expired sessions:", error);
912
+ }
913
+ }
914
+ async disconnect() {
915
+ }
916
+ };
917
+
918
+ // src/server/storage/types.ts
919
+ init_cjs_shims();
920
+
729
921
  // src/server/storage/index.ts
730
922
  var storageInstance = null;
731
923
  var storagePromise = null;
@@ -744,53 +936,85 @@ async function createStorage() {
744
936
  try {
745
937
  const { getRedis: getRedis2 } = await Promise.resolve().then(() => (init_redis(), redis_exports));
746
938
  const redis2 = await getRedis2();
747
- console.log("[Storage] Using Redis storage (Explicit)");
748
- return new RedisStorageBackend(redis2);
939
+ console.log('[mcp-ts][Storage] Explicit selection: "redis"');
940
+ return await initializeStorage(new RedisStorageBackend(redis2));
749
941
  } catch (error) {
750
- console.error("[Storage] Failed to initialize Redis:", error.message);
751
- console.log("[Storage] Falling back to In-Memory storage");
752
- return new MemoryStorageBackend();
942
+ console.error("[mcp-ts][Storage] Failed to initialize Redis:", error.message);
943
+ console.log("[mcp-ts][Storage] Falling back to In-Memory storage");
944
+ return await initializeStorage(new MemoryStorageBackend());
753
945
  }
754
946
  }
755
947
  if (type === "file") {
756
948
  const filePath = process.env.MCP_TS_STORAGE_FILE;
757
- if (!filePath) {
758
- console.warn('[Storage] MCP_TS_STORAGE_TYPE is "file" but MCP_TS_STORAGE_FILE is missing');
759
- }
760
- console.log(`[Storage] Using File storage (${filePath}) (Explicit)`);
949
+ console.log(`[mcp-ts][Storage] Explicit selection: "file" (${filePath || "default"})`);
761
950
  return await initializeStorage(new FileStorageBackend({ path: filePath }));
762
951
  }
763
952
  if (type === "sqlite") {
764
953
  const dbPath = process.env.MCP_TS_STORAGE_SQLITE_PATH;
765
- console.log(`[Storage] Using SQLite storage (${dbPath || "default"}) (Explicit)`);
954
+ console.log(`[mcp-ts][Storage] Explicit selection: "sqlite" (${dbPath || "default"})`);
766
955
  return await initializeStorage(new SqliteStorage({ path: dbPath }));
767
956
  }
957
+ if (type === "supabase") {
958
+ const url = process.env.SUPABASE_URL;
959
+ const key = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SUPABASE_ANON_KEY;
960
+ if (!url || !key) {
961
+ console.warn('[mcp-ts][Storage] Explicit selection "supabase" requires SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY.');
962
+ } else {
963
+ if (!process.env.SUPABASE_SERVICE_ROLE_KEY) {
964
+ console.warn('[mcp-ts][Storage] \u26A0\uFE0F Warning: Using "SUPABASE_ANON_KEY" for server-side storage. You may encounter RLS policy violations. "SUPABASE_SERVICE_ROLE_KEY" is recommended.');
965
+ }
966
+ try {
967
+ const { createClient } = await import('@supabase/supabase-js');
968
+ const client = createClient(url, key);
969
+ console.log('[mcp-ts][Storage] Explicit selection: "supabase"');
970
+ return await initializeStorage(new SupabaseStorageBackend(client));
971
+ } catch (error) {
972
+ console.error("[mcp-ts][Storage] Failed to initialize Supabase:", error.message);
973
+ console.log("[mcp-ts][Storage] Falling back to In-Memory storage");
974
+ return await initializeStorage(new MemoryStorageBackend());
975
+ }
976
+ }
977
+ }
768
978
  if (type === "memory") {
769
- console.log("[Storage] Using In-Memory storage (Explicit)");
770
- return new MemoryStorageBackend();
979
+ console.log('[mcp-ts][Storage] Explicit selection: "memory"');
980
+ return await initializeStorage(new MemoryStorageBackend());
771
981
  }
772
982
  if (process.env.REDIS_URL) {
773
983
  try {
774
984
  const { getRedis: getRedis2 } = await Promise.resolve().then(() => (init_redis(), redis_exports));
775
985
  const redis2 = await getRedis2();
776
- console.log("[Storage] Auto-detected REDIS_URL. Using Redis storage.");
777
- return new RedisStorageBackend(redis2);
986
+ console.log('[mcp-ts][Storage] Auto-detection: "redis" (via REDIS_URL)');
987
+ return await initializeStorage(new RedisStorageBackend(redis2));
778
988
  } catch (error) {
779
- console.error("[Storage] Redis auto-detection failed:", error.message);
780
- console.log("[Storage] Falling back to In-Memory storage");
781
- return new MemoryStorageBackend();
989
+ console.error("[mcp-ts][Storage] Redis auto-detection failed:", error.message);
990
+ console.log("[mcp-ts][Storage] Falling back to next available backend");
782
991
  }
783
992
  }
784
993
  if (process.env.MCP_TS_STORAGE_FILE) {
785
- console.log(`[Storage] Auto-detected MCP_TS_STORAGE_FILE. Using File storage (${process.env.MCP_TS_STORAGE_FILE}).`);
994
+ console.log(`[mcp-ts][Storage] Auto-detection: "file" (${process.env.MCP_TS_STORAGE_FILE})`);
786
995
  return await initializeStorage(new FileStorageBackend({ path: process.env.MCP_TS_STORAGE_FILE }));
787
996
  }
788
997
  if (process.env.MCP_TS_STORAGE_SQLITE_PATH) {
789
- console.log(`[Storage] Auto-detected MCP_TS_STORAGE_SQLITE_PATH. Using SQLite storage (${process.env.MCP_TS_STORAGE_SQLITE_PATH}).`);
998
+ console.log(`[mcp-ts][Storage] Auto-detection: "sqlite" (${process.env.MCP_TS_STORAGE_SQLITE_PATH})`);
790
999
  return await initializeStorage(new SqliteStorage({ path: process.env.MCP_TS_STORAGE_SQLITE_PATH }));
791
1000
  }
792
- console.log("[Storage] No storage configured. Using In-Memory storage (Default).");
793
- return new MemoryStorageBackend();
1001
+ if (process.env.SUPABASE_URL && (process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SUPABASE_ANON_KEY)) {
1002
+ try {
1003
+ const { createClient } = await import('@supabase/supabase-js');
1004
+ const url = process.env.SUPABASE_URL;
1005
+ const key = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SUPABASE_ANON_KEY;
1006
+ if (!process.env.SUPABASE_SERVICE_ROLE_KEY) {
1007
+ console.warn('[mcp-ts][Storage] \u26A0\uFE0F Warning: Using "SUPABASE_ANON_KEY" for server-side storage. You may encounter RLS policy violations. "SUPABASE_SERVICE_ROLE_KEY" is recommended.');
1008
+ }
1009
+ const client = createClient(url, key);
1010
+ console.log('[mcp-ts][Storage] Auto-detection: "supabase" (via SUPABASE_URL)');
1011
+ return await initializeStorage(new SupabaseStorageBackend(client));
1012
+ } catch (error) {
1013
+ console.error("[mcp-ts][Storage] Supabase auto-detection failed:", error.message);
1014
+ }
1015
+ }
1016
+ console.log('[mcp-ts][Storage] Defaulting to: "memory"');
1017
+ return await initializeStorage(new MemoryStorageBackend());
794
1018
  }
795
1019
  async function getStorage() {
796
1020
  if (storageInstance) {
@@ -1000,16 +1224,8 @@ var StorageOAuthClientProvider = class {
1000
1224
  }
1001
1225
  };
1002
1226
 
1003
- // src/shared/utils.ts
1004
- function sanitizeServerLabel(name) {
1005
- let sanitized = name.replace(/[^a-zA-Z0-9-_]/g, "_").replace(/_{2,}/g, "_").toLowerCase();
1006
- if (!/^[a-zA-Z]/.test(sanitized)) {
1007
- sanitized = "s_" + sanitized;
1008
- }
1009
- return sanitized;
1010
- }
1011
-
1012
1227
  // src/shared/events.ts
1228
+ init_cjs_shims();
1013
1229
  var Emitter = class {
1014
1230
  constructor() {
1015
1231
  __publicField(this, "listeners", /* @__PURE__ */ new Set());
@@ -1057,6 +1273,7 @@ var Emitter = class {
1057
1273
  };
1058
1274
 
1059
1275
  // src/shared/errors.ts
1276
+ init_cjs_shims();
1060
1277
  var McpError = class extends Error {
1061
1278
  constructor(code, message, cause) {
1062
1279
  super(message);
@@ -1971,47 +2188,133 @@ var MCPClient = class _MCPClient {
1971
2188
  };
1972
2189
 
1973
2190
  // src/server/mcp/multi-session-client.ts
2191
+ init_cjs_shims();
2192
+ var DEFAULT_TIMEOUT_MS = 15e3;
2193
+ var DEFAULT_MAX_RETRIES = 2;
2194
+ var DEFAULT_RETRY_DELAY_MS = 1e3;
2195
+ var CONNECTION_BATCH_SIZE = 5;
1974
2196
  var MultiSessionClient = class {
2197
+ /**
2198
+ * Creates a new MultiSessionClient for the given user identity.
2199
+ *
2200
+ * @param identity - A unique string identifying the user (e.g. user ID or email).
2201
+ * @param options - Optional tuning for connection timeout, retry count, and retry delay.
2202
+ * Falls back to sensible defaults if not provided.
2203
+ */
1975
2204
  constructor(identity, options = {}) {
1976
2205
  __publicField(this, "clients", []);
1977
2206
  __publicField(this, "identity");
1978
2207
  __publicField(this, "options");
2208
+ __publicField(this, "connectionPromises", /* @__PURE__ */ new Map());
1979
2209
  this.identity = identity;
1980
2210
  this.options = {
1981
- timeout: 15e3,
1982
- maxRetries: 2,
1983
- retryDelay: 1e3,
2211
+ timeout: DEFAULT_TIMEOUT_MS,
2212
+ maxRetries: DEFAULT_MAX_RETRIES,
2213
+ retryDelay: DEFAULT_RETRY_DELAY_MS,
1984
2214
  ...options
1985
2215
  };
1986
2216
  }
2217
+ /**
2218
+ * Fetches all sessions for this identity from storage and returns only the
2219
+ * ones that are ready to connect.
2220
+ *
2221
+ * A session is considered connectable when:
2222
+ * - It has a `serverId`, `serverUrl`, and `callbackUrl` (i.e. it was fully initialized)
2223
+ * - Its `active` flag is not explicitly `false` — sessions with `active: false` are
2224
+ * either mid-OAuth flow, auth-pending, or previously failed. We skip those here
2225
+ * and let the OAuth flow complete separately before we try to reconnect them.
2226
+ *
2227
+ * Note: Sessions where `active` is `undefined` (legacy records) are included
2228
+ * for backwards compatibility.
2229
+ */
1987
2230
  async getActiveSessions() {
1988
2231
  const sessions = await storage.getIdentitySessionsData(this.identity);
1989
- console.log(
1990
- `[MultiSessionClient] All sessions for ${this.identity}:`,
1991
- sessions.map((s) => ({ sessionId: s.sessionId, serverId: s.serverId }))
2232
+ const valid = sessions.filter(
2233
+ (s) => s.serverId && s.serverUrl && s.callbackUrl && s.active !== false
2234
+ // exclude OAuth-pending / failed sessions
1992
2235
  );
1993
- const valid = sessions.filter((s) => s.serverId && s.serverUrl && s.callbackUrl);
1994
- console.log(`[MultiSessionClient] Filtered valid sessions:`, valid.length);
1995
2236
  return valid;
1996
2237
  }
2238
+ /**
2239
+ * Connects to a list of sessions in controlled batches of `CONNECTION_BATCH_SIZE`.
2240
+ *
2241
+ * Batching prevents overwhelming the event loop or external servers when a user
2242
+ * has many active MCP sessions (e.g. 20+ servers). Within each batch, sessions
2243
+ * are connected concurrently using `Promise.all` for speed.
2244
+ */
1997
2245
  async connectInBatches(sessions) {
1998
- const BATCH_SIZE = 5;
1999
- for (let i = 0; i < sessions.length; i += BATCH_SIZE) {
2000
- const batch = sessions.slice(i, i + BATCH_SIZE);
2246
+ for (let i = 0; i < sessions.length; i += CONNECTION_BATCH_SIZE) {
2247
+ const batch = sessions.slice(i, i + CONNECTION_BATCH_SIZE);
2001
2248
  await Promise.all(batch.map((session) => this.connectSession(session)));
2002
2249
  }
2003
2250
  }
2251
+ /**
2252
+ * Connects a single session, with built-in deduplication to prevent race conditions.
2253
+ *
2254
+ * - If a client for this session already exists and is connected, returns immediately.
2255
+ * - If a connection attempt for this session is already in-flight (e.g. from a
2256
+ * concurrent call), it joins the existing promise instead of starting a new one.
2257
+ * This is the key concurrency lock — the `connectionPromises` map acts as a
2258
+ * per-session mutex so we never spin up two physical connections for the same session.
2259
+ * - On completion (success or failure), the promise is cleaned up from the map.
2260
+ */
2004
2261
  async connectSession(session) {
2005
2262
  const existingClient = this.clients.find((c) => c.getSessionId() === session.sessionId);
2006
2263
  if (existingClient?.isConnected()) {
2007
2264
  return;
2008
2265
  }
2009
- const maxRetries = this.options.maxRetries ?? 2;
2010
- const retryDelay = this.options.retryDelay ?? 1e3;
2266
+ if (this.connectionPromises.has(session.sessionId)) {
2267
+ return this.connectionPromises.get(session.sessionId);
2268
+ }
2269
+ const connectPromise = this.establishConnectionWithRetries(session);
2270
+ this.connectionPromises.set(session.sessionId, connectPromise);
2271
+ try {
2272
+ await connectPromise;
2273
+ } finally {
2274
+ this.connectionPromises.delete(session.sessionId);
2275
+ }
2276
+ }
2277
+ /**
2278
+ * The core connection loop for a single session.
2279
+ *
2280
+ * Attempts to establish a physical MCP connection, retrying up to `maxRetries` times
2281
+ * if the connection fails. Each attempt:
2282
+ * 1. Creates a fresh `MCPClient` instance from the session data.
2283
+ * 2. Races the connect call against a timeout promise — if the server doesn't respond
2284
+ * within `timeoutMs`, the attempt is aborted and counted as a failure.
2285
+ * 3. On success, replaces any stale client entry for this session in the `clients` array.
2286
+ * 4. On failure, waits `retryDelay` ms before the next attempt.
2287
+ *
2288
+ * If all attempts are exhausted, logs an error and returns silently (does not throw),
2289
+ * so a single bad server doesn't block the rest of the batch from connecting.
2290
+ */
2291
+ async establishConnectionWithRetries(session) {
2292
+ const maxRetries = this.options.maxRetries ?? DEFAULT_MAX_RETRIES;
2293
+ const retryDelay = this.options.retryDelay ?? DEFAULT_RETRY_DELAY_MS;
2011
2294
  let lastError;
2012
2295
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
2013
2296
  try {
2014
- const client = await this.createAndConnectClient(session);
2297
+ const client = new MCPClient({
2298
+ identity: this.identity,
2299
+ sessionId: session.sessionId,
2300
+ serverId: session.serverId,
2301
+ serverUrl: session.serverUrl,
2302
+ callbackUrl: session.callbackUrl,
2303
+ serverName: session.serverName,
2304
+ transportType: session.transportType,
2305
+ headers: session.headers
2306
+ });
2307
+ const timeoutMs = this.options.timeout ?? DEFAULT_TIMEOUT_MS;
2308
+ let timeoutTimer;
2309
+ const timeoutPromise = new Promise((_, reject) => {
2310
+ timeoutTimer = setTimeout(() => reject(new Error(`Connection timed out after ${timeoutMs}ms`)), timeoutMs);
2311
+ });
2312
+ try {
2313
+ await Promise.race([client.connect(), timeoutPromise]);
2314
+ } finally {
2315
+ clearTimeout(timeoutTimer);
2316
+ }
2317
+ this.clients = this.clients.filter((c) => c.getSessionId() !== session.sessionId);
2015
2318
  this.clients.push(client);
2016
2319
  return;
2017
2320
  } catch (error) {
@@ -2023,36 +2326,32 @@ var MultiSessionClient = class {
2023
2326
  }
2024
2327
  console.error(`[MultiSessionClient] Failed to connect to session ${session.sessionId} after ${maxRetries + 1} attempts:`, lastError);
2025
2328
  }
2026
- async createAndConnectClient(session) {
2027
- const client = new MCPClient({
2028
- identity: this.identity,
2029
- sessionId: session.sessionId,
2030
- serverId: session.serverId,
2031
- serverUrl: session.serverUrl,
2032
- callbackUrl: session.callbackUrl,
2033
- serverName: session.serverName,
2034
- transportType: session.transportType,
2035
- headers: session.headers
2036
- });
2037
- const timeoutMs = this.options.timeout ?? 15e3;
2038
- const timeoutPromise = new Promise((_, reject) => {
2039
- setTimeout(() => reject(new Error(`Connection timed out after ${timeoutMs}ms`)), timeoutMs);
2040
- });
2041
- await Promise.race([client.connect(), timeoutPromise]);
2042
- return client;
2043
- }
2329
+ /**
2330
+ * The main entry point. Fetches all active sessions for this identity from
2331
+ * storage and establishes connections to all of them in batches.
2332
+ *
2333
+ * Call this once after creating the client. On traditional servers, you can
2334
+ * cache the `MultiSessionClient` instance after calling `connect()` to avoid
2335
+ * re-fetching and re-connecting on every request.
2336
+ */
2044
2337
  async connect() {
2045
2338
  const sessions = await this.getActiveSessions();
2046
2339
  await this.connectInBatches(sessions);
2047
2340
  }
2048
2341
  /**
2049
- * Returns the array of currently connected clients.
2342
+ * Returns all currently connected `MCPClient` instances.
2343
+ *
2344
+ * Use this to enumerate available tools across all connected servers,
2345
+ * or to route a tool call to the right client by `serverId`.
2050
2346
  */
2051
2347
  getClients() {
2052
2348
  return this.clients;
2053
2349
  }
2054
2350
  /**
2055
- * Disconnects all clients.
2351
+ * Gracefully disconnects all active MCP clients and clears the internal client list.
2352
+ *
2353
+ * Call this during server shutdown or when a user logs out to free up
2354
+ * underlying transport resources (SSE streams, HTTP connections, etc.).
2056
2355
  */
2057
2356
  disconnect() {
2058
2357
  this.clients.forEach((client) => client.disconnect());
@@ -2060,7 +2359,11 @@ var MultiSessionClient = class {
2060
2359
  }
2061
2360
  };
2062
2361
 
2362
+ // src/server/handlers/sse-handler.ts
2363
+ init_cjs_shims();
2364
+
2063
2365
  // src/shared/event-routing.ts
2366
+ init_cjs_shims();
2064
2367
  function isRpcResponseEvent(event) {
2065
2368
  return "id" in event && ("result" in event || "error" in event);
2066
2369
  }
@@ -2499,6 +2802,7 @@ function writeSSEEvent(res, event, data) {
2499
2802
  }
2500
2803
 
2501
2804
  // src/server/handlers/nextjs-handler.ts
2805
+ init_cjs_shims();
2502
2806
  function createNextMcpHandler(options = {}) {
2503
2807
  const {
2504
2808
  getIdentity = (request) => new URL(request.url).searchParams.get("identity"),