@mcp-ts/sdk 2.3.3 → 2.4.0
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 +113 -30
- package/dist/adapters/agui-adapter.d.mts +3 -3
- package/dist/adapters/agui-adapter.d.ts +3 -3
- package/dist/adapters/agui-middleware.d.mts +3 -3
- package/dist/adapters/agui-middleware.d.ts +3 -3
- package/dist/adapters/ai-adapter.d.mts +3 -3
- package/dist/adapters/ai-adapter.d.ts +3 -3
- package/dist/adapters/langchain-adapter.d.mts +3 -3
- package/dist/adapters/langchain-adapter.d.ts +3 -3
- package/dist/adapters/mastra-adapter.d.mts +1 -1
- package/dist/adapters/mastra-adapter.d.ts +1 -1
- package/dist/client/index.d.mts +2 -2
- package/dist/client/index.d.ts +2 -2
- package/dist/client/index.js +2 -2
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +2 -2
- package/dist/client/index.mjs.map +1 -1
- package/dist/client/react.d.mts +9 -8
- package/dist/client/react.d.ts +9 -8
- package/dist/client/react.js +66 -26
- package/dist/client/react.js.map +1 -1
- package/dist/client/react.mjs +67 -27
- package/dist/client/react.mjs.map +1 -1
- package/dist/client/vue.d.mts +6 -5
- package/dist/client/vue.d.ts +6 -5
- package/dist/client/vue.js +27 -17
- package/dist/client/vue.js.map +1 -1
- package/dist/client/vue.mjs +27 -17
- package/dist/client/vue.mjs.map +1 -1
- package/dist/{index-Cfjsme-a.d.mts → index-ByIjEReo.d.mts} +2 -2
- package/dist/{index-CmjMd2ac.d.ts → index-CtXvKl8N.d.ts} +2 -2
- package/dist/index.d.mts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +729 -397
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +724 -396
- package/dist/index.mjs.map +1 -1
- package/dist/{multi-session-client-DYNe6az3.d.mts → multi-session-client-CIMUGF8S.d.mts} +15 -41
- package/dist/{multi-session-client-BYtguGJm.d.ts → multi-session-client-CnvZEGPY.d.ts} +15 -41
- package/dist/server/index.d.mts +45 -17
- package/dist/server/index.d.ts +45 -17
- package/dist/server/index.js +721 -391
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +718 -391
- package/dist/server/index.mjs.map +1 -1
- package/dist/shared/index.d.mts +9 -8
- package/dist/shared/index.d.ts +9 -8
- package/dist/shared/index.js +7 -5
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/index.mjs +5 -4
- package/dist/shared/index.mjs.map +1 -1
- package/dist/{tool-router-BMzhoNYt.d.ts → tool-router-CZMrOG8J.d.ts} +1 -1
- package/dist/{tool-router-BhHsvBfP.d.mts → tool-router-CuApsDiV.d.mts} +1 -1
- package/dist/{types-CxFaaZrM.d.mts → types-DCk_IF4L.d.mts} +5 -3
- package/dist/{types-CxFaaZrM.d.ts → types-DCk_IF4L.d.ts} +5 -3
- package/migrations/neon/20260513010000_install_mcp_sessions.sql +40 -3
- package/migrations/neon/20260513020000_add_session_cleanup_cron.sql +4 -4
- package/migrations/supabase/20260330195700_install_mcp_sessions.sql +67 -3
- package/migrations/supabase/20260421010000_add_session_cleanup_cron.sql +6 -6
- package/package.json +13 -3
- package/src/client/core/sse-client.ts +2 -2
- package/src/client/react/index.ts +1 -1
- package/src/client/react/oauth-popup.tsx +34 -13
- package/src/client/react/use-mcp.ts +19 -45
- package/src/client/utils/session-state.ts +43 -0
- package/src/client/vue/use-mcp.ts +18 -47
- package/src/server/handlers/sse-handler.ts +9 -4
- package/src/server/mcp/multi-session-client.ts +2 -6
- package/src/server/mcp/oauth-client.ts +73 -220
- package/src/server/mcp/storage-oauth-provider.ts +79 -50
- package/src/server/storage/file-backend.ts +74 -18
- package/src/server/storage/index.ts +2 -4
- package/src/server/storage/memory-backend.ts +49 -13
- package/src/server/storage/neon-backend.ts +201 -68
- package/src/server/storage/redis-backend.ts +81 -23
- package/src/server/storage/session-lifecycle.ts +78 -0
- package/src/server/storage/sqlite-backend.ts +89 -15
- package/src/server/storage/supabase-backend.ts +188 -63
- package/src/server/storage/types.ts +49 -16
- package/src/shared/constants.ts +5 -6
- package/src/shared/types.ts +5 -3
- package/src/shared/utils.ts +26 -0
package/dist/index.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
|
2
2
|
import { customAlphabet, nanoid } from 'nanoid';
|
|
3
3
|
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
4
4
|
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
|
|
5
|
-
import { UnauthorizedError as UnauthorizedError$1
|
|
5
|
+
import { UnauthorizedError as UnauthorizedError$1 } from '@modelcontextprotocol/sdk/client/auth.js';
|
|
6
6
|
import { ListToolsResultSchema, CallToolResultSchema, ListPromptsResultSchema, GetPromptResultSchema, ListResourcesResultSchema, ReadResourceResultSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
7
7
|
import * as fs2 from 'fs';
|
|
8
8
|
import { promises } from 'fs';
|
|
@@ -112,21 +112,7 @@ var init_redis = __esm({
|
|
|
112
112
|
});
|
|
113
113
|
}
|
|
114
114
|
});
|
|
115
|
-
|
|
116
|
-
// src/shared/constants.ts
|
|
117
|
-
var SESSION_TTL_SECONDS = 43200;
|
|
118
|
-
var STATE_EXPIRATION_MS = 10 * 60 * 1e3;
|
|
119
|
-
var DEFAULT_HEARTBEAT_INTERVAL_MS = 3e4;
|
|
120
|
-
var REDIS_KEY_PREFIX = "mcp:session:";
|
|
121
|
-
var TOKEN_EXPIRY_BUFFER_MS = 5 * 60 * 1e3;
|
|
122
|
-
var DEFAULT_CLIENT_NAME = "MCP Assistant";
|
|
123
|
-
var DEFAULT_CLIENT_URI = "https://mcp-assistant.in";
|
|
124
|
-
var DEFAULT_LOGO_URI = "https://mcp-assistant.in/logo.svg";
|
|
125
|
-
var DEFAULT_POLICY_URI = "https://mcp-assistant.in/privacy";
|
|
126
|
-
var SOFTWARE_ID = "@mcp-ts";
|
|
127
|
-
var SOFTWARE_VERSION = "1.3.4";
|
|
128
|
-
var MCP_CLIENT_NAME = "mcp-ts-oauth-client";
|
|
129
|
-
var MCP_CLIENT_VERSION = "2.0";
|
|
115
|
+
var OAUTH_STATE_SEPARATOR = ".";
|
|
130
116
|
var firstChar = customAlphabet(
|
|
131
117
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
|
|
132
118
|
1
|
|
@@ -145,13 +131,98 @@ function sanitizeServerLabel(name) {
|
|
|
145
131
|
function generateSessionId() {
|
|
146
132
|
return firstChar() + rest();
|
|
147
133
|
}
|
|
134
|
+
function formatOAuthState(nonce, sessionId) {
|
|
135
|
+
return `${nonce}${OAUTH_STATE_SEPARATOR}${sessionId}`;
|
|
136
|
+
}
|
|
137
|
+
function parseOAuthState(state) {
|
|
138
|
+
const separatorIndex = state.indexOf(OAUTH_STATE_SEPARATOR);
|
|
139
|
+
if (separatorIndex <= 0 || separatorIndex === state.length - 1) {
|
|
140
|
+
return void 0;
|
|
141
|
+
}
|
|
142
|
+
const nonce = state.slice(0, separatorIndex);
|
|
143
|
+
const sessionId = state.slice(separatorIndex + 1);
|
|
144
|
+
if (!nonce || !sessionId || sessionId.includes(OAUTH_STATE_SEPARATOR)) {
|
|
145
|
+
return void 0;
|
|
146
|
+
}
|
|
147
|
+
return { nonce, sessionId };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// src/shared/constants.ts
|
|
151
|
+
var STATE_EXPIRATION_MS = 10 * 60 * 1e3;
|
|
152
|
+
var PENDING_SESSION_EXPIRATION_SECONDS = Math.floor(STATE_EXPIRATION_MS / 1e3);
|
|
153
|
+
var DORMANT_SESSION_EXPIRATION_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
154
|
+
var DORMANT_SESSION_EXPIRATION_SECONDS = Math.floor(DORMANT_SESSION_EXPIRATION_MS / 1e3);
|
|
155
|
+
var DEFAULT_HEARTBEAT_INTERVAL_MS = 3e4;
|
|
156
|
+
var REDIS_KEY_PREFIX = "mcp:session:";
|
|
157
|
+
var DEFAULT_CLIENT_NAME = "MCP Assistant";
|
|
158
|
+
var DEFAULT_CLIENT_URI = "https://mcp-assistant.in";
|
|
159
|
+
var DEFAULT_LOGO_URI = "https://mcp-assistant.in/logo.svg";
|
|
160
|
+
var DEFAULT_POLICY_URI = "https://mcp-assistant.in/privacy";
|
|
161
|
+
var SOFTWARE_ID = "@mcp-ts";
|
|
162
|
+
var SOFTWARE_VERSION = "2.3.4";
|
|
163
|
+
var MCP_CLIENT_NAME = "mcp-ts-oauth-client";
|
|
164
|
+
var MCP_CLIENT_VERSION = "2.0";
|
|
165
|
+
|
|
166
|
+
// src/server/storage/session-lifecycle.ts
|
|
167
|
+
function resolveSessionExpiresAt(status = "pending", referenceTime = Date.now()) {
|
|
168
|
+
return status === "active" ? null : referenceTime + STATE_EXPIRATION_MS;
|
|
169
|
+
}
|
|
170
|
+
function resolveSessionRedisTtlSeconds(session) {
|
|
171
|
+
return session.status === "active" ? DORMANT_SESSION_EXPIRATION_SECONDS : Math.floor(STATE_EXPIRATION_MS / 1e3);
|
|
172
|
+
}
|
|
173
|
+
function normalizeNewSession(session, now = Date.now()) {
|
|
174
|
+
const createdAt = session.createdAt || now;
|
|
175
|
+
const updatedAt = session.updatedAt ?? createdAt;
|
|
176
|
+
const status = session.status ?? "pending";
|
|
177
|
+
return {
|
|
178
|
+
...session,
|
|
179
|
+
status,
|
|
180
|
+
createdAt,
|
|
181
|
+
updatedAt,
|
|
182
|
+
expiresAt: resolveSessionExpiresAt(status, status === "active" ? updatedAt : createdAt)
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
function mergeSessionUpdate(current, data, now = Date.now()) {
|
|
186
|
+
const updatedAt = data.updatedAt ?? now;
|
|
187
|
+
const updated = {
|
|
188
|
+
...current,
|
|
189
|
+
...data,
|
|
190
|
+
updatedAt
|
|
191
|
+
};
|
|
192
|
+
const status = updated.status ?? "pending";
|
|
193
|
+
return {
|
|
194
|
+
...updated,
|
|
195
|
+
status,
|
|
196
|
+
expiresAt: resolveSessionExpiresAt(status, updatedAt)
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
function normalizeStoredSession(session) {
|
|
200
|
+
const createdAt = session.createdAt || Date.now();
|
|
201
|
+
const updatedAt = session.updatedAt ?? createdAt;
|
|
202
|
+
const status = session.status ?? "pending";
|
|
203
|
+
const expiresAt = status === "active" ? null : session.expiresAt ?? resolveSessionExpiresAt(status, createdAt);
|
|
204
|
+
return {
|
|
205
|
+
...session,
|
|
206
|
+
status,
|
|
207
|
+
createdAt,
|
|
208
|
+
updatedAt,
|
|
209
|
+
expiresAt
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
function isSessionExpired(session, now = Date.now()) {
|
|
213
|
+
const hydrated = normalizeStoredSession(session);
|
|
214
|
+
if (hydrated.status === "active") {
|
|
215
|
+
return hydrated.updatedAt !== void 0 && hydrated.updatedAt < now - DORMANT_SESSION_EXPIRATION_MS;
|
|
216
|
+
}
|
|
217
|
+
return typeof hydrated.expiresAt === "number" && hydrated.expiresAt < now;
|
|
218
|
+
}
|
|
148
219
|
|
|
149
220
|
// src/server/storage/redis-backend.ts
|
|
150
221
|
var RedisStorageBackend = class {
|
|
151
222
|
constructor(redis2) {
|
|
152
223
|
this.redis = redis2;
|
|
153
|
-
__publicField(this, "DEFAULT_TTL", SESSION_TTL_SECONDS);
|
|
154
224
|
__publicField(this, "KEY_PREFIX", "mcp:session:");
|
|
225
|
+
__publicField(this, "CREDENTIALS_KEY_PREFIX", "mcp:credentials:");
|
|
155
226
|
__publicField(this, "USER_ID_KEY_PREFIX", "mcp:userId:");
|
|
156
227
|
__publicField(this, "USER_ID_KEY_SUFFIX", ":sessions");
|
|
157
228
|
}
|
|
@@ -170,6 +241,9 @@ var RedisStorageBackend = class {
|
|
|
170
241
|
getSessionKey(userId, sessionId) {
|
|
171
242
|
return `${this.KEY_PREFIX}${userId}:${sessionId}`;
|
|
172
243
|
}
|
|
244
|
+
getCredentialsKey(userId, sessionId) {
|
|
245
|
+
return `${this.CREDENTIALS_KEY_PREFIX}${userId}:${sessionId}`;
|
|
246
|
+
}
|
|
173
247
|
/**
|
|
174
248
|
* Generates Redis key for tracking all sessions for a user
|
|
175
249
|
* @private
|
|
@@ -207,15 +281,16 @@ var RedisStorageBackend = class {
|
|
|
207
281
|
generateSessionId() {
|
|
208
282
|
return generateSessionId();
|
|
209
283
|
}
|
|
210
|
-
async create(session
|
|
284
|
+
async create(session) {
|
|
211
285
|
const { sessionId, userId } = session;
|
|
212
286
|
if (!sessionId || !userId) throw new Error("userId and sessionId required");
|
|
213
287
|
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
214
288
|
const userIdKey = this.getUserIdKey(userId);
|
|
215
|
-
const
|
|
289
|
+
const sessionWithLifecycle = normalizeNewSession(session);
|
|
290
|
+
const effectiveTtl = resolveSessionRedisTtlSeconds(sessionWithLifecycle);
|
|
216
291
|
const result = await this.redis.set(
|
|
217
292
|
sessionKey,
|
|
218
|
-
JSON.stringify(
|
|
293
|
+
JSON.stringify(sessionWithLifecycle),
|
|
219
294
|
"EX",
|
|
220
295
|
effectiveTtl,
|
|
221
296
|
"NX"
|
|
@@ -225,9 +300,8 @@ var RedisStorageBackend = class {
|
|
|
225
300
|
}
|
|
226
301
|
await this.redis.sadd(userIdKey, sessionId);
|
|
227
302
|
}
|
|
228
|
-
async update(userId, sessionId, data
|
|
303
|
+
async update(userId, sessionId, data) {
|
|
229
304
|
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
230
|
-
const effectiveTtl = ttl ?? this.DEFAULT_TTL;
|
|
231
305
|
const script = `
|
|
232
306
|
local currentStr = redis.call("GET", KEYS[1])
|
|
233
307
|
if not currentStr then
|
|
@@ -235,26 +309,47 @@ var RedisStorageBackend = class {
|
|
|
235
309
|
end
|
|
236
310
|
|
|
237
311
|
local current = cjson.decode(currentStr)
|
|
238
|
-
local
|
|
239
|
-
|
|
240
|
-
for k,v in pairs(updates) do
|
|
241
|
-
current[k] = v
|
|
242
|
-
end
|
|
312
|
+
local updated = cjson.decode(ARGV[1])
|
|
243
313
|
|
|
244
|
-
redis.call("SET", KEYS[1], cjson.encode(
|
|
314
|
+
redis.call("SET", KEYS[1], cjson.encode(updated), "EX", ARGV[2])
|
|
245
315
|
return 1
|
|
246
316
|
`;
|
|
317
|
+
const current = await this.get(userId, sessionId);
|
|
318
|
+
if (!current) {
|
|
319
|
+
throw new Error(`Session ${sessionId} not found for userId ${userId}`);
|
|
320
|
+
}
|
|
321
|
+
const updated = mergeSessionUpdate(current, data);
|
|
322
|
+
const effectiveTtl = resolveSessionRedisTtlSeconds(updated);
|
|
247
323
|
const result = await this.redis.eval(
|
|
248
324
|
script,
|
|
249
325
|
1,
|
|
250
326
|
sessionKey,
|
|
251
|
-
JSON.stringify(
|
|
327
|
+
JSON.stringify(updated),
|
|
252
328
|
effectiveTtl
|
|
253
329
|
);
|
|
254
330
|
if (result === 0) {
|
|
255
331
|
throw new Error(`Session ${sessionId} not found for userId ${userId}`);
|
|
256
332
|
}
|
|
257
333
|
}
|
|
334
|
+
async patchCredentials(userId, sessionId, data) {
|
|
335
|
+
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
336
|
+
const credentialsKey = this.getCredentialsKey(userId, sessionId);
|
|
337
|
+
const sessionExists = await this.redis.exists(sessionKey);
|
|
338
|
+
if (!sessionExists) {
|
|
339
|
+
throw new Error(`Session ${sessionId} not found for userId ${userId}`);
|
|
340
|
+
}
|
|
341
|
+
const session = await this.get(userId, sessionId);
|
|
342
|
+
const currentTtl = await this.redis.ttl(sessionKey);
|
|
343
|
+
const effectiveTtl = currentTtl > 0 && session ? currentTtl : resolveSessionRedisTtlSeconds(session ?? { status: "pending" });
|
|
344
|
+
const currentStr = await this.redis.get(credentialsKey);
|
|
345
|
+
const current = currentStr ? JSON.parse(currentStr) : { sessionId, userId };
|
|
346
|
+
const credentials = { ...current, ...data, sessionId, userId };
|
|
347
|
+
if (effectiveTtl > 0) {
|
|
348
|
+
await this.redis.set(credentialsKey, JSON.stringify(credentials), "EX", effectiveTtl);
|
|
349
|
+
} else {
|
|
350
|
+
await this.redis.set(credentialsKey, JSON.stringify(credentials));
|
|
351
|
+
}
|
|
352
|
+
}
|
|
258
353
|
async get(userId, sessionId) {
|
|
259
354
|
try {
|
|
260
355
|
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
@@ -262,13 +357,28 @@ var RedisStorageBackend = class {
|
|
|
262
357
|
if (!sessionDataStr) {
|
|
263
358
|
return null;
|
|
264
359
|
}
|
|
265
|
-
const
|
|
266
|
-
return
|
|
360
|
+
const session = JSON.parse(sessionDataStr);
|
|
361
|
+
return normalizeStoredSession(session);
|
|
267
362
|
} catch (error) {
|
|
268
363
|
console.error("[RedisStorageBackend] Failed to get session:", error);
|
|
269
364
|
return null;
|
|
270
365
|
}
|
|
271
366
|
}
|
|
367
|
+
async getCredentials(userId, sessionId) {
|
|
368
|
+
const session = await this.get(userId, sessionId);
|
|
369
|
+
if (!session) return null;
|
|
370
|
+
const credentialsStr = await this.redis.get(this.getCredentialsKey(userId, sessionId));
|
|
371
|
+
return credentialsStr ? JSON.parse(credentialsStr) : { sessionId, userId };
|
|
372
|
+
}
|
|
373
|
+
async clearCredentials(userId, sessionId) {
|
|
374
|
+
await this.patchCredentials(userId, sessionId, {
|
|
375
|
+
clientInformation: null,
|
|
376
|
+
tokens: null,
|
|
377
|
+
codeVerifier: null,
|
|
378
|
+
clientId: null,
|
|
379
|
+
oauthState: null
|
|
380
|
+
});
|
|
381
|
+
}
|
|
272
382
|
async listIds(userId) {
|
|
273
383
|
const sessions2 = await this.list(userId);
|
|
274
384
|
return sessions2.map((session) => session.sessionId);
|
|
@@ -288,7 +398,7 @@ var RedisStorageBackend = class {
|
|
|
288
398
|
if (staleSessionIds.length > 0) {
|
|
289
399
|
await this.redis.srem(userIdKey, ...staleSessionIds);
|
|
290
400
|
}
|
|
291
|
-
return results.filter((session) => session !== null);
|
|
401
|
+
return results.filter((session) => session !== null).map((session) => normalizeStoredSession(session));
|
|
292
402
|
} catch (error) {
|
|
293
403
|
console.error(`[RedisStorageBackend] Failed to get session data for ${userId}:`, error);
|
|
294
404
|
return [];
|
|
@@ -297,9 +407,10 @@ var RedisStorageBackend = class {
|
|
|
297
407
|
async delete(userId, sessionId) {
|
|
298
408
|
try {
|
|
299
409
|
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
410
|
+
const credentialsKey = this.getCredentialsKey(userId, sessionId);
|
|
300
411
|
const userIdKey = this.getUserIdKey(userId);
|
|
301
412
|
await this.redis.srem(userIdKey, sessionId);
|
|
302
|
-
await this.redis.del(sessionKey);
|
|
413
|
+
await this.redis.del(sessionKey, credentialsKey);
|
|
303
414
|
} catch (error) {
|
|
304
415
|
console.error("[RedisStorageBackend] Failed to remove session:", error);
|
|
305
416
|
}
|
|
@@ -314,7 +425,7 @@ var RedisStorageBackend = class {
|
|
|
314
425
|
return null;
|
|
315
426
|
}
|
|
316
427
|
try {
|
|
317
|
-
return JSON.parse(data).sessionId;
|
|
428
|
+
return normalizeStoredSession(JSON.parse(data)).sessionId;
|
|
318
429
|
} catch (error) {
|
|
319
430
|
console.error("[RedisStorageBackend] Failed to parse session while listing all session IDs:", error);
|
|
320
431
|
return null;
|
|
@@ -330,8 +441,9 @@ var RedisStorageBackend = class {
|
|
|
330
441
|
async clearAll() {
|
|
331
442
|
try {
|
|
332
443
|
const keys = await this.scanKeys(`${this.KEY_PREFIX}*`);
|
|
444
|
+
const credentialKeys = await this.scanKeys(`${this.CREDENTIALS_KEY_PREFIX}*`);
|
|
333
445
|
const userIdKeys = await this.scanKeys(`${this.USER_ID_KEY_PREFIX}*${this.USER_ID_KEY_SUFFIX}`);
|
|
334
|
-
const allKeys = [...keys, ...userIdKeys];
|
|
446
|
+
const allKeys = [...keys, ...credentialKeys, ...userIdKeys];
|
|
335
447
|
if (allKeys.length > 0) {
|
|
336
448
|
await this.redis.del(...allKeys);
|
|
337
449
|
}
|
|
@@ -355,6 +467,7 @@ var RedisStorageBackend = class {
|
|
|
355
467
|
const staleSessionIds = sessionIds.filter((_, index) => existenceChecks[index] === 0);
|
|
356
468
|
if (staleSessionIds.length > 0) {
|
|
357
469
|
await this.redis.srem(userIdKey, ...staleSessionIds);
|
|
470
|
+
await this.redis.del(...staleSessionIds.map((sessionId) => this.getCredentialsKey(userId, sessionId)));
|
|
358
471
|
}
|
|
359
472
|
const remainingCount = await this.redis.scard(userIdKey);
|
|
360
473
|
if (remainingCount === 0) {
|
|
@@ -379,6 +492,7 @@ var MemoryStorageBackend = class {
|
|
|
379
492
|
constructor() {
|
|
380
493
|
// Map<userId:sessionId, Session>
|
|
381
494
|
__publicField(this, "sessions", /* @__PURE__ */ new Map());
|
|
495
|
+
__publicField(this, "credentials", /* @__PURE__ */ new Map());
|
|
382
496
|
// Map<userId, Set<sessionId>>
|
|
383
497
|
__publicField(this, "userIdSessions", /* @__PURE__ */ new Map());
|
|
384
498
|
}
|
|
@@ -391,36 +505,55 @@ var MemoryStorageBackend = class {
|
|
|
391
505
|
generateSessionId() {
|
|
392
506
|
return generateSessionId();
|
|
393
507
|
}
|
|
394
|
-
async create(session
|
|
508
|
+
async create(session) {
|
|
395
509
|
const { sessionId, userId } = session;
|
|
396
510
|
if (!sessionId || !userId) throw new Error("userId and sessionId required");
|
|
397
511
|
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
398
512
|
if (this.sessions.has(sessionKey)) {
|
|
399
513
|
throw new Error(`Session ${sessionId} already exists`);
|
|
400
514
|
}
|
|
401
|
-
this.sessions.set(sessionKey, session);
|
|
515
|
+
this.sessions.set(sessionKey, normalizeNewSession(session));
|
|
402
516
|
if (!this.userIdSessions.has(userId)) {
|
|
403
517
|
this.userIdSessions.set(userId, /* @__PURE__ */ new Set());
|
|
404
518
|
}
|
|
405
519
|
this.userIdSessions.get(userId).add(sessionId);
|
|
406
520
|
}
|
|
407
|
-
async update(userId, sessionId, data
|
|
521
|
+
async update(userId, sessionId, data) {
|
|
408
522
|
if (!userId || !sessionId) throw new Error("userId and sessionId required");
|
|
409
523
|
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
410
524
|
const current = this.sessions.get(sessionKey);
|
|
411
525
|
if (!current) {
|
|
412
526
|
throw new Error(`Session ${sessionId} not found`);
|
|
413
527
|
}
|
|
414
|
-
const updated =
|
|
415
|
-
...current,
|
|
416
|
-
...data
|
|
417
|
-
};
|
|
528
|
+
const updated = mergeSessionUpdate(current, data);
|
|
418
529
|
this.sessions.set(sessionKey, updated);
|
|
419
530
|
}
|
|
531
|
+
async patchCredentials(userId, sessionId, data) {
|
|
532
|
+
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
533
|
+
if (!this.sessions.has(sessionKey)) {
|
|
534
|
+
throw new Error(`Session ${sessionId} not found`);
|
|
535
|
+
}
|
|
536
|
+
const current = this.credentials.get(sessionKey) ?? { sessionId, userId };
|
|
537
|
+
this.credentials.set(sessionKey, { ...current, ...data, sessionId, userId });
|
|
538
|
+
}
|
|
420
539
|
async get(userId, sessionId) {
|
|
421
540
|
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
422
541
|
return this.sessions.get(sessionKey) || null;
|
|
423
542
|
}
|
|
543
|
+
async getCredentials(userId, sessionId) {
|
|
544
|
+
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
545
|
+
if (!this.sessions.has(sessionKey)) return null;
|
|
546
|
+
return this.credentials.get(sessionKey) ?? { sessionId, userId };
|
|
547
|
+
}
|
|
548
|
+
async clearCredentials(userId, sessionId) {
|
|
549
|
+
await this.patchCredentials(userId, sessionId, {
|
|
550
|
+
clientInformation: null,
|
|
551
|
+
tokens: null,
|
|
552
|
+
codeVerifier: null,
|
|
553
|
+
clientId: null,
|
|
554
|
+
oauthState: null
|
|
555
|
+
});
|
|
556
|
+
}
|
|
424
557
|
async listIds(userId) {
|
|
425
558
|
const set = this.userIdSessions.get(userId);
|
|
426
559
|
return set ? Array.from(set) : [];
|
|
@@ -440,6 +573,7 @@ var MemoryStorageBackend = class {
|
|
|
440
573
|
async delete(userId, sessionId) {
|
|
441
574
|
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
442
575
|
this.sessions.delete(sessionKey);
|
|
576
|
+
this.credentials.delete(sessionKey);
|
|
443
577
|
const set = this.userIdSessions.get(userId);
|
|
444
578
|
if (set) {
|
|
445
579
|
set.delete(sessionId);
|
|
@@ -453,9 +587,22 @@ var MemoryStorageBackend = class {
|
|
|
453
587
|
}
|
|
454
588
|
async clearAll() {
|
|
455
589
|
this.sessions.clear();
|
|
590
|
+
this.credentials.clear();
|
|
456
591
|
this.userIdSessions.clear();
|
|
457
592
|
}
|
|
458
593
|
async cleanupExpired() {
|
|
594
|
+
for (const [key, session] of this.sessions.entries()) {
|
|
595
|
+
if (!isSessionExpired(session)) continue;
|
|
596
|
+
this.sessions.delete(key);
|
|
597
|
+
this.credentials.delete(key);
|
|
598
|
+
const set = this.userIdSessions.get(session.userId);
|
|
599
|
+
if (set) {
|
|
600
|
+
set.delete(session.sessionId);
|
|
601
|
+
if (set.size === 0) {
|
|
602
|
+
this.userIdSessions.delete(session.userId);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
459
606
|
}
|
|
460
607
|
async disconnect() {
|
|
461
608
|
}
|
|
@@ -467,6 +614,7 @@ var FileStorageBackend = class {
|
|
|
467
614
|
constructor(options = {}) {
|
|
468
615
|
__publicField(this, "filePath");
|
|
469
616
|
__publicField(this, "memoryCache", null);
|
|
617
|
+
__publicField(this, "credentialsCache", null);
|
|
470
618
|
__publicField(this, "initialized", false);
|
|
471
619
|
this.filePath = options.path || "./sessions.json";
|
|
472
620
|
}
|
|
@@ -481,14 +629,22 @@ var FileStorageBackend = class {
|
|
|
481
629
|
const data = await promises.readFile(this.filePath, "utf-8");
|
|
482
630
|
const json = JSON.parse(data);
|
|
483
631
|
this.memoryCache = /* @__PURE__ */ new Map();
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
632
|
+
this.credentialsCache = /* @__PURE__ */ new Map();
|
|
633
|
+
if (Array.isArray(json.sessions)) {
|
|
634
|
+
json.sessions.forEach((s) => {
|
|
635
|
+
const session = normalizeStoredSession(s);
|
|
636
|
+
this.memoryCache.set(this.getSessionKey(session.userId || "unknown", session.sessionId), session);
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
if (Array.isArray(json.credentials)) {
|
|
640
|
+
json.credentials.forEach((c) => {
|
|
641
|
+
this.credentialsCache.set(this.getSessionKey(c.userId, c.sessionId), c);
|
|
487
642
|
});
|
|
488
643
|
}
|
|
489
644
|
} catch (error) {
|
|
490
645
|
if (error.code === "ENOENT") {
|
|
491
646
|
this.memoryCache = /* @__PURE__ */ new Map();
|
|
647
|
+
this.credentialsCache = /* @__PURE__ */ new Map();
|
|
492
648
|
await this.flush();
|
|
493
649
|
} else {
|
|
494
650
|
console.error("[FileStorage] Failed to load sessions:", error);
|
|
@@ -502,9 +658,11 @@ var FileStorageBackend = class {
|
|
|
502
658
|
if (!this.initialized) await this.init();
|
|
503
659
|
}
|
|
504
660
|
async flush() {
|
|
505
|
-
if (!this.memoryCache) return;
|
|
506
|
-
|
|
507
|
-
|
|
661
|
+
if (!this.memoryCache || !this.credentialsCache) return;
|
|
662
|
+
await promises.writeFile(this.filePath, JSON.stringify({
|
|
663
|
+
sessions: Array.from(this.memoryCache.values()),
|
|
664
|
+
credentials: Array.from(this.credentialsCache.values())
|
|
665
|
+
}, null, 2), "utf-8");
|
|
508
666
|
}
|
|
509
667
|
getSessionKey(userId, sessionId) {
|
|
510
668
|
return `${userId}:${sessionId}`;
|
|
@@ -512,7 +670,7 @@ var FileStorageBackend = class {
|
|
|
512
670
|
generateSessionId() {
|
|
513
671
|
return generateSessionId();
|
|
514
672
|
}
|
|
515
|
-
async create(session
|
|
673
|
+
async create(session) {
|
|
516
674
|
await this.ensureInitialized();
|
|
517
675
|
const { sessionId, userId } = session;
|
|
518
676
|
if (!sessionId || !userId) throw new Error("userId and sessionId required");
|
|
@@ -520,10 +678,10 @@ var FileStorageBackend = class {
|
|
|
520
678
|
if (this.memoryCache.has(sessionKey)) {
|
|
521
679
|
throw new Error(`Session ${sessionId} already exists`);
|
|
522
680
|
}
|
|
523
|
-
this.memoryCache.set(sessionKey, session);
|
|
681
|
+
this.memoryCache.set(sessionKey, normalizeNewSession(session));
|
|
524
682
|
await this.flush();
|
|
525
683
|
}
|
|
526
|
-
async update(userId, sessionId, data
|
|
684
|
+
async update(userId, sessionId, data) {
|
|
527
685
|
await this.ensureInitialized();
|
|
528
686
|
if (!userId || !sessionId) throw new Error("userId and sessionId required");
|
|
529
687
|
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
@@ -531,18 +689,40 @@ var FileStorageBackend = class {
|
|
|
531
689
|
if (!current) {
|
|
532
690
|
throw new Error(`Session ${sessionId} not found`);
|
|
533
691
|
}
|
|
534
|
-
const updated =
|
|
535
|
-
...current,
|
|
536
|
-
...data
|
|
537
|
-
};
|
|
692
|
+
const updated = mergeSessionUpdate(current, data);
|
|
538
693
|
this.memoryCache.set(sessionKey, updated);
|
|
539
694
|
await this.flush();
|
|
540
695
|
}
|
|
696
|
+
async patchCredentials(userId, sessionId, data) {
|
|
697
|
+
await this.ensureInitialized();
|
|
698
|
+
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
699
|
+
if (!this.memoryCache.has(sessionKey)) {
|
|
700
|
+
throw new Error(`Session ${sessionId} not found`);
|
|
701
|
+
}
|
|
702
|
+
const current = this.credentialsCache.get(sessionKey) ?? { sessionId, userId };
|
|
703
|
+
this.credentialsCache.set(sessionKey, { ...current, ...data, sessionId, userId });
|
|
704
|
+
await this.flush();
|
|
705
|
+
}
|
|
541
706
|
async get(userId, sessionId) {
|
|
542
707
|
await this.ensureInitialized();
|
|
543
708
|
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
544
709
|
return this.memoryCache.get(sessionKey) || null;
|
|
545
710
|
}
|
|
711
|
+
async getCredentials(userId, sessionId) {
|
|
712
|
+
await this.ensureInitialized();
|
|
713
|
+
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
714
|
+
if (!this.memoryCache.has(sessionKey)) return null;
|
|
715
|
+
return this.credentialsCache.get(sessionKey) ?? { sessionId, userId };
|
|
716
|
+
}
|
|
717
|
+
async clearCredentials(userId, sessionId) {
|
|
718
|
+
await this.patchCredentials(userId, sessionId, {
|
|
719
|
+
clientInformation: null,
|
|
720
|
+
tokens: null,
|
|
721
|
+
codeVerifier: null,
|
|
722
|
+
clientId: null,
|
|
723
|
+
oauthState: null
|
|
724
|
+
});
|
|
725
|
+
}
|
|
546
726
|
async list(userId) {
|
|
547
727
|
await this.ensureInitialized();
|
|
548
728
|
return Array.from(this.memoryCache.values()).filter((s) => s.userId === userId);
|
|
@@ -554,7 +734,9 @@ var FileStorageBackend = class {
|
|
|
554
734
|
async delete(userId, sessionId) {
|
|
555
735
|
await this.ensureInitialized();
|
|
556
736
|
const sessionKey = this.getSessionKey(userId, sessionId);
|
|
557
|
-
|
|
737
|
+
const deleted = this.memoryCache.delete(sessionKey);
|
|
738
|
+
this.credentialsCache.delete(sessionKey);
|
|
739
|
+
if (deleted) {
|
|
558
740
|
await this.flush();
|
|
559
741
|
}
|
|
560
742
|
}
|
|
@@ -565,10 +747,21 @@ var FileStorageBackend = class {
|
|
|
565
747
|
async clearAll() {
|
|
566
748
|
await this.ensureInitialized();
|
|
567
749
|
this.memoryCache.clear();
|
|
750
|
+
this.credentialsCache.clear();
|
|
568
751
|
await this.flush();
|
|
569
752
|
}
|
|
570
753
|
async cleanupExpired() {
|
|
571
754
|
await this.ensureInitialized();
|
|
755
|
+
let changed = false;
|
|
756
|
+
for (const [key, session] of this.memoryCache.entries()) {
|
|
757
|
+
if (!isSessionExpired(session)) continue;
|
|
758
|
+
this.memoryCache.delete(key);
|
|
759
|
+
this.credentialsCache.delete(key);
|
|
760
|
+
changed = true;
|
|
761
|
+
}
|
|
762
|
+
if (changed) {
|
|
763
|
+
await this.flush();
|
|
764
|
+
}
|
|
572
765
|
}
|
|
573
766
|
async disconnect() {
|
|
574
767
|
}
|
|
@@ -577,10 +770,12 @@ var SqliteStorage = class {
|
|
|
577
770
|
constructor(options = {}) {
|
|
578
771
|
__publicField(this, "db", null);
|
|
579
772
|
__publicField(this, "table");
|
|
773
|
+
__publicField(this, "credentialsTable");
|
|
580
774
|
__publicField(this, "initialized", false);
|
|
581
775
|
__publicField(this, "dbPath");
|
|
582
776
|
this.dbPath = options.path || "./sessions.db";
|
|
583
777
|
this.table = options.table || "mcp_sessions";
|
|
778
|
+
this.credentialsTable = `${this.table}_credentials`;
|
|
584
779
|
}
|
|
585
780
|
async init() {
|
|
586
781
|
if (this.initialized) return;
|
|
@@ -591,6 +786,7 @@ var SqliteStorage = class {
|
|
|
591
786
|
fs2.mkdirSync(dir, { recursive: true });
|
|
592
787
|
}
|
|
593
788
|
this.db = new DatabaseConstructor(this.dbPath);
|
|
789
|
+
this.db.pragma("foreign_keys = ON");
|
|
594
790
|
this.db.exec(`
|
|
595
791
|
CREATE TABLE IF NOT EXISTS ${this.table} (
|
|
596
792
|
sessionId TEXT PRIMARY KEY,
|
|
@@ -599,6 +795,13 @@ var SqliteStorage = class {
|
|
|
599
795
|
expiresAt INTEGER
|
|
600
796
|
);
|
|
601
797
|
CREATE INDEX IF NOT EXISTS idx_${this.table}_userId ON ${this.table}(userId);
|
|
798
|
+
CREATE TABLE IF NOT EXISTS ${this.credentialsTable} (
|
|
799
|
+
sessionId TEXT NOT NULL,
|
|
800
|
+
userId TEXT NOT NULL,
|
|
801
|
+
data TEXT NOT NULL,
|
|
802
|
+
PRIMARY KEY (userId, sessionId),
|
|
803
|
+
FOREIGN KEY (sessionId) REFERENCES ${this.table}(sessionId) ON DELETE CASCADE
|
|
804
|
+
);
|
|
602
805
|
`);
|
|
603
806
|
this.initialized = true;
|
|
604
807
|
console.log(`[mcp-ts][Storage] SQLite: \u2713 database at ${this.dbPath} verified.`);
|
|
@@ -619,18 +822,18 @@ var SqliteStorage = class {
|
|
|
619
822
|
generateSessionId() {
|
|
620
823
|
return generateSessionId();
|
|
621
824
|
}
|
|
622
|
-
async create(session
|
|
825
|
+
async create(session) {
|
|
623
826
|
this.ensureInitialized();
|
|
624
827
|
const { sessionId, userId } = session;
|
|
625
828
|
if (!sessionId || !userId) {
|
|
626
829
|
throw new Error("userId and sessionId required");
|
|
627
830
|
}
|
|
628
|
-
const
|
|
831
|
+
const sessionWithLifecycle = normalizeNewSession(session);
|
|
629
832
|
try {
|
|
630
833
|
const stmt = this.db.prepare(
|
|
631
834
|
`INSERT INTO ${this.table} (sessionId, userId, data, expiresAt) VALUES (?, ?, ?, ?)`
|
|
632
835
|
);
|
|
633
|
-
stmt.run(sessionId, userId, JSON.stringify(
|
|
836
|
+
stmt.run(sessionId, userId, JSON.stringify(sessionWithLifecycle), sessionWithLifecycle.expiresAt ?? null);
|
|
634
837
|
} catch (error) {
|
|
635
838
|
if (error.code === "SQLITE_CONSTRAINT_PRIMARYKEY") {
|
|
636
839
|
throw new Error(`Session ${sessionId} already exists`);
|
|
@@ -638,7 +841,7 @@ var SqliteStorage = class {
|
|
|
638
841
|
throw error;
|
|
639
842
|
}
|
|
640
843
|
}
|
|
641
|
-
async update(userId, sessionId, data
|
|
844
|
+
async update(userId, sessionId, data) {
|
|
642
845
|
this.ensureInitialized();
|
|
643
846
|
if (!sessionId || !userId) {
|
|
644
847
|
throw new Error("userId and sessionId required");
|
|
@@ -647,12 +850,25 @@ var SqliteStorage = class {
|
|
|
647
850
|
if (!currentSession) {
|
|
648
851
|
throw new Error(`Session ${sessionId} not found for userId ${userId}`);
|
|
649
852
|
}
|
|
650
|
-
const updatedSession =
|
|
651
|
-
const expiresAt = ttl ? Date.now() + ttl * 1e3 : null;
|
|
853
|
+
const updatedSession = mergeSessionUpdate(currentSession, data);
|
|
652
854
|
const stmt = this.db.prepare(
|
|
653
855
|
`UPDATE ${this.table} SET data = ?, expiresAt = ? WHERE sessionId = ? AND userId = ?`
|
|
654
856
|
);
|
|
655
|
-
stmt.run(JSON.stringify(updatedSession), expiresAt, sessionId, userId);
|
|
857
|
+
stmt.run(JSON.stringify(updatedSession), updatedSession.expiresAt ?? null, sessionId, userId);
|
|
858
|
+
}
|
|
859
|
+
async patchCredentials(userId, sessionId, data) {
|
|
860
|
+
this.ensureInitialized();
|
|
861
|
+
if (!await this.get(userId, sessionId)) {
|
|
862
|
+
throw new Error(`Session ${sessionId} not found for userId ${userId}`);
|
|
863
|
+
}
|
|
864
|
+
const current = await this.getCredentials(userId, sessionId) ?? { sessionId, userId };
|
|
865
|
+
const credentials = { ...current, ...data, sessionId, userId };
|
|
866
|
+
const stmt = this.db.prepare(
|
|
867
|
+
`INSERT INTO ${this.credentialsTable} (sessionId, userId, data)
|
|
868
|
+
VALUES (?, ?, ?)
|
|
869
|
+
ON CONFLICT(userId, sessionId) DO UPDATE SET data = excluded.data`
|
|
870
|
+
);
|
|
871
|
+
stmt.run(sessionId, userId, JSON.stringify(credentials));
|
|
656
872
|
}
|
|
657
873
|
async get(userId, sessionId) {
|
|
658
874
|
this.ensureInitialized();
|
|
@@ -661,7 +877,25 @@ var SqliteStorage = class {
|
|
|
661
877
|
);
|
|
662
878
|
const row = stmt.get(sessionId, userId);
|
|
663
879
|
if (!row) return null;
|
|
664
|
-
return JSON.parse(row.data);
|
|
880
|
+
return normalizeStoredSession(JSON.parse(row.data));
|
|
881
|
+
}
|
|
882
|
+
async getCredentials(userId, sessionId) {
|
|
883
|
+
this.ensureInitialized();
|
|
884
|
+
if (!await this.get(userId, sessionId)) return null;
|
|
885
|
+
const stmt = this.db.prepare(
|
|
886
|
+
`SELECT data FROM ${this.credentialsTable} WHERE sessionId = ? AND userId = ?`
|
|
887
|
+
);
|
|
888
|
+
const row = stmt.get(sessionId, userId);
|
|
889
|
+
return row ? JSON.parse(row.data) : { sessionId, userId };
|
|
890
|
+
}
|
|
891
|
+
async clearCredentials(userId, sessionId) {
|
|
892
|
+
await this.patchCredentials(userId, sessionId, {
|
|
893
|
+
clientInformation: null,
|
|
894
|
+
tokens: null,
|
|
895
|
+
codeVerifier: null,
|
|
896
|
+
clientId: null,
|
|
897
|
+
oauthState: null
|
|
898
|
+
});
|
|
665
899
|
}
|
|
666
900
|
async list(userId) {
|
|
667
901
|
this.ensureInitialized();
|
|
@@ -669,7 +903,7 @@ var SqliteStorage = class {
|
|
|
669
903
|
`SELECT data FROM ${this.table} WHERE userId = ?`
|
|
670
904
|
);
|
|
671
905
|
const rows = stmt.all(userId);
|
|
672
|
-
return rows.map((row) => JSON.parse(row.data));
|
|
906
|
+
return rows.map((row) => normalizeStoredSession(JSON.parse(row.data)));
|
|
673
907
|
}
|
|
674
908
|
async listIds(userId) {
|
|
675
909
|
this.ensureInitialized();
|
|
@@ -684,6 +918,9 @@ var SqliteStorage = class {
|
|
|
684
918
|
const stmt = this.db.prepare(
|
|
685
919
|
`DELETE FROM ${this.table} WHERE sessionId = ? AND userId = ?`
|
|
686
920
|
);
|
|
921
|
+
this.db.prepare(
|
|
922
|
+
`DELETE FROM ${this.credentialsTable} WHERE sessionId = ? AND userId = ?`
|
|
923
|
+
).run(sessionId, userId);
|
|
687
924
|
stmt.run(sessionId, userId);
|
|
688
925
|
}
|
|
689
926
|
async listAllIds() {
|
|
@@ -694,16 +931,28 @@ var SqliteStorage = class {
|
|
|
694
931
|
}
|
|
695
932
|
async clearAll() {
|
|
696
933
|
this.ensureInitialized();
|
|
934
|
+
this.db.prepare(`DELETE FROM ${this.credentialsTable}`).run();
|
|
697
935
|
const stmt = this.db.prepare(`DELETE FROM ${this.table}`);
|
|
698
936
|
stmt.run();
|
|
699
937
|
}
|
|
700
938
|
async cleanupExpired() {
|
|
701
939
|
this.ensureInitialized();
|
|
702
|
-
const
|
|
703
|
-
const
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
940
|
+
const rows = this.db.prepare(`SELECT sessionId, userId, data FROM ${this.table}`).all();
|
|
941
|
+
const deleteStmt = this.db.prepare(`DELETE FROM ${this.table} WHERE sessionId = ? AND userId = ?`);
|
|
942
|
+
for (const row of rows) {
|
|
943
|
+
const session = normalizeStoredSession(JSON.parse(row.data));
|
|
944
|
+
if (isSessionExpired(session)) {
|
|
945
|
+
deleteStmt.run(row.sessionId, row.userId);
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
this.db.prepare(
|
|
949
|
+
`DELETE FROM ${this.credentialsTable}
|
|
950
|
+
WHERE NOT EXISTS (
|
|
951
|
+
SELECT 1 FROM ${this.table}
|
|
952
|
+
WHERE ${this.table}.sessionId = ${this.credentialsTable}.sessionId
|
|
953
|
+
AND ${this.table}.userId = ${this.credentialsTable}.userId
|
|
954
|
+
)`
|
|
955
|
+
).run();
|
|
707
956
|
}
|
|
708
957
|
async disconnect() {
|
|
709
958
|
if (this.db) {
|
|
@@ -782,19 +1031,21 @@ function decryptObject(data) {
|
|
|
782
1031
|
var SupabaseStorageBackend = class {
|
|
783
1032
|
constructor(supabase) {
|
|
784
1033
|
this.supabase = supabase;
|
|
785
|
-
__publicField(this, "DEFAULT_TTL", SESSION_TTL_SECONDS);
|
|
786
1034
|
}
|
|
787
1035
|
async init() {
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
1036
|
+
await this.assertTable("mcp_sessions", "session_id");
|
|
1037
|
+
await this.assertTable("mcp_credentials", "session_id");
|
|
1038
|
+
console.log("[mcp-ts][Storage] Supabase: storage tables verified.");
|
|
1039
|
+
}
|
|
1040
|
+
async assertTable(table, column) {
|
|
1041
|
+
const { error } = await this.supabase.from(table).select(column).limit(0);
|
|
1042
|
+
if (!error) return;
|
|
1043
|
+
if (error.code === "42P01") {
|
|
1044
|
+
throw new Error(
|
|
1045
|
+
`[SupabaseStorage] Table "${table}" not found in your database. Please run "npx mcp-ts supabase-init" to set up the required storage schema.`
|
|
1046
|
+
);
|
|
796
1047
|
}
|
|
797
|
-
|
|
1048
|
+
throw new Error(`[SupabaseStorage] Initialization check failed for "${table}": ${error.message}`);
|
|
798
1049
|
}
|
|
799
1050
|
generateSessionId() {
|
|
800
1051
|
return generateSessionId();
|
|
@@ -808,37 +1059,49 @@ var SupabaseStorageBackend = class {
|
|
|
808
1059
|
transportType: row.transport_type,
|
|
809
1060
|
callbackUrl: row.callback_url,
|
|
810
1061
|
createdAt: new Date(row.created_at).getTime(),
|
|
1062
|
+
updatedAt: new Date(row.updated_at ?? row.created_at).getTime(),
|
|
1063
|
+
expiresAt: row.expires_at ? new Date(row.expires_at).getTime() : null,
|
|
811
1064
|
userId: row.user_id,
|
|
812
1065
|
headers: decryptObject(row.headers),
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
1066
|
+
authUrl: row.auth_url,
|
|
1067
|
+
status: row.status ?? "pending"
|
|
1068
|
+
};
|
|
1069
|
+
}
|
|
1070
|
+
mapRowToCredentials(row, userId, sessionId) {
|
|
1071
|
+
return {
|
|
1072
|
+
sessionId,
|
|
1073
|
+
userId,
|
|
1074
|
+
clientInformation: decryptObject(row?.client_information),
|
|
1075
|
+
tokens: decryptObject(row?.tokens),
|
|
1076
|
+
codeVerifier: decryptObject(row?.code_verifier),
|
|
1077
|
+
clientId: row?.client_id,
|
|
1078
|
+
oauthState: row?.oauth_state
|
|
818
1079
|
};
|
|
819
1080
|
}
|
|
820
|
-
|
|
1081
|
+
hasCredentialData(data) {
|
|
1082
|
+
return "clientInformation" in data || "tokens" in data || "codeVerifier" in data || "clientId" in data || "oauthState" in data;
|
|
1083
|
+
}
|
|
1084
|
+
async create(session) {
|
|
821
1085
|
const { sessionId, userId } = session;
|
|
822
1086
|
if (!sessionId || !userId) throw new Error("userId and sessionId required");
|
|
823
|
-
const
|
|
824
|
-
const
|
|
1087
|
+
const status = session.status ?? "pending";
|
|
1088
|
+
const createdAt = new Date(session.createdAt || Date.now()).toISOString();
|
|
1089
|
+
const updatedAt = new Date(session.updatedAt ?? session.createdAt ?? Date.now()).toISOString();
|
|
1090
|
+
const expiresAt = resolveSessionExpiresAt(status, new Date(createdAt).getTime());
|
|
825
1091
|
const { error } = await this.supabase.from("mcp_sessions").insert({
|
|
826
1092
|
session_id: sessionId,
|
|
827
1093
|
user_id: userId,
|
|
828
|
-
// Maps user_id to userId to support RLS using auth.uid()
|
|
829
1094
|
server_id: session.serverId,
|
|
830
1095
|
server_name: session.serverName,
|
|
831
1096
|
server_url: session.serverUrl,
|
|
832
1097
|
transport_type: session.transportType,
|
|
833
1098
|
callback_url: session.callbackUrl,
|
|
834
|
-
created_at:
|
|
1099
|
+
created_at: createdAt,
|
|
1100
|
+
updated_at: updatedAt,
|
|
835
1101
|
headers: encryptObject(session.headers),
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
code_verifier: session.codeVerifier,
|
|
840
|
-
client_id: session.clientId,
|
|
841
|
-
expires_at: expiresAt
|
|
1102
|
+
auth_url: session.authUrl ?? null,
|
|
1103
|
+
status,
|
|
1104
|
+
expires_at: expiresAt === null ? null : new Date(expiresAt).toISOString()
|
|
842
1105
|
});
|
|
843
1106
|
if (error) {
|
|
844
1107
|
if (error.code === "23505") {
|
|
@@ -847,11 +1110,8 @@ var SupabaseStorageBackend = class {
|
|
|
847
1110
|
throw new Error(`Failed to create session in Supabase: ${error.message}`);
|
|
848
1111
|
}
|
|
849
1112
|
}
|
|
850
|
-
async update(userId, sessionId, data
|
|
851
|
-
const effectiveTtl = ttl ?? this.DEFAULT_TTL;
|
|
852
|
-
const expiresAt = new Date(Date.now() + effectiveTtl * 1e3).toISOString();
|
|
1113
|
+
async update(userId, sessionId, data) {
|
|
853
1114
|
const updateData = {
|
|
854
|
-
expires_at: expiresAt,
|
|
855
1115
|
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
856
1116
|
};
|
|
857
1117
|
if ("serverId" in data) updateData.server_id = data.serverId;
|
|
@@ -859,20 +1119,50 @@ var SupabaseStorageBackend = class {
|
|
|
859
1119
|
if ("serverUrl" in data) updateData.server_url = data.serverUrl;
|
|
860
1120
|
if ("transportType" in data) updateData.transport_type = data.transportType;
|
|
861
1121
|
if ("callbackUrl" in data) updateData.callback_url = data.callbackUrl;
|
|
862
|
-
if ("
|
|
1122
|
+
if ("status" in data) {
|
|
1123
|
+
const status = data.status ?? "pending";
|
|
1124
|
+
const expiresAt = resolveSessionExpiresAt(status);
|
|
1125
|
+
updateData.status = status;
|
|
1126
|
+
updateData.expires_at = expiresAt === null ? null : new Date(expiresAt).toISOString();
|
|
1127
|
+
}
|
|
863
1128
|
if ("headers" in data) updateData.headers = encryptObject(data.headers);
|
|
864
|
-
if ("
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
if (
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
1129
|
+
if ("authUrl" in data) updateData.auth_url = data.authUrl ?? null;
|
|
1130
|
+
const shouldUpdateSession = Object.keys(updateData).some((key) => key !== "updated_at");
|
|
1131
|
+
let updatedRows = null;
|
|
1132
|
+
if (shouldUpdateSession) {
|
|
1133
|
+
const result = await this.supabase.from("mcp_sessions").update(updateData).eq("user_id", userId).eq("session_id", sessionId).select("id");
|
|
1134
|
+
if (result.error) {
|
|
1135
|
+
throw new Error(`Failed to update session: ${result.error.message}`);
|
|
1136
|
+
}
|
|
1137
|
+
updatedRows = result.data;
|
|
1138
|
+
} else {
|
|
1139
|
+
const result = await this.supabase.from("mcp_sessions").select("id").eq("user_id", userId).eq("session_id", sessionId);
|
|
1140
|
+
if (result.error) {
|
|
1141
|
+
throw new Error(`Failed to update session: ${result.error.message}`);
|
|
1142
|
+
}
|
|
1143
|
+
updatedRows = result.data;
|
|
871
1144
|
}
|
|
872
1145
|
if (!updatedRows || updatedRows.length === 0) {
|
|
873
1146
|
throw new Error(`Session ${sessionId} not found for userId ${userId}`);
|
|
874
1147
|
}
|
|
875
1148
|
}
|
|
1149
|
+
async patchCredentials(userId, sessionId, data) {
|
|
1150
|
+
if (!this.hasCredentialData(data)) return;
|
|
1151
|
+
const row = {
|
|
1152
|
+
user_id: userId,
|
|
1153
|
+
session_id: sessionId,
|
|
1154
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1155
|
+
};
|
|
1156
|
+
if ("clientInformation" in data) row.client_information = data.clientInformation == null ? null : encryptObject(data.clientInformation);
|
|
1157
|
+
if ("tokens" in data) row.tokens = data.tokens == null ? null : encryptObject(data.tokens);
|
|
1158
|
+
if ("codeVerifier" in data) row.code_verifier = data.codeVerifier == null ? null : encryptObject(data.codeVerifier);
|
|
1159
|
+
if ("clientId" in data) row.client_id = data.clientId ?? null;
|
|
1160
|
+
if ("oauthState" in data) row.oauth_state = data.oauthState ?? null;
|
|
1161
|
+
const { error } = await this.supabase.from("mcp_credentials").upsert(row, { onConflict: "user_id,session_id" });
|
|
1162
|
+
if (error) {
|
|
1163
|
+
throw new Error(`Failed to update credentials: ${error.message}`);
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
876
1166
|
async get(userId, sessionId) {
|
|
877
1167
|
const { data, error } = await this.supabase.from("mcp_sessions").select("*").eq("user_id", userId).eq("session_id", sessionId).maybeSingle();
|
|
878
1168
|
if (error) {
|
|
@@ -882,6 +1172,21 @@ var SupabaseStorageBackend = class {
|
|
|
882
1172
|
if (!data) return null;
|
|
883
1173
|
return this.mapRowToSessionData(data);
|
|
884
1174
|
}
|
|
1175
|
+
async getCredentials(userId, sessionId) {
|
|
1176
|
+
const { data, error } = await this.supabase.from("mcp_credentials").select("*").eq("user_id", userId).eq("session_id", sessionId).maybeSingle();
|
|
1177
|
+
if (error) {
|
|
1178
|
+
console.error("[SupabaseStorage] Failed to get credentials:", error);
|
|
1179
|
+
return null;
|
|
1180
|
+
}
|
|
1181
|
+
if (data) {
|
|
1182
|
+
return this.mapRowToCredentials(data, userId, sessionId);
|
|
1183
|
+
}
|
|
1184
|
+
const { data: sessionRows, error: sessionError } = await this.supabase.from("mcp_sessions").select("id").eq("user_id", userId).eq("session_id", sessionId);
|
|
1185
|
+
if (sessionError || !sessionRows || sessionRows.length === 0) {
|
|
1186
|
+
return null;
|
|
1187
|
+
}
|
|
1188
|
+
return { sessionId, userId };
|
|
1189
|
+
}
|
|
885
1190
|
async list(userId) {
|
|
886
1191
|
const { data, error } = await this.supabase.from("mcp_sessions").select("*").eq("user_id", userId);
|
|
887
1192
|
if (error) {
|
|
@@ -890,6 +1195,12 @@ var SupabaseStorageBackend = class {
|
|
|
890
1195
|
}
|
|
891
1196
|
return data.map((row) => this.mapRowToSessionData(row));
|
|
892
1197
|
}
|
|
1198
|
+
async clearCredentials(userId, sessionId) {
|
|
1199
|
+
const { error } = await this.supabase.from("mcp_credentials").delete().eq("user_id", userId).eq("session_id", sessionId);
|
|
1200
|
+
if (error) {
|
|
1201
|
+
throw new Error(`Failed to clear credentials: ${error.message}`);
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
893
1204
|
async delete(userId, sessionId) {
|
|
894
1205
|
const { error } = await this.supabase.from("mcp_sessions").delete().eq("user_id", userId).eq("session_id", sessionId);
|
|
895
1206
|
if (error) {
|
|
@@ -913,15 +1224,24 @@ var SupabaseStorageBackend = class {
|
|
|
913
1224
|
return data.map((row) => row.session_id);
|
|
914
1225
|
}
|
|
915
1226
|
async clearAll() {
|
|
1227
|
+
const { error: credentialsError } = await this.supabase.from("mcp_credentials").delete().neq("session_id", "");
|
|
1228
|
+
if (credentialsError) {
|
|
1229
|
+
console.error("[SupabaseStorage] Failed to clear credentials:", credentialsError);
|
|
1230
|
+
}
|
|
916
1231
|
const { error } = await this.supabase.from("mcp_sessions").delete().neq("session_id", "");
|
|
917
1232
|
if (error) {
|
|
918
1233
|
console.error("[SupabaseStorage] Failed to clear sessions:", error);
|
|
919
1234
|
}
|
|
920
1235
|
}
|
|
921
1236
|
async cleanupExpired() {
|
|
922
|
-
const { error } = await this.supabase.from("mcp_sessions").delete().lt("expires_at", (/* @__PURE__ */ new Date()).toISOString());
|
|
923
|
-
if (
|
|
924
|
-
console.error("[SupabaseStorage] Failed to cleanup expired sessions:",
|
|
1237
|
+
const { error: transientError } = await this.supabase.from("mcp_sessions").delete().not("expires_at", "is", null).neq("status", "active").lt("expires_at", (/* @__PURE__ */ new Date()).toISOString());
|
|
1238
|
+
if (transientError) {
|
|
1239
|
+
console.error("[SupabaseStorage] Failed to cleanup expired inactive sessions:", transientError);
|
|
1240
|
+
}
|
|
1241
|
+
const dormantCutoff = new Date(Date.now() - DORMANT_SESSION_EXPIRATION_MS).toISOString();
|
|
1242
|
+
const { error: dormantError } = await this.supabase.from("mcp_sessions").delete().eq("status", "active").lt("updated_at", dormantCutoff);
|
|
1243
|
+
if (dormantError) {
|
|
1244
|
+
console.error("[SupabaseStorage] Failed to cleanup dormant active sessions:", dormantError);
|
|
925
1245
|
}
|
|
926
1246
|
}
|
|
927
1247
|
async disconnect() {
|
|
@@ -932,23 +1252,29 @@ var SupabaseStorageBackend = class {
|
|
|
932
1252
|
var NeonStorageBackend = class {
|
|
933
1253
|
constructor(sql, options = {}) {
|
|
934
1254
|
this.sql = sql;
|
|
935
|
-
__publicField(this, "DEFAULT_TTL", SESSION_TTL_SECONDS);
|
|
936
1255
|
__publicField(this, "tableName");
|
|
1256
|
+
__publicField(this, "credentialsTableName");
|
|
937
1257
|
const schema = options.schema || "public";
|
|
938
1258
|
const table = options.table || "mcp_sessions";
|
|
1259
|
+
const credentialsTable = options.credentialsTable || "mcp_credentials";
|
|
939
1260
|
this.tableName = `${this.quoteIdentifier(schema)}.${this.quoteIdentifier(table)}`;
|
|
1261
|
+
this.credentialsTableName = `${this.quoteIdentifier(schema)}.${this.quoteIdentifier(credentialsTable)}`;
|
|
940
1262
|
}
|
|
941
1263
|
async init() {
|
|
1264
|
+
await this.assertTable(this.tableName, "mcp_sessions");
|
|
1265
|
+
await this.assertTable(this.credentialsTableName, "mcp_credentials");
|
|
1266
|
+
console.log("[mcp-ts][Storage] Neon: storage tables verified.");
|
|
1267
|
+
}
|
|
1268
|
+
async assertTable(qualifiedName, displayName) {
|
|
942
1269
|
const [{ exists } = { exists: null }] = await this.sql.query(
|
|
943
1270
|
"SELECT to_regclass($1) AS exists",
|
|
944
|
-
[
|
|
1271
|
+
[qualifiedName.replace(/"/g, "")]
|
|
945
1272
|
);
|
|
946
1273
|
if (!exists) {
|
|
947
1274
|
throw new Error(
|
|
948
|
-
|
|
1275
|
+
`[NeonStorage] Table "${displayName}" not found in your database. Please create it using the Neon storage guide in docs/storage-backends/neon.md.`
|
|
949
1276
|
);
|
|
950
1277
|
}
|
|
951
|
-
console.log('[mcp-ts][Storage] Neon: "mcp_sessions" table verified.');
|
|
952
1278
|
}
|
|
953
1279
|
generateSessionId() {
|
|
954
1280
|
return generateSessionId();
|
|
@@ -968,20 +1294,35 @@ var NeonStorageBackend = class {
|
|
|
968
1294
|
transportType: row.transport_type,
|
|
969
1295
|
callbackUrl: row.callback_url,
|
|
970
1296
|
createdAt: new Date(row.created_at).getTime(),
|
|
1297
|
+
updatedAt: new Date(row.updated_at ?? row.created_at).getTime(),
|
|
1298
|
+
expiresAt: row.expires_at ? new Date(row.expires_at).getTime() : null,
|
|
971
1299
|
userId: row.user_id,
|
|
972
1300
|
headers: decryptObject(row.headers),
|
|
973
|
-
|
|
974
|
-
|
|
1301
|
+
authUrl: row.auth_url ?? void 0,
|
|
1302
|
+
status: row.status ?? "pending"
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1305
|
+
mapRowToCredentials(row, userId, sessionId) {
|
|
1306
|
+
return {
|
|
1307
|
+
sessionId,
|
|
1308
|
+
userId,
|
|
1309
|
+
clientInformation: decryptObject(row.client_information),
|
|
975
1310
|
tokens: decryptObject(row.tokens),
|
|
976
|
-
codeVerifier: row.code_verifier
|
|
977
|
-
clientId: row.client_id ?? void 0
|
|
1311
|
+
codeVerifier: decryptObject(row.code_verifier),
|
|
1312
|
+
clientId: row.client_id ?? void 0,
|
|
1313
|
+
oauthState: row.oauth_state
|
|
978
1314
|
};
|
|
979
1315
|
}
|
|
980
|
-
|
|
1316
|
+
hasCredentialData(data) {
|
|
1317
|
+
return "clientInformation" in data || "tokens" in data || "codeVerifier" in data || "clientId" in data || "oauthState" in data;
|
|
1318
|
+
}
|
|
1319
|
+
async create(session) {
|
|
981
1320
|
const { sessionId, userId } = session;
|
|
982
1321
|
if (!sessionId || !userId) throw new Error("userId and sessionId required");
|
|
983
|
-
const
|
|
984
|
-
const
|
|
1322
|
+
const status = session.status ?? "pending";
|
|
1323
|
+
const createdAt = new Date(session.createdAt || Date.now()).toISOString();
|
|
1324
|
+
const updatedAt = new Date(session.updatedAt ?? session.createdAt ?? Date.now()).toISOString();
|
|
1325
|
+
const expiresAt = resolveSessionExpiresAt(status, new Date(createdAt).getTime());
|
|
985
1326
|
try {
|
|
986
1327
|
await this.sql.query(
|
|
987
1328
|
`INSERT INTO ${this.tableName} (
|
|
@@ -993,16 +1334,14 @@ var NeonStorageBackend = class {
|
|
|
993
1334
|
transport_type,
|
|
994
1335
|
callback_url,
|
|
995
1336
|
created_at,
|
|
1337
|
+
updated_at,
|
|
996
1338
|
headers,
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
tokens,
|
|
1000
|
-
code_verifier,
|
|
1001
|
-
client_id,
|
|
1339
|
+
auth_url,
|
|
1340
|
+
status,
|
|
1002
1341
|
expires_at
|
|
1003
1342
|
) VALUES (
|
|
1004
1343
|
$1, $2, $3, $4, $5, $6, $7, $8,
|
|
1005
|
-
$9, $10, $11, $12, $13
|
|
1344
|
+
$9, $10, $11, $12, $13
|
|
1006
1345
|
)`,
|
|
1007
1346
|
[
|
|
1008
1347
|
sessionId,
|
|
@@ -1012,14 +1351,12 @@ var NeonStorageBackend = class {
|
|
|
1012
1351
|
session.serverUrl,
|
|
1013
1352
|
session.transportType,
|
|
1014
1353
|
session.callbackUrl,
|
|
1015
|
-
|
|
1354
|
+
createdAt,
|
|
1355
|
+
updatedAt,
|
|
1016
1356
|
encryptObject(session.headers),
|
|
1017
|
-
session.
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
session.codeVerifier,
|
|
1021
|
-
session.clientId,
|
|
1022
|
-
expiresAt
|
|
1357
|
+
session.authUrl ?? null,
|
|
1358
|
+
status,
|
|
1359
|
+
expiresAt === null ? null : new Date(expiresAt).toISOString()
|
|
1023
1360
|
]
|
|
1024
1361
|
);
|
|
1025
1362
|
} catch (error) {
|
|
@@ -1029,52 +1366,86 @@ var NeonStorageBackend = class {
|
|
|
1029
1366
|
throw new Error(`Failed to create session in Neon: ${error.message}`);
|
|
1030
1367
|
}
|
|
1031
1368
|
}
|
|
1032
|
-
async update(userId, sessionId, data
|
|
1369
|
+
async update(userId, sessionId, data) {
|
|
1033
1370
|
const currentSession = await this.get(userId, sessionId);
|
|
1034
1371
|
if (!currentSession) {
|
|
1035
1372
|
throw new Error(`Session ${sessionId} not found for userId ${userId}`);
|
|
1036
1373
|
}
|
|
1037
1374
|
const updatedSession = { ...currentSession, ...data };
|
|
1038
|
-
const
|
|
1039
|
-
const expiresAt =
|
|
1040
|
-
const
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1375
|
+
const status = updatedSession.status ?? "pending";
|
|
1376
|
+
const expiresAt = resolveSessionExpiresAt(status);
|
|
1377
|
+
const shouldUpdateSession = "serverId" in data || "serverName" in data || "serverUrl" in data || "transportType" in data || "callbackUrl" in data || "status" in data || "headers" in data || "authUrl" in data;
|
|
1378
|
+
if (shouldUpdateSession) {
|
|
1379
|
+
const updatedRows = await this.sql.query(
|
|
1380
|
+
`UPDATE ${this.tableName}
|
|
1381
|
+
SET
|
|
1382
|
+
server_id = $1,
|
|
1383
|
+
server_name = $2,
|
|
1384
|
+
server_url = $3,
|
|
1385
|
+
transport_type = $4,
|
|
1386
|
+
callback_url = $5,
|
|
1387
|
+
status = $6,
|
|
1388
|
+
headers = $7,
|
|
1389
|
+
auth_url = $8,
|
|
1390
|
+
expires_at = $9,
|
|
1391
|
+
updated_at = now()
|
|
1392
|
+
WHERE user_id = $10 AND session_id = $11
|
|
1393
|
+
RETURNING id`,
|
|
1394
|
+
[
|
|
1395
|
+
updatedSession.serverId,
|
|
1396
|
+
updatedSession.serverName,
|
|
1397
|
+
updatedSession.serverUrl,
|
|
1398
|
+
updatedSession.transportType,
|
|
1399
|
+
updatedSession.callbackUrl,
|
|
1400
|
+
status,
|
|
1401
|
+
encryptObject(updatedSession.headers),
|
|
1402
|
+
updatedSession.authUrl ?? null,
|
|
1403
|
+
expiresAt === null ? null : new Date(expiresAt).toISOString(),
|
|
1404
|
+
userId,
|
|
1405
|
+
sessionId
|
|
1406
|
+
]
|
|
1407
|
+
);
|
|
1408
|
+
if (updatedRows.length === 0) {
|
|
1409
|
+
throw new Error(`Session ${sessionId} not found for userId ${userId}`);
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
async patchCredentials(userId, sessionId, data) {
|
|
1414
|
+
if (!this.hasCredentialData(data)) return;
|
|
1415
|
+
await this.sql.query(
|
|
1416
|
+
`INSERT INTO ${this.credentialsTableName} (
|
|
1417
|
+
user_id,
|
|
1418
|
+
session_id,
|
|
1419
|
+
client_information,
|
|
1420
|
+
tokens,
|
|
1421
|
+
code_verifier,
|
|
1422
|
+
client_id,
|
|
1423
|
+
oauth_state,
|
|
1424
|
+
updated_at
|
|
1425
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7, now())
|
|
1426
|
+
ON CONFLICT (user_id, session_id)
|
|
1427
|
+
DO UPDATE SET
|
|
1428
|
+
client_information = CASE WHEN $8 THEN EXCLUDED.client_information ELSE ${this.credentialsTableName}.client_information END,
|
|
1429
|
+
tokens = CASE WHEN $9 THEN EXCLUDED.tokens ELSE ${this.credentialsTableName}.tokens END,
|
|
1430
|
+
code_verifier = CASE WHEN $10 THEN EXCLUDED.code_verifier ELSE ${this.credentialsTableName}.code_verifier END,
|
|
1431
|
+
client_id = CASE WHEN $11 THEN EXCLUDED.client_id ELSE ${this.credentialsTableName}.client_id END,
|
|
1432
|
+
oauth_state = CASE WHEN $12 THEN EXCLUDED.oauth_state ELSE ${this.credentialsTableName}.oauth_state END,
|
|
1433
|
+
updated_at = now()`,
|
|
1058
1434
|
[
|
|
1059
|
-
updatedSession.serverId,
|
|
1060
|
-
updatedSession.serverName,
|
|
1061
|
-
updatedSession.serverUrl,
|
|
1062
|
-
updatedSession.transportType,
|
|
1063
|
-
updatedSession.callbackUrl,
|
|
1064
|
-
updatedSession.active ?? false,
|
|
1065
|
-
encryptObject(updatedSession.headers),
|
|
1066
|
-
updatedSession.clientInformation,
|
|
1067
|
-
encryptObject(updatedSession.tokens),
|
|
1068
|
-
updatedSession.codeVerifier,
|
|
1069
|
-
updatedSession.clientId,
|
|
1070
|
-
expiresAt,
|
|
1071
1435
|
userId,
|
|
1072
|
-
sessionId
|
|
1436
|
+
sessionId,
|
|
1437
|
+
"clientInformation" in data ? data.clientInformation == null ? null : encryptObject(data.clientInformation) : null,
|
|
1438
|
+
"tokens" in data ? data.tokens == null ? null : encryptObject(data.tokens) : null,
|
|
1439
|
+
"codeVerifier" in data ? data.codeVerifier == null ? null : encryptObject(data.codeVerifier) : null,
|
|
1440
|
+
"clientId" in data ? data.clientId ?? null : null,
|
|
1441
|
+
"oauthState" in data ? data.oauthState ?? null : null,
|
|
1442
|
+
"clientInformation" in data,
|
|
1443
|
+
"tokens" in data,
|
|
1444
|
+
"codeVerifier" in data,
|
|
1445
|
+
"clientId" in data,
|
|
1446
|
+
"oauthState" in data
|
|
1073
1447
|
]
|
|
1074
1448
|
);
|
|
1075
|
-
if (updatedRows.length === 0) {
|
|
1076
|
-
throw new Error(`Session ${sessionId} not found for userId ${userId}`);
|
|
1077
|
-
}
|
|
1078
1449
|
}
|
|
1079
1450
|
async get(userId, sessionId) {
|
|
1080
1451
|
try {
|
|
@@ -1082,12 +1453,32 @@ var NeonStorageBackend = class {
|
|
|
1082
1453
|
`SELECT * FROM ${this.tableName} WHERE user_id = $1 AND session_id = $2`,
|
|
1083
1454
|
[userId, sessionId]
|
|
1084
1455
|
);
|
|
1085
|
-
|
|
1456
|
+
if (!rows[0]) return null;
|
|
1457
|
+
return this.mapRowToSessionData(rows[0]);
|
|
1086
1458
|
} catch (error) {
|
|
1087
1459
|
console.error("[NeonStorage] Failed to get session:", error);
|
|
1088
1460
|
return null;
|
|
1089
1461
|
}
|
|
1090
1462
|
}
|
|
1463
|
+
async getCredentials(userId, sessionId) {
|
|
1464
|
+
try {
|
|
1465
|
+
const credentialRows = await this.sql.query(
|
|
1466
|
+
`SELECT * FROM ${this.credentialsTableName} WHERE user_id = $1 AND session_id = $2`,
|
|
1467
|
+
[userId, sessionId]
|
|
1468
|
+
);
|
|
1469
|
+
if (credentialRows[0]) {
|
|
1470
|
+
return this.mapRowToCredentials(credentialRows[0], userId, sessionId);
|
|
1471
|
+
}
|
|
1472
|
+
const sessionRows = await this.sql.query(
|
|
1473
|
+
`SELECT id FROM ${this.tableName} WHERE user_id = $1 AND session_id = $2`,
|
|
1474
|
+
[userId, sessionId]
|
|
1475
|
+
);
|
|
1476
|
+
return sessionRows[0] ? { sessionId, userId } : null;
|
|
1477
|
+
} catch (error) {
|
|
1478
|
+
console.error("[NeonStorage] Failed to get credentials:", error);
|
|
1479
|
+
return null;
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1091
1482
|
async list(userId) {
|
|
1092
1483
|
try {
|
|
1093
1484
|
const rows = await this.sql.query(
|
|
@@ -1100,6 +1491,16 @@ var NeonStorageBackend = class {
|
|
|
1100
1491
|
return [];
|
|
1101
1492
|
}
|
|
1102
1493
|
}
|
|
1494
|
+
async clearCredentials(userId, sessionId) {
|
|
1495
|
+
try {
|
|
1496
|
+
await this.sql.query(
|
|
1497
|
+
`DELETE FROM ${this.credentialsTableName} WHERE user_id = $1 AND session_id = $2`,
|
|
1498
|
+
[userId, sessionId]
|
|
1499
|
+
);
|
|
1500
|
+
} catch (error) {
|
|
1501
|
+
console.error("[NeonStorage] Failed to clear credentials:", error);
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1103
1504
|
async delete(userId, sessionId) {
|
|
1104
1505
|
try {
|
|
1105
1506
|
await this.sql.query(
|
|
@@ -1135,6 +1536,7 @@ var NeonStorageBackend = class {
|
|
|
1135
1536
|
}
|
|
1136
1537
|
async clearAll() {
|
|
1137
1538
|
try {
|
|
1539
|
+
await this.sql.query(`DELETE FROM ${this.credentialsTableName}`);
|
|
1138
1540
|
await this.sql.query(`DELETE FROM ${this.tableName}`);
|
|
1139
1541
|
} catch (error) {
|
|
1140
1542
|
console.error("[NeonStorage] Failed to clear sessions:", error);
|
|
@@ -1143,9 +1545,17 @@ var NeonStorageBackend = class {
|
|
|
1143
1545
|
async cleanupExpired() {
|
|
1144
1546
|
try {
|
|
1145
1547
|
await this.sql.query(
|
|
1146
|
-
`DELETE FROM ${this.tableName}
|
|
1548
|
+
`DELETE FROM ${this.tableName}
|
|
1549
|
+
WHERE expires_at IS NOT NULL
|
|
1550
|
+
AND expires_at < $1
|
|
1551
|
+
AND status <> 'active'`,
|
|
1147
1552
|
[(/* @__PURE__ */ new Date()).toISOString()]
|
|
1148
1553
|
);
|
|
1554
|
+
await this.sql.query(
|
|
1555
|
+
`DELETE FROM ${this.tableName}
|
|
1556
|
+
WHERE status = 'active' AND updated_at < $1`,
|
|
1557
|
+
[new Date(Date.now() - DORMANT_SESSION_EXPIRATION_MS).toISOString()]
|
|
1558
|
+
);
|
|
1149
1559
|
} catch (error) {
|
|
1150
1560
|
console.error("[NeonStorage] Failed to cleanup expired sessions:", error);
|
|
1151
1561
|
}
|
|
@@ -1192,26 +1602,24 @@ function emitSessionMutation(event) {
|
|
|
1192
1602
|
function createSessionMutationEvent(prop, args) {
|
|
1193
1603
|
const timestamp = Date.now();
|
|
1194
1604
|
if (prop === "create") {
|
|
1195
|
-
const [session
|
|
1605
|
+
const [session] = args;
|
|
1196
1606
|
if (!session?.userId || !session?.sessionId) return null;
|
|
1197
1607
|
return {
|
|
1198
1608
|
type: "create",
|
|
1199
1609
|
userId: session.userId,
|
|
1200
1610
|
sessionId: session.sessionId,
|
|
1201
1611
|
session,
|
|
1202
|
-
ttl,
|
|
1203
1612
|
timestamp
|
|
1204
1613
|
};
|
|
1205
1614
|
}
|
|
1206
1615
|
if (prop === "update") {
|
|
1207
|
-
const [userId, sessionId, patch
|
|
1616
|
+
const [userId, sessionId, patch] = args;
|
|
1208
1617
|
if (!userId || !sessionId) return null;
|
|
1209
1618
|
return {
|
|
1210
1619
|
type: "update",
|
|
1211
1620
|
userId,
|
|
1212
1621
|
sessionId,
|
|
1213
1622
|
patch,
|
|
1214
|
-
ttl,
|
|
1215
1623
|
timestamp
|
|
1216
1624
|
};
|
|
1217
1625
|
}
|
|
@@ -1407,7 +1815,6 @@ var StorageOAuthClientProvider = class {
|
|
|
1407
1815
|
__publicField(this, "_authUrl");
|
|
1408
1816
|
__publicField(this, "_clientId");
|
|
1409
1817
|
__publicField(this, "onRedirectCallback");
|
|
1410
|
-
__publicField(this, "tokenExpiresAt");
|
|
1411
1818
|
this.userId = options.userId;
|
|
1412
1819
|
this.serverId = options.serverId;
|
|
1413
1820
|
this.sessionId = options.sessionId;
|
|
@@ -1441,30 +1848,30 @@ var StorageOAuthClientProvider = class {
|
|
|
1441
1848
|
this._clientId = clientId_;
|
|
1442
1849
|
}
|
|
1443
1850
|
/**
|
|
1444
|
-
* Loads OAuth
|
|
1851
|
+
* Loads OAuth credentials from the session store
|
|
1445
1852
|
* @private
|
|
1446
1853
|
*/
|
|
1447
|
-
async
|
|
1448
|
-
const data = await sessions.
|
|
1854
|
+
async getCredentials() {
|
|
1855
|
+
const data = await sessions.getCredentials(this.userId, this.sessionId);
|
|
1449
1856
|
if (!data) {
|
|
1450
|
-
return {};
|
|
1857
|
+
return { userId: this.userId, sessionId: this.sessionId };
|
|
1451
1858
|
}
|
|
1452
1859
|
return data;
|
|
1453
1860
|
}
|
|
1454
1861
|
/**
|
|
1455
|
-
* Saves OAuth
|
|
1456
|
-
* @param data - Partial OAuth
|
|
1862
|
+
* Saves OAuth credentials to the session store
|
|
1863
|
+
* @param data - Partial OAuth credentials to save
|
|
1457
1864
|
* @private
|
|
1458
1865
|
* @throws Error if session doesn't exist (session must be created by controller layer)
|
|
1459
1866
|
*/
|
|
1460
|
-
async
|
|
1461
|
-
await sessions.
|
|
1867
|
+
async patchCredentials(data) {
|
|
1868
|
+
await sessions.patchCredentials(this.userId, this.sessionId, data);
|
|
1462
1869
|
}
|
|
1463
1870
|
/**
|
|
1464
1871
|
* Retrieves stored OAuth client information
|
|
1465
1872
|
*/
|
|
1466
1873
|
async clientInformation() {
|
|
1467
|
-
const data = await this.
|
|
1874
|
+
const data = await this.getCredentials();
|
|
1468
1875
|
if (data.clientId && !this._clientId) {
|
|
1469
1876
|
this._clientId = data.clientId;
|
|
1470
1877
|
}
|
|
@@ -1483,7 +1890,7 @@ var StorageOAuthClientProvider = class {
|
|
|
1483
1890
|
* Stores OAuth client information
|
|
1484
1891
|
*/
|
|
1485
1892
|
async saveClientInformation(clientInformation) {
|
|
1486
|
-
await this.
|
|
1893
|
+
await this.patchCredentials({
|
|
1487
1894
|
clientInformation,
|
|
1488
1895
|
clientId: clientInformation.client_id
|
|
1489
1896
|
});
|
|
@@ -1493,29 +1900,58 @@ var StorageOAuthClientProvider = class {
|
|
|
1493
1900
|
* Stores OAuth tokens
|
|
1494
1901
|
*/
|
|
1495
1902
|
async saveTokens(tokens) {
|
|
1496
|
-
|
|
1497
|
-
if (tokens.expires_in) {
|
|
1498
|
-
this.tokenExpiresAt = Date.now() + tokens.expires_in * 1e3 - TOKEN_EXPIRY_BUFFER_MS;
|
|
1499
|
-
}
|
|
1500
|
-
await this.saveSessionData(data);
|
|
1903
|
+
await this.patchCredentials({ tokens });
|
|
1501
1904
|
}
|
|
1502
1905
|
get authUrl() {
|
|
1503
1906
|
return this._authUrl;
|
|
1504
1907
|
}
|
|
1505
1908
|
async state() {
|
|
1506
|
-
|
|
1909
|
+
const nonce = nanoid(32);
|
|
1910
|
+
await this.patchCredentials({
|
|
1911
|
+
oauthState: {
|
|
1912
|
+
nonce,
|
|
1913
|
+
sessionId: this.sessionId,
|
|
1914
|
+
serverId: this.serverId,
|
|
1915
|
+
createdAt: Date.now()
|
|
1916
|
+
},
|
|
1917
|
+
codeVerifier: null
|
|
1918
|
+
});
|
|
1919
|
+
return formatOAuthState(nonce, this.sessionId);
|
|
1507
1920
|
}
|
|
1508
|
-
async checkState(
|
|
1509
|
-
const
|
|
1921
|
+
async checkState(state) {
|
|
1922
|
+
const parsed = parseOAuthState(state);
|
|
1923
|
+
if (!parsed) {
|
|
1924
|
+
return { valid: false, error: "Invalid OAuth state" };
|
|
1925
|
+
}
|
|
1926
|
+
if (parsed.sessionId !== this.sessionId) {
|
|
1927
|
+
return { valid: false, error: "OAuth state mismatch" };
|
|
1928
|
+
}
|
|
1929
|
+
const data = await sessions.getCredentials(this.userId, parsed.sessionId);
|
|
1510
1930
|
if (!data) {
|
|
1511
1931
|
return { valid: false, error: "Session not found" };
|
|
1512
1932
|
}
|
|
1513
|
-
|
|
1933
|
+
const oauthState = data.oauthState;
|
|
1934
|
+
if (!oauthState) {
|
|
1935
|
+
return { valid: false, error: "OAuth state not found" };
|
|
1936
|
+
}
|
|
1937
|
+
if (oauthState.nonce !== parsed.nonce || oauthState.sessionId !== parsed.sessionId || oauthState.serverId !== this.serverId) {
|
|
1938
|
+
return { valid: false, error: "OAuth state mismatch" };
|
|
1939
|
+
}
|
|
1940
|
+
if (Date.now() - oauthState.createdAt > STATE_EXPIRATION_MS) {
|
|
1941
|
+
return { valid: false, error: "OAuth state expired" };
|
|
1942
|
+
}
|
|
1943
|
+
return { valid: true, serverId: oauthState.serverId };
|
|
1514
1944
|
}
|
|
1515
|
-
async consumeState(
|
|
1945
|
+
async consumeState(state) {
|
|
1946
|
+
const result = await this.checkState(state);
|
|
1947
|
+
if (!result.valid) {
|
|
1948
|
+
throw new Error(result.error || "Invalid OAuth state");
|
|
1949
|
+
}
|
|
1950
|
+
await this.patchCredentials({ oauthState: null });
|
|
1516
1951
|
}
|
|
1517
1952
|
async redirectToAuthorization(authUrl) {
|
|
1518
1953
|
this._authUrl = authUrl.toString();
|
|
1954
|
+
await sessions.update(this.userId, this.sessionId, { authUrl: this._authUrl });
|
|
1519
1955
|
if (this.onRedirectCallback) {
|
|
1520
1956
|
this.onRedirectCallback(authUrl.toString());
|
|
1521
1957
|
}
|
|
@@ -1526,21 +1962,25 @@ var StorageOAuthClientProvider = class {
|
|
|
1526
1962
|
} else {
|
|
1527
1963
|
const updates = {};
|
|
1528
1964
|
if (scope === "client") {
|
|
1529
|
-
updates.clientInformation =
|
|
1530
|
-
updates.clientId =
|
|
1965
|
+
updates.clientInformation = null;
|
|
1966
|
+
updates.clientId = null;
|
|
1531
1967
|
} else if (scope === "tokens") {
|
|
1532
|
-
updates.tokens =
|
|
1968
|
+
updates.tokens = null;
|
|
1533
1969
|
} else if (scope === "verifier") {
|
|
1534
|
-
updates.codeVerifier =
|
|
1970
|
+
updates.codeVerifier = null;
|
|
1535
1971
|
}
|
|
1536
|
-
await this.
|
|
1972
|
+
await this.patchCredentials(updates);
|
|
1537
1973
|
}
|
|
1538
1974
|
}
|
|
1539
1975
|
async saveCodeVerifier(verifier) {
|
|
1540
|
-
await this.
|
|
1976
|
+
const data = await this.getCredentials();
|
|
1977
|
+
if (data.codeVerifier) {
|
|
1978
|
+
return;
|
|
1979
|
+
}
|
|
1980
|
+
await this.patchCredentials({ codeVerifier: verifier });
|
|
1541
1981
|
}
|
|
1542
1982
|
async codeVerifier() {
|
|
1543
|
-
const data = await this.
|
|
1983
|
+
const data = await this.getCredentials();
|
|
1544
1984
|
if (data.clientId && !this._clientId) {
|
|
1545
1985
|
this._clientId = data.clientId;
|
|
1546
1986
|
}
|
|
@@ -1550,23 +1990,14 @@ var StorageOAuthClientProvider = class {
|
|
|
1550
1990
|
return data.codeVerifier;
|
|
1551
1991
|
}
|
|
1552
1992
|
async deleteCodeVerifier() {
|
|
1553
|
-
await this.
|
|
1993
|
+
await this.patchCredentials({ codeVerifier: null });
|
|
1554
1994
|
}
|
|
1555
1995
|
async tokens() {
|
|
1556
|
-
const data = await this.
|
|
1996
|
+
const data = await this.getCredentials();
|
|
1557
1997
|
if (data.clientId && !this._clientId) {
|
|
1558
1998
|
this._clientId = data.clientId;
|
|
1559
1999
|
}
|
|
1560
|
-
return data.tokens;
|
|
1561
|
-
}
|
|
1562
|
-
isTokenExpired() {
|
|
1563
|
-
if (!this.tokenExpiresAt) {
|
|
1564
|
-
return false;
|
|
1565
|
-
}
|
|
1566
|
-
return Date.now() >= this.tokenExpiresAt;
|
|
1567
|
-
}
|
|
1568
|
-
setTokenExpiresAt(expiresAt) {
|
|
1569
|
-
this.tokenExpiresAt = expiresAt;
|
|
2000
|
+
return data.tokens ?? void 0;
|
|
1570
2001
|
}
|
|
1571
2002
|
};
|
|
1572
2003
|
|
|
@@ -1713,14 +2144,7 @@ var RpcErrorCodes = {
|
|
|
1713
2144
|
};
|
|
1714
2145
|
|
|
1715
2146
|
// src/server/mcp/oauth-client.ts
|
|
1716
|
-
|
|
1717
|
-
if (!(error instanceof Error)) {
|
|
1718
|
-
return false;
|
|
1719
|
-
}
|
|
1720
|
-
const text = `${error.name} ${error.message}`.toLowerCase();
|
|
1721
|
-
return text.includes("invalidgrant") || text.includes("invalid_grant") || text.includes("invalid refresh token") || /refresh\s+token\s+(?:is\s+)?(?:invalid|expired|revoked)/i.test(text);
|
|
1722
|
-
}
|
|
1723
|
-
var MCPClient = class _MCPClient {
|
|
2147
|
+
var MCPClient = class {
|
|
1724
2148
|
/**
|
|
1725
2149
|
* Creates a new MCP client instance
|
|
1726
2150
|
* Can be initialized with minimal options (userId + sessionId) for session restoration
|
|
@@ -1895,17 +2319,18 @@ var MCPClient = class _MCPClient {
|
|
|
1895
2319
|
}
|
|
1896
2320
|
this.emitStateChange("INITIALIZING");
|
|
1897
2321
|
this.emitProgress("Loading session configuration...");
|
|
2322
|
+
let existingSession = null;
|
|
1898
2323
|
if (!this.serverUrl || !this.callbackUrl || !this.serverId) {
|
|
1899
|
-
|
|
1900
|
-
if (!
|
|
2324
|
+
existingSession = await sessions.get(this.userId, this.sessionId);
|
|
2325
|
+
if (!existingSession) {
|
|
1901
2326
|
throw new Error(`Session not found: ${this.sessionId}`);
|
|
1902
2327
|
}
|
|
1903
|
-
this.serverUrl = this.serverUrl ||
|
|
1904
|
-
this.callbackUrl = this.callbackUrl ||
|
|
1905
|
-
this.serverName = this.serverName ||
|
|
1906
|
-
this.serverId = this.serverId ||
|
|
1907
|
-
this.headers = this.headers ||
|
|
1908
|
-
this.createdAt =
|
|
2328
|
+
this.serverUrl = this.serverUrl || existingSession.serverUrl;
|
|
2329
|
+
this.callbackUrl = this.callbackUrl || existingSession.callbackUrl;
|
|
2330
|
+
this.serverName = this.serverName || existingSession.serverName;
|
|
2331
|
+
this.serverId = this.serverId || existingSession.serverId || "unknown";
|
|
2332
|
+
this.headers = this.headers || existingSession.headers;
|
|
2333
|
+
this.createdAt = existingSession.createdAt;
|
|
1909
2334
|
}
|
|
1910
2335
|
if (!this.serverUrl || !this.callbackUrl || !this.serverId) {
|
|
1911
2336
|
throw new Error("Missing required connection metadata");
|
|
@@ -1949,10 +2374,13 @@ var MCPClient = class _MCPClient {
|
|
|
1949
2374
|
}
|
|
1950
2375
|
);
|
|
1951
2376
|
}
|
|
1952
|
-
|
|
2377
|
+
if (existingSession === null) {
|
|
2378
|
+
existingSession = await sessions.get(this.userId, this.sessionId);
|
|
2379
|
+
}
|
|
1953
2380
|
if (!existingSession && this.serverId && this.serverUrl && this.callbackUrl) {
|
|
1954
2381
|
this.createdAt = Date.now();
|
|
1955
|
-
|
|
2382
|
+
const updatedAt = this.createdAt;
|
|
2383
|
+
console.log(`[MCPClient] Creating pending session ${this.sessionId} for connection setup`);
|
|
1956
2384
|
await sessions.create({
|
|
1957
2385
|
sessionId: this.sessionId,
|
|
1958
2386
|
userId: this.userId,
|
|
@@ -1963,18 +2391,18 @@ var MCPClient = class _MCPClient {
|
|
|
1963
2391
|
transportType: this.transportType || "streamable-http",
|
|
1964
2392
|
headers: this.headers,
|
|
1965
2393
|
createdAt: this.createdAt,
|
|
1966
|
-
|
|
1967
|
-
|
|
2394
|
+
updatedAt,
|
|
2395
|
+
status: "pending"
|
|
2396
|
+
});
|
|
1968
2397
|
}
|
|
1969
2398
|
}
|
|
1970
2399
|
/**
|
|
1971
2400
|
* Saves current session state to the session store
|
|
1972
2401
|
* Creates new session if it doesn't exist, updates if it does
|
|
1973
|
-
* @param
|
|
1974
|
-
* @param active - Session status marker used to avoid unnecessary TTL rewrites
|
|
2402
|
+
* @param status - Session lifecycle status used by storage cleanup
|
|
1975
2403
|
* @private
|
|
1976
2404
|
*/
|
|
1977
|
-
async saveSession(
|
|
2405
|
+
async saveSession(status = "active", existingSession) {
|
|
1978
2406
|
if (!this.sessionId || !this.serverId || !this.serverUrl || !this.callbackUrl) {
|
|
1979
2407
|
return;
|
|
1980
2408
|
}
|
|
@@ -1988,13 +2416,29 @@ var MCPClient = class _MCPClient {
|
|
|
1988
2416
|
transportType: this.transportType || "streamable-http",
|
|
1989
2417
|
headers: this.headers,
|
|
1990
2418
|
createdAt: this.createdAt || Date.now(),
|
|
1991
|
-
|
|
2419
|
+
updatedAt: Date.now(),
|
|
2420
|
+
status
|
|
1992
2421
|
};
|
|
1993
|
-
|
|
2422
|
+
if (status === "active") {
|
|
2423
|
+
sessionData.authUrl = null;
|
|
2424
|
+
}
|
|
2425
|
+
if (existingSession === void 0) {
|
|
2426
|
+
existingSession = await sessions.get(this.userId, this.sessionId);
|
|
2427
|
+
}
|
|
1994
2428
|
if (existingSession) {
|
|
1995
|
-
await sessions.update(this.userId, this.sessionId, sessionData
|
|
2429
|
+
await sessions.update(this.userId, this.sessionId, sessionData);
|
|
1996
2430
|
} else {
|
|
1997
|
-
await sessions.create(sessionData
|
|
2431
|
+
await sessions.create(sessionData);
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
/**
|
|
2435
|
+
* Removes transient setup/auth sessions without masking the original error.
|
|
2436
|
+
* @private
|
|
2437
|
+
*/
|
|
2438
|
+
async deleteTransientSession() {
|
|
2439
|
+
try {
|
|
2440
|
+
await sessions.delete(this.userId, this.sessionId);
|
|
2441
|
+
} catch {
|
|
1998
2442
|
}
|
|
1999
2443
|
}
|
|
2000
2444
|
/**
|
|
@@ -2054,15 +2498,13 @@ var MCPClient = class _MCPClient {
|
|
|
2054
2498
|
throw new Error(error);
|
|
2055
2499
|
}
|
|
2056
2500
|
try {
|
|
2057
|
-
this.emitProgress("Validating OAuth tokens...");
|
|
2058
|
-
await this.getValidTokens();
|
|
2059
2501
|
this.emitStateChange("CONNECTING");
|
|
2060
2502
|
const { transportType } = await this.tryConnect();
|
|
2061
2503
|
this.transportType = transportType;
|
|
2062
2504
|
this.emitStateChange("CONNECTED");
|
|
2063
2505
|
this.emitProgress("Connected successfully");
|
|
2064
|
-
console.log(`[MCPClient] Saving session ${this.sessionId}
|
|
2065
|
-
await this.saveSession(
|
|
2506
|
+
console.log(`[MCPClient] Saving active session ${this.sessionId} (connect success)`);
|
|
2507
|
+
await this.saveSession("active");
|
|
2066
2508
|
} catch (error) {
|
|
2067
2509
|
if (error instanceof UnauthorizedError$1 || error instanceof Error && error.message.toLowerCase().includes("unauthorized")) {
|
|
2068
2510
|
let authUrl = "";
|
|
@@ -2074,15 +2516,12 @@ var MCPClient = class _MCPClient {
|
|
|
2074
2516
|
const message = detail.toLowerCase() === "unauthorized" ? "OAuth authorization URL not available" : `OAuth authorization URL not available: ${detail}`;
|
|
2075
2517
|
this.emitError(message, "auth");
|
|
2076
2518
|
this.emitStateChange("FAILED");
|
|
2077
|
-
|
|
2078
|
-
await sessions.delete(this.userId, this.sessionId);
|
|
2079
|
-
} catch {
|
|
2080
|
-
}
|
|
2519
|
+
await this.deleteTransientSession();
|
|
2081
2520
|
throw new Error(message);
|
|
2082
2521
|
}
|
|
2083
2522
|
this.emitStateChange("AUTHENTICATING");
|
|
2084
|
-
console.log(`[MCPClient] Saving session ${this.sessionId}
|
|
2085
|
-
await this.saveSession(
|
|
2523
|
+
console.log(`[MCPClient] Saving pending OAuth session ${this.sessionId}`);
|
|
2524
|
+
await this.saveSession("pending");
|
|
2086
2525
|
if (this.serverId) {
|
|
2087
2526
|
this._onConnectionEvent.fire({
|
|
2088
2527
|
type: "auth_required",
|
|
@@ -2102,7 +2541,7 @@ var MCPClient = class _MCPClient {
|
|
|
2102
2541
|
this.emitStateChange("FAILED");
|
|
2103
2542
|
try {
|
|
2104
2543
|
const existingSession = await sessions.get(this.userId, this.sessionId);
|
|
2105
|
-
if (!existingSession || existingSession.
|
|
2544
|
+
if (!existingSession || existingSession.status !== "active") {
|
|
2106
2545
|
await sessions.delete(this.userId, this.sessionId);
|
|
2107
2546
|
}
|
|
2108
2547
|
} catch {
|
|
@@ -2117,7 +2556,7 @@ var MCPClient = class _MCPClient {
|
|
|
2117
2556
|
* @param authCode - Authorization code received from OAuth callback
|
|
2118
2557
|
*/
|
|
2119
2558
|
// TODO: needs to be optimized
|
|
2120
|
-
async finishAuth(authCode) {
|
|
2559
|
+
async finishAuth(authCode, state) {
|
|
2121
2560
|
this.emitStateChange("AUTHENTICATING");
|
|
2122
2561
|
this.emitProgress("Exchanging authorization code for tokens...");
|
|
2123
2562
|
await this.initialize();
|
|
@@ -2127,6 +2566,16 @@ var MCPClient = class _MCPClient {
|
|
|
2127
2566
|
this.emitStateChange("FAILED");
|
|
2128
2567
|
throw new Error(error);
|
|
2129
2568
|
}
|
|
2569
|
+
if (state) {
|
|
2570
|
+
const stateCheck = await this.oauthProvider.checkState(state);
|
|
2571
|
+
if (!stateCheck.valid) {
|
|
2572
|
+
const error = stateCheck.error || "Invalid OAuth state";
|
|
2573
|
+
this.emitError(error, "auth");
|
|
2574
|
+
this.emitStateChange("FAILED");
|
|
2575
|
+
throw new Error(error);
|
|
2576
|
+
}
|
|
2577
|
+
await this.oauthProvider.consumeState(state);
|
|
2578
|
+
}
|
|
2130
2579
|
const transportsToTry = this.transportType ? [this.transportType] : ["streamable-http", "sse"];
|
|
2131
2580
|
let lastError;
|
|
2132
2581
|
let tokensExchanged = false;
|
|
@@ -2166,8 +2615,8 @@ var MCPClient = class _MCPClient {
|
|
|
2166
2615
|
await this.client.connect(this.transport);
|
|
2167
2616
|
this.transportType = currentType;
|
|
2168
2617
|
this.emitStateChange("CONNECTED");
|
|
2169
|
-
console.log(`[MCPClient]
|
|
2170
|
-
await this.saveSession(
|
|
2618
|
+
console.log(`[MCPClient] Saving active session ${this.sessionId} (OAuth complete)`);
|
|
2619
|
+
await this.saveSession("active");
|
|
2171
2620
|
return;
|
|
2172
2621
|
} catch (error) {
|
|
2173
2622
|
lastError = error;
|
|
@@ -2180,12 +2629,14 @@ var MCPClient = class _MCPClient {
|
|
|
2180
2629
|
const msg = error instanceof Error ? error.message : "Authentication failed";
|
|
2181
2630
|
this.emitError(msg, "auth");
|
|
2182
2631
|
this.emitStateChange("FAILED");
|
|
2632
|
+
await this.deleteTransientSession();
|
|
2183
2633
|
throw error;
|
|
2184
2634
|
}
|
|
2185
2635
|
if (isLastAttempt) {
|
|
2186
2636
|
const msg = error instanceof Error ? error.message : "Authentication failed";
|
|
2187
2637
|
this.emitError(msg, "auth");
|
|
2188
2638
|
this.emitStateChange("FAILED");
|
|
2639
|
+
await this.deleteTransientSession();
|
|
2189
2640
|
throw error;
|
|
2190
2641
|
}
|
|
2191
2642
|
this.emitProgress(`Auth attempt with ${currentType} failed: ${errorMessage}. Retrying...`);
|
|
@@ -2195,6 +2646,7 @@ var MCPClient = class _MCPClient {
|
|
|
2195
2646
|
const errorMessage = lastError instanceof Error ? lastError.message : "Authentication failed";
|
|
2196
2647
|
this.emitError(errorMessage, "auth");
|
|
2197
2648
|
this.emitStateChange("FAILED");
|
|
2649
|
+
await this.deleteTransientSession();
|
|
2198
2650
|
throw lastError;
|
|
2199
2651
|
}
|
|
2200
2652
|
}
|
|
@@ -2380,67 +2832,6 @@ var MCPClient = class _MCPClient {
|
|
|
2380
2832
|
};
|
|
2381
2833
|
return await this.client.request(request, ReadResourceResultSchema);
|
|
2382
2834
|
}
|
|
2383
|
-
/**
|
|
2384
|
-
* Refreshes the OAuth access token using the refresh token
|
|
2385
|
-
* Discovers OAuth metadata from server and exchanges refresh token for new access token
|
|
2386
|
-
* @returns True if refresh was successful, false otherwise
|
|
2387
|
-
*/
|
|
2388
|
-
async refreshToken() {
|
|
2389
|
-
await this.initialize();
|
|
2390
|
-
if (!this.oauthProvider) {
|
|
2391
|
-
return false;
|
|
2392
|
-
}
|
|
2393
|
-
const tokens = await this.oauthProvider.tokens();
|
|
2394
|
-
if (!tokens || !tokens.refresh_token) {
|
|
2395
|
-
return false;
|
|
2396
|
-
}
|
|
2397
|
-
const clientInformation = await this.oauthProvider.clientInformation();
|
|
2398
|
-
if (!clientInformation) {
|
|
2399
|
-
return false;
|
|
2400
|
-
}
|
|
2401
|
-
try {
|
|
2402
|
-
const resourceMetadata = await discoverOAuthProtectedResourceMetadata(this.serverUrl);
|
|
2403
|
-
const authServerUrl = resourceMetadata?.authorization_servers?.[0] || this.serverUrl;
|
|
2404
|
-
const authMetadata = await discoverAuthorizationServerMetadata(authServerUrl);
|
|
2405
|
-
const newTokens = await refreshAuthorization(authServerUrl, {
|
|
2406
|
-
metadata: authMetadata,
|
|
2407
|
-
clientInformation,
|
|
2408
|
-
refreshToken: tokens.refresh_token
|
|
2409
|
-
});
|
|
2410
|
-
await this.oauthProvider.saveTokens(newTokens);
|
|
2411
|
-
return true;
|
|
2412
|
-
} catch (error) {
|
|
2413
|
-
console.error("[OAuth] Token refresh failed:", error);
|
|
2414
|
-
if (isInvalidRefreshTokenError(error)) {
|
|
2415
|
-
try {
|
|
2416
|
-
await this.oauthProvider.invalidateCredentials?.("tokens");
|
|
2417
|
-
this.emitProgress("OAuth refresh token is invalid; requesting reauthorization...");
|
|
2418
|
-
} catch (invalidateError) {
|
|
2419
|
-
console.warn("[OAuth] Failed to invalidate stale refresh token credentials:", invalidateError);
|
|
2420
|
-
}
|
|
2421
|
-
}
|
|
2422
|
-
return false;
|
|
2423
|
-
}
|
|
2424
|
-
}
|
|
2425
|
-
/**
|
|
2426
|
-
* Ensures OAuth tokens are valid, refreshing them if expired
|
|
2427
|
-
* Called automatically by connect() - rarely needs to be called manually
|
|
2428
|
-
* @returns True if valid tokens are available, false otherwise
|
|
2429
|
-
*/
|
|
2430
|
-
async getValidTokens() {
|
|
2431
|
-
await this.initialize();
|
|
2432
|
-
if (!this.oauthProvider) {
|
|
2433
|
-
return false;
|
|
2434
|
-
}
|
|
2435
|
-
const tokens = await this.oauthProvider.tokens();
|
|
2436
|
-
if (!tokens) {
|
|
2437
|
-
return false;
|
|
2438
|
-
}
|
|
2439
|
-
if (this.oauthProvider.isTokenExpired()) {
|
|
2440
|
-
return await this.refreshToken();
|
|
2441
|
-
}
|
|
2442
|
-
return true;
|
|
2443
|
-
}
|
|
2444
2835
|
/**
|
|
2445
2836
|
* Reconnects to MCP server using existing OAuth provider from Redis
|
|
2446
2837
|
* Used for session restoration in serverless environments
|
|
@@ -2570,68 +2961,6 @@ var MCPClient = class _MCPClient {
|
|
|
2570
2961
|
getSessionId() {
|
|
2571
2962
|
return this.sessionId;
|
|
2572
2963
|
}
|
|
2573
|
-
/**
|
|
2574
|
-
* Gets MCP server configuration for all active user sessions
|
|
2575
|
-
* Loads sessions from Redis, validates OAuth tokens, refreshes if expired
|
|
2576
|
-
* Returns ready-to-use configuration with valid auth headers
|
|
2577
|
-
* @param userId - User ID to fetch sessions for
|
|
2578
|
-
* @returns Object keyed by sanitized server labels containing transport, url, headers, etc.
|
|
2579
|
-
* @static
|
|
2580
|
-
*/
|
|
2581
|
-
static async getMcpServerConfig(userId) {
|
|
2582
|
-
const mcpConfig = {};
|
|
2583
|
-
const sessionList = await sessions.list(userId);
|
|
2584
|
-
await Promise.all(
|
|
2585
|
-
sessionList.map(async (sessionData) => {
|
|
2586
|
-
const { sessionId } = sessionData;
|
|
2587
|
-
try {
|
|
2588
|
-
if (!sessionData.serverId || !sessionData.transportType || !sessionData.serverUrl || !sessionData.callbackUrl) {
|
|
2589
|
-
await sessions.delete(userId, sessionId);
|
|
2590
|
-
return;
|
|
2591
|
-
}
|
|
2592
|
-
let headers;
|
|
2593
|
-
try {
|
|
2594
|
-
const client = new _MCPClient({
|
|
2595
|
-
userId,
|
|
2596
|
-
sessionId,
|
|
2597
|
-
serverId: sessionData.serverId,
|
|
2598
|
-
serverUrl: sessionData.serverUrl,
|
|
2599
|
-
callbackUrl: sessionData.callbackUrl,
|
|
2600
|
-
serverName: sessionData.serverName,
|
|
2601
|
-
transportType: sessionData.transportType,
|
|
2602
|
-
headers: sessionData.headers
|
|
2603
|
-
});
|
|
2604
|
-
await client.initialize();
|
|
2605
|
-
const hasValidTokens = await client.getValidTokens();
|
|
2606
|
-
if (hasValidTokens && client.oauthProvider) {
|
|
2607
|
-
const tokens = await client.oauthProvider.tokens();
|
|
2608
|
-
if (tokens?.access_token) {
|
|
2609
|
-
headers = { Authorization: `Bearer ${tokens.access_token}` };
|
|
2610
|
-
}
|
|
2611
|
-
}
|
|
2612
|
-
} catch (error) {
|
|
2613
|
-
console.warn(`[MCP] Failed to get OAuth tokens for ${sessionId}:`, error);
|
|
2614
|
-
}
|
|
2615
|
-
const label = sanitizeServerLabel(
|
|
2616
|
-
sessionData.serverName || sessionData.serverId || "server"
|
|
2617
|
-
);
|
|
2618
|
-
mcpConfig[label] = {
|
|
2619
|
-
transport: sessionData.transportType,
|
|
2620
|
-
url: sessionData.serverUrl,
|
|
2621
|
-
...sessionData.serverName && {
|
|
2622
|
-
serverName: sessionData.serverName,
|
|
2623
|
-
serverLabel: label
|
|
2624
|
-
},
|
|
2625
|
-
...headers && { headers }
|
|
2626
|
-
};
|
|
2627
|
-
} catch (error) {
|
|
2628
|
-
await sessions.delete(userId, sessionId);
|
|
2629
|
-
console.warn(`[MCP] Failed to process session ${sessionId}:`, error);
|
|
2630
|
-
}
|
|
2631
|
-
})
|
|
2632
|
-
);
|
|
2633
|
-
return mcpConfig;
|
|
2634
|
-
}
|
|
2635
2964
|
};
|
|
2636
2965
|
|
|
2637
2966
|
// src/server/mcp/multi-session-client.ts
|
|
@@ -2666,18 +2995,13 @@ var MultiSessionClient = class {
|
|
|
2666
2995
|
*
|
|
2667
2996
|
* A session is considered connectable when:
|
|
2668
2997
|
* - It has a `serverId`, `serverUrl`, and `callbackUrl` (i.e. it was fully initialized)
|
|
2669
|
-
* - Its
|
|
2670
|
-
* either mid-OAuth flow, auth-pending, or previously failed. We skip those here
|
|
2998
|
+
* - Its status is `active`. Pending sessions are skipped here
|
|
2671
2999
|
* and let the OAuth flow complete separately before we try to reconnect them.
|
|
2672
|
-
*
|
|
2673
|
-
* Note: Sessions where `active` is `undefined` (legacy records) are included
|
|
2674
|
-
* for backwards compatibility.
|
|
2675
3000
|
*/
|
|
2676
3001
|
async getActiveSessions() {
|
|
2677
3002
|
const sessionList = await sessions.list(this.userId);
|
|
2678
3003
|
const valid = sessionList.filter(
|
|
2679
|
-
(s) => s.serverId && s.serverUrl && s.callbackUrl && s.
|
|
2680
|
-
// exclude OAuth-pending / failed sessions
|
|
3004
|
+
(s) => s.serverId && s.serverUrl && s.callbackUrl && s.status === "active"
|
|
2681
3005
|
);
|
|
2682
3006
|
return valid;
|
|
2683
3007
|
}
|
|
@@ -2948,7 +3272,8 @@ var SSEConnectionManager = class {
|
|
|
2948
3272
|
serverUrl: s.serverUrl,
|
|
2949
3273
|
transport: s.transportType,
|
|
2950
3274
|
createdAt: s.createdAt,
|
|
2951
|
-
|
|
3275
|
+
updatedAt: s.updatedAt ?? s.createdAt,
|
|
3276
|
+
status: s.status ?? "pending"
|
|
2952
3277
|
}))
|
|
2953
3278
|
};
|
|
2954
3279
|
}
|
|
@@ -2964,7 +3289,7 @@ var SSEConnectionManager = class {
|
|
|
2964
3289
|
(s) => s.serverId === serverId || s.serverUrl === serverUrl
|
|
2965
3290
|
);
|
|
2966
3291
|
if (duplicate) {
|
|
2967
|
-
if (duplicate.
|
|
3292
|
+
if (duplicate.status === "pending") {
|
|
2968
3293
|
await this.getSession({ sessionId: duplicate.sessionId });
|
|
2969
3294
|
return {
|
|
2970
3295
|
sessionId: duplicate.sessionId,
|
|
@@ -3151,7 +3476,10 @@ var SSEConnectionManager = class {
|
|
|
3151
3476
|
* Complete OAuth authorization flow
|
|
3152
3477
|
*/
|
|
3153
3478
|
async finishAuth(params) {
|
|
3154
|
-
const {
|
|
3479
|
+
const { code } = params;
|
|
3480
|
+
const oauthState = params.state;
|
|
3481
|
+
const parsedState = parseOAuthState(oauthState);
|
|
3482
|
+
const sessionId = parsedState?.sessionId || oauthState;
|
|
3155
3483
|
const session = await sessions.get(this.userId, sessionId);
|
|
3156
3484
|
if (!session) {
|
|
3157
3485
|
throw new Error("Session not found");
|
|
@@ -3176,7 +3504,7 @@ var SSEConnectionManager = class {
|
|
|
3176
3504
|
headers: session.headers
|
|
3177
3505
|
});
|
|
3178
3506
|
client.onConnectionEvent((event) => this.emitConnectionEvent(event));
|
|
3179
|
-
await client.finishAuth(code);
|
|
3507
|
+
await client.finishAuth(code, oauthState);
|
|
3180
3508
|
this.clients.set(sessionId, client);
|
|
3181
3509
|
const tools = await client.listTools();
|
|
3182
3510
|
return { success: true, toolCount: tools.tools.length };
|
|
@@ -3481,8 +3809,8 @@ var SSEClient = class {
|
|
|
3481
3809
|
async getSession(sessionId) {
|
|
3482
3810
|
return this.sendRequest("getSession", { sessionId });
|
|
3483
3811
|
}
|
|
3484
|
-
async finishAuth(
|
|
3485
|
-
return this.sendRequest("finishAuth", {
|
|
3812
|
+
async finishAuth(state, code) {
|
|
3813
|
+
return this.sendRequest("finishAuth", { state, code });
|
|
3486
3814
|
}
|
|
3487
3815
|
async listPrompts(sessionId) {
|
|
3488
3816
|
return this.sendRequest("listPrompts", { sessionId });
|
|
@@ -5336,6 +5664,6 @@ function globToRegExp(pattern) {
|
|
|
5336
5664
|
return new RegExp(regexPattern);
|
|
5337
5665
|
}
|
|
5338
5666
|
|
|
5339
|
-
export { APP_HOST_DEFAULTS, AppHost, AuthenticationError, ConfigurationError, ConnectionError, DEFAULT_CLIENT_NAME, DEFAULT_CLIENT_URI, DEFAULT_HEARTBEAT_INTERVAL_MS, DEFAULT_LOGO_URI, DEFAULT_MCP_APP_CSP, DEFAULT_POLICY_URI, DisposableStore, Emitter, InvalidStateError, MCPClient, MCP_CLIENT_NAME, MCP_CLIENT_VERSION, McpError, MultiSessionClient, NotConnectedError, REDIS_KEY_PREFIX, RpcErrorCodes, SANDBOX_PROXY_READY_METHOD, SANDBOX_RESOURCE_READY_METHOD,
|
|
5667
|
+
export { APP_HOST_DEFAULTS, AppHost, AuthenticationError, ConfigurationError, ConnectionError, DEFAULT_CLIENT_NAME, DEFAULT_CLIENT_URI, DEFAULT_HEARTBEAT_INTERVAL_MS, DEFAULT_LOGO_URI, DEFAULT_MCP_APP_CSP, DEFAULT_POLICY_URI, DORMANT_SESSION_EXPIRATION_MS, DORMANT_SESSION_EXPIRATION_SECONDS, DisposableStore, Emitter, InvalidStateError, MCPClient, MCP_CLIENT_NAME, MCP_CLIENT_VERSION, McpError, MultiSessionClient, NotConnectedError, PENDING_SESSION_EXPIRATION_SECONDS, REDIS_KEY_PREFIX, RpcErrorCodes, SANDBOX_PROXY_READY_METHOD, SANDBOX_RESOURCE_READY_METHOD, SOFTWARE_ID, SOFTWARE_VERSION, SSEClient, SSEConnectionManager, STATE_EXPIRATION_MS, SchemaCompressor, SessionNotFoundError, SessionValidationError, StorageOAuthClientProvider, ToolExecutionError, ToolIndex, ToolRouter, UnauthorizedError, createExecuteToolDefinition, createGetSchemaToolDefinition, createListServersToolDefinition, createNextMcpHandler, createRegexSearchToolDefinition, createSSEHandler, createSearchToolDefinition, executeMetaTool, findToolByName, getToolUiResourceUri, isCallToolSuccess, isConnectAuthRequired, isConnectError, isConnectSuccess, isListToolsSuccess, isMetaTool, onSessionMutation, resolveMetaToolProxy, sanitizeServerLabel, sessions };
|
|
5340
5668
|
//# sourceMappingURL=index.mjs.map
|
|
5341
5669
|
//# sourceMappingURL=index.mjs.map
|