@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.
- package/README.md +404 -400
- package/dist/adapters/agui-adapter.d.mts +1 -1
- package/dist/adapters/agui-adapter.d.ts +1 -1
- package/dist/adapters/agui-middleware.d.mts +1 -1
- package/dist/adapters/agui-middleware.d.ts +1 -1
- package/dist/adapters/ai-adapter.d.mts +1 -1
- package/dist/adapters/ai-adapter.d.ts +1 -1
- package/dist/adapters/langchain-adapter.d.mts +1 -1
- package/dist/adapters/langchain-adapter.d.ts +1 -1
- package/dist/adapters/mastra-adapter.d.mts +1 -1
- package/dist/adapters/mastra-adapter.d.ts +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/client/index.d.mts +1 -0
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.js +14 -5
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +14 -5
- package/dist/client/index.mjs.map +1 -1
- package/dist/client/react.js +15 -6
- package/dist/client/react.js.map +1 -1
- package/dist/client/react.mjs +15 -6
- package/dist/client/react.mjs.map +1 -1
- package/dist/client/vue.js +15 -6
- package/dist/client/vue.js.map +1 -1
- package/dist/client/vue.mjs +15 -6
- package/dist/client/vue.mjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +480 -179
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +418 -179
- package/dist/index.mjs.map +1 -1
- package/dist/{multi-session-client-FAFpUzZ4.d.ts → multi-session-client-BYLarghq.d.ts} +29 -19
- package/dist/{multi-session-client-DzjmT7FX.d.mts → multi-session-client-CzhMkE0k.d.mts} +29 -19
- package/dist/server/index.d.mts +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +455 -172
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +410 -172
- package/dist/server/index.mjs.map +1 -1
- package/dist/shared/index.d.mts +2 -2
- package/dist/shared/index.d.ts +2 -2
- package/dist/shared/index.js +2 -2
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/index.mjs +2 -2
- package/dist/shared/index.mjs.map +1 -1
- package/package.json +19 -6
- package/src/bin/mcp-ts.ts +102 -0
- package/src/client/core/sse-client.ts +371 -354
- package/src/client/react/use-mcp.ts +31 -31
- package/src/client/vue/use-mcp.ts +77 -77
- package/src/server/handlers/nextjs-handler.ts +204 -207
- package/src/server/handlers/sse-handler.ts +14 -63
- package/src/server/mcp/oauth-client.ts +67 -79
- package/src/server/mcp/storage-oauth-provider.ts +71 -38
- package/src/server/storage/file-backend.ts +1 -0
- package/src/server/storage/index.ts +82 -38
- package/src/server/storage/memory-backend.ts +4 -0
- package/src/server/storage/redis-backend.ts +102 -23
- package/src/server/storage/sqlite-backend.ts +1 -0
- package/src/server/storage/supabase-backend.ts +227 -0
- package/src/server/storage/types.ts +12 -12
- package/src/shared/constants.ts +2 -2
- package/src/shared/event-routing.ts +28 -0
- 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.
|
|
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.
|
|
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
|
|
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
|
|
226
|
-
|
|
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
|
|
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
|
|
262
|
-
const
|
|
263
|
-
|
|
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
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
|
|
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
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
const
|
|
286
|
-
if (
|
|
287
|
-
await this.redis.del(
|
|
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(
|
|
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
|
-
|
|
678
|
-
|
|
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]
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
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(
|
|
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(
|
|
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
|
|
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-
|
|
710
|
-
|
|
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-
|
|
716
|
-
|
|
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
|
-
|
|
721
|
-
|
|
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
|
|
750
|
-
* @param
|
|
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(
|
|
758
|
-
this
|
|
759
|
-
this
|
|
760
|
-
this
|
|
761
|
-
this
|
|
762
|
-
this
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
1274
|
-
this.
|
|
1275
|
-
|
|
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:
|
|
1289
|
-
version:
|
|
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:
|
|
1485
|
-
version:
|
|
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:
|
|
1781
|
-
version:
|
|
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
|
-
|
|
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 (
|
|
2714
|
+
if (isRpcResponseEvent(event)) {
|
|
2482
2715
|
writeSSEEvent(res, "rpc-response", event);
|
|
2483
|
-
} else if (
|
|
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 (
|
|
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;
|