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