@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
@@ -115,6 +115,14 @@ var init_redis = __esm({
115
115
  var SESSION_TTL_SECONDS = 43200;
116
116
  var STATE_EXPIRATION_MS = 10 * 60 * 1e3;
117
117
  var TOKEN_EXPIRY_BUFFER_MS = 5 * 60 * 1e3;
118
+ var DEFAULT_CLIENT_NAME = "MCP Assistant";
119
+ var DEFAULT_CLIENT_URI = "https://mcp-assistant.in";
120
+ var DEFAULT_LOGO_URI = "https://mcp-assistant.in/logo.svg";
121
+ var DEFAULT_POLICY_URI = "https://mcp-assistant.in/privacy";
122
+ var SOFTWARE_ID = "@mcp-ts";
123
+ var SOFTWARE_VERSION = "1.3.4";
124
+ var MCP_CLIENT_NAME = "mcp-ts-oauth-client";
125
+ var MCP_CLIENT_VERSION = "2.0";
118
126
 
119
127
  // src/server/storage/redis-backend.ts
120
128
  var firstChar = customAlphabet(
@@ -130,6 +138,16 @@ var RedisStorageBackend = class {
130
138
  this.redis = redis2;
131
139
  __publicField(this, "DEFAULT_TTL", SESSION_TTL_SECONDS);
132
140
  __publicField(this, "KEY_PREFIX", "mcp:session:");
141
+ __publicField(this, "IDENTITY_KEY_PREFIX", "mcp:identity:");
142
+ __publicField(this, "IDENTITY_KEY_SUFFIX", ":sessions");
143
+ }
144
+ async init() {
145
+ try {
146
+ await this.redis.ping();
147
+ console.log("[mcp-ts][Storage] Redis: \u2713 Connected to server.");
148
+ } catch (error) {
149
+ throw new Error(`[RedisStorage] Failed to connect to Redis: ${error.message}`);
150
+ }
133
151
  }
134
152
  /**
135
153
  * Generates Redis key for a specific session
@@ -143,7 +161,34 @@ var RedisStorageBackend = class {
143
161
  * @private
144
162
  */
145
163
  getIdentityKey(identity) {
146
- return `mcp:identity:${identity}:sessions`;
164
+ return `${this.IDENTITY_KEY_PREFIX}${identity}${this.IDENTITY_KEY_SUFFIX}`;
165
+ }
166
+ parseIdentityFromKey(identityKey) {
167
+ return identityKey.slice(
168
+ this.IDENTITY_KEY_PREFIX.length,
169
+ identityKey.length - this.IDENTITY_KEY_SUFFIX.length
170
+ );
171
+ }
172
+ async scanKeys(pattern) {
173
+ const redis2 = this.redis;
174
+ if (typeof redis2.scan !== "function") {
175
+ return await this.redis.keys(pattern);
176
+ }
177
+ const keys = /* @__PURE__ */ new Set();
178
+ let cursor = "0";
179
+ try {
180
+ do {
181
+ const [nextCursor, batch] = await redis2.scan(cursor, "MATCH", pattern, "COUNT", 100);
182
+ cursor = nextCursor;
183
+ for (const key of batch) {
184
+ keys.add(key);
185
+ }
186
+ } while (cursor !== "0");
187
+ } catch (error) {
188
+ console.warn("[RedisStorage] SCAN failed, falling back to KEYS:", error);
189
+ return await this.redis.keys(pattern);
190
+ }
191
+ return Array.from(keys);
147
192
  }
148
193
  generateSessionId() {
149
194
  return firstChar() + rest();
@@ -211,17 +256,13 @@ var RedisStorageBackend = class {
211
256
  }
212
257
  }
213
258
  async getIdentityMcpSessions(identity) {
214
- const identityKey = this.getIdentityKey(identity);
215
- try {
216
- return await this.redis.smembers(identityKey);
217
- } catch (error) {
218
- console.error(`[RedisStorage] Failed to get sessions for ${identity}:`, error);
219
- return [];
220
- }
259
+ const sessions = await this.getIdentitySessionsData(identity);
260
+ return sessions.map((session) => session.sessionId);
221
261
  }
222
262
  async getIdentitySessionsData(identity) {
223
263
  try {
224
- const sessionIds = await this.redis.smembers(this.getIdentityKey(identity));
264
+ const identityKey = this.getIdentityKey(identity);
265
+ const sessionIds = await this.redis.smembers(identityKey);
225
266
  if (sessionIds.length === 0) return [];
226
267
  const results = await Promise.all(
227
268
  sessionIds.map(async (sessionId) => {
@@ -229,6 +270,10 @@ var RedisStorageBackend = class {
229
270
  return data ? JSON.parse(data) : null;
230
271
  })
231
272
  );
273
+ const staleSessionIds = sessionIds.filter((_, index) => results[index] === null);
274
+ if (staleSessionIds.length > 0) {
275
+ await this.redis.srem(identityKey, ...staleSessionIds);
276
+ }
232
277
  return results.filter((session) => session !== null);
233
278
  } catch (error) {
234
279
  console.error(`[RedisStorage] Failed to get session data for ${identity}:`, error);
@@ -247,9 +292,22 @@ var RedisStorageBackend = class {
247
292
  }
248
293
  async getAllSessionIds() {
249
294
  try {
250
- const pattern = `${this.KEY_PREFIX}*`;
251
- const keys = await this.redis.keys(pattern);
252
- return keys.map((key) => key.replace(this.KEY_PREFIX, ""));
295
+ const keys = await this.scanKeys(`${this.KEY_PREFIX}*`);
296
+ const sessions = await Promise.all(
297
+ keys.map(async (key) => {
298
+ const data = await this.redis.get(key);
299
+ if (!data) {
300
+ return null;
301
+ }
302
+ try {
303
+ return JSON.parse(data).sessionId;
304
+ } catch (error) {
305
+ console.error("[RedisStorage] Failed to parse session while listing all session IDs:", error);
306
+ return null;
307
+ }
308
+ })
309
+ );
310
+ return sessions.filter((sessionId) => sessionId !== null);
253
311
  } catch (error) {
254
312
  console.error("[RedisStorage] Failed to get all sessions:", error);
255
313
  return [];
@@ -257,10 +315,11 @@ var RedisStorageBackend = class {
257
315
  }
258
316
  async clearAll() {
259
317
  try {
260
- const pattern = `${this.KEY_PREFIX}*`;
261
- const keys = await this.redis.keys(pattern);
262
- if (keys.length > 0) {
263
- await this.redis.del(...keys);
318
+ const keys = await this.scanKeys(`${this.KEY_PREFIX}*`);
319
+ const identityKeys = await this.scanKeys(`${this.IDENTITY_KEY_PREFIX}*${this.IDENTITY_KEY_SUFFIX}`);
320
+ const allKeys = [...keys, ...identityKeys];
321
+ if (allKeys.length > 0) {
322
+ await this.redis.del(...allKeys);
264
323
  }
265
324
  } catch (error) {
266
325
  console.error("[RedisStorage] Failed to clear sessions:", error);
@@ -268,12 +327,24 @@ var RedisStorageBackend = class {
268
327
  }
269
328
  async cleanupExpiredSessions() {
270
329
  try {
271
- const pattern = `${this.KEY_PREFIX}*`;
272
- const keys = await this.redis.keys(pattern);
273
- for (const key of keys) {
274
- const ttl = await this.redis.ttl(key);
275
- if (ttl <= 0) {
276
- await this.redis.del(key);
330
+ const identityKeys = await this.scanKeys(`${this.IDENTITY_KEY_PREFIX}*${this.IDENTITY_KEY_SUFFIX}`);
331
+ for (const identityKey of identityKeys) {
332
+ const identity = this.parseIdentityFromKey(identityKey);
333
+ const sessionIds = await this.redis.smembers(identityKey);
334
+ if (sessionIds.length === 0) {
335
+ await this.redis.del(identityKey);
336
+ continue;
337
+ }
338
+ const existenceChecks = await Promise.all(
339
+ sessionIds.map((sessionId) => this.redis.exists(this.getSessionKey(identity, sessionId)))
340
+ );
341
+ const staleSessionIds = sessionIds.filter((_, index) => existenceChecks[index] === 0);
342
+ if (staleSessionIds.length > 0) {
343
+ await this.redis.srem(identityKey, ...staleSessionIds);
344
+ }
345
+ const remainingCount = await this.redis.scard(identityKey);
346
+ if (remainingCount === 0) {
347
+ await this.redis.del(identityKey);
277
348
  }
278
349
  }
279
350
  } catch (error) {
@@ -303,6 +374,9 @@ var MemoryStorageBackend = class {
303
374
  // Map<identity, Set<sessionId>>
304
375
  __publicField(this, "identitySessions", /* @__PURE__ */ new Map());
305
376
  }
377
+ async init() {
378
+ console.log("[mcp-ts][Storage] Memory: \u2713 internal memory store active.");
379
+ }
306
380
  getSessionKey(identity, sessionId) {
307
381
  return `${identity}:${sessionId}`;
308
382
  }
@@ -422,6 +496,7 @@ var FileStorageBackend = class {
422
496
  }
423
497
  }
424
498
  this.initialized = true;
499
+ console.log(`[mcp-ts][Storage] File: \u2713 storage directory at ${path.dirname(this.filePath)} verified.`);
425
500
  }
426
501
  async ensureInitialized() {
427
502
  if (!this.initialized) await this.init();
@@ -526,6 +601,7 @@ var SqliteStorage = class {
526
601
  CREATE INDEX IF NOT EXISTS idx_${this.table}_identity ON ${this.table}(identity);
527
602
  `);
528
603
  this.initialized = true;
604
+ console.log(`[mcp-ts][Storage] SQLite: \u2713 database at ${this.dbPath} verified.`);
529
605
  } catch (error) {
530
606
  if (error.code === "MODULE_NOT_FOUND" || error.message?.includes("better-sqlite3")) {
531
607
  throw new Error(
@@ -641,9 +717,166 @@ var SqliteStorage = class {
641
717
  }
642
718
  };
643
719
 
720
+ // src/server/storage/supabase-backend.ts
721
+ var SupabaseStorageBackend = class {
722
+ constructor(supabase) {
723
+ this.supabase = supabase;
724
+ __publicField(this, "DEFAULT_TTL", SESSION_TTL_SECONDS);
725
+ }
726
+ async init() {
727
+ const { error } = await this.supabase.from("mcp_sessions").select("session_id").limit(0);
728
+ if (error) {
729
+ if (error.code === "42P01") {
730
+ throw new Error(
731
+ '[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.'
732
+ );
733
+ }
734
+ throw new Error(`[SupabaseStorage] Initialization check failed: ${error.message}`);
735
+ }
736
+ console.log('[mcp-ts][Storage] Supabase: \u2713 "mcp_sessions" table verified.');
737
+ }
738
+ generateSessionId() {
739
+ return crypto.randomUUID();
740
+ }
741
+ mapRowToSessionData(row) {
742
+ return {
743
+ sessionId: row.session_id,
744
+ serverId: row.server_id,
745
+ serverName: row.server_name,
746
+ serverUrl: row.server_url,
747
+ transportType: row.transport_type,
748
+ callbackUrl: row.callback_url,
749
+ createdAt: new Date(row.created_at).getTime(),
750
+ identity: row.identity,
751
+ headers: row.headers,
752
+ active: row.active,
753
+ clientInformation: row.client_information,
754
+ tokens: row.tokens,
755
+ codeVerifier: row.code_verifier,
756
+ clientId: row.client_id
757
+ };
758
+ }
759
+ async createSession(session, ttl) {
760
+ const { sessionId, identity } = session;
761
+ if (!sessionId || !identity) throw new Error("identity and sessionId required");
762
+ const effectiveTtl = ttl ?? this.DEFAULT_TTL;
763
+ const expiresAt = new Date(Date.now() + effectiveTtl * 1e3).toISOString();
764
+ const { error } = await this.supabase.from("mcp_sessions").insert({
765
+ session_id: sessionId,
766
+ user_id: identity,
767
+ // Maps user_id to identity to support RLS using auth.uid()
768
+ server_id: session.serverId,
769
+ server_name: session.serverName,
770
+ server_url: session.serverUrl,
771
+ transport_type: session.transportType,
772
+ callback_url: session.callbackUrl,
773
+ created_at: new Date(session.createdAt || Date.now()).toISOString(),
774
+ identity,
775
+ headers: session.headers,
776
+ active: session.active ?? false,
777
+ client_information: session.clientInformation,
778
+ tokens: session.tokens,
779
+ code_verifier: session.codeVerifier,
780
+ client_id: session.clientId,
781
+ expires_at: expiresAt
782
+ });
783
+ if (error) {
784
+ if (error.code === "23505") {
785
+ throw new Error(`Session ${sessionId} already exists`);
786
+ }
787
+ throw new Error(`Failed to create session in Supabase: ${error.message}`);
788
+ }
789
+ }
790
+ async updateSession(identity, sessionId, data, ttl) {
791
+ const effectiveTtl = ttl ?? this.DEFAULT_TTL;
792
+ const expiresAt = new Date(Date.now() + effectiveTtl * 1e3).toISOString();
793
+ const updateData = {
794
+ expires_at: expiresAt,
795
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
796
+ };
797
+ if ("serverId" in data) updateData.server_id = data.serverId;
798
+ if ("serverName" in data) updateData.server_name = data.serverName;
799
+ if ("serverUrl" in data) updateData.server_url = data.serverUrl;
800
+ if ("transportType" in data) updateData.transport_type = data.transportType;
801
+ if ("callbackUrl" in data) updateData.callback_url = data.callbackUrl;
802
+ if ("active" in data) updateData.active = data.active;
803
+ if ("headers" in data) updateData.headers = data.headers;
804
+ if ("clientInformation" in data) updateData.client_information = data.clientInformation;
805
+ if ("tokens" in data) updateData.tokens = data.tokens;
806
+ if ("codeVerifier" in data) updateData.code_verifier = data.codeVerifier;
807
+ if ("clientId" in data) updateData.client_id = data.clientId;
808
+ const { data: updatedRows, error } = await this.supabase.from("mcp_sessions").update(updateData).eq("identity", identity).eq("session_id", sessionId).select("id");
809
+ if (error) {
810
+ throw new Error(`Failed to update session: ${error.message}`);
811
+ }
812
+ if (!updatedRows || updatedRows.length === 0) {
813
+ throw new Error(`Session ${sessionId} not found for identity ${identity}`);
814
+ }
815
+ }
816
+ async getSession(identity, sessionId) {
817
+ const { data, error } = await this.supabase.from("mcp_sessions").select("*").eq("identity", identity).eq("session_id", sessionId).maybeSingle();
818
+ if (error) {
819
+ console.error("[SupabaseStorage] Failed to get session:", error);
820
+ return null;
821
+ }
822
+ if (!data) return null;
823
+ return this.mapRowToSessionData(data);
824
+ }
825
+ async getIdentitySessionsData(identity) {
826
+ const { data, error } = await this.supabase.from("mcp_sessions").select("*").eq("identity", identity);
827
+ if (error) {
828
+ console.error(`[SupabaseStorage] Failed to get session data for ${identity}:`, error);
829
+ return [];
830
+ }
831
+ return data.map((row) => this.mapRowToSessionData(row));
832
+ }
833
+ async removeSession(identity, sessionId) {
834
+ const { error } = await this.supabase.from("mcp_sessions").delete().eq("identity", identity).eq("session_id", sessionId);
835
+ if (error) {
836
+ console.error("[SupabaseStorage] Failed to remove session:", error);
837
+ }
838
+ }
839
+ async getIdentityMcpSessions(identity) {
840
+ const { data, error } = await this.supabase.from("mcp_sessions").select("session_id").eq("identity", identity);
841
+ if (error) {
842
+ console.error(`[SupabaseStorage] Failed to get sessions for ${identity}:`, error);
843
+ return [];
844
+ }
845
+ return data.map((row) => row.session_id);
846
+ }
847
+ async getAllSessionIds() {
848
+ const { data, error } = await this.supabase.from("mcp_sessions").select("session_id");
849
+ if (error) {
850
+ console.error("[SupabaseStorage] Failed to get all sessions:", error);
851
+ return [];
852
+ }
853
+ return data.map((row) => row.session_id);
854
+ }
855
+ async clearAll() {
856
+ const { error } = await this.supabase.from("mcp_sessions").delete().neq("session_id", "");
857
+ if (error) {
858
+ console.error("[SupabaseStorage] Failed to clear sessions:", error);
859
+ }
860
+ }
861
+ async cleanupExpiredSessions() {
862
+ const { error } = await this.supabase.from("mcp_sessions").delete().lt("expires_at", (/* @__PURE__ */ new Date()).toISOString());
863
+ if (error) {
864
+ console.error("[SupabaseStorage] Failed to cleanup expired sessions:", error);
865
+ }
866
+ }
867
+ async disconnect() {
868
+ }
869
+ };
870
+
644
871
  // src/server/storage/index.ts
645
872
  var storageInstance = null;
646
873
  var storagePromise = null;
874
+ async function initializeStorage(store) {
875
+ if (typeof store.init === "function") {
876
+ await store.init();
877
+ }
878
+ return store;
879
+ }
647
880
  async function createStorage() {
648
881
  const type = process.env.MCP_TS_STORAGE_TYPE?.toLowerCase();
649
882
  if (type === "redis") {
@@ -653,68 +886,95 @@ async function createStorage() {
653
886
  try {
654
887
  const { getRedis: getRedis2 } = await Promise.resolve().then(() => (init_redis(), redis_exports));
655
888
  const redis2 = await getRedis2();
656
- console.log("[Storage] Using Redis storage (Explicit)");
657
- return new RedisStorageBackend(redis2);
889
+ console.log('[mcp-ts][Storage] Explicit selection: "redis"');
890
+ return await initializeStorage(new RedisStorageBackend(redis2));
658
891
  } catch (error) {
659
- console.error("[Storage] Failed to initialize Redis:", error.message);
660
- console.log("[Storage] Falling back to In-Memory storage");
661
- return new MemoryStorageBackend();
892
+ console.error("[mcp-ts][Storage] Failed to initialize Redis:", error.message);
893
+ console.log("[mcp-ts][Storage] Falling back to In-Memory storage");
894
+ return await initializeStorage(new MemoryStorageBackend());
662
895
  }
663
896
  }
664
897
  if (type === "file") {
665
898
  const filePath = process.env.MCP_TS_STORAGE_FILE;
666
- if (!filePath) {
667
- console.warn('[Storage] MCP_TS_STORAGE_TYPE is "file" but MCP_TS_STORAGE_FILE is missing');
668
- }
669
- console.log(`[Storage] Using File storage (${filePath}) (Explicit)`);
670
- const store = new FileStorageBackend({ path: filePath });
671
- store.init().catch((err) => console.error("[Storage] Failed to initialize file storage:", err));
672
- return store;
899
+ console.log(`[mcp-ts][Storage] Explicit selection: "file" (${filePath || "default"})`);
900
+ return await initializeStorage(new FileStorageBackend({ path: filePath }));
673
901
  }
674
902
  if (type === "sqlite") {
675
903
  const dbPath = process.env.MCP_TS_STORAGE_SQLITE_PATH;
676
- console.log(`[Storage] Using SQLite storage (${dbPath || "default"}) (Explicit)`);
677
- const store = new SqliteStorage({ path: dbPath });
678
- store.init().catch((err) => console.error("[Storage] Failed to initialize SQLite storage:", err));
679
- return store;
904
+ console.log(`[mcp-ts][Storage] Explicit selection: "sqlite" (${dbPath || "default"})`);
905
+ return await initializeStorage(new SqliteStorage({ path: dbPath }));
906
+ }
907
+ if (type === "supabase") {
908
+ const url = process.env.SUPABASE_URL;
909
+ const key = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SUPABASE_ANON_KEY;
910
+ if (!url || !key) {
911
+ console.warn('[mcp-ts][Storage] Explicit selection "supabase" requires SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY.');
912
+ } else {
913
+ if (!process.env.SUPABASE_SERVICE_ROLE_KEY) {
914
+ 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.');
915
+ }
916
+ try {
917
+ const { createClient } = await import('@supabase/supabase-js');
918
+ const client = createClient(url, key);
919
+ console.log('[mcp-ts][Storage] Explicit selection: "supabase"');
920
+ return await initializeStorage(new SupabaseStorageBackend(client));
921
+ } catch (error) {
922
+ console.error("[mcp-ts][Storage] Failed to initialize Supabase:", error.message);
923
+ console.log("[mcp-ts][Storage] Falling back to In-Memory storage");
924
+ return await initializeStorage(new MemoryStorageBackend());
925
+ }
926
+ }
680
927
  }
681
928
  if (type === "memory") {
682
- console.log("[Storage] Using In-Memory storage (Explicit)");
683
- return new MemoryStorageBackend();
929
+ console.log('[mcp-ts][Storage] Explicit selection: "memory"');
930
+ return await initializeStorage(new MemoryStorageBackend());
684
931
  }
685
932
  if (process.env.REDIS_URL) {
686
933
  try {
687
934
  const { getRedis: getRedis2 } = await Promise.resolve().then(() => (init_redis(), redis_exports));
688
935
  const redis2 = await getRedis2();
689
- console.log("[Storage] Auto-detected REDIS_URL. Using Redis storage.");
690
- return new RedisStorageBackend(redis2);
936
+ console.log('[mcp-ts][Storage] Auto-detection: "redis" (via REDIS_URL)');
937
+ return await initializeStorage(new RedisStorageBackend(redis2));
691
938
  } catch (error) {
692
- console.error("[Storage] Redis auto-detection failed:", error.message);
693
- console.log("[Storage] Falling back to In-Memory storage");
694
- return new MemoryStorageBackend();
939
+ console.error("[mcp-ts][Storage] Redis auto-detection failed:", error.message);
940
+ console.log("[mcp-ts][Storage] Falling back to next available backend");
695
941
  }
696
942
  }
697
943
  if (process.env.MCP_TS_STORAGE_FILE) {
698
- console.log(`[Storage] Auto-detected MCP_TS_STORAGE_FILE. Using File storage (${process.env.MCP_TS_STORAGE_FILE}).`);
699
- const store = new FileStorageBackend({ path: process.env.MCP_TS_STORAGE_FILE });
700
- store.init().catch((err) => console.error("[Storage] Failed to initialize file storage:", err));
701
- return store;
944
+ console.log(`[mcp-ts][Storage] Auto-detection: "file" (${process.env.MCP_TS_STORAGE_FILE})`);
945
+ return await initializeStorage(new FileStorageBackend({ path: process.env.MCP_TS_STORAGE_FILE }));
702
946
  }
703
947
  if (process.env.MCP_TS_STORAGE_SQLITE_PATH) {
704
- console.log(`[Storage] Auto-detected MCP_TS_STORAGE_SQLITE_PATH. Using SQLite storage (${process.env.MCP_TS_STORAGE_SQLITE_PATH}).`);
705
- const store = new SqliteStorage({ path: process.env.MCP_TS_STORAGE_SQLITE_PATH });
706
- store.init().catch((err) => console.error("[Storage] Failed to initialize SQLite storage:", err));
707
- return store;
948
+ console.log(`[mcp-ts][Storage] Auto-detection: "sqlite" (${process.env.MCP_TS_STORAGE_SQLITE_PATH})`);
949
+ return await initializeStorage(new SqliteStorage({ path: process.env.MCP_TS_STORAGE_SQLITE_PATH }));
950
+ }
951
+ if (process.env.SUPABASE_URL && (process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SUPABASE_ANON_KEY)) {
952
+ try {
953
+ const { createClient } = await import('@supabase/supabase-js');
954
+ const url = process.env.SUPABASE_URL;
955
+ const key = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SUPABASE_ANON_KEY;
956
+ if (!process.env.SUPABASE_SERVICE_ROLE_KEY) {
957
+ 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.');
958
+ }
959
+ const client = createClient(url, key);
960
+ console.log('[mcp-ts][Storage] Auto-detection: "supabase" (via SUPABASE_URL)');
961
+ return await initializeStorage(new SupabaseStorageBackend(client));
962
+ } catch (error) {
963
+ console.error("[mcp-ts][Storage] Supabase auto-detection failed:", error.message);
964
+ }
708
965
  }
709
- console.log("[Storage] No storage configured. Using In-Memory storage (Default).");
710
- return new MemoryStorageBackend();
966
+ console.log('[mcp-ts][Storage] Defaulting to: "memory"');
967
+ return await initializeStorage(new MemoryStorageBackend());
711
968
  }
712
969
  async function getStorage() {
713
970
  if (storageInstance) {
714
971
  return storageInstance;
715
972
  }
716
973
  if (!storagePromise) {
717
- storagePromise = createStorage();
974
+ storagePromise = createStorage().catch((error) => {
975
+ storagePromise = null;
976
+ throw error;
977
+ });
718
978
  }
719
979
  storageInstance = await storagePromise;
720
980
  return storageInstance;
@@ -735,43 +995,49 @@ var storage = new Proxy({}, {
735
995
  // src/server/mcp/storage-oauth-provider.ts
736
996
  var StorageOAuthClientProvider = class {
737
997
  /**
738
- * Creates a new Storage-backed OAuth provider
739
- * @param identity - User/Client identifier
740
- * @param serverId - Server identifier (for tracking which server this OAuth session belongs to)
741
- * @param sessionId - Session identifier (used as OAuth state)
742
- * @param clientName - OAuth client name
743
- * @param baseRedirectUrl - OAuth callback URL
744
- * @param onRedirect - Optional callback when redirect to authorization is needed
998
+ * Creates a new storage-backed OAuth provider
999
+ * @param options - Provider configuration
745
1000
  */
746
- constructor(identity, serverId, sessionId, clientName, baseRedirectUrl, onRedirect) {
747
- this.identity = identity;
748
- this.serverId = serverId;
749
- this.sessionId = sessionId;
750
- this.clientName = clientName;
751
- this.baseRedirectUrl = baseRedirectUrl;
1001
+ constructor(options) {
1002
+ __publicField(this, "identity");
1003
+ __publicField(this, "serverId");
1004
+ __publicField(this, "sessionId");
1005
+ __publicField(this, "redirectUrl");
1006
+ __publicField(this, "clientName");
1007
+ __publicField(this, "clientUri");
1008
+ __publicField(this, "logoUri");
1009
+ __publicField(this, "policyUri");
1010
+ __publicField(this, "clientSecret");
752
1011
  __publicField(this, "_authUrl");
753
1012
  __publicField(this, "_clientId");
754
1013
  __publicField(this, "onRedirectCallback");
755
1014
  __publicField(this, "tokenExpiresAt");
756
- this.onRedirectCallback = onRedirect;
1015
+ this.identity = options.identity;
1016
+ this.serverId = options.serverId;
1017
+ this.sessionId = options.sessionId;
1018
+ this.redirectUrl = options.redirectUrl;
1019
+ this.clientName = options.clientName;
1020
+ this.clientUri = options.clientUri;
1021
+ this.logoUri = options.logoUri;
1022
+ this.policyUri = options.policyUri;
1023
+ this._clientId = options.clientId;
1024
+ this.clientSecret = options.clientSecret;
1025
+ this.onRedirectCallback = options.onRedirect;
757
1026
  }
758
1027
  get clientMetadata() {
759
1028
  return {
760
- client_name: this.clientName,
761
- client_uri: this.clientUri,
1029
+ client_name: this.clientName || DEFAULT_CLIENT_NAME,
1030
+ client_uri: this.clientUri || DEFAULT_CLIENT_URI,
1031
+ logo_uri: this.logoUri || DEFAULT_LOGO_URI,
1032
+ policy_uri: this.policyUri || DEFAULT_POLICY_URI,
762
1033
  grant_types: ["authorization_code", "refresh_token"],
763
1034
  redirect_uris: [this.redirectUrl],
764
1035
  response_types: ["code"],
765
- token_endpoint_auth_method: "none",
766
- ...this._clientId ? { client_id: this._clientId } : {}
1036
+ token_endpoint_auth_method: this.clientSecret ? "client_secret_basic" : "none",
1037
+ software_id: SOFTWARE_ID,
1038
+ software_version: SOFTWARE_VERSION
767
1039
  };
768
1040
  }
769
- get clientUri() {
770
- return new URL(this.redirectUrl).origin;
771
- }
772
- get redirectUrl() {
773
- return this.baseRedirectUrl;
774
- }
775
1041
  get clientId() {
776
1042
  return this._clientId;
777
1043
  }
@@ -806,7 +1072,16 @@ var StorageOAuthClientProvider = class {
806
1072
  if (data.clientId && !this._clientId) {
807
1073
  this._clientId = data.clientId;
808
1074
  }
809
- return data.clientInformation;
1075
+ if (data.clientInformation) {
1076
+ return data.clientInformation;
1077
+ }
1078
+ if (!this._clientId) {
1079
+ return void 0;
1080
+ }
1081
+ return {
1082
+ client_id: this._clientId,
1083
+ ...this.clientSecret ? { client_secret: this.clientSecret } : {}
1084
+ };
810
1085
  }
811
1086
  /**
812
1087
  * Stores OAuth client information
@@ -834,14 +1109,14 @@ var StorageOAuthClientProvider = class {
834
1109
  async state() {
835
1110
  return this.sessionId;
836
1111
  }
837
- async checkState(state) {
1112
+ async checkState(_state) {
838
1113
  const data = await storage.getSession(this.identity, this.sessionId);
839
1114
  if (!data) {
840
1115
  return { valid: false, error: "Session not found" };
841
1116
  }
842
1117
  return { valid: true, serverId: this.serverId };
843
1118
  }
844
- async consumeState(state) {
1119
+ async consumeState(_state) {
845
1120
  }
846
1121
  async redirectToAuthorization(authUrl) {
847
1122
  this._authUrl = authUrl.toString();
@@ -853,7 +1128,6 @@ var StorageOAuthClientProvider = class {
853
1128
  if (scope === "all") {
854
1129
  await storage.removeSession(this.identity, this.sessionId);
855
1130
  } else {
856
- await this.getSessionData();
857
1131
  const updates = {};
858
1132
  if (scope === "client") {
859
1133
  updates.clientInformation = void 0;
@@ -1173,41 +1447,33 @@ var MCPClient = class _MCPClient {
1173
1447
  if (!this.serverUrl || !this.callbackUrl || !this.serverId) {
1174
1448
  throw new Error("Missing required connection metadata");
1175
1449
  }
1176
- const clientMetadata = {
1177
- client_name: this.clientName || "MCP Assistant",
1178
- redirect_uris: [this.callbackUrl],
1179
- token_endpoint_auth_method: this.clientSecret ? "client_secret_basic" : "none",
1180
- client_uri: this.clientUri || "https://mcp-assistant.in",
1181
- logo_uri: this.logoUri || "https://mcp-assistant.in/logo.png",
1182
- policy_uri: this.policyUri || "https://mcp-assistant.in/privacy",
1183
- ...this.clientId ? { client_id: this.clientId } : {},
1184
- ...this.clientSecret ? { client_secret: this.clientSecret } : {}
1185
- };
1186
1450
  if (!this.oauthProvider) {
1187
1451
  if (!this.serverId) {
1188
1452
  throw new Error("serverId required for OAuth provider initialization");
1189
1453
  }
1190
- this.oauthProvider = new StorageOAuthClientProvider(
1191
- this.identity,
1192
- this.serverId,
1193
- this.sessionId,
1194
- clientMetadata.client_name ?? "MCP Assistant",
1195
- this.callbackUrl,
1196
- (redirectUrl) => {
1454
+ this.oauthProvider = new StorageOAuthClientProvider({
1455
+ identity: this.identity,
1456
+ serverId: this.serverId,
1457
+ sessionId: this.sessionId,
1458
+ redirectUrl: this.callbackUrl,
1459
+ clientName: this.clientName,
1460
+ clientUri: this.clientUri,
1461
+ logoUri: this.logoUri,
1462
+ policyUri: this.policyUri,
1463
+ clientId: this.clientId,
1464
+ clientSecret: this.clientSecret,
1465
+ onRedirect: (redirectUrl) => {
1197
1466
  if (this.onRedirect) {
1198
1467
  this.onRedirect(redirectUrl);
1199
1468
  }
1200
1469
  }
1201
- );
1202
- if (this.clientId && this.oauthProvider) {
1203
- this.oauthProvider.clientId = this.clientId;
1204
- }
1470
+ });
1205
1471
  }
1206
1472
  if (!this.client) {
1207
1473
  this.client = new Client(
1208
1474
  {
1209
- name: "mcp-ts-oauth-client",
1210
- version: "2.0"
1475
+ name: MCP_CLIENT_NAME,
1476
+ version: MCP_CLIENT_VERSION
1211
1477
  },
1212
1478
  {
1213
1479
  capabilities: {
@@ -1402,8 +1668,8 @@ var MCPClient = class _MCPClient {
1402
1668
  this.emitProgress("Creating authenticated client...");
1403
1669
  this.client = new Client(
1404
1670
  {
1405
- name: "mcp-ts-oauth-client",
1406
- version: "2.0"
1671
+ name: MCP_CLIENT_NAME,
1672
+ version: MCP_CLIENT_VERSION
1407
1673
  },
1408
1674
  {
1409
1675
  capabilities: {
@@ -1698,8 +1964,8 @@ var MCPClient = class _MCPClient {
1698
1964
  }
1699
1965
  this.client = new Client(
1700
1966
  {
1701
- name: "mcp-ts-oauth-client",
1702
- version: "2.0"
1967
+ name: MCP_CLIENT_NAME,
1968
+ version: MCP_CLIENT_VERSION
1703
1969
  },
1704
1970
  { capabilities: {} }
1705
1971
  );
@@ -1968,6 +2234,27 @@ var MultiSessionClient = class {
1968
2234
  }
1969
2235
  };
1970
2236
 
2237
+ // src/shared/event-routing.ts
2238
+ function isRpcResponseEvent(event) {
2239
+ return "id" in event && ("result" in event || "error" in event);
2240
+ }
2241
+ function isConnectionEvent(event) {
2242
+ if (!("type" in event)) {
2243
+ return false;
2244
+ }
2245
+ switch (event.type) {
2246
+ case "state_changed":
2247
+ case "tools_discovered":
2248
+ case "auth_required":
2249
+ case "error":
2250
+ case "disconnected":
2251
+ case "progress":
2252
+ return true;
2253
+ default:
2254
+ return false;
2255
+ }
2256
+ }
2257
+
1971
2258
  // src/server/handlers/sse-handler.ts
1972
2259
  var DEFAULT_HEARTBEAT_INTERVAL = 3e4;
1973
2260
  var SSEConnectionManager = class {
@@ -2110,16 +2397,6 @@ var SSEConnectionManager = class {
2110
2397
  throw new Error(`Connection already exists for server: ${duplicate.serverUrl || duplicate.serverId} (${duplicate.serverName})`);
2111
2398
  }
2112
2399
  const sessionId = await storage.generateSessionId();
2113
- this.emitConnectionEvent({
2114
- type: "state_changed",
2115
- sessionId,
2116
- serverId,
2117
- serverName,
2118
- serverUrl,
2119
- state: "CONNECTING",
2120
- previousState: "DISCONNECTED",
2121
- timestamp: Date.now()
2122
- });
2123
2400
  try {
2124
2401
  const clientMetadata = await this.getResolvedClientMetadata();
2125
2402
  const client = new MCPClient({
@@ -2130,17 +2407,8 @@ var SSEConnectionManager = class {
2130
2407
  serverUrl,
2131
2408
  callbackUrl,
2132
2409
  transportType,
2133
- ...clientMetadata,
2410
+ ...clientMetadata
2134
2411
  // Spread client metadata (clientName, clientUri, logoUri, policyUri)
2135
- onRedirect: (authUrl) => {
2136
- this.emitConnectionEvent({
2137
- type: "auth_required",
2138
- sessionId,
2139
- serverId,
2140
- authUrl,
2141
- timestamp: Date.now()
2142
- });
2143
- }
2144
2412
  });
2145
2413
  this.clients.set(sessionId, client);
2146
2414
  client.onConnectionEvent((event) => {
@@ -2150,20 +2418,19 @@ var SSEConnectionManager = class {
2150
2418
  this.sendEvent(event);
2151
2419
  });
2152
2420
  await client.connect();
2153
- const tools = await client.listTools();
2154
- this.emitConnectionEvent({
2155
- type: "tools_discovered",
2156
- sessionId,
2157
- serverId,
2158
- toolCount: tools.tools.length,
2159
- tools: tools.tools,
2160
- timestamp: Date.now()
2161
- });
2421
+ await client.listTools();
2162
2422
  return {
2163
2423
  sessionId,
2164
2424
  success: true
2165
2425
  };
2166
2426
  } catch (error) {
2427
+ if (error instanceof UnauthorizedError) {
2428
+ this.clients.delete(sessionId);
2429
+ return {
2430
+ sessionId,
2431
+ success: true
2432
+ };
2433
+ }
2167
2434
  this.emitConnectionEvent({
2168
2435
  type: "error",
2169
2436
  sessionId,
@@ -2265,14 +2532,6 @@ var SSEConnectionManager = class {
2265
2532
  await client.connect();
2266
2533
  this.clients.set(sessionId, client);
2267
2534
  const tools = await client.listTools();
2268
- this.emitConnectionEvent({
2269
- type: "tools_discovered",
2270
- sessionId,
2271
- serverId: session.serverId ?? "unknown",
2272
- toolCount: tools.tools.length,
2273
- tools: tools.tools,
2274
- timestamp: Date.now()
2275
- });
2276
2535
  return { success: true, toolCount: tools.tools.length };
2277
2536
  } catch (error) {
2278
2537
  this.emitConnectionEvent({
@@ -2295,16 +2554,6 @@ var SSEConnectionManager = class {
2295
2554
  if (!session) {
2296
2555
  throw new Error("Session not found");
2297
2556
  }
2298
- this.emitConnectionEvent({
2299
- type: "state_changed",
2300
- sessionId,
2301
- serverId: session.serverId ?? "unknown",
2302
- serverName: session.serverName ?? "Unknown",
2303
- serverUrl: session.serverUrl,
2304
- state: "AUTHENTICATING",
2305
- previousState: "DISCONNECTED",
2306
- timestamp: Date.now()
2307
- });
2308
2557
  try {
2309
2558
  const client = new MCPClient({
2310
2559
  identity: this.identity,
@@ -2314,14 +2563,6 @@ var SSEConnectionManager = class {
2314
2563
  await client.finishAuth(code);
2315
2564
  this.clients.set(sessionId, client);
2316
2565
  const tools = await client.listTools();
2317
- this.emitConnectionEvent({
2318
- type: "tools_discovered",
2319
- sessionId,
2320
- serverId: session.serverId ?? "unknown",
2321
- toolCount: tools.tools.length,
2322
- tools: tools.tools,
2323
- timestamp: Date.now()
2324
- });
2325
2566
  return { success: true, toolCount: tools.tools.length };
2326
2567
  } catch (error) {
2327
2568
  this.emitConnectionEvent({
@@ -2399,9 +2640,9 @@ function createSSEHandler(options) {
2399
2640
  });
2400
2641
  writeSSEEvent(res, "connected", { timestamp: Date.now() });
2401
2642
  const manager = new SSEConnectionManager(options, (event) => {
2402
- if ("id" in event) {
2643
+ if (isRpcResponseEvent(event)) {
2403
2644
  writeSSEEvent(res, "rpc-response", event);
2404
- } else if ("type" in event && "sessionId" in event) {
2645
+ } else if (isConnectionEvent(event)) {
2405
2646
  writeSSEEvent(res, "connection", event);
2406
2647
  } else {
2407
2648
  writeSSEEvent(res, "observability", event);
@@ -2432,9 +2673,6 @@ function writeSSEEvent(res, event, data) {
2432
2673
  }
2433
2674
 
2434
2675
  // src/server/handlers/nextjs-handler.ts
2435
- function isRpcResponseEvent(event) {
2436
- return "id" in event && ("result" in event || "error" in event);
2437
- }
2438
2676
  function createNextMcpHandler(options = {}) {
2439
2677
  const {
2440
2678
  getIdentity = (request) => new URL(request.url).searchParams.get("identity"),
@@ -2525,7 +2763,7 @@ data: ${JSON.stringify(data)}
2525
2763
  (event) => {
2526
2764
  if (isRpcResponseEvent(event)) {
2527
2765
  sendSSE("rpc-response", event);
2528
- } else if ("type" in event && "sessionId" in event) {
2766
+ } else if (isConnectionEvent(event)) {
2529
2767
  sendSSE("connection", event);
2530
2768
  } else {
2531
2769
  sendSSE("observability", event);