@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
package/dist/index.mjs CHANGED
@@ -120,10 +120,10 @@ var REDIS_KEY_PREFIX = "mcp:session:";
120
120
  var TOKEN_EXPIRY_BUFFER_MS = 5 * 60 * 1e3;
121
121
  var DEFAULT_CLIENT_NAME = "MCP Assistant";
122
122
  var DEFAULT_CLIENT_URI = "https://mcp-assistant.in";
123
- var DEFAULT_LOGO_URI = "https://mcp-assistant.in/logo.png";
123
+ var DEFAULT_LOGO_URI = "https://mcp-assistant.in/logo.svg";
124
124
  var DEFAULT_POLICY_URI = "https://mcp-assistant.in/privacy";
125
125
  var SOFTWARE_ID = "@mcp-ts";
126
- var SOFTWARE_VERSION = "1.0.0-beta.5";
126
+ var SOFTWARE_VERSION = "1.3.4";
127
127
  var MCP_CLIENT_NAME = "mcp-ts-oauth-client";
128
128
  var MCP_CLIENT_VERSION = "2.0";
129
129
 
@@ -141,6 +141,16 @@ var RedisStorageBackend = class {
141
141
  this.redis = redis2;
142
142
  __publicField(this, "DEFAULT_TTL", SESSION_TTL_SECONDS);
143
143
  __publicField(this, "KEY_PREFIX", "mcp:session:");
144
+ __publicField(this, "IDENTITY_KEY_PREFIX", "mcp:identity:");
145
+ __publicField(this, "IDENTITY_KEY_SUFFIX", ":sessions");
146
+ }
147
+ async init() {
148
+ try {
149
+ await this.redis.ping();
150
+ console.log("[mcp-ts][Storage] Redis: \u2713 Connected to server.");
151
+ } catch (error) {
152
+ throw new Error(`[RedisStorage] Failed to connect to Redis: ${error.message}`);
153
+ }
144
154
  }
145
155
  /**
146
156
  * Generates Redis key for a specific session
@@ -154,7 +164,34 @@ var RedisStorageBackend = class {
154
164
  * @private
155
165
  */
156
166
  getIdentityKey(identity) {
157
- return `mcp:identity:${identity}:sessions`;
167
+ return `${this.IDENTITY_KEY_PREFIX}${identity}${this.IDENTITY_KEY_SUFFIX}`;
168
+ }
169
+ parseIdentityFromKey(identityKey) {
170
+ return identityKey.slice(
171
+ this.IDENTITY_KEY_PREFIX.length,
172
+ identityKey.length - this.IDENTITY_KEY_SUFFIX.length
173
+ );
174
+ }
175
+ async scanKeys(pattern) {
176
+ const redis2 = this.redis;
177
+ if (typeof redis2.scan !== "function") {
178
+ return await this.redis.keys(pattern);
179
+ }
180
+ const keys = /* @__PURE__ */ new Set();
181
+ let cursor = "0";
182
+ try {
183
+ do {
184
+ const [nextCursor, batch] = await redis2.scan(cursor, "MATCH", pattern, "COUNT", 100);
185
+ cursor = nextCursor;
186
+ for (const key of batch) {
187
+ keys.add(key);
188
+ }
189
+ } while (cursor !== "0");
190
+ } catch (error) {
191
+ console.warn("[RedisStorage] SCAN failed, falling back to KEYS:", error);
192
+ return await this.redis.keys(pattern);
193
+ }
194
+ return Array.from(keys);
158
195
  }
159
196
  generateSessionId() {
160
197
  return firstChar() + rest();
@@ -222,17 +259,13 @@ var RedisStorageBackend = class {
222
259
  }
223
260
  }
224
261
  async getIdentityMcpSessions(identity) {
225
- const identityKey = this.getIdentityKey(identity);
226
- try {
227
- return await this.redis.smembers(identityKey);
228
- } catch (error) {
229
- console.error(`[RedisStorage] Failed to get sessions for ${identity}:`, error);
230
- return [];
231
- }
262
+ const sessions = await this.getIdentitySessionsData(identity);
263
+ return sessions.map((session) => session.sessionId);
232
264
  }
233
265
  async getIdentitySessionsData(identity) {
234
266
  try {
235
- const sessionIds = await this.redis.smembers(this.getIdentityKey(identity));
267
+ const identityKey = this.getIdentityKey(identity);
268
+ const sessionIds = await this.redis.smembers(identityKey);
236
269
  if (sessionIds.length === 0) return [];
237
270
  const results = await Promise.all(
238
271
  sessionIds.map(async (sessionId) => {
@@ -240,6 +273,10 @@ var RedisStorageBackend = class {
240
273
  return data ? JSON.parse(data) : null;
241
274
  })
242
275
  );
276
+ const staleSessionIds = sessionIds.filter((_, index) => results[index] === null);
277
+ if (staleSessionIds.length > 0) {
278
+ await this.redis.srem(identityKey, ...staleSessionIds);
279
+ }
243
280
  return results.filter((session) => session !== null);
244
281
  } catch (error) {
245
282
  console.error(`[RedisStorage] Failed to get session data for ${identity}:`, error);
@@ -258,9 +295,22 @@ var RedisStorageBackend = class {
258
295
  }
259
296
  async getAllSessionIds() {
260
297
  try {
261
- const pattern = `${this.KEY_PREFIX}*`;
262
- const keys = await this.redis.keys(pattern);
263
- return keys.map((key) => key.replace(this.KEY_PREFIX, ""));
298
+ const keys = await this.scanKeys(`${this.KEY_PREFIX}*`);
299
+ const sessions = await Promise.all(
300
+ keys.map(async (key) => {
301
+ const data = await this.redis.get(key);
302
+ if (!data) {
303
+ return null;
304
+ }
305
+ try {
306
+ return JSON.parse(data).sessionId;
307
+ } catch (error) {
308
+ console.error("[RedisStorage] Failed to parse session while listing all session IDs:", error);
309
+ return null;
310
+ }
311
+ })
312
+ );
313
+ return sessions.filter((sessionId) => sessionId !== null);
264
314
  } catch (error) {
265
315
  console.error("[RedisStorage] Failed to get all sessions:", error);
266
316
  return [];
@@ -268,10 +318,11 @@ var RedisStorageBackend = class {
268
318
  }
269
319
  async clearAll() {
270
320
  try {
271
- const pattern = `${this.KEY_PREFIX}*`;
272
- const keys = await this.redis.keys(pattern);
273
- if (keys.length > 0) {
274
- await this.redis.del(...keys);
321
+ const keys = await this.scanKeys(`${this.KEY_PREFIX}*`);
322
+ const identityKeys = await this.scanKeys(`${this.IDENTITY_KEY_PREFIX}*${this.IDENTITY_KEY_SUFFIX}`);
323
+ const allKeys = [...keys, ...identityKeys];
324
+ if (allKeys.length > 0) {
325
+ await this.redis.del(...allKeys);
275
326
  }
276
327
  } catch (error) {
277
328
  console.error("[RedisStorage] Failed to clear sessions:", error);
@@ -279,12 +330,24 @@ var RedisStorageBackend = class {
279
330
  }
280
331
  async cleanupExpiredSessions() {
281
332
  try {
282
- const pattern = `${this.KEY_PREFIX}*`;
283
- const keys = await this.redis.keys(pattern);
284
- for (const key of keys) {
285
- const ttl = await this.redis.ttl(key);
286
- if (ttl <= 0) {
287
- await this.redis.del(key);
333
+ const identityKeys = await this.scanKeys(`${this.IDENTITY_KEY_PREFIX}*${this.IDENTITY_KEY_SUFFIX}`);
334
+ for (const identityKey of identityKeys) {
335
+ const identity = this.parseIdentityFromKey(identityKey);
336
+ const sessionIds = await this.redis.smembers(identityKey);
337
+ if (sessionIds.length === 0) {
338
+ await this.redis.del(identityKey);
339
+ continue;
340
+ }
341
+ const existenceChecks = await Promise.all(
342
+ sessionIds.map((sessionId) => this.redis.exists(this.getSessionKey(identity, sessionId)))
343
+ );
344
+ const staleSessionIds = sessionIds.filter((_, index) => existenceChecks[index] === 0);
345
+ if (staleSessionIds.length > 0) {
346
+ await this.redis.srem(identityKey, ...staleSessionIds);
347
+ }
348
+ const remainingCount = await this.redis.scard(identityKey);
349
+ if (remainingCount === 0) {
350
+ await this.redis.del(identityKey);
288
351
  }
289
352
  }
290
353
  } catch (error) {
@@ -314,6 +377,9 @@ var MemoryStorageBackend = class {
314
377
  // Map<identity, Set<sessionId>>
315
378
  __publicField(this, "identitySessions", /* @__PURE__ */ new Map());
316
379
  }
380
+ async init() {
381
+ console.log("[mcp-ts][Storage] Memory: \u2713 internal memory store active.");
382
+ }
317
383
  getSessionKey(identity, sessionId) {
318
384
  return `${identity}:${sessionId}`;
319
385
  }
@@ -433,6 +499,7 @@ var FileStorageBackend = class {
433
499
  }
434
500
  }
435
501
  this.initialized = true;
502
+ console.log(`[mcp-ts][Storage] File: \u2713 storage directory at ${path.dirname(this.filePath)} verified.`);
436
503
  }
437
504
  async ensureInitialized() {
438
505
  if (!this.initialized) await this.init();
@@ -537,6 +604,7 @@ var SqliteStorage = class {
537
604
  CREATE INDEX IF NOT EXISTS idx_${this.table}_identity ON ${this.table}(identity);
538
605
  `);
539
606
  this.initialized = true;
607
+ console.log(`[mcp-ts][Storage] SQLite: \u2713 database at ${this.dbPath} verified.`);
540
608
  } catch (error) {
541
609
  if (error.code === "MODULE_NOT_FOUND" || error.message?.includes("better-sqlite3")) {
542
610
  throw new Error(
@@ -652,9 +720,166 @@ var SqliteStorage = class {
652
720
  }
653
721
  };
654
722
 
723
+ // src/server/storage/supabase-backend.ts
724
+ var SupabaseStorageBackend = class {
725
+ constructor(supabase) {
726
+ this.supabase = supabase;
727
+ __publicField(this, "DEFAULT_TTL", SESSION_TTL_SECONDS);
728
+ }
729
+ async init() {
730
+ const { error } = await this.supabase.from("mcp_sessions").select("session_id").limit(0);
731
+ if (error) {
732
+ if (error.code === "42P01") {
733
+ throw new Error(
734
+ '[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.'
735
+ );
736
+ }
737
+ throw new Error(`[SupabaseStorage] Initialization check failed: ${error.message}`);
738
+ }
739
+ console.log('[mcp-ts][Storage] Supabase: \u2713 "mcp_sessions" table verified.');
740
+ }
741
+ generateSessionId() {
742
+ return crypto.randomUUID();
743
+ }
744
+ mapRowToSessionData(row) {
745
+ return {
746
+ sessionId: row.session_id,
747
+ serverId: row.server_id,
748
+ serverName: row.server_name,
749
+ serverUrl: row.server_url,
750
+ transportType: row.transport_type,
751
+ callbackUrl: row.callback_url,
752
+ createdAt: new Date(row.created_at).getTime(),
753
+ identity: row.identity,
754
+ headers: row.headers,
755
+ active: row.active,
756
+ clientInformation: row.client_information,
757
+ tokens: row.tokens,
758
+ codeVerifier: row.code_verifier,
759
+ clientId: row.client_id
760
+ };
761
+ }
762
+ async createSession(session, ttl) {
763
+ const { sessionId, identity } = session;
764
+ if (!sessionId || !identity) throw new Error("identity and sessionId required");
765
+ const effectiveTtl = ttl ?? this.DEFAULT_TTL;
766
+ const expiresAt = new Date(Date.now() + effectiveTtl * 1e3).toISOString();
767
+ const { error } = await this.supabase.from("mcp_sessions").insert({
768
+ session_id: sessionId,
769
+ user_id: identity,
770
+ // Maps user_id to identity to support RLS using auth.uid()
771
+ server_id: session.serverId,
772
+ server_name: session.serverName,
773
+ server_url: session.serverUrl,
774
+ transport_type: session.transportType,
775
+ callback_url: session.callbackUrl,
776
+ created_at: new Date(session.createdAt || Date.now()).toISOString(),
777
+ identity,
778
+ headers: session.headers,
779
+ active: session.active ?? false,
780
+ client_information: session.clientInformation,
781
+ tokens: session.tokens,
782
+ code_verifier: session.codeVerifier,
783
+ client_id: session.clientId,
784
+ expires_at: expiresAt
785
+ });
786
+ if (error) {
787
+ if (error.code === "23505") {
788
+ throw new Error(`Session ${sessionId} already exists`);
789
+ }
790
+ throw new Error(`Failed to create session in Supabase: ${error.message}`);
791
+ }
792
+ }
793
+ async updateSession(identity, sessionId, data, ttl) {
794
+ const effectiveTtl = ttl ?? this.DEFAULT_TTL;
795
+ const expiresAt = new Date(Date.now() + effectiveTtl * 1e3).toISOString();
796
+ const updateData = {
797
+ expires_at: expiresAt,
798
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
799
+ };
800
+ if ("serverId" in data) updateData.server_id = data.serverId;
801
+ if ("serverName" in data) updateData.server_name = data.serverName;
802
+ if ("serverUrl" in data) updateData.server_url = data.serverUrl;
803
+ if ("transportType" in data) updateData.transport_type = data.transportType;
804
+ if ("callbackUrl" in data) updateData.callback_url = data.callbackUrl;
805
+ if ("active" in data) updateData.active = data.active;
806
+ if ("headers" in data) updateData.headers = data.headers;
807
+ if ("clientInformation" in data) updateData.client_information = data.clientInformation;
808
+ if ("tokens" in data) updateData.tokens = data.tokens;
809
+ if ("codeVerifier" in data) updateData.code_verifier = data.codeVerifier;
810
+ if ("clientId" in data) updateData.client_id = data.clientId;
811
+ const { data: updatedRows, error } = await this.supabase.from("mcp_sessions").update(updateData).eq("identity", identity).eq("session_id", sessionId).select("id");
812
+ if (error) {
813
+ throw new Error(`Failed to update session: ${error.message}`);
814
+ }
815
+ if (!updatedRows || updatedRows.length === 0) {
816
+ throw new Error(`Session ${sessionId} not found for identity ${identity}`);
817
+ }
818
+ }
819
+ async getSession(identity, sessionId) {
820
+ const { data, error } = await this.supabase.from("mcp_sessions").select("*").eq("identity", identity).eq("session_id", sessionId).maybeSingle();
821
+ if (error) {
822
+ console.error("[SupabaseStorage] Failed to get session:", error);
823
+ return null;
824
+ }
825
+ if (!data) return null;
826
+ return this.mapRowToSessionData(data);
827
+ }
828
+ async getIdentitySessionsData(identity) {
829
+ const { data, error } = await this.supabase.from("mcp_sessions").select("*").eq("identity", identity);
830
+ if (error) {
831
+ console.error(`[SupabaseStorage] Failed to get session data for ${identity}:`, error);
832
+ return [];
833
+ }
834
+ return data.map((row) => this.mapRowToSessionData(row));
835
+ }
836
+ async removeSession(identity, sessionId) {
837
+ const { error } = await this.supabase.from("mcp_sessions").delete().eq("identity", identity).eq("session_id", sessionId);
838
+ if (error) {
839
+ console.error("[SupabaseStorage] Failed to remove session:", error);
840
+ }
841
+ }
842
+ async getIdentityMcpSessions(identity) {
843
+ const { data, error } = await this.supabase.from("mcp_sessions").select("session_id").eq("identity", identity);
844
+ if (error) {
845
+ console.error(`[SupabaseStorage] Failed to get sessions for ${identity}:`, error);
846
+ return [];
847
+ }
848
+ return data.map((row) => row.session_id);
849
+ }
850
+ async getAllSessionIds() {
851
+ const { data, error } = await this.supabase.from("mcp_sessions").select("session_id");
852
+ if (error) {
853
+ console.error("[SupabaseStorage] Failed to get all sessions:", error);
854
+ return [];
855
+ }
856
+ return data.map((row) => row.session_id);
857
+ }
858
+ async clearAll() {
859
+ const { error } = await this.supabase.from("mcp_sessions").delete().neq("session_id", "");
860
+ if (error) {
861
+ console.error("[SupabaseStorage] Failed to clear sessions:", error);
862
+ }
863
+ }
864
+ async cleanupExpiredSessions() {
865
+ const { error } = await this.supabase.from("mcp_sessions").delete().lt("expires_at", (/* @__PURE__ */ new Date()).toISOString());
866
+ if (error) {
867
+ console.error("[SupabaseStorage] Failed to cleanup expired sessions:", error);
868
+ }
869
+ }
870
+ async disconnect() {
871
+ }
872
+ };
873
+
655
874
  // src/server/storage/index.ts
656
875
  var storageInstance = null;
657
876
  var storagePromise = null;
877
+ async function initializeStorage(store) {
878
+ if (typeof store.init === "function") {
879
+ await store.init();
880
+ }
881
+ return store;
882
+ }
658
883
  async function createStorage() {
659
884
  const type = process.env.MCP_TS_STORAGE_TYPE?.toLowerCase();
660
885
  if (type === "redis") {
@@ -664,68 +889,95 @@ async function createStorage() {
664
889
  try {
665
890
  const { getRedis: getRedis2 } = await Promise.resolve().then(() => (init_redis(), redis_exports));
666
891
  const redis2 = await getRedis2();
667
- console.log("[Storage] Using Redis storage (Explicit)");
668
- return new RedisStorageBackend(redis2);
892
+ console.log('[mcp-ts][Storage] Explicit selection: "redis"');
893
+ return await initializeStorage(new RedisStorageBackend(redis2));
669
894
  } catch (error) {
670
- console.error("[Storage] Failed to initialize Redis:", error.message);
671
- console.log("[Storage] Falling back to In-Memory storage");
672
- return new MemoryStorageBackend();
895
+ console.error("[mcp-ts][Storage] Failed to initialize Redis:", error.message);
896
+ console.log("[mcp-ts][Storage] Falling back to In-Memory storage");
897
+ return await initializeStorage(new MemoryStorageBackend());
673
898
  }
674
899
  }
675
900
  if (type === "file") {
676
901
  const filePath = process.env.MCP_TS_STORAGE_FILE;
677
- if (!filePath) {
678
- console.warn('[Storage] MCP_TS_STORAGE_TYPE is "file" but MCP_TS_STORAGE_FILE is missing');
679
- }
680
- console.log(`[Storage] Using File storage (${filePath}) (Explicit)`);
681
- const store = new FileStorageBackend({ path: filePath });
682
- store.init().catch((err) => console.error("[Storage] Failed to initialize file storage:", err));
683
- return store;
902
+ console.log(`[mcp-ts][Storage] Explicit selection: "file" (${filePath || "default"})`);
903
+ return await initializeStorage(new FileStorageBackend({ path: filePath }));
684
904
  }
685
905
  if (type === "sqlite") {
686
906
  const dbPath = process.env.MCP_TS_STORAGE_SQLITE_PATH;
687
- console.log(`[Storage] Using SQLite storage (${dbPath || "default"}) (Explicit)`);
688
- const store = new SqliteStorage({ path: dbPath });
689
- store.init().catch((err) => console.error("[Storage] Failed to initialize SQLite storage:", err));
690
- return store;
907
+ console.log(`[mcp-ts][Storage] Explicit selection: "sqlite" (${dbPath || "default"})`);
908
+ return await initializeStorage(new SqliteStorage({ path: dbPath }));
909
+ }
910
+ if (type === "supabase") {
911
+ const url = process.env.SUPABASE_URL;
912
+ const key = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SUPABASE_ANON_KEY;
913
+ if (!url || !key) {
914
+ console.warn('[mcp-ts][Storage] Explicit selection "supabase" requires SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY.');
915
+ } else {
916
+ if (!process.env.SUPABASE_SERVICE_ROLE_KEY) {
917
+ 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.');
918
+ }
919
+ try {
920
+ const { createClient } = await import('@supabase/supabase-js');
921
+ const client = createClient(url, key);
922
+ console.log('[mcp-ts][Storage] Explicit selection: "supabase"');
923
+ return await initializeStorage(new SupabaseStorageBackend(client));
924
+ } catch (error) {
925
+ console.error("[mcp-ts][Storage] Failed to initialize Supabase:", error.message);
926
+ console.log("[mcp-ts][Storage] Falling back to In-Memory storage");
927
+ return await initializeStorage(new MemoryStorageBackend());
928
+ }
929
+ }
691
930
  }
692
931
  if (type === "memory") {
693
- console.log("[Storage] Using In-Memory storage (Explicit)");
694
- return new MemoryStorageBackend();
932
+ console.log('[mcp-ts][Storage] Explicit selection: "memory"');
933
+ return await initializeStorage(new MemoryStorageBackend());
695
934
  }
696
935
  if (process.env.REDIS_URL) {
697
936
  try {
698
937
  const { getRedis: getRedis2 } = await Promise.resolve().then(() => (init_redis(), redis_exports));
699
938
  const redis2 = await getRedis2();
700
- console.log("[Storage] Auto-detected REDIS_URL. Using Redis storage.");
701
- return new RedisStorageBackend(redis2);
939
+ console.log('[mcp-ts][Storage] Auto-detection: "redis" (via REDIS_URL)');
940
+ return await initializeStorage(new RedisStorageBackend(redis2));
702
941
  } catch (error) {
703
- console.error("[Storage] Redis auto-detection failed:", error.message);
704
- console.log("[Storage] Falling back to In-Memory storage");
705
- return new MemoryStorageBackend();
942
+ console.error("[mcp-ts][Storage] Redis auto-detection failed:", error.message);
943
+ console.log("[mcp-ts][Storage] Falling back to next available backend");
706
944
  }
707
945
  }
708
946
  if (process.env.MCP_TS_STORAGE_FILE) {
709
- console.log(`[Storage] Auto-detected MCP_TS_STORAGE_FILE. Using File storage (${process.env.MCP_TS_STORAGE_FILE}).`);
710
- const store = new FileStorageBackend({ path: process.env.MCP_TS_STORAGE_FILE });
711
- store.init().catch((err) => console.error("[Storage] Failed to initialize file storage:", err));
712
- return store;
947
+ console.log(`[mcp-ts][Storage] Auto-detection: "file" (${process.env.MCP_TS_STORAGE_FILE})`);
948
+ return await initializeStorage(new FileStorageBackend({ path: process.env.MCP_TS_STORAGE_FILE }));
713
949
  }
714
950
  if (process.env.MCP_TS_STORAGE_SQLITE_PATH) {
715
- console.log(`[Storage] Auto-detected MCP_TS_STORAGE_SQLITE_PATH. Using SQLite storage (${process.env.MCP_TS_STORAGE_SQLITE_PATH}).`);
716
- const store = new SqliteStorage({ path: process.env.MCP_TS_STORAGE_SQLITE_PATH });
717
- store.init().catch((err) => console.error("[Storage] Failed to initialize SQLite storage:", err));
718
- return store;
951
+ console.log(`[mcp-ts][Storage] Auto-detection: "sqlite" (${process.env.MCP_TS_STORAGE_SQLITE_PATH})`);
952
+ return await initializeStorage(new SqliteStorage({ path: process.env.MCP_TS_STORAGE_SQLITE_PATH }));
719
953
  }
720
- console.log("[Storage] No storage configured. Using In-Memory storage (Default).");
721
- return new MemoryStorageBackend();
954
+ if (process.env.SUPABASE_URL && (process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SUPABASE_ANON_KEY)) {
955
+ try {
956
+ const { createClient } = await import('@supabase/supabase-js');
957
+ const url = process.env.SUPABASE_URL;
958
+ const key = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SUPABASE_ANON_KEY;
959
+ if (!process.env.SUPABASE_SERVICE_ROLE_KEY) {
960
+ 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.');
961
+ }
962
+ const client = createClient(url, key);
963
+ console.log('[mcp-ts][Storage] Auto-detection: "supabase" (via SUPABASE_URL)');
964
+ return await initializeStorage(new SupabaseStorageBackend(client));
965
+ } catch (error) {
966
+ console.error("[mcp-ts][Storage] Supabase auto-detection failed:", error.message);
967
+ }
968
+ }
969
+ console.log('[mcp-ts][Storage] Defaulting to: "memory"');
970
+ return await initializeStorage(new MemoryStorageBackend());
722
971
  }
723
972
  async function getStorage() {
724
973
  if (storageInstance) {
725
974
  return storageInstance;
726
975
  }
727
976
  if (!storagePromise) {
728
- storagePromise = createStorage();
977
+ storagePromise = createStorage().catch((error) => {
978
+ storagePromise = null;
979
+ throw error;
980
+ });
729
981
  }
730
982
  storageInstance = await storagePromise;
731
983
  return storageInstance;
@@ -746,43 +998,49 @@ var storage = new Proxy({}, {
746
998
  // src/server/mcp/storage-oauth-provider.ts
747
999
  var StorageOAuthClientProvider = class {
748
1000
  /**
749
- * Creates a new Storage-backed OAuth provider
750
- * @param identity - User/Client identifier
751
- * @param serverId - Server identifier (for tracking which server this OAuth session belongs to)
752
- * @param sessionId - Session identifier (used as OAuth state)
753
- * @param clientName - OAuth client name
754
- * @param baseRedirectUrl - OAuth callback URL
755
- * @param onRedirect - Optional callback when redirect to authorization is needed
1001
+ * Creates a new storage-backed OAuth provider
1002
+ * @param options - Provider configuration
756
1003
  */
757
- constructor(identity, serverId, sessionId, clientName, baseRedirectUrl, onRedirect) {
758
- this.identity = identity;
759
- this.serverId = serverId;
760
- this.sessionId = sessionId;
761
- this.clientName = clientName;
762
- this.baseRedirectUrl = baseRedirectUrl;
1004
+ constructor(options) {
1005
+ __publicField(this, "identity");
1006
+ __publicField(this, "serverId");
1007
+ __publicField(this, "sessionId");
1008
+ __publicField(this, "redirectUrl");
1009
+ __publicField(this, "clientName");
1010
+ __publicField(this, "clientUri");
1011
+ __publicField(this, "logoUri");
1012
+ __publicField(this, "policyUri");
1013
+ __publicField(this, "clientSecret");
763
1014
  __publicField(this, "_authUrl");
764
1015
  __publicField(this, "_clientId");
765
1016
  __publicField(this, "onRedirectCallback");
766
1017
  __publicField(this, "tokenExpiresAt");
767
- this.onRedirectCallback = onRedirect;
1018
+ this.identity = options.identity;
1019
+ this.serverId = options.serverId;
1020
+ this.sessionId = options.sessionId;
1021
+ this.redirectUrl = options.redirectUrl;
1022
+ this.clientName = options.clientName;
1023
+ this.clientUri = options.clientUri;
1024
+ this.logoUri = options.logoUri;
1025
+ this.policyUri = options.policyUri;
1026
+ this._clientId = options.clientId;
1027
+ this.clientSecret = options.clientSecret;
1028
+ this.onRedirectCallback = options.onRedirect;
768
1029
  }
769
1030
  get clientMetadata() {
770
1031
  return {
771
- client_name: this.clientName,
772
- client_uri: this.clientUri,
1032
+ client_name: this.clientName || DEFAULT_CLIENT_NAME,
1033
+ client_uri: this.clientUri || DEFAULT_CLIENT_URI,
1034
+ logo_uri: this.logoUri || DEFAULT_LOGO_URI,
1035
+ policy_uri: this.policyUri || DEFAULT_POLICY_URI,
773
1036
  grant_types: ["authorization_code", "refresh_token"],
774
1037
  redirect_uris: [this.redirectUrl],
775
1038
  response_types: ["code"],
776
- token_endpoint_auth_method: "none",
777
- ...this._clientId ? { client_id: this._clientId } : {}
1039
+ token_endpoint_auth_method: this.clientSecret ? "client_secret_basic" : "none",
1040
+ software_id: SOFTWARE_ID,
1041
+ software_version: SOFTWARE_VERSION
778
1042
  };
779
1043
  }
780
- get clientUri() {
781
- return new URL(this.redirectUrl).origin;
782
- }
783
- get redirectUrl() {
784
- return this.baseRedirectUrl;
785
- }
786
1044
  get clientId() {
787
1045
  return this._clientId;
788
1046
  }
@@ -817,7 +1075,16 @@ var StorageOAuthClientProvider = class {
817
1075
  if (data.clientId && !this._clientId) {
818
1076
  this._clientId = data.clientId;
819
1077
  }
820
- return data.clientInformation;
1078
+ if (data.clientInformation) {
1079
+ return data.clientInformation;
1080
+ }
1081
+ if (!this._clientId) {
1082
+ return void 0;
1083
+ }
1084
+ return {
1085
+ client_id: this._clientId,
1086
+ ...this.clientSecret ? { client_secret: this.clientSecret } : {}
1087
+ };
821
1088
  }
822
1089
  /**
823
1090
  * Stores OAuth client information
@@ -845,14 +1112,14 @@ var StorageOAuthClientProvider = class {
845
1112
  async state() {
846
1113
  return this.sessionId;
847
1114
  }
848
- async checkState(state) {
1115
+ async checkState(_state) {
849
1116
  const data = await storage.getSession(this.identity, this.sessionId);
850
1117
  if (!data) {
851
1118
  return { valid: false, error: "Session not found" };
852
1119
  }
853
1120
  return { valid: true, serverId: this.serverId };
854
1121
  }
855
- async consumeState(state) {
1122
+ async consumeState(_state) {
856
1123
  }
857
1124
  async redirectToAuthorization(authUrl) {
858
1125
  this._authUrl = authUrl.toString();
@@ -864,7 +1131,6 @@ var StorageOAuthClientProvider = class {
864
1131
  if (scope === "all") {
865
1132
  await storage.removeSession(this.identity, this.sessionId);
866
1133
  } else {
867
- await this.getSessionData();
868
1134
  const updates = {};
869
1135
  if (scope === "client") {
870
1136
  updates.clientInformation = void 0;
@@ -1252,41 +1518,33 @@ var MCPClient = class _MCPClient {
1252
1518
  if (!this.serverUrl || !this.callbackUrl || !this.serverId) {
1253
1519
  throw new Error("Missing required connection metadata");
1254
1520
  }
1255
- const clientMetadata = {
1256
- client_name: this.clientName || "MCP Assistant",
1257
- redirect_uris: [this.callbackUrl],
1258
- token_endpoint_auth_method: this.clientSecret ? "client_secret_basic" : "none",
1259
- client_uri: this.clientUri || "https://mcp-assistant.in",
1260
- logo_uri: this.logoUri || "https://mcp-assistant.in/logo.png",
1261
- policy_uri: this.policyUri || "https://mcp-assistant.in/privacy",
1262
- ...this.clientId ? { client_id: this.clientId } : {},
1263
- ...this.clientSecret ? { client_secret: this.clientSecret } : {}
1264
- };
1265
1521
  if (!this.oauthProvider) {
1266
1522
  if (!this.serverId) {
1267
1523
  throw new Error("serverId required for OAuth provider initialization");
1268
1524
  }
1269
- this.oauthProvider = new StorageOAuthClientProvider(
1270
- this.identity,
1271
- this.serverId,
1272
- this.sessionId,
1273
- clientMetadata.client_name ?? "MCP Assistant",
1274
- this.callbackUrl,
1275
- (redirectUrl) => {
1525
+ this.oauthProvider = new StorageOAuthClientProvider({
1526
+ identity: this.identity,
1527
+ serverId: this.serverId,
1528
+ sessionId: this.sessionId,
1529
+ redirectUrl: this.callbackUrl,
1530
+ clientName: this.clientName,
1531
+ clientUri: this.clientUri,
1532
+ logoUri: this.logoUri,
1533
+ policyUri: this.policyUri,
1534
+ clientId: this.clientId,
1535
+ clientSecret: this.clientSecret,
1536
+ onRedirect: (redirectUrl) => {
1276
1537
  if (this.onRedirect) {
1277
1538
  this.onRedirect(redirectUrl);
1278
1539
  }
1279
1540
  }
1280
- );
1281
- if (this.clientId && this.oauthProvider) {
1282
- this.oauthProvider.clientId = this.clientId;
1283
- }
1541
+ });
1284
1542
  }
1285
1543
  if (!this.client) {
1286
1544
  this.client = new Client(
1287
1545
  {
1288
- name: "mcp-ts-oauth-client",
1289
- version: "2.0"
1546
+ name: MCP_CLIENT_NAME,
1547
+ version: MCP_CLIENT_VERSION
1290
1548
  },
1291
1549
  {
1292
1550
  capabilities: {
@@ -1481,8 +1739,8 @@ var MCPClient = class _MCPClient {
1481
1739
  this.emitProgress("Creating authenticated client...");
1482
1740
  this.client = new Client(
1483
1741
  {
1484
- name: "mcp-ts-oauth-client",
1485
- version: "2.0"
1742
+ name: MCP_CLIENT_NAME,
1743
+ version: MCP_CLIENT_VERSION
1486
1744
  },
1487
1745
  {
1488
1746
  capabilities: {
@@ -1777,8 +2035,8 @@ var MCPClient = class _MCPClient {
1777
2035
  }
1778
2036
  this.client = new Client(
1779
2037
  {
1780
- name: "mcp-ts-oauth-client",
1781
- version: "2.0"
2038
+ name: MCP_CLIENT_NAME,
2039
+ version: MCP_CLIENT_VERSION
1782
2040
  },
1783
2041
  { capabilities: {} }
1784
2042
  );
@@ -2047,6 +2305,27 @@ var MultiSessionClient = class {
2047
2305
  }
2048
2306
  };
2049
2307
 
2308
+ // src/shared/event-routing.ts
2309
+ function isRpcResponseEvent(event) {
2310
+ return "id" in event && ("result" in event || "error" in event);
2311
+ }
2312
+ function isConnectionEvent(event) {
2313
+ if (!("type" in event)) {
2314
+ return false;
2315
+ }
2316
+ switch (event.type) {
2317
+ case "state_changed":
2318
+ case "tools_discovered":
2319
+ case "auth_required":
2320
+ case "error":
2321
+ case "disconnected":
2322
+ case "progress":
2323
+ return true;
2324
+ default:
2325
+ return false;
2326
+ }
2327
+ }
2328
+
2050
2329
  // src/server/handlers/sse-handler.ts
2051
2330
  var DEFAULT_HEARTBEAT_INTERVAL = 3e4;
2052
2331
  var SSEConnectionManager = class {
@@ -2189,16 +2468,6 @@ var SSEConnectionManager = class {
2189
2468
  throw new Error(`Connection already exists for server: ${duplicate.serverUrl || duplicate.serverId} (${duplicate.serverName})`);
2190
2469
  }
2191
2470
  const sessionId = await storage.generateSessionId();
2192
- this.emitConnectionEvent({
2193
- type: "state_changed",
2194
- sessionId,
2195
- serverId,
2196
- serverName,
2197
- serverUrl,
2198
- state: "CONNECTING",
2199
- previousState: "DISCONNECTED",
2200
- timestamp: Date.now()
2201
- });
2202
2471
  try {
2203
2472
  const clientMetadata = await this.getResolvedClientMetadata();
2204
2473
  const client = new MCPClient({
@@ -2209,17 +2478,8 @@ var SSEConnectionManager = class {
2209
2478
  serverUrl,
2210
2479
  callbackUrl,
2211
2480
  transportType,
2212
- ...clientMetadata,
2481
+ ...clientMetadata
2213
2482
  // Spread client metadata (clientName, clientUri, logoUri, policyUri)
2214
- onRedirect: (authUrl) => {
2215
- this.emitConnectionEvent({
2216
- type: "auth_required",
2217
- sessionId,
2218
- serverId,
2219
- authUrl,
2220
- timestamp: Date.now()
2221
- });
2222
- }
2223
2483
  });
2224
2484
  this.clients.set(sessionId, client);
2225
2485
  client.onConnectionEvent((event) => {
@@ -2229,20 +2489,19 @@ var SSEConnectionManager = class {
2229
2489
  this.sendEvent(event);
2230
2490
  });
2231
2491
  await client.connect();
2232
- const tools = await client.listTools();
2233
- this.emitConnectionEvent({
2234
- type: "tools_discovered",
2235
- sessionId,
2236
- serverId,
2237
- toolCount: tools.tools.length,
2238
- tools: tools.tools,
2239
- timestamp: Date.now()
2240
- });
2492
+ await client.listTools();
2241
2493
  return {
2242
2494
  sessionId,
2243
2495
  success: true
2244
2496
  };
2245
2497
  } catch (error) {
2498
+ if (error instanceof UnauthorizedError) {
2499
+ this.clients.delete(sessionId);
2500
+ return {
2501
+ sessionId,
2502
+ success: true
2503
+ };
2504
+ }
2246
2505
  this.emitConnectionEvent({
2247
2506
  type: "error",
2248
2507
  sessionId,
@@ -2344,14 +2603,6 @@ var SSEConnectionManager = class {
2344
2603
  await client.connect();
2345
2604
  this.clients.set(sessionId, client);
2346
2605
  const tools = await client.listTools();
2347
- this.emitConnectionEvent({
2348
- type: "tools_discovered",
2349
- sessionId,
2350
- serverId: session.serverId ?? "unknown",
2351
- toolCount: tools.tools.length,
2352
- tools: tools.tools,
2353
- timestamp: Date.now()
2354
- });
2355
2606
  return { success: true, toolCount: tools.tools.length };
2356
2607
  } catch (error) {
2357
2608
  this.emitConnectionEvent({
@@ -2374,16 +2625,6 @@ var SSEConnectionManager = class {
2374
2625
  if (!session) {
2375
2626
  throw new Error("Session not found");
2376
2627
  }
2377
- this.emitConnectionEvent({
2378
- type: "state_changed",
2379
- sessionId,
2380
- serverId: session.serverId ?? "unknown",
2381
- serverName: session.serverName ?? "Unknown",
2382
- serverUrl: session.serverUrl,
2383
- state: "AUTHENTICATING",
2384
- previousState: "DISCONNECTED",
2385
- timestamp: Date.now()
2386
- });
2387
2628
  try {
2388
2629
  const client = new MCPClient({
2389
2630
  identity: this.identity,
@@ -2393,14 +2634,6 @@ var SSEConnectionManager = class {
2393
2634
  await client.finishAuth(code);
2394
2635
  this.clients.set(sessionId, client);
2395
2636
  const tools = await client.listTools();
2396
- this.emitConnectionEvent({
2397
- type: "tools_discovered",
2398
- sessionId,
2399
- serverId: session.serverId ?? "unknown",
2400
- toolCount: tools.tools.length,
2401
- tools: tools.tools,
2402
- timestamp: Date.now()
2403
- });
2404
2637
  return { success: true, toolCount: tools.tools.length };
2405
2638
  } catch (error) {
2406
2639
  this.emitConnectionEvent({
@@ -2478,9 +2711,9 @@ function createSSEHandler(options) {
2478
2711
  });
2479
2712
  writeSSEEvent(res, "connected", { timestamp: Date.now() });
2480
2713
  const manager = new SSEConnectionManager(options, (event) => {
2481
- if ("id" in event) {
2714
+ if (isRpcResponseEvent(event)) {
2482
2715
  writeSSEEvent(res, "rpc-response", event);
2483
- } else if ("type" in event && "sessionId" in event) {
2716
+ } else if (isConnectionEvent(event)) {
2484
2717
  writeSSEEvent(res, "connection", event);
2485
2718
  } else {
2486
2719
  writeSSEEvent(res, "observability", event);
@@ -2511,9 +2744,6 @@ function writeSSEEvent(res, event, data) {
2511
2744
  }
2512
2745
 
2513
2746
  // src/server/handlers/nextjs-handler.ts
2514
- function isRpcResponseEvent(event) {
2515
- return "id" in event && ("result" in event || "error" in event);
2516
- }
2517
2747
  function createNextMcpHandler(options = {}) {
2518
2748
  const {
2519
2749
  getIdentity = (request) => new URL(request.url).searchParams.get("identity"),
@@ -2604,7 +2834,7 @@ data: ${JSON.stringify(data)}
2604
2834
  (event) => {
2605
2835
  if (isRpcResponseEvent(event)) {
2606
2836
  sendSSE("rpc-response", event);
2607
- } else if ("type" in event && "sessionId" in event) {
2837
+ } else if (isConnectionEvent(event)) {
2608
2838
  sendSSE("connection", event);
2609
2839
  } else {
2610
2840
  sendSSE("observability", event);
@@ -2661,6 +2891,7 @@ data: ${JSON.stringify(data)}
2661
2891
  }
2662
2892
  return { GET, POST };
2663
2893
  }
2894
+ var CONNECTION_EVENT_INTERVAL_MS = 300;
2664
2895
  var SSEClient = class {
2665
2896
  constructor(options) {
2666
2897
  this.options = options;
@@ -2765,10 +2996,12 @@ var SSEClient = class {
2765
2996
  const data2 = await response.json();
2766
2997
  return this.parseRpcResponse(data2);
2767
2998
  }
2768
- const data = await this.readRpcResponseFromStream(response);
2999
+ const data = await this.readRpcResponseFromStream(response, {
3000
+ delayConnectionEvents: method === "connect" || method === "restoreSession" || method === "finishAuth"
3001
+ });
2769
3002
  return this.parseRpcResponse(data);
2770
3003
  }
2771
- async readRpcResponseFromStream(response) {
3004
+ async readRpcResponseFromStream(response, options = {}) {
2772
3005
  if (!response.body) {
2773
3006
  throw new Error("Streaming response body is missing");
2774
3007
  }
@@ -2776,7 +3009,7 @@ var SSEClient = class {
2776
3009
  const decoder = new TextDecoder();
2777
3010
  let buffer = "";
2778
3011
  let rpcResponse = null;
2779
- const dispatchBlock = (block) => {
3012
+ const dispatchBlock = async (block) => {
2780
3013
  const lines = block.split("\n");
2781
3014
  let eventName = "message";
2782
3015
  const dataLines = [];
@@ -2804,6 +3037,9 @@ var SSEClient = class {
2804
3037
  break;
2805
3038
  case "connection":
2806
3039
  this.options.onConnectionEvent?.(payload);
3040
+ if (options.delayConnectionEvents) {
3041
+ await this.sleep(CONNECTION_EVENT_INTERVAL_MS);
3042
+ }
2807
3043
  break;
2808
3044
  case "observability":
2809
3045
  this.options.onObservabilityEvent?.(payload);
@@ -2823,18 +3059,21 @@ var SSEClient = class {
2823
3059
  const separatorLength = separatorMatch[0].length;
2824
3060
  const block = buffer.slice(0, separatorIndex);
2825
3061
  buffer = buffer.slice(separatorIndex + separatorLength);
2826
- dispatchBlock(block);
3062
+ await dispatchBlock(block);
2827
3063
  separatorMatch = buffer.match(/\r?\n\r?\n/);
2828
3064
  }
2829
3065
  }
2830
3066
  if (buffer.trim()) {
2831
- dispatchBlock(buffer);
3067
+ await dispatchBlock(buffer);
2832
3068
  }
2833
3069
  if (!rpcResponse) {
2834
3070
  throw new Error("Missing rpc-response event in streamed RPC result");
2835
3071
  }
2836
3072
  return rpcResponse;
2837
3073
  }
3074
+ sleep(ms) {
3075
+ return new Promise((resolve) => setTimeout(resolve, ms));
3076
+ }
2838
3077
  parseRpcResponse(data) {
2839
3078
  if ("result" in data) {
2840
3079
  return data.result;