@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
package/dist/index.js CHANGED
@@ -43,6 +43,12 @@ var __export = (target, all) => {
43
43
  };
44
44
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
45
45
 
46
+ // node_modules/tsup/assets/cjs_shims.js
47
+ var init_cjs_shims = __esm({
48
+ "node_modules/tsup/assets/cjs_shims.js"() {
49
+ }
50
+ });
51
+
46
52
  // src/server/storage/redis.ts
47
53
  var redis_exports = {};
48
54
  __export(redis_exports, {
@@ -118,6 +124,7 @@ async function closeRedis() {
118
124
  var redisInstance, redis;
119
125
  var init_redis = __esm({
120
126
  "src/server/storage/redis.ts"() {
127
+ init_cjs_shims();
121
128
  redisInstance = null;
122
129
  redis = new Proxy({}, {
123
130
  get(_target, prop) {
@@ -134,7 +141,26 @@ var init_redis = __esm({
134
141
  }
135
142
  });
136
143
 
144
+ // src/index.ts
145
+ init_cjs_shims();
146
+
147
+ // src/server/index.ts
148
+ init_cjs_shims();
149
+
150
+ // src/server/mcp/oauth-client.ts
151
+ init_cjs_shims();
152
+
153
+ // src/server/mcp/storage-oauth-provider.ts
154
+ init_cjs_shims();
155
+
156
+ // src/server/storage/index.ts
157
+ init_cjs_shims();
158
+
159
+ // src/server/storage/redis-backend.ts
160
+ init_cjs_shims();
161
+
137
162
  // src/shared/constants.ts
163
+ init_cjs_shims();
138
164
  var SESSION_TTL_SECONDS = 43200;
139
165
  var STATE_EXPIRATION_MS = 10 * 60 * 1e3;
140
166
  var DEFAULT_HEARTBEAT_INTERVAL_MS = 3e4;
@@ -149,7 +175,8 @@ var SOFTWARE_VERSION = "1.3.4";
149
175
  var MCP_CLIENT_NAME = "mcp-ts-oauth-client";
150
176
  var MCP_CLIENT_VERSION = "2.0";
151
177
 
152
- // src/server/storage/redis-backend.ts
178
+ // src/shared/utils.ts
179
+ init_cjs_shims();
153
180
  var firstChar = nanoid.customAlphabet(
154
181
  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
155
182
  1
@@ -158,6 +185,18 @@ var rest = nanoid.customAlphabet(
158
185
  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
159
186
  11
160
187
  );
188
+ function sanitizeServerLabel(name) {
189
+ let sanitized = name.replace(/[^a-zA-Z0-9-_]/g, "_").replace(/_{2,}/g, "_").toLowerCase();
190
+ if (!/^[a-zA-Z]/.test(sanitized)) {
191
+ sanitized = "s_" + sanitized;
192
+ }
193
+ return sanitized;
194
+ }
195
+ function generateSessionId() {
196
+ return firstChar() + rest();
197
+ }
198
+
199
+ // src/server/storage/redis-backend.ts
161
200
  var RedisStorageBackend = class {
162
201
  constructor(redis2) {
163
202
  this.redis = redis2;
@@ -166,6 +205,14 @@ var RedisStorageBackend = class {
166
205
  __publicField(this, "IDENTITY_KEY_PREFIX", "mcp:identity:");
167
206
  __publicField(this, "IDENTITY_KEY_SUFFIX", ":sessions");
168
207
  }
208
+ async init() {
209
+ try {
210
+ await this.redis.ping();
211
+ console.log("[mcp-ts][Storage] Redis: \u2713 Connected to server.");
212
+ } catch (error) {
213
+ throw new Error(`[RedisStorage] Failed to connect to Redis: ${error.message}`);
214
+ }
215
+ }
169
216
  /**
170
217
  * Generates Redis key for a specific session
171
218
  * @private
@@ -208,7 +255,7 @@ var RedisStorageBackend = class {
208
255
  return Array.from(keys);
209
256
  }
210
257
  generateSessionId() {
211
- return firstChar() + rest();
258
+ return generateSessionId();
212
259
  }
213
260
  async createSession(session, ttl) {
214
261
  const { sessionId, identity } = session;
@@ -376,14 +423,9 @@ var RedisStorageBackend = class {
376
423
  }
377
424
  }
378
425
  };
379
- var firstChar2 = nanoid.customAlphabet(
380
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
381
- 1
382
- );
383
- var rest2 = nanoid.customAlphabet(
384
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
385
- 11
386
- );
426
+
427
+ // src/server/storage/memory-backend.ts
428
+ init_cjs_shims();
387
429
  var MemoryStorageBackend = class {
388
430
  constructor() {
389
431
  // Map<identity:sessionId, SessionData>
@@ -391,11 +433,14 @@ var MemoryStorageBackend = class {
391
433
  // Map<identity, Set<sessionId>>
392
434
  __publicField(this, "identitySessions", /* @__PURE__ */ new Map());
393
435
  }
436
+ async init() {
437
+ console.log("[mcp-ts][Storage] Memory: \u2713 internal memory store active.");
438
+ }
394
439
  getSessionKey(identity, sessionId) {
395
440
  return `${identity}:${sessionId}`;
396
441
  }
397
442
  generateSessionId() {
398
- return firstChar2() + rest2();
443
+ return generateSessionId();
399
444
  }
400
445
  async createSession(session, ttl) {
401
446
  const { sessionId, identity } = session;
@@ -466,14 +511,9 @@ var MemoryStorageBackend = class {
466
511
  async disconnect() {
467
512
  }
468
513
  };
469
- var firstChar3 = nanoid.customAlphabet(
470
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
471
- 1
472
- );
473
- var rest3 = nanoid.customAlphabet(
474
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
475
- 11
476
- );
514
+
515
+ // src/server/storage/file-backend.ts
516
+ init_cjs_shims();
477
517
  var FileStorageBackend = class {
478
518
  /**
479
519
  * @param options.path Path to the JSON file storage (default: ./sessions.json)
@@ -510,6 +550,7 @@ var FileStorageBackend = class {
510
550
  }
511
551
  }
512
552
  this.initialized = true;
553
+ console.log(`[mcp-ts][Storage] File: \u2713 storage directory at ${path__namespace.dirname(this.filePath)} verified.`);
513
554
  }
514
555
  async ensureInitialized() {
515
556
  if (!this.initialized) await this.init();
@@ -523,7 +564,7 @@ var FileStorageBackend = class {
523
564
  return `${identity}:${sessionId}`;
524
565
  }
525
566
  generateSessionId() {
526
- return firstChar3() + rest3();
567
+ return generateSessionId();
527
568
  }
528
569
  async createSession(session, ttl) {
529
570
  await this.ensureInitialized();
@@ -586,6 +627,9 @@ var FileStorageBackend = class {
586
627
  async disconnect() {
587
628
  }
588
629
  };
630
+
631
+ // src/server/storage/sqlite-backend.ts
632
+ init_cjs_shims();
589
633
  var SqliteStorage = class {
590
634
  constructor(options = {}) {
591
635
  __publicField(this, "db", null);
@@ -614,6 +658,7 @@ var SqliteStorage = class {
614
658
  CREATE INDEX IF NOT EXISTS idx_${this.table}_identity ON ${this.table}(identity);
615
659
  `);
616
660
  this.initialized = true;
661
+ console.log(`[mcp-ts][Storage] SQLite: \u2713 database at ${this.dbPath} verified.`);
617
662
  } catch (error) {
618
663
  if (error.code === "MODULE_NOT_FOUND" || error.message?.includes("better-sqlite3")) {
619
664
  throw new Error(
@@ -629,12 +674,7 @@ var SqliteStorage = class {
629
674
  }
630
675
  }
631
676
  generateSessionId() {
632
- const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
633
- let result = "";
634
- for (let i = 0; i < 32; i++) {
635
- result += chars.charAt(Math.floor(Math.random() * chars.length));
636
- }
637
- return result;
677
+ return generateSessionId();
638
678
  }
639
679
  async createSession(session, ttl) {
640
680
  this.ensureInitialized();
@@ -729,6 +769,161 @@ var SqliteStorage = class {
729
769
  }
730
770
  };
731
771
 
772
+ // src/server/storage/supabase-backend.ts
773
+ init_cjs_shims();
774
+ var SupabaseStorageBackend = class {
775
+ constructor(supabase) {
776
+ this.supabase = supabase;
777
+ __publicField(this, "DEFAULT_TTL", SESSION_TTL_SECONDS);
778
+ }
779
+ async init() {
780
+ const { error } = await this.supabase.from("mcp_sessions").select("session_id").limit(0);
781
+ if (error) {
782
+ if (error.code === "42P01") {
783
+ throw new Error(
784
+ '[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.'
785
+ );
786
+ }
787
+ throw new Error(`[SupabaseStorage] Initialization check failed: ${error.message}`);
788
+ }
789
+ console.log('[mcp-ts][Storage] Supabase: \u2713 "mcp_sessions" table verified.');
790
+ }
791
+ generateSessionId() {
792
+ return generateSessionId();
793
+ }
794
+ mapRowToSessionData(row) {
795
+ return {
796
+ sessionId: row.session_id,
797
+ serverId: row.server_id,
798
+ serverName: row.server_name,
799
+ serverUrl: row.server_url,
800
+ transportType: row.transport_type,
801
+ callbackUrl: row.callback_url,
802
+ createdAt: new Date(row.created_at).getTime(),
803
+ identity: row.identity,
804
+ headers: row.headers,
805
+ active: row.active,
806
+ clientInformation: row.client_information,
807
+ tokens: row.tokens,
808
+ codeVerifier: row.code_verifier,
809
+ clientId: row.client_id
810
+ };
811
+ }
812
+ async createSession(session, ttl) {
813
+ const { sessionId, identity } = session;
814
+ if (!sessionId || !identity) throw new Error("identity and sessionId required");
815
+ const effectiveTtl = ttl ?? this.DEFAULT_TTL;
816
+ const expiresAt = new Date(Date.now() + effectiveTtl * 1e3).toISOString();
817
+ const { error } = await this.supabase.from("mcp_sessions").insert({
818
+ session_id: sessionId,
819
+ user_id: identity,
820
+ // Maps user_id to identity to support RLS using auth.uid()
821
+ server_id: session.serverId,
822
+ server_name: session.serverName,
823
+ server_url: session.serverUrl,
824
+ transport_type: session.transportType,
825
+ callback_url: session.callbackUrl,
826
+ created_at: new Date(session.createdAt || Date.now()).toISOString(),
827
+ identity,
828
+ headers: session.headers,
829
+ active: session.active ?? false,
830
+ client_information: session.clientInformation,
831
+ tokens: session.tokens,
832
+ code_verifier: session.codeVerifier,
833
+ client_id: session.clientId,
834
+ expires_at: expiresAt
835
+ });
836
+ if (error) {
837
+ if (error.code === "23505") {
838
+ throw new Error(`Session ${sessionId} already exists`);
839
+ }
840
+ throw new Error(`Failed to create session in Supabase: ${error.message}`);
841
+ }
842
+ }
843
+ async updateSession(identity, sessionId, data, ttl) {
844
+ const effectiveTtl = ttl ?? this.DEFAULT_TTL;
845
+ const expiresAt = new Date(Date.now() + effectiveTtl * 1e3).toISOString();
846
+ const updateData = {
847
+ expires_at: expiresAt,
848
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
849
+ };
850
+ if ("serverId" in data) updateData.server_id = data.serverId;
851
+ if ("serverName" in data) updateData.server_name = data.serverName;
852
+ if ("serverUrl" in data) updateData.server_url = data.serverUrl;
853
+ if ("transportType" in data) updateData.transport_type = data.transportType;
854
+ if ("callbackUrl" in data) updateData.callback_url = data.callbackUrl;
855
+ if ("active" in data) updateData.active = data.active;
856
+ if ("headers" in data) updateData.headers = data.headers;
857
+ if ("clientInformation" in data) updateData.client_information = data.clientInformation;
858
+ if ("tokens" in data) updateData.tokens = data.tokens;
859
+ if ("codeVerifier" in data) updateData.code_verifier = data.codeVerifier;
860
+ if ("clientId" in data) updateData.client_id = data.clientId;
861
+ const { data: updatedRows, error } = await this.supabase.from("mcp_sessions").update(updateData).eq("identity", identity).eq("session_id", sessionId).select("id");
862
+ if (error) {
863
+ throw new Error(`Failed to update session: ${error.message}`);
864
+ }
865
+ if (!updatedRows || updatedRows.length === 0) {
866
+ throw new Error(`Session ${sessionId} not found for identity ${identity}`);
867
+ }
868
+ }
869
+ async getSession(identity, sessionId) {
870
+ const { data, error } = await this.supabase.from("mcp_sessions").select("*").eq("identity", identity).eq("session_id", sessionId).maybeSingle();
871
+ if (error) {
872
+ console.error("[SupabaseStorage] Failed to get session:", error);
873
+ return null;
874
+ }
875
+ if (!data) return null;
876
+ return this.mapRowToSessionData(data);
877
+ }
878
+ async getIdentitySessionsData(identity) {
879
+ const { data, error } = await this.supabase.from("mcp_sessions").select("*").eq("identity", identity);
880
+ if (error) {
881
+ console.error(`[SupabaseStorage] Failed to get session data for ${identity}:`, error);
882
+ return [];
883
+ }
884
+ return data.map((row) => this.mapRowToSessionData(row));
885
+ }
886
+ async removeSession(identity, sessionId) {
887
+ const { error } = await this.supabase.from("mcp_sessions").delete().eq("identity", identity).eq("session_id", sessionId);
888
+ if (error) {
889
+ console.error("[SupabaseStorage] Failed to remove session:", error);
890
+ }
891
+ }
892
+ async getIdentityMcpSessions(identity) {
893
+ const { data, error } = await this.supabase.from("mcp_sessions").select("session_id").eq("identity", identity);
894
+ if (error) {
895
+ console.error(`[SupabaseStorage] Failed to get sessions for ${identity}:`, error);
896
+ return [];
897
+ }
898
+ return data.map((row) => row.session_id);
899
+ }
900
+ async getAllSessionIds() {
901
+ const { data, error } = await this.supabase.from("mcp_sessions").select("session_id");
902
+ if (error) {
903
+ console.error("[SupabaseStorage] Failed to get all sessions:", error);
904
+ return [];
905
+ }
906
+ return data.map((row) => row.session_id);
907
+ }
908
+ async clearAll() {
909
+ const { error } = await this.supabase.from("mcp_sessions").delete().neq("session_id", "");
910
+ if (error) {
911
+ console.error("[SupabaseStorage] Failed to clear sessions:", error);
912
+ }
913
+ }
914
+ async cleanupExpiredSessions() {
915
+ const { error } = await this.supabase.from("mcp_sessions").delete().lt("expires_at", (/* @__PURE__ */ new Date()).toISOString());
916
+ if (error) {
917
+ console.error("[SupabaseStorage] Failed to cleanup expired sessions:", error);
918
+ }
919
+ }
920
+ async disconnect() {
921
+ }
922
+ };
923
+
924
+ // src/server/storage/types.ts
925
+ init_cjs_shims();
926
+
732
927
  // src/server/storage/index.ts
733
928
  var storageInstance = null;
734
929
  var storagePromise = null;
@@ -747,53 +942,85 @@ async function createStorage() {
747
942
  try {
748
943
  const { getRedis: getRedis2 } = await Promise.resolve().then(() => (init_redis(), redis_exports));
749
944
  const redis2 = await getRedis2();
750
- console.log("[Storage] Using Redis storage (Explicit)");
751
- return new RedisStorageBackend(redis2);
945
+ console.log('[mcp-ts][Storage] Explicit selection: "redis"');
946
+ return await initializeStorage(new RedisStorageBackend(redis2));
752
947
  } catch (error) {
753
- console.error("[Storage] Failed to initialize Redis:", error.message);
754
- console.log("[Storage] Falling back to In-Memory storage");
755
- return new MemoryStorageBackend();
948
+ console.error("[mcp-ts][Storage] Failed to initialize Redis:", error.message);
949
+ console.log("[mcp-ts][Storage] Falling back to In-Memory storage");
950
+ return await initializeStorage(new MemoryStorageBackend());
756
951
  }
757
952
  }
758
953
  if (type === "file") {
759
954
  const filePath = process.env.MCP_TS_STORAGE_FILE;
760
- if (!filePath) {
761
- console.warn('[Storage] MCP_TS_STORAGE_TYPE is "file" but MCP_TS_STORAGE_FILE is missing');
762
- }
763
- console.log(`[Storage] Using File storage (${filePath}) (Explicit)`);
955
+ console.log(`[mcp-ts][Storage] Explicit selection: "file" (${filePath || "default"})`);
764
956
  return await initializeStorage(new FileStorageBackend({ path: filePath }));
765
957
  }
766
958
  if (type === "sqlite") {
767
959
  const dbPath = process.env.MCP_TS_STORAGE_SQLITE_PATH;
768
- console.log(`[Storage] Using SQLite storage (${dbPath || "default"}) (Explicit)`);
960
+ console.log(`[mcp-ts][Storage] Explicit selection: "sqlite" (${dbPath || "default"})`);
769
961
  return await initializeStorage(new SqliteStorage({ path: dbPath }));
770
962
  }
963
+ if (type === "supabase") {
964
+ const url = process.env.SUPABASE_URL;
965
+ const key = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SUPABASE_ANON_KEY;
966
+ if (!url || !key) {
967
+ console.warn('[mcp-ts][Storage] Explicit selection "supabase" requires SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY.');
968
+ } else {
969
+ if (!process.env.SUPABASE_SERVICE_ROLE_KEY) {
970
+ 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.');
971
+ }
972
+ try {
973
+ const { createClient } = await import('@supabase/supabase-js');
974
+ const client = createClient(url, key);
975
+ console.log('[mcp-ts][Storage] Explicit selection: "supabase"');
976
+ return await initializeStorage(new SupabaseStorageBackend(client));
977
+ } catch (error) {
978
+ console.error("[mcp-ts][Storage] Failed to initialize Supabase:", error.message);
979
+ console.log("[mcp-ts][Storage] Falling back to In-Memory storage");
980
+ return await initializeStorage(new MemoryStorageBackend());
981
+ }
982
+ }
983
+ }
771
984
  if (type === "memory") {
772
- console.log("[Storage] Using In-Memory storage (Explicit)");
773
- return new MemoryStorageBackend();
985
+ console.log('[mcp-ts][Storage] Explicit selection: "memory"');
986
+ return await initializeStorage(new MemoryStorageBackend());
774
987
  }
775
988
  if (process.env.REDIS_URL) {
776
989
  try {
777
990
  const { getRedis: getRedis2 } = await Promise.resolve().then(() => (init_redis(), redis_exports));
778
991
  const redis2 = await getRedis2();
779
- console.log("[Storage] Auto-detected REDIS_URL. Using Redis storage.");
780
- return new RedisStorageBackend(redis2);
992
+ console.log('[mcp-ts][Storage] Auto-detection: "redis" (via REDIS_URL)');
993
+ return await initializeStorage(new RedisStorageBackend(redis2));
781
994
  } catch (error) {
782
- console.error("[Storage] Redis auto-detection failed:", error.message);
783
- console.log("[Storage] Falling back to In-Memory storage");
784
- return new MemoryStorageBackend();
995
+ console.error("[mcp-ts][Storage] Redis auto-detection failed:", error.message);
996
+ console.log("[mcp-ts][Storage] Falling back to next available backend");
785
997
  }
786
998
  }
787
999
  if (process.env.MCP_TS_STORAGE_FILE) {
788
- console.log(`[Storage] Auto-detected MCP_TS_STORAGE_FILE. Using File storage (${process.env.MCP_TS_STORAGE_FILE}).`);
1000
+ console.log(`[mcp-ts][Storage] Auto-detection: "file" (${process.env.MCP_TS_STORAGE_FILE})`);
789
1001
  return await initializeStorage(new FileStorageBackend({ path: process.env.MCP_TS_STORAGE_FILE }));
790
1002
  }
791
1003
  if (process.env.MCP_TS_STORAGE_SQLITE_PATH) {
792
- console.log(`[Storage] Auto-detected MCP_TS_STORAGE_SQLITE_PATH. Using SQLite storage (${process.env.MCP_TS_STORAGE_SQLITE_PATH}).`);
1004
+ console.log(`[mcp-ts][Storage] Auto-detection: "sqlite" (${process.env.MCP_TS_STORAGE_SQLITE_PATH})`);
793
1005
  return await initializeStorage(new SqliteStorage({ path: process.env.MCP_TS_STORAGE_SQLITE_PATH }));
794
1006
  }
795
- console.log("[Storage] No storage configured. Using In-Memory storage (Default).");
796
- return new MemoryStorageBackend();
1007
+ if (process.env.SUPABASE_URL && (process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SUPABASE_ANON_KEY)) {
1008
+ try {
1009
+ const { createClient } = await import('@supabase/supabase-js');
1010
+ const url = process.env.SUPABASE_URL;
1011
+ const key = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SUPABASE_ANON_KEY;
1012
+ if (!process.env.SUPABASE_SERVICE_ROLE_KEY) {
1013
+ 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.');
1014
+ }
1015
+ const client = createClient(url, key);
1016
+ console.log('[mcp-ts][Storage] Auto-detection: "supabase" (via SUPABASE_URL)');
1017
+ return await initializeStorage(new SupabaseStorageBackend(client));
1018
+ } catch (error) {
1019
+ console.error("[mcp-ts][Storage] Supabase auto-detection failed:", error.message);
1020
+ }
1021
+ }
1022
+ console.log('[mcp-ts][Storage] Defaulting to: "memory"');
1023
+ return await initializeStorage(new MemoryStorageBackend());
797
1024
  }
798
1025
  async function getStorage() {
799
1026
  if (storageInstance) {
@@ -1003,16 +1230,8 @@ var StorageOAuthClientProvider = class {
1003
1230
  }
1004
1231
  };
1005
1232
 
1006
- // src/shared/utils.ts
1007
- function sanitizeServerLabel(name) {
1008
- let sanitized = name.replace(/[^a-zA-Z0-9-_]/g, "_").replace(/_{2,}/g, "_").toLowerCase();
1009
- if (!/^[a-zA-Z]/.test(sanitized)) {
1010
- sanitized = "s_" + sanitized;
1011
- }
1012
- return sanitized;
1013
- }
1014
-
1015
1233
  // src/shared/events.ts
1234
+ init_cjs_shims();
1016
1235
  var Emitter = class {
1017
1236
  constructor() {
1018
1237
  __publicField(this, "listeners", /* @__PURE__ */ new Set());
@@ -1074,6 +1293,7 @@ var DisposableStore = class {
1074
1293
  };
1075
1294
 
1076
1295
  // src/shared/errors.ts
1296
+ init_cjs_shims();
1077
1297
  var McpError = class extends Error {
1078
1298
  constructor(code, message, cause) {
1079
1299
  super(message);
@@ -2042,47 +2262,133 @@ var MCPClient = class _MCPClient {
2042
2262
  };
2043
2263
 
2044
2264
  // src/server/mcp/multi-session-client.ts
2265
+ init_cjs_shims();
2266
+ var DEFAULT_TIMEOUT_MS = 15e3;
2267
+ var DEFAULT_MAX_RETRIES = 2;
2268
+ var DEFAULT_RETRY_DELAY_MS = 1e3;
2269
+ var CONNECTION_BATCH_SIZE = 5;
2045
2270
  var MultiSessionClient = class {
2271
+ /**
2272
+ * Creates a new MultiSessionClient for the given user identity.
2273
+ *
2274
+ * @param identity - A unique string identifying the user (e.g. user ID or email).
2275
+ * @param options - Optional tuning for connection timeout, retry count, and retry delay.
2276
+ * Falls back to sensible defaults if not provided.
2277
+ */
2046
2278
  constructor(identity, options = {}) {
2047
2279
  __publicField(this, "clients", []);
2048
2280
  __publicField(this, "identity");
2049
2281
  __publicField(this, "options");
2282
+ __publicField(this, "connectionPromises", /* @__PURE__ */ new Map());
2050
2283
  this.identity = identity;
2051
2284
  this.options = {
2052
- timeout: 15e3,
2053
- maxRetries: 2,
2054
- retryDelay: 1e3,
2285
+ timeout: DEFAULT_TIMEOUT_MS,
2286
+ maxRetries: DEFAULT_MAX_RETRIES,
2287
+ retryDelay: DEFAULT_RETRY_DELAY_MS,
2055
2288
  ...options
2056
2289
  };
2057
2290
  }
2291
+ /**
2292
+ * Fetches all sessions for this identity from storage and returns only the
2293
+ * ones that are ready to connect.
2294
+ *
2295
+ * A session is considered connectable when:
2296
+ * - It has a `serverId`, `serverUrl`, and `callbackUrl` (i.e. it was fully initialized)
2297
+ * - Its `active` flag is not explicitly `false` — sessions with `active: false` are
2298
+ * either mid-OAuth flow, auth-pending, or previously failed. We skip those here
2299
+ * and let the OAuth flow complete separately before we try to reconnect them.
2300
+ *
2301
+ * Note: Sessions where `active` is `undefined` (legacy records) are included
2302
+ * for backwards compatibility.
2303
+ */
2058
2304
  async getActiveSessions() {
2059
2305
  const sessions = await storage.getIdentitySessionsData(this.identity);
2060
- console.log(
2061
- `[MultiSessionClient] All sessions for ${this.identity}:`,
2062
- sessions.map((s) => ({ sessionId: s.sessionId, serverId: s.serverId }))
2306
+ const valid = sessions.filter(
2307
+ (s) => s.serverId && s.serverUrl && s.callbackUrl && s.active !== false
2308
+ // exclude OAuth-pending / failed sessions
2063
2309
  );
2064
- const valid = sessions.filter((s) => s.serverId && s.serverUrl && s.callbackUrl);
2065
- console.log(`[MultiSessionClient] Filtered valid sessions:`, valid.length);
2066
2310
  return valid;
2067
2311
  }
2312
+ /**
2313
+ * Connects to a list of sessions in controlled batches of `CONNECTION_BATCH_SIZE`.
2314
+ *
2315
+ * Batching prevents overwhelming the event loop or external servers when a user
2316
+ * has many active MCP sessions (e.g. 20+ servers). Within each batch, sessions
2317
+ * are connected concurrently using `Promise.all` for speed.
2318
+ */
2068
2319
  async connectInBatches(sessions) {
2069
- const BATCH_SIZE = 5;
2070
- for (let i = 0; i < sessions.length; i += BATCH_SIZE) {
2071
- const batch = sessions.slice(i, i + BATCH_SIZE);
2320
+ for (let i = 0; i < sessions.length; i += CONNECTION_BATCH_SIZE) {
2321
+ const batch = sessions.slice(i, i + CONNECTION_BATCH_SIZE);
2072
2322
  await Promise.all(batch.map((session) => this.connectSession(session)));
2073
2323
  }
2074
2324
  }
2325
+ /**
2326
+ * Connects a single session, with built-in deduplication to prevent race conditions.
2327
+ *
2328
+ * - If a client for this session already exists and is connected, returns immediately.
2329
+ * - If a connection attempt for this session is already in-flight (e.g. from a
2330
+ * concurrent call), it joins the existing promise instead of starting a new one.
2331
+ * This is the key concurrency lock — the `connectionPromises` map acts as a
2332
+ * per-session mutex so we never spin up two physical connections for the same session.
2333
+ * - On completion (success or failure), the promise is cleaned up from the map.
2334
+ */
2075
2335
  async connectSession(session) {
2076
2336
  const existingClient = this.clients.find((c) => c.getSessionId() === session.sessionId);
2077
2337
  if (existingClient?.isConnected()) {
2078
2338
  return;
2079
2339
  }
2080
- const maxRetries = this.options.maxRetries ?? 2;
2081
- const retryDelay = this.options.retryDelay ?? 1e3;
2340
+ if (this.connectionPromises.has(session.sessionId)) {
2341
+ return this.connectionPromises.get(session.sessionId);
2342
+ }
2343
+ const connectPromise = this.establishConnectionWithRetries(session);
2344
+ this.connectionPromises.set(session.sessionId, connectPromise);
2345
+ try {
2346
+ await connectPromise;
2347
+ } finally {
2348
+ this.connectionPromises.delete(session.sessionId);
2349
+ }
2350
+ }
2351
+ /**
2352
+ * The core connection loop for a single session.
2353
+ *
2354
+ * Attempts to establish a physical MCP connection, retrying up to `maxRetries` times
2355
+ * if the connection fails. Each attempt:
2356
+ * 1. Creates a fresh `MCPClient` instance from the session data.
2357
+ * 2. Races the connect call against a timeout promise — if the server doesn't respond
2358
+ * within `timeoutMs`, the attempt is aborted and counted as a failure.
2359
+ * 3. On success, replaces any stale client entry for this session in the `clients` array.
2360
+ * 4. On failure, waits `retryDelay` ms before the next attempt.
2361
+ *
2362
+ * If all attempts are exhausted, logs an error and returns silently (does not throw),
2363
+ * so a single bad server doesn't block the rest of the batch from connecting.
2364
+ */
2365
+ async establishConnectionWithRetries(session) {
2366
+ const maxRetries = this.options.maxRetries ?? DEFAULT_MAX_RETRIES;
2367
+ const retryDelay = this.options.retryDelay ?? DEFAULT_RETRY_DELAY_MS;
2082
2368
  let lastError;
2083
2369
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
2084
2370
  try {
2085
- const client = await this.createAndConnectClient(session);
2371
+ const client = new MCPClient({
2372
+ identity: this.identity,
2373
+ sessionId: session.sessionId,
2374
+ serverId: session.serverId,
2375
+ serverUrl: session.serverUrl,
2376
+ callbackUrl: session.callbackUrl,
2377
+ serverName: session.serverName,
2378
+ transportType: session.transportType,
2379
+ headers: session.headers
2380
+ });
2381
+ const timeoutMs = this.options.timeout ?? DEFAULT_TIMEOUT_MS;
2382
+ let timeoutTimer;
2383
+ const timeoutPromise = new Promise((_, reject) => {
2384
+ timeoutTimer = setTimeout(() => reject(new Error(`Connection timed out after ${timeoutMs}ms`)), timeoutMs);
2385
+ });
2386
+ try {
2387
+ await Promise.race([client.connect(), timeoutPromise]);
2388
+ } finally {
2389
+ clearTimeout(timeoutTimer);
2390
+ }
2391
+ this.clients = this.clients.filter((c) => c.getSessionId() !== session.sessionId);
2086
2392
  this.clients.push(client);
2087
2393
  return;
2088
2394
  } catch (error) {
@@ -2094,36 +2400,32 @@ var MultiSessionClient = class {
2094
2400
  }
2095
2401
  console.error(`[MultiSessionClient] Failed to connect to session ${session.sessionId} after ${maxRetries + 1} attempts:`, lastError);
2096
2402
  }
2097
- async createAndConnectClient(session) {
2098
- const client = new MCPClient({
2099
- identity: this.identity,
2100
- sessionId: session.sessionId,
2101
- serverId: session.serverId,
2102
- serverUrl: session.serverUrl,
2103
- callbackUrl: session.callbackUrl,
2104
- serverName: session.serverName,
2105
- transportType: session.transportType,
2106
- headers: session.headers
2107
- });
2108
- const timeoutMs = this.options.timeout ?? 15e3;
2109
- const timeoutPromise = new Promise((_, reject) => {
2110
- setTimeout(() => reject(new Error(`Connection timed out after ${timeoutMs}ms`)), timeoutMs);
2111
- });
2112
- await Promise.race([client.connect(), timeoutPromise]);
2113
- return client;
2114
- }
2403
+ /**
2404
+ * The main entry point. Fetches all active sessions for this identity from
2405
+ * storage and establishes connections to all of them in batches.
2406
+ *
2407
+ * Call this once after creating the client. On traditional servers, you can
2408
+ * cache the `MultiSessionClient` instance after calling `connect()` to avoid
2409
+ * re-fetching and re-connecting on every request.
2410
+ */
2115
2411
  async connect() {
2116
2412
  const sessions = await this.getActiveSessions();
2117
2413
  await this.connectInBatches(sessions);
2118
2414
  }
2119
2415
  /**
2120
- * Returns the array of currently connected clients.
2416
+ * Returns all currently connected `MCPClient` instances.
2417
+ *
2418
+ * Use this to enumerate available tools across all connected servers,
2419
+ * or to route a tool call to the right client by `serverId`.
2121
2420
  */
2122
2421
  getClients() {
2123
2422
  return this.clients;
2124
2423
  }
2125
2424
  /**
2126
- * Disconnects all clients.
2425
+ * Gracefully disconnects all active MCP clients and clears the internal client list.
2426
+ *
2427
+ * Call this during server shutdown or when a user logs out to free up
2428
+ * underlying transport resources (SSE streams, HTTP connections, etc.).
2127
2429
  */
2128
2430
  disconnect() {
2129
2431
  this.clients.forEach((client) => client.disconnect());
@@ -2131,7 +2433,11 @@ var MultiSessionClient = class {
2131
2433
  }
2132
2434
  };
2133
2435
 
2436
+ // src/server/handlers/sse-handler.ts
2437
+ init_cjs_shims();
2438
+
2134
2439
  // src/shared/event-routing.ts
2440
+ init_cjs_shims();
2135
2441
  function isRpcResponseEvent(event) {
2136
2442
  return "id" in event && ("result" in event || "error" in event);
2137
2443
  }
@@ -2570,6 +2876,7 @@ function writeSSEEvent(res, event, data) {
2570
2876
  }
2571
2877
 
2572
2878
  // src/server/handlers/nextjs-handler.ts
2879
+ init_cjs_shims();
2573
2880
  function createNextMcpHandler(options = {}) {
2574
2881
  const {
2575
2882
  getIdentity = (request) => new URL(request.url).searchParams.get("identity"),
@@ -2717,6 +3024,12 @@ data: ${JSON.stringify(data)}
2717
3024
  }
2718
3025
  return { GET, POST };
2719
3026
  }
3027
+
3028
+ // src/client/index.ts
3029
+ init_cjs_shims();
3030
+
3031
+ // src/client/core/sse-client.ts
3032
+ init_cjs_shims();
2720
3033
  var CONNECTION_EVENT_INTERVAL_MS = 300;
2721
3034
  var SSEClient = class {
2722
3035
  constructor(options) {
@@ -2965,6 +3278,9 @@ var SSEClient = class {
2965
3278
  }
2966
3279
  }
2967
3280
  };
3281
+
3282
+ // src/client/core/app-host.ts
3283
+ init_cjs_shims();
2968
3284
  var HOST_INFO = { name: "mcp-ts-host", version: "1.0.0" };
2969
3285
  var SANDBOX_PERMISSIONS = [
2970
3286
  "allow-scripts",
@@ -3262,7 +3578,11 @@ var AppHost = class {
3262
3578
  }
3263
3579
  };
3264
3580
 
3581
+ // src/shared/index.ts
3582
+ init_cjs_shims();
3583
+
3265
3584
  // src/shared/types.ts
3585
+ init_cjs_shims();
3266
3586
  function isConnectSuccess(response) {
3267
3587
  return "success" in response && response.success === true;
3268
3588
  }
@@ -3280,6 +3600,7 @@ function isCallToolSuccess(response) {
3280
3600
  }
3281
3601
 
3282
3602
  // src/shared/tool-utils.ts
3603
+ init_cjs_shims();
3283
3604
  function getToolUiResourceUri(tool) {
3284
3605
  const meta = tool._meta;
3285
3606
  if (!meta?.ui) return void 0;