@mcp-ts/sdk 1.0.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.
Files changed (107) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +297 -0
  3. package/dist/adapters/agui-adapter.d.mts +119 -0
  4. package/dist/adapters/agui-adapter.d.ts +119 -0
  5. package/dist/adapters/agui-adapter.js +109 -0
  6. package/dist/adapters/agui-adapter.js.map +1 -0
  7. package/dist/adapters/agui-adapter.mjs +107 -0
  8. package/dist/adapters/agui-adapter.mjs.map +1 -0
  9. package/dist/adapters/agui-middleware.d.mts +171 -0
  10. package/dist/adapters/agui-middleware.d.ts +171 -0
  11. package/dist/adapters/agui-middleware.js +429 -0
  12. package/dist/adapters/agui-middleware.js.map +1 -0
  13. package/dist/adapters/agui-middleware.mjs +417 -0
  14. package/dist/adapters/agui-middleware.mjs.map +1 -0
  15. package/dist/adapters/ai-adapter.d.mts +38 -0
  16. package/dist/adapters/ai-adapter.d.ts +38 -0
  17. package/dist/adapters/ai-adapter.js +82 -0
  18. package/dist/adapters/ai-adapter.js.map +1 -0
  19. package/dist/adapters/ai-adapter.mjs +80 -0
  20. package/dist/adapters/ai-adapter.mjs.map +1 -0
  21. package/dist/adapters/langchain-adapter.d.mts +46 -0
  22. package/dist/adapters/langchain-adapter.d.ts +46 -0
  23. package/dist/adapters/langchain-adapter.js +102 -0
  24. package/dist/adapters/langchain-adapter.js.map +1 -0
  25. package/dist/adapters/langchain-adapter.mjs +100 -0
  26. package/dist/adapters/langchain-adapter.mjs.map +1 -0
  27. package/dist/adapters/mastra-adapter.d.mts +49 -0
  28. package/dist/adapters/mastra-adapter.d.ts +49 -0
  29. package/dist/adapters/mastra-adapter.js +95 -0
  30. package/dist/adapters/mastra-adapter.js.map +1 -0
  31. package/dist/adapters/mastra-adapter.mjs +93 -0
  32. package/dist/adapters/mastra-adapter.mjs.map +1 -0
  33. package/dist/client/index.d.mts +119 -0
  34. package/dist/client/index.d.ts +119 -0
  35. package/dist/client/index.js +225 -0
  36. package/dist/client/index.js.map +1 -0
  37. package/dist/client/index.mjs +223 -0
  38. package/dist/client/index.mjs.map +1 -0
  39. package/dist/client/react.d.mts +151 -0
  40. package/dist/client/react.d.ts +151 -0
  41. package/dist/client/react.js +492 -0
  42. package/dist/client/react.js.map +1 -0
  43. package/dist/client/react.mjs +489 -0
  44. package/dist/client/react.mjs.map +1 -0
  45. package/dist/client/vue.d.mts +157 -0
  46. package/dist/client/vue.d.ts +157 -0
  47. package/dist/client/vue.js +474 -0
  48. package/dist/client/vue.js.map +1 -0
  49. package/dist/client/vue.mjs +471 -0
  50. package/dist/client/vue.mjs.map +1 -0
  51. package/dist/events-BP6WyRNh.d.mts +110 -0
  52. package/dist/events-BP6WyRNh.d.ts +110 -0
  53. package/dist/index.d.mts +10 -0
  54. package/dist/index.d.ts +10 -0
  55. package/dist/index.js +2784 -0
  56. package/dist/index.js.map +1 -0
  57. package/dist/index.mjs +2723 -0
  58. package/dist/index.mjs.map +1 -0
  59. package/dist/multi-session-client-BOFgPypS.d.ts +389 -0
  60. package/dist/multi-session-client-DMF3ED2O.d.mts +389 -0
  61. package/dist/server/index.d.mts +269 -0
  62. package/dist/server/index.d.ts +269 -0
  63. package/dist/server/index.js +2444 -0
  64. package/dist/server/index.js.map +1 -0
  65. package/dist/server/index.mjs +2414 -0
  66. package/dist/server/index.mjs.map +1 -0
  67. package/dist/shared/index.d.mts +24 -0
  68. package/dist/shared/index.d.ts +24 -0
  69. package/dist/shared/index.js +223 -0
  70. package/dist/shared/index.js.map +1 -0
  71. package/dist/shared/index.mjs +190 -0
  72. package/dist/shared/index.mjs.map +1 -0
  73. package/dist/types-SbDlA2VX.d.mts +153 -0
  74. package/dist/types-SbDlA2VX.d.ts +153 -0
  75. package/dist/utils-0qmYrqoa.d.mts +92 -0
  76. package/dist/utils-0qmYrqoa.d.ts +92 -0
  77. package/package.json +165 -0
  78. package/src/adapters/agui-adapter.ts +210 -0
  79. package/src/adapters/agui-middleware.ts +512 -0
  80. package/src/adapters/ai-adapter.ts +115 -0
  81. package/src/adapters/langchain-adapter.ts +127 -0
  82. package/src/adapters/mastra-adapter.ts +126 -0
  83. package/src/client/core/sse-client.ts +340 -0
  84. package/src/client/index.ts +26 -0
  85. package/src/client/react/index.ts +10 -0
  86. package/src/client/react/useMcp.ts +558 -0
  87. package/src/client/vue/index.ts +10 -0
  88. package/src/client/vue/useMcp.ts +542 -0
  89. package/src/index.ts +11 -0
  90. package/src/server/handlers/nextjs-handler.ts +216 -0
  91. package/src/server/handlers/sse-handler.ts +699 -0
  92. package/src/server/index.ts +57 -0
  93. package/src/server/mcp/multi-session-client.ts +132 -0
  94. package/src/server/mcp/oauth-client.ts +1168 -0
  95. package/src/server/mcp/storage-oauth-provider.ts +239 -0
  96. package/src/server/storage/file-backend.ts +169 -0
  97. package/src/server/storage/index.ts +115 -0
  98. package/src/server/storage/memory-backend.ts +132 -0
  99. package/src/server/storage/redis-backend.ts +210 -0
  100. package/src/server/storage/redis.ts +160 -0
  101. package/src/server/storage/types.ts +109 -0
  102. package/src/shared/constants.ts +29 -0
  103. package/src/shared/errors.ts +133 -0
  104. package/src/shared/events.ts +166 -0
  105. package/src/shared/index.ts +70 -0
  106. package/src/shared/types.ts +274 -0
  107. package/src/shared/utils.ts +16 -0
@@ -0,0 +1,2414 @@
1
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
2
+ import { customAlphabet, nanoid } from 'nanoid';
3
+ import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
4
+ import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
5
+ import { UnauthorizedError as UnauthorizedError$1, discoverOAuthProtectedResourceMetadata, discoverAuthorizationServerMetadata, refreshAuthorization } from '@modelcontextprotocol/sdk/client/auth.js';
6
+ import { ListToolsResultSchema, CallToolResultSchema, ListPromptsResultSchema, GetPromptResultSchema, ListResourcesResultSchema, ReadResourceResultSchema } from '@modelcontextprotocol/sdk/types.js';
7
+ import { promises } from 'fs';
8
+ import * as path from 'path';
9
+
10
+ var __defProp = Object.defineProperty;
11
+ var __getOwnPropNames = Object.getOwnPropertyNames;
12
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
13
+ var __esm = (fn, res) => function __init() {
14
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
15
+ };
16
+ var __export = (target, all) => {
17
+ for (var name in all)
18
+ __defProp(target, name, { get: all[name], enumerable: true });
19
+ };
20
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
21
+
22
+ // src/server/storage/redis.ts
23
+ var redis_exports = {};
24
+ __export(redis_exports, {
25
+ closeRedis: () => closeRedis,
26
+ getRedis: () => getRedis,
27
+ initRedis: () => initRedis,
28
+ redis: () => redis,
29
+ setRedisInstance: () => setRedisInstance
30
+ });
31
+ async function initRedis(config) {
32
+ if (redisInstance) {
33
+ return redisInstance;
34
+ }
35
+ const url = config.url ?? process.env.REDIS_URL;
36
+ if (!url) {
37
+ throw new Error(
38
+ "Redis URL is required. Set REDIS_URL environment variable or pass url in config."
39
+ );
40
+ }
41
+ let Redis;
42
+ if (config.RedisConstructor) {
43
+ Redis = config.RedisConstructor;
44
+ } else {
45
+ try {
46
+ const ioredis = await import('ioredis');
47
+ Redis = ioredis.Redis;
48
+ } catch (error) {
49
+ throw new Error(
50
+ "ioredis is not installed. Install it with:\n npm install ioredis\n\nOr use a different storage backend:\n MCP_TS_STORAGE_TYPE=memory (for development)\n MCP_TS_STORAGE_TYPE=file (for local persistence)"
51
+ );
52
+ }
53
+ }
54
+ redisInstance = new Redis(url, {
55
+ lazyConnect: config.lazyConnect ?? true,
56
+ maxRetriesPerRequest: config.maxRetriesPerRequest ?? 1
57
+ });
58
+ if (config.verbose !== false) {
59
+ redisInstance.on("ready", () => {
60
+ console.log("\u2705 Redis connected");
61
+ });
62
+ redisInstance.on("error", (err) => {
63
+ console.error("\u274C Redis error:", err.message);
64
+ });
65
+ redisInstance.on("reconnecting", () => {
66
+ console.log("\u{1F504} Redis reconnecting...");
67
+ });
68
+ }
69
+ global.__redis = redisInstance;
70
+ global.__redisConfig = config;
71
+ return redisInstance;
72
+ }
73
+ async function getRedis() {
74
+ if (redisInstance) {
75
+ return redisInstance;
76
+ }
77
+ if (global.__redis) {
78
+ redisInstance = global.__redis;
79
+ return redisInstance;
80
+ }
81
+ return await initRedis({});
82
+ }
83
+ function setRedisInstance(instance) {
84
+ redisInstance = instance;
85
+ global.__redis = instance;
86
+ }
87
+ async function closeRedis() {
88
+ if (redisInstance) {
89
+ await redisInstance.quit();
90
+ redisInstance = null;
91
+ global.__redis = void 0;
92
+ }
93
+ }
94
+ var redisInstance, redis;
95
+ var init_redis = __esm({
96
+ "src/server/storage/redis.ts"() {
97
+ redisInstance = null;
98
+ redis = new Proxy({}, {
99
+ get(_target, prop) {
100
+ return async (...args) => {
101
+ const instance = await getRedis();
102
+ const value = instance[prop];
103
+ if (typeof value === "function") {
104
+ return value.apply(instance, args);
105
+ }
106
+ return value;
107
+ };
108
+ }
109
+ });
110
+ }
111
+ });
112
+
113
+ // src/shared/constants.ts
114
+ var SESSION_TTL_SECONDS = 43200;
115
+ var STATE_EXPIRATION_MS = 10 * 60 * 1e3;
116
+ var TOKEN_EXPIRY_BUFFER_MS = 5 * 60 * 1e3;
117
+
118
+ // src/server/storage/redis-backend.ts
119
+ var firstChar = customAlphabet(
120
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
121
+ 1
122
+ );
123
+ var rest = customAlphabet(
124
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
125
+ 11
126
+ );
127
+ var RedisStorageBackend = class {
128
+ constructor(redis2) {
129
+ this.redis = redis2;
130
+ __publicField(this, "DEFAULT_TTL", SESSION_TTL_SECONDS);
131
+ __publicField(this, "KEY_PREFIX", "mcp:session:");
132
+ }
133
+ /**
134
+ * Generates Redis key for a specific session
135
+ * @private
136
+ */
137
+ getSessionKey(identity, sessionId) {
138
+ return `${this.KEY_PREFIX}${identity}:${sessionId}`;
139
+ }
140
+ /**
141
+ * Generates Redis key for tracking all sessions for an identity
142
+ * @private
143
+ */
144
+ getIdentityKey(identity) {
145
+ return `mcp:identity:${identity}:sessions`;
146
+ }
147
+ generateSessionId() {
148
+ return firstChar() + rest();
149
+ }
150
+ async createSession(session, ttl) {
151
+ const { sessionId, identity } = session;
152
+ if (!sessionId || !identity) throw new Error("identity and sessionId required");
153
+ const sessionKey = this.getSessionKey(identity, sessionId);
154
+ const identityKey = this.getIdentityKey(identity);
155
+ const effectiveTtl = ttl ?? this.DEFAULT_TTL;
156
+ const result = await this.redis.set(
157
+ sessionKey,
158
+ JSON.stringify(session),
159
+ "EX",
160
+ effectiveTtl,
161
+ "NX"
162
+ );
163
+ if (result !== "OK") {
164
+ throw new Error(`Session ${sessionId} already exists`);
165
+ }
166
+ await this.redis.sadd(identityKey, sessionId);
167
+ }
168
+ async updateSession(identity, sessionId, data, ttl) {
169
+ const sessionKey = this.getSessionKey(identity, sessionId);
170
+ const effectiveTtl = ttl ?? this.DEFAULT_TTL;
171
+ const script = `
172
+ local currentStr = redis.call("GET", KEYS[1])
173
+ if not currentStr then
174
+ return 0
175
+ end
176
+
177
+ local current = cjson.decode(currentStr)
178
+ local updates = cjson.decode(ARGV[1])
179
+
180
+ for k,v in pairs(updates) do
181
+ current[k] = v
182
+ end
183
+
184
+ redis.call("SET", KEYS[1], cjson.encode(current), "EX", ARGV[2])
185
+ return 1
186
+ `;
187
+ const result = await this.redis.eval(
188
+ script,
189
+ 1,
190
+ sessionKey,
191
+ JSON.stringify(data),
192
+ effectiveTtl
193
+ );
194
+ if (result === 0) {
195
+ throw new Error(`Session ${sessionId} not found for identity ${identity}`);
196
+ }
197
+ }
198
+ async getSession(identity, sessionId) {
199
+ try {
200
+ const sessionKey = this.getSessionKey(identity, sessionId);
201
+ const sessionDataStr = await this.redis.get(sessionKey);
202
+ if (!sessionDataStr) {
203
+ return null;
204
+ }
205
+ const sessionData = JSON.parse(sessionDataStr);
206
+ return sessionData;
207
+ } catch (error) {
208
+ console.error("[RedisStorage] Failed to get session:", error);
209
+ return null;
210
+ }
211
+ }
212
+ async getIdentityMcpSessions(identity) {
213
+ const identityKey = this.getIdentityKey(identity);
214
+ try {
215
+ return await this.redis.smembers(identityKey);
216
+ } catch (error) {
217
+ console.error(`[RedisStorage] Failed to get sessions for ${identity}:`, error);
218
+ return [];
219
+ }
220
+ }
221
+ async getIdentitySessionsData(identity) {
222
+ try {
223
+ const sessionIds = await this.redis.smembers(this.getIdentityKey(identity));
224
+ if (sessionIds.length === 0) return [];
225
+ const results = await Promise.all(
226
+ sessionIds.map(async (sessionId) => {
227
+ const data = await this.redis.get(this.getSessionKey(identity, sessionId));
228
+ return data ? JSON.parse(data) : null;
229
+ })
230
+ );
231
+ return results.filter((session) => session !== null);
232
+ } catch (error) {
233
+ console.error(`[RedisStorage] Failed to get session data for ${identity}:`, error);
234
+ return [];
235
+ }
236
+ }
237
+ async removeSession(identity, sessionId) {
238
+ try {
239
+ const sessionKey = this.getSessionKey(identity, sessionId);
240
+ const identityKey = this.getIdentityKey(identity);
241
+ await this.redis.srem(identityKey, sessionId);
242
+ await this.redis.del(sessionKey);
243
+ } catch (error) {
244
+ console.error("[RedisStorage] Failed to remove session:", error);
245
+ }
246
+ }
247
+ async getAllSessionIds() {
248
+ try {
249
+ const pattern = `${this.KEY_PREFIX}*`;
250
+ const keys = await this.redis.keys(pattern);
251
+ return keys.map((key) => key.replace(this.KEY_PREFIX, ""));
252
+ } catch (error) {
253
+ console.error("[RedisStorage] Failed to get all sessions:", error);
254
+ return [];
255
+ }
256
+ }
257
+ async clearAll() {
258
+ try {
259
+ const pattern = `${this.KEY_PREFIX}*`;
260
+ const keys = await this.redis.keys(pattern);
261
+ if (keys.length > 0) {
262
+ await this.redis.del(...keys);
263
+ }
264
+ } catch (error) {
265
+ console.error("[RedisStorage] Failed to clear sessions:", error);
266
+ }
267
+ }
268
+ async cleanupExpiredSessions() {
269
+ try {
270
+ const pattern = `${this.KEY_PREFIX}*`;
271
+ const keys = await this.redis.keys(pattern);
272
+ for (const key of keys) {
273
+ const ttl = await this.redis.ttl(key);
274
+ if (ttl <= 0) {
275
+ await this.redis.del(key);
276
+ }
277
+ }
278
+ } catch (error) {
279
+ console.error("[RedisStorage] Failed to cleanup expired sessions:", error);
280
+ }
281
+ }
282
+ async disconnect() {
283
+ try {
284
+ await this.redis.quit();
285
+ } catch (error) {
286
+ console.error("[RedisStorage] Failed to disconnect:", error);
287
+ }
288
+ }
289
+ };
290
+ var firstChar2 = customAlphabet(
291
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
292
+ 1
293
+ );
294
+ var rest2 = customAlphabet(
295
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
296
+ 11
297
+ );
298
+ var MemoryStorageBackend = class {
299
+ constructor() {
300
+ // Map<identity:sessionId, SessionData>
301
+ __publicField(this, "sessions", /* @__PURE__ */ new Map());
302
+ // Map<identity, Set<sessionId>>
303
+ __publicField(this, "identitySessions", /* @__PURE__ */ new Map());
304
+ }
305
+ getSessionKey(identity, sessionId) {
306
+ return `${identity}:${sessionId}`;
307
+ }
308
+ generateSessionId() {
309
+ return firstChar2() + rest2();
310
+ }
311
+ async createSession(session, ttl) {
312
+ const { sessionId, identity } = session;
313
+ if (!sessionId || !identity) throw new Error("identity and sessionId required");
314
+ const sessionKey = this.getSessionKey(identity, sessionId);
315
+ if (this.sessions.has(sessionKey)) {
316
+ throw new Error(`Session ${sessionId} already exists`);
317
+ }
318
+ this.sessions.set(sessionKey, session);
319
+ if (!this.identitySessions.has(identity)) {
320
+ this.identitySessions.set(identity, /* @__PURE__ */ new Set());
321
+ }
322
+ this.identitySessions.get(identity).add(sessionId);
323
+ }
324
+ async updateSession(identity, sessionId, data, ttl) {
325
+ if (!identity || !sessionId) throw new Error("identity and sessionId required");
326
+ const sessionKey = this.getSessionKey(identity, sessionId);
327
+ const current = this.sessions.get(sessionKey);
328
+ if (!current) {
329
+ throw new Error(`Session ${sessionId} not found`);
330
+ }
331
+ const updated = {
332
+ ...current,
333
+ ...data
334
+ };
335
+ this.sessions.set(sessionKey, updated);
336
+ }
337
+ async getSession(identity, sessionId) {
338
+ const sessionKey = this.getSessionKey(identity, sessionId);
339
+ return this.sessions.get(sessionKey) || null;
340
+ }
341
+ async getIdentityMcpSessions(identity) {
342
+ const set = this.identitySessions.get(identity);
343
+ return set ? Array.from(set) : [];
344
+ }
345
+ async getIdentitySessionsData(identity) {
346
+ const set = this.identitySessions.get(identity);
347
+ if (!set) return [];
348
+ const results = [];
349
+ for (const sessionId of set) {
350
+ const session = this.sessions.get(this.getSessionKey(identity, sessionId));
351
+ if (session) {
352
+ results.push(session);
353
+ }
354
+ }
355
+ return results;
356
+ }
357
+ async removeSession(identity, sessionId) {
358
+ const sessionKey = this.getSessionKey(identity, sessionId);
359
+ this.sessions.delete(sessionKey);
360
+ const set = this.identitySessions.get(identity);
361
+ if (set) {
362
+ set.delete(sessionId);
363
+ if (set.size === 0) {
364
+ this.identitySessions.delete(identity);
365
+ }
366
+ }
367
+ }
368
+ async getAllSessionIds() {
369
+ return Array.from(this.sessions.values()).map((s) => s.sessionId);
370
+ }
371
+ async clearAll() {
372
+ this.sessions.clear();
373
+ this.identitySessions.clear();
374
+ }
375
+ async cleanupExpiredSessions() {
376
+ }
377
+ async disconnect() {
378
+ }
379
+ };
380
+ var firstChar3 = customAlphabet(
381
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
382
+ 1
383
+ );
384
+ var rest3 = customAlphabet(
385
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
386
+ 11
387
+ );
388
+ var FileStorageBackend = class {
389
+ /**
390
+ * @param options.path Path to the JSON file storage (default: ./sessions.json)
391
+ */
392
+ constructor(options = {}) {
393
+ __publicField(this, "filePath");
394
+ __publicField(this, "memoryCache", null);
395
+ __publicField(this, "initialized", false);
396
+ this.filePath = options.path || "./sessions.json";
397
+ }
398
+ /**
399
+ * Initialize storage: ensure file exists and load into memory cache
400
+ */
401
+ async init() {
402
+ if (this.initialized) return;
403
+ try {
404
+ const dir = path.dirname(this.filePath);
405
+ await promises.mkdir(dir, { recursive: true });
406
+ const data = await promises.readFile(this.filePath, "utf-8");
407
+ const json = JSON.parse(data);
408
+ this.memoryCache = /* @__PURE__ */ new Map();
409
+ if (Array.isArray(json)) {
410
+ json.forEach((s) => {
411
+ this.memoryCache.set(this.getSessionKey(s.identity || "unknown", s.sessionId), s);
412
+ });
413
+ }
414
+ } catch (error) {
415
+ if (error.code === "ENOENT") {
416
+ this.memoryCache = /* @__PURE__ */ new Map();
417
+ await this.flush();
418
+ } else {
419
+ console.error("[FileStorage] Failed to load sessions:", error);
420
+ throw error;
421
+ }
422
+ }
423
+ this.initialized = true;
424
+ }
425
+ async ensureInitialized() {
426
+ if (!this.initialized) await this.init();
427
+ }
428
+ async flush() {
429
+ if (!this.memoryCache) return;
430
+ const sessions = Array.from(this.memoryCache.values());
431
+ await promises.writeFile(this.filePath, JSON.stringify(sessions, null, 2), "utf-8");
432
+ }
433
+ getSessionKey(identity, sessionId) {
434
+ return `${identity}:${sessionId}`;
435
+ }
436
+ generateSessionId() {
437
+ return firstChar3() + rest3();
438
+ }
439
+ async createSession(session, ttl) {
440
+ await this.ensureInitialized();
441
+ const { sessionId, identity } = session;
442
+ if (!sessionId || !identity) throw new Error("identity and sessionId required");
443
+ const sessionKey = this.getSessionKey(identity, sessionId);
444
+ if (this.memoryCache.has(sessionKey)) {
445
+ throw new Error(`Session ${sessionId} already exists`);
446
+ }
447
+ this.memoryCache.set(sessionKey, session);
448
+ await this.flush();
449
+ }
450
+ async updateSession(identity, sessionId, data, ttl) {
451
+ await this.ensureInitialized();
452
+ if (!identity || !sessionId) throw new Error("identity and sessionId required");
453
+ const sessionKey = this.getSessionKey(identity, sessionId);
454
+ const current = this.memoryCache.get(sessionKey);
455
+ if (!current) {
456
+ throw new Error(`Session ${sessionId} not found`);
457
+ }
458
+ const updated = {
459
+ ...current,
460
+ ...data
461
+ };
462
+ this.memoryCache.set(sessionKey, updated);
463
+ await this.flush();
464
+ }
465
+ async getSession(identity, sessionId) {
466
+ await this.ensureInitialized();
467
+ const sessionKey = this.getSessionKey(identity, sessionId);
468
+ return this.memoryCache.get(sessionKey) || null;
469
+ }
470
+ async getIdentitySessionsData(identity) {
471
+ await this.ensureInitialized();
472
+ return Array.from(this.memoryCache.values()).filter((s) => s.identity === identity);
473
+ }
474
+ async getIdentityMcpSessions(identity) {
475
+ await this.ensureInitialized();
476
+ return Array.from(this.memoryCache.values()).filter((s) => s.identity === identity).map((s) => s.sessionId);
477
+ }
478
+ async removeSession(identity, sessionId) {
479
+ await this.ensureInitialized();
480
+ const sessionKey = this.getSessionKey(identity, sessionId);
481
+ if (this.memoryCache.delete(sessionKey)) {
482
+ await this.flush();
483
+ }
484
+ }
485
+ async getAllSessionIds() {
486
+ await this.ensureInitialized();
487
+ return Array.from(this.memoryCache.values()).map((s) => s.sessionId);
488
+ }
489
+ async clearAll() {
490
+ await this.ensureInitialized();
491
+ this.memoryCache.clear();
492
+ await this.flush();
493
+ }
494
+ async cleanupExpiredSessions() {
495
+ await this.ensureInitialized();
496
+ }
497
+ async disconnect() {
498
+ }
499
+ };
500
+
501
+ // src/server/storage/index.ts
502
+ var storageInstance = null;
503
+ var storagePromise = null;
504
+ async function createStorage() {
505
+ const type = process.env.MCP_TS_STORAGE_TYPE?.toLowerCase();
506
+ if (type === "redis") {
507
+ if (!process.env.REDIS_URL) {
508
+ console.warn('[Storage] MCP_TS_STORAGE_TYPE is "redis" but REDIS_URL is missing');
509
+ }
510
+ try {
511
+ const { getRedis: getRedis2 } = await Promise.resolve().then(() => (init_redis(), redis_exports));
512
+ const redis2 = await getRedis2();
513
+ console.log("[Storage] Using Redis storage (Explicit)");
514
+ return new RedisStorageBackend(redis2);
515
+ } catch (error) {
516
+ console.error("[Storage] Failed to initialize Redis:", error.message);
517
+ console.log("[Storage] Falling back to In-Memory storage");
518
+ return new MemoryStorageBackend();
519
+ }
520
+ }
521
+ if (type === "file") {
522
+ const filePath = process.env.MCP_TS_STORAGE_FILE;
523
+ if (!filePath) {
524
+ console.warn('[Storage] MCP_TS_STORAGE_TYPE is "file" but MCP_TS_STORAGE_FILE is missing');
525
+ }
526
+ console.log(`[Storage] Using File storage (${filePath}) (Explicit)`);
527
+ const store = new FileStorageBackend({ path: filePath });
528
+ store.init().catch((err) => console.error("[Storage] Failed to initialize file storage:", err));
529
+ return store;
530
+ }
531
+ if (type === "memory") {
532
+ console.log("[Storage] Using In-Memory storage (Explicit)");
533
+ return new MemoryStorageBackend();
534
+ }
535
+ if (process.env.REDIS_URL) {
536
+ try {
537
+ const { getRedis: getRedis2 } = await Promise.resolve().then(() => (init_redis(), redis_exports));
538
+ const redis2 = await getRedis2();
539
+ console.log("[Storage] Auto-detected REDIS_URL. Using Redis storage.");
540
+ return new RedisStorageBackend(redis2);
541
+ } catch (error) {
542
+ console.error("[Storage] Redis auto-detection failed:", error.message);
543
+ console.log("[Storage] Falling back to In-Memory storage");
544
+ return new MemoryStorageBackend();
545
+ }
546
+ }
547
+ if (process.env.MCP_TS_STORAGE_FILE) {
548
+ console.log(`[Storage] Auto-detected MCP_TS_STORAGE_FILE. Using File storage (${process.env.MCP_TS_STORAGE_FILE}).`);
549
+ const store = new FileStorageBackend({ path: process.env.MCP_TS_STORAGE_FILE });
550
+ store.init().catch((err) => console.error("[Storage] Failed to initialize file storage:", err));
551
+ return store;
552
+ }
553
+ console.log("[Storage] No storage configured. Using In-Memory storage (Default).");
554
+ return new MemoryStorageBackend();
555
+ }
556
+ async function getStorage() {
557
+ if (storageInstance) {
558
+ return storageInstance;
559
+ }
560
+ if (!storagePromise) {
561
+ storagePromise = createStorage();
562
+ }
563
+ storageInstance = await storagePromise;
564
+ return storageInstance;
565
+ }
566
+ var storage = new Proxy({}, {
567
+ get(_target, prop) {
568
+ return async (...args) => {
569
+ const instance = await getStorage();
570
+ const value = instance[prop];
571
+ if (typeof value === "function") {
572
+ return value.apply(instance, args);
573
+ }
574
+ return value;
575
+ };
576
+ }
577
+ });
578
+
579
+ // src/server/mcp/storage-oauth-provider.ts
580
+ var StorageOAuthClientProvider = class {
581
+ /**
582
+ * Creates a new Storage-backed OAuth provider
583
+ * @param identity - User/Client identifier
584
+ * @param serverId - Server identifier (for tracking which server this OAuth session belongs to)
585
+ * @param sessionId - Session identifier (used as OAuth state)
586
+ * @param clientName - OAuth client name
587
+ * @param baseRedirectUrl - OAuth callback URL
588
+ * @param onRedirect - Optional callback when redirect to authorization is needed
589
+ */
590
+ constructor(identity, serverId, sessionId, clientName, baseRedirectUrl, onRedirect) {
591
+ this.identity = identity;
592
+ this.serverId = serverId;
593
+ this.sessionId = sessionId;
594
+ this.clientName = clientName;
595
+ this.baseRedirectUrl = baseRedirectUrl;
596
+ __publicField(this, "_authUrl");
597
+ __publicField(this, "_clientId");
598
+ __publicField(this, "onRedirectCallback");
599
+ __publicField(this, "tokenExpiresAt");
600
+ this.onRedirectCallback = onRedirect;
601
+ }
602
+ get clientMetadata() {
603
+ return {
604
+ client_name: this.clientName,
605
+ client_uri: this.clientUri,
606
+ grant_types: ["authorization_code", "refresh_token"],
607
+ redirect_uris: [this.redirectUrl],
608
+ response_types: ["code"],
609
+ token_endpoint_auth_method: "none",
610
+ ...this._clientId ? { client_id: this._clientId } : {}
611
+ };
612
+ }
613
+ get clientUri() {
614
+ return new URL(this.redirectUrl).origin;
615
+ }
616
+ get redirectUrl() {
617
+ return this.baseRedirectUrl;
618
+ }
619
+ get clientId() {
620
+ return this._clientId;
621
+ }
622
+ set clientId(clientId_) {
623
+ this._clientId = clientId_;
624
+ }
625
+ /**
626
+ * Loads OAuth data from storage session
627
+ * @private
628
+ */
629
+ async getSessionData() {
630
+ const data = await storage.getSession(this.identity, this.sessionId);
631
+ if (!data) {
632
+ return {};
633
+ }
634
+ return data;
635
+ }
636
+ /**
637
+ * Saves OAuth data to storage
638
+ * @param data - Partial OAuth data to save
639
+ * @private
640
+ * @throws Error if session doesn't exist (session must be created by controller layer)
641
+ */
642
+ async saveSessionData(data) {
643
+ await storage.updateSession(this.identity, this.sessionId, data);
644
+ }
645
+ /**
646
+ * Retrieves stored OAuth client information
647
+ */
648
+ async clientInformation() {
649
+ const data = await this.getSessionData();
650
+ if (data.clientId && !this._clientId) {
651
+ this._clientId = data.clientId;
652
+ }
653
+ return data.clientInformation;
654
+ }
655
+ /**
656
+ * Stores OAuth client information
657
+ */
658
+ async saveClientInformation(clientInformation) {
659
+ await this.saveSessionData({
660
+ clientInformation,
661
+ clientId: clientInformation.client_id
662
+ });
663
+ this.clientId = clientInformation.client_id;
664
+ }
665
+ /**
666
+ * Stores OAuth tokens
667
+ */
668
+ async saveTokens(tokens) {
669
+ const data = { tokens };
670
+ if (tokens.expires_in) {
671
+ this.tokenExpiresAt = Date.now() + tokens.expires_in * 1e3 - TOKEN_EXPIRY_BUFFER_MS;
672
+ }
673
+ await this.saveSessionData(data);
674
+ }
675
+ get authUrl() {
676
+ return this._authUrl;
677
+ }
678
+ async state() {
679
+ return this.sessionId;
680
+ }
681
+ async checkState(state) {
682
+ const data = await storage.getSession(this.identity, this.sessionId);
683
+ if (!data) {
684
+ return { valid: false, error: "Session not found" };
685
+ }
686
+ return { valid: true, serverId: this.serverId };
687
+ }
688
+ async consumeState(state) {
689
+ }
690
+ async redirectToAuthorization(authUrl) {
691
+ this._authUrl = authUrl.toString();
692
+ if (this.onRedirectCallback) {
693
+ this.onRedirectCallback(authUrl.toString());
694
+ }
695
+ }
696
+ async invalidateCredentials(scope) {
697
+ if (scope === "all") {
698
+ await storage.removeSession(this.identity, this.sessionId);
699
+ } else {
700
+ await this.getSessionData();
701
+ const updates = {};
702
+ if (scope === "client") {
703
+ updates.clientInformation = void 0;
704
+ updates.clientId = void 0;
705
+ } else if (scope === "tokens") {
706
+ updates.tokens = void 0;
707
+ } else if (scope === "verifier") {
708
+ updates.codeVerifier = void 0;
709
+ }
710
+ await this.saveSessionData(updates);
711
+ }
712
+ }
713
+ async saveCodeVerifier(verifier) {
714
+ await this.saveSessionData({ codeVerifier: verifier });
715
+ }
716
+ async codeVerifier() {
717
+ const data = await this.getSessionData();
718
+ if (data.clientId && !this._clientId) {
719
+ this._clientId = data.clientId;
720
+ }
721
+ if (!data.codeVerifier) {
722
+ throw new Error("No code verifier found");
723
+ }
724
+ return data.codeVerifier;
725
+ }
726
+ async deleteCodeVerifier() {
727
+ await this.saveSessionData({ codeVerifier: void 0 });
728
+ }
729
+ async tokens() {
730
+ const data = await this.getSessionData();
731
+ if (data.clientId && !this._clientId) {
732
+ this._clientId = data.clientId;
733
+ }
734
+ return data.tokens;
735
+ }
736
+ isTokenExpired() {
737
+ if (!this.tokenExpiresAt) {
738
+ return false;
739
+ }
740
+ return Date.now() >= this.tokenExpiresAt;
741
+ }
742
+ setTokenExpiresAt(expiresAt) {
743
+ this.tokenExpiresAt = expiresAt;
744
+ }
745
+ };
746
+
747
+ // src/shared/utils.ts
748
+ function sanitizeServerLabel(name) {
749
+ let sanitized = name.replace(/[^a-zA-Z0-9-_]/g, "_").replace(/_{2,}/g, "_").toLowerCase();
750
+ if (!/^[a-zA-Z]/.test(sanitized)) {
751
+ sanitized = "s_" + sanitized;
752
+ }
753
+ return sanitized;
754
+ }
755
+
756
+ // src/shared/events.ts
757
+ var Emitter = class {
758
+ constructor() {
759
+ __publicField(this, "listeners", /* @__PURE__ */ new Set());
760
+ }
761
+ /**
762
+ * Subscribe to events
763
+ * @param listener - Callback function to handle events
764
+ * @returns Disposable to unsubscribe
765
+ */
766
+ get event() {
767
+ return (listener) => {
768
+ this.listeners.add(listener);
769
+ return {
770
+ dispose: () => {
771
+ this.listeners.delete(listener);
772
+ }
773
+ };
774
+ };
775
+ }
776
+ /**
777
+ * Fire an event to all listeners
778
+ * @param event - Event data to emit
779
+ */
780
+ fire(event) {
781
+ for (const listener of this.listeners) {
782
+ try {
783
+ listener(event);
784
+ } catch (error) {
785
+ console.error("[Emitter] Error in event listener:", error);
786
+ }
787
+ }
788
+ }
789
+ /**
790
+ * Clear all listeners
791
+ */
792
+ dispose() {
793
+ this.listeners.clear();
794
+ }
795
+ /**
796
+ * Get number of active listeners
797
+ */
798
+ get listenerCount() {
799
+ return this.listeners.size;
800
+ }
801
+ };
802
+
803
+ // src/shared/errors.ts
804
+ var McpError = class extends Error {
805
+ constructor(code, message, cause) {
806
+ super(message);
807
+ this.code = code;
808
+ this.cause = cause;
809
+ this.name = "McpError";
810
+ Object.setPrototypeOf(this, new.target.prototype);
811
+ }
812
+ toJSON() {
813
+ return {
814
+ name: this.name,
815
+ code: this.code,
816
+ message: this.message,
817
+ ...this.cause ? { cause: this.cause.message } : {}
818
+ };
819
+ }
820
+ };
821
+ var UnauthorizedError = class extends McpError {
822
+ constructor(message = "OAuth authorization required", cause) {
823
+ super("UNAUTHORIZED", message, cause);
824
+ this.name = "UnauthorizedError";
825
+ }
826
+ };
827
+ var RpcErrorCodes = {
828
+ EXECUTION_ERROR: "EXECUTION_ERROR"};
829
+
830
+ // src/server/mcp/oauth-client.ts
831
+ var MCPClient = class _MCPClient {
832
+ /**
833
+ * Creates a new MCP client instance
834
+ * Can be initialized with minimal options (identity + sessionId) for session restoration
835
+ * @param options - Client configuration options
836
+ */
837
+ constructor(options) {
838
+ __publicField(this, "client", null);
839
+ __publicField(this, "oauthProvider", null);
840
+ __publicField(this, "transport", null);
841
+ __publicField(this, "identity");
842
+ __publicField(this, "serverId");
843
+ __publicField(this, "sessionId");
844
+ __publicField(this, "serverName");
845
+ __publicField(this, "transportType");
846
+ __publicField(this, "serverUrl");
847
+ __publicField(this, "callbackUrl");
848
+ __publicField(this, "onRedirect");
849
+ __publicField(this, "tokens");
850
+ __publicField(this, "tokenExpiresAt");
851
+ __publicField(this, "clientInformation");
852
+ __publicField(this, "clientId");
853
+ __publicField(this, "clientSecret");
854
+ __publicField(this, "onSaveTokens");
855
+ __publicField(this, "headers");
856
+ /** OAuth Client Metadata */
857
+ __publicField(this, "clientName");
858
+ __publicField(this, "clientUri");
859
+ __publicField(this, "logoUri");
860
+ __publicField(this, "policyUri");
861
+ /** Event emitters for connection lifecycle */
862
+ __publicField(this, "_onConnectionEvent", new Emitter());
863
+ __publicField(this, "onConnectionEvent", this._onConnectionEvent.event);
864
+ __publicField(this, "_onObservabilityEvent", new Emitter());
865
+ __publicField(this, "onObservabilityEvent", this._onObservabilityEvent.event);
866
+ __publicField(this, "currentState", "DISCONNECTED");
867
+ this.serverUrl = options.serverUrl;
868
+ this.serverName = options.serverName;
869
+ this.callbackUrl = options.callbackUrl;
870
+ this.onRedirect = options.onRedirect;
871
+ this.identity = options.identity;
872
+ this.serverId = options.serverId;
873
+ this.sessionId = options.sessionId;
874
+ this.transportType = options.transportType;
875
+ this.tokens = options.tokens;
876
+ this.tokenExpiresAt = options.tokenExpiresAt;
877
+ this.clientInformation = options.clientInformation;
878
+ this.clientId = options.clientId;
879
+ this.clientSecret = options.clientSecret;
880
+ this.onSaveTokens = options.onSaveTokens;
881
+ this.headers = options.headers;
882
+ this.clientName = options.clientName;
883
+ this.clientUri = options.clientUri;
884
+ this.logoUri = options.logoUri;
885
+ this.policyUri = options.policyUri;
886
+ }
887
+ /**
888
+ * Emit a connection state change event
889
+ * @private
890
+ */
891
+ emitStateChange(newState) {
892
+ const previousState = this.currentState;
893
+ this.currentState = newState;
894
+ if (!this.serverId) return;
895
+ this._onConnectionEvent.fire({
896
+ type: "state_changed",
897
+ sessionId: this.sessionId,
898
+ serverId: this.serverId,
899
+ serverName: this.serverName || this.serverId,
900
+ state: newState,
901
+ previousState,
902
+ timestamp: Date.now()
903
+ });
904
+ this._onObservabilityEvent.fire({
905
+ type: "mcp:client:state_change",
906
+ level: "info",
907
+ message: `Connection state: ${previousState} \u2192 ${newState}`,
908
+ displayMessage: `State changed to ${newState}`,
909
+ sessionId: this.sessionId,
910
+ serverId: this.serverId,
911
+ payload: { previousState, newState },
912
+ timestamp: Date.now(),
913
+ id: nanoid()
914
+ });
915
+ }
916
+ /**
917
+ * Emit an error event
918
+ * @private
919
+ */
920
+ emitError(error, errorType = "unknown") {
921
+ if (!this.serverId) return;
922
+ this._onConnectionEvent.fire({
923
+ type: "error",
924
+ sessionId: this.sessionId,
925
+ serverId: this.serverId,
926
+ error,
927
+ errorType,
928
+ timestamp: Date.now()
929
+ });
930
+ this._onObservabilityEvent.fire({
931
+ type: "mcp:client:error",
932
+ level: "error",
933
+ message: error,
934
+ displayMessage: error,
935
+ sessionId: this.sessionId,
936
+ serverId: this.serverId,
937
+ payload: { errorType, error },
938
+ timestamp: Date.now(),
939
+ id: nanoid()
940
+ });
941
+ }
942
+ /**
943
+ * Emit a progress event
944
+ * @private
945
+ */
946
+ emitProgress(message) {
947
+ if (!this.serverId) return;
948
+ this._onConnectionEvent.fire({
949
+ type: "progress",
950
+ sessionId: this.sessionId,
951
+ serverId: this.serverId,
952
+ message,
953
+ timestamp: Date.now()
954
+ });
955
+ }
956
+ /**
957
+ * Get current connection state
958
+ */
959
+ getConnectionState() {
960
+ return this.currentState;
961
+ }
962
+ /**
963
+ * Helper to create a transport instance
964
+ * @param type - The transport type to create
965
+ * @returns Configured transport instance
966
+ * @private
967
+ */
968
+ getTransport(type) {
969
+ if (!this.serverUrl) {
970
+ throw new Error("Server URL is required to create transport");
971
+ }
972
+ const baseUrl = new URL(this.serverUrl);
973
+ const transportOptions = {
974
+ authProvider: this.oauthProvider,
975
+ ...this.headers && { headers: this.headers },
976
+ /**
977
+ * Custom fetch implementation to handle connection timeouts.
978
+ * Observation: SDK 1.24.0+ connections may hang indefinitely in some environments.
979
+ * This wrapper enforces a timeout and properly uses AbortController to unblock the request.
980
+ */
981
+ fetch: (url, init) => {
982
+ const timeout = 3e4;
983
+ const controller = new AbortController();
984
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
985
+ const signal = init?.signal ? (
986
+ // @ts-ignore: AbortSignal.any is available in Node 20+
987
+ AbortSignal.any ? AbortSignal.any([init.signal, controller.signal]) : controller.signal
988
+ ) : controller.signal;
989
+ return fetch(url, { ...init, signal }).finally(() => clearTimeout(timeoutId));
990
+ }
991
+ };
992
+ if (type === "sse") {
993
+ return new SSEClientTransport(baseUrl, transportOptions);
994
+ } else {
995
+ return new StreamableHTTPClientTransport(baseUrl, transportOptions);
996
+ }
997
+ }
998
+ /**
999
+ * Initializes client components (client, transport, OAuth provider)
1000
+ * Loads missing configuration from Redis session store if needed
1001
+ * This method is idempotent and safe to call multiple times
1002
+ * @private
1003
+ */
1004
+ async initialize() {
1005
+ if (this.client && this.oauthProvider) {
1006
+ return;
1007
+ }
1008
+ this.emitStateChange("INITIALIZING");
1009
+ this.emitProgress("Loading session configuration...");
1010
+ if (!this.serverUrl || !this.callbackUrl || !this.serverId) {
1011
+ const sessionData = await storage.getSession(this.identity, this.sessionId);
1012
+ if (!sessionData) {
1013
+ throw new Error(`Session not found: ${this.sessionId}`);
1014
+ }
1015
+ this.serverUrl = this.serverUrl || sessionData.serverUrl;
1016
+ this.callbackUrl = this.callbackUrl || sessionData.callbackUrl;
1017
+ this.serverName = this.serverName || sessionData.serverName;
1018
+ this.serverId = this.serverId || sessionData.serverId || "unknown";
1019
+ this.headers = this.headers || sessionData.headers;
1020
+ }
1021
+ if (!this.serverUrl || !this.callbackUrl || !this.serverId) {
1022
+ throw new Error("Missing required connection metadata");
1023
+ }
1024
+ const clientMetadata = {
1025
+ client_name: this.clientName || "MCP Assistant",
1026
+ redirect_uris: [this.callbackUrl],
1027
+ token_endpoint_auth_method: this.clientSecret ? "client_secret_basic" : "none",
1028
+ client_uri: this.clientUri || "https://mcp-assistant.in",
1029
+ logo_uri: this.logoUri || "https://mcp-assistant.in/logo.png",
1030
+ policy_uri: this.policyUri || "https://mcp-assistant.in/privacy",
1031
+ ...this.clientId ? { client_id: this.clientId } : {},
1032
+ ...this.clientSecret ? { client_secret: this.clientSecret } : {}
1033
+ };
1034
+ if (!this.oauthProvider) {
1035
+ if (!this.serverId) {
1036
+ throw new Error("serverId required for OAuth provider initialization");
1037
+ }
1038
+ this.oauthProvider = new StorageOAuthClientProvider(
1039
+ this.identity,
1040
+ this.serverId,
1041
+ this.sessionId,
1042
+ clientMetadata.client_name ?? "MCP Assistant",
1043
+ this.callbackUrl,
1044
+ (redirectUrl) => {
1045
+ if (this.onRedirect) {
1046
+ this.onRedirect(redirectUrl);
1047
+ }
1048
+ }
1049
+ );
1050
+ if (this.clientId && this.oauthProvider) {
1051
+ this.oauthProvider.clientId = this.clientId;
1052
+ }
1053
+ }
1054
+ if (!this.client) {
1055
+ this.client = new Client(
1056
+ {
1057
+ name: "mcp-ts-oauth-client",
1058
+ version: "2.0"
1059
+ },
1060
+ { capabilities: {} }
1061
+ );
1062
+ }
1063
+ const existingSession = await storage.getSession(this.identity, this.sessionId);
1064
+ if (!existingSession && this.serverId && this.serverUrl && this.callbackUrl) {
1065
+ console.log(`[MCPClient] Creating initial session ${this.sessionId} for OAuth flow`);
1066
+ await storage.createSession({
1067
+ sessionId: this.sessionId,
1068
+ identity: this.identity,
1069
+ serverId: this.serverId,
1070
+ serverName: this.serverName,
1071
+ serverUrl: this.serverUrl,
1072
+ callbackUrl: this.callbackUrl,
1073
+ transportType: this.transportType || "streamable_http",
1074
+ createdAt: Date.now()
1075
+ }, Math.floor(STATE_EXPIRATION_MS / 1e3));
1076
+ }
1077
+ }
1078
+ /**
1079
+ * Saves current session state to storage
1080
+ * Creates new session if it doesn't exist, updates if it does
1081
+ * @param ttl - Time-to-live in seconds (defaults to 12hr for connected sessions)
1082
+ * @private
1083
+ */
1084
+ async saveSession(ttl = SESSION_TTL_SECONDS) {
1085
+ if (!this.sessionId || !this.serverId || !this.serverUrl || !this.callbackUrl) {
1086
+ return;
1087
+ }
1088
+ const sessionData = {
1089
+ sessionId: this.sessionId,
1090
+ identity: this.identity,
1091
+ serverId: this.serverId,
1092
+ serverName: this.serverName,
1093
+ serverUrl: this.serverUrl,
1094
+ callbackUrl: this.callbackUrl,
1095
+ transportType: this.transportType || "streamable_http",
1096
+ createdAt: Date.now()
1097
+ };
1098
+ const existingSession = await storage.getSession(this.identity, this.sessionId);
1099
+ if (existingSession) {
1100
+ await storage.updateSession(this.identity, this.sessionId, sessionData, ttl);
1101
+ } else {
1102
+ await storage.createSession(sessionData, ttl);
1103
+ }
1104
+ }
1105
+ /**
1106
+ * Try to connect using available transports
1107
+ * @returns The corrected transport type object if successful
1108
+ * @private
1109
+ */
1110
+ async tryConnect() {
1111
+ const transportsToTry = this.transportType ? [this.transportType] : ["streamable_http", "sse"];
1112
+ let lastError;
1113
+ for (const currentType of transportsToTry) {
1114
+ const isLastAttempt = currentType === transportsToTry[transportsToTry.length - 1];
1115
+ try {
1116
+ const transport = this.getTransport(currentType);
1117
+ this.transport = transport;
1118
+ await this.client.connect(transport);
1119
+ return { transportType: currentType };
1120
+ } catch (error) {
1121
+ lastError = error;
1122
+ const isAuthError = error instanceof UnauthorizedError$1 || error instanceof Error && error.message.toLowerCase().includes("unauthorized");
1123
+ if (isAuthError) {
1124
+ throw error;
1125
+ }
1126
+ if (isLastAttempt) {
1127
+ throw error;
1128
+ }
1129
+ const errorMessage = error instanceof Error ? error.message : String(error);
1130
+ this.emitProgress(`Connection attempt with ${currentType} failed: ${errorMessage}. Retrying...`);
1131
+ this._onObservabilityEvent.fire({
1132
+ level: "warn",
1133
+ message: `Transport ${currentType} failed, falling back`,
1134
+ sessionId: this.sessionId,
1135
+ serverId: this.serverId,
1136
+ metadata: {
1137
+ failedTransport: currentType,
1138
+ error: errorMessage
1139
+ },
1140
+ timestamp: Date.now()
1141
+ });
1142
+ }
1143
+ }
1144
+ throw lastError || new Error("No transports available");
1145
+ }
1146
+ /**
1147
+ * Connects to the MCP server
1148
+ * Automatically validates and refreshes OAuth tokens if needed
1149
+ * Saves session to Redis on first successful connection
1150
+ * @throws {UnauthorizedError} When OAuth authorization is required
1151
+ * @throws {Error} When connection fails for other reasons
1152
+ */
1153
+ async connect() {
1154
+ await this.initialize();
1155
+ if (!this.client || !this.oauthProvider) {
1156
+ const error = "Client or OAuth provider not initialized";
1157
+ this.emitError(error, "connection");
1158
+ this.emitStateChange("FAILED");
1159
+ throw new Error(error);
1160
+ }
1161
+ try {
1162
+ this.emitProgress("Validating OAuth tokens...");
1163
+ await this.getValidTokens();
1164
+ this.emitStateChange("CONNECTING");
1165
+ const { transportType } = await this.tryConnect();
1166
+ this.transportType = transportType;
1167
+ this.emitStateChange("CONNECTED");
1168
+ this.emitProgress("Connected successfully");
1169
+ const existingSession = await storage.getSession(this.identity, this.sessionId);
1170
+ if (!existingSession || existingSession.transportType !== this.transportType) {
1171
+ console.log(`[MCPClient] Saving session ${this.sessionId} (new or transport changed)`);
1172
+ await this.saveSession(SESSION_TTL_SECONDS);
1173
+ }
1174
+ } catch (error) {
1175
+ if (error instanceof UnauthorizedError$1 || error instanceof Error && error.message.toLowerCase().includes("unauthorized")) {
1176
+ this.emitStateChange("AUTHENTICATING");
1177
+ console.log(`[MCPClient] Saving session ${this.sessionId} with 10min TTL (OAuth pending)`);
1178
+ await this.saveSession(Math.floor(STATE_EXPIRATION_MS / 1e3));
1179
+ let authUrl = "";
1180
+ if (this.oauthProvider) {
1181
+ authUrl = this.oauthProvider.authUrl || "";
1182
+ }
1183
+ if (this.serverId) {
1184
+ this._onConnectionEvent.fire({
1185
+ type: "auth_required",
1186
+ sessionId: this.sessionId,
1187
+ serverId: this.serverId,
1188
+ authUrl,
1189
+ timestamp: Date.now()
1190
+ });
1191
+ if (authUrl && this.onRedirect) {
1192
+ this.onRedirect(authUrl);
1193
+ }
1194
+ }
1195
+ throw new UnauthorizedError("OAuth authorization required");
1196
+ }
1197
+ const errorMessage = error instanceof Error ? error.message : "Connection failed";
1198
+ this.emitError(errorMessage, "connection");
1199
+ this.emitStateChange("FAILED");
1200
+ throw error;
1201
+ }
1202
+ }
1203
+ /**
1204
+ * Completes OAuth authorization flow by exchanging authorization code for tokens
1205
+ * Creates new authenticated client and transport, then establishes connection
1206
+ * Saves active session to Redis after successful authentication
1207
+ * @param authCode - Authorization code received from OAuth callback
1208
+ */
1209
+ // TODO: needs to be optimized
1210
+ async finishAuth(authCode) {
1211
+ this.emitStateChange("AUTHENTICATING");
1212
+ this.emitProgress("Exchanging authorization code for tokens...");
1213
+ await this.initialize();
1214
+ if (!this.oauthProvider) {
1215
+ const error = "OAuth provider not initialized";
1216
+ this.emitError(error, "auth");
1217
+ this.emitStateChange("FAILED");
1218
+ throw new Error(error);
1219
+ }
1220
+ const transportsToTry = this.transportType ? [this.transportType] : ["streamable_http", "sse"];
1221
+ let lastError;
1222
+ let tokensExchanged = false;
1223
+ for (const currentType of transportsToTry) {
1224
+ const isLastAttempt = currentType === transportsToTry[transportsToTry.length - 1];
1225
+ try {
1226
+ const transport = this.getTransport(currentType);
1227
+ this.transport = transport;
1228
+ if (!tokensExchanged) {
1229
+ await transport.finishAuth(authCode);
1230
+ tokensExchanged = true;
1231
+ } else {
1232
+ this.emitProgress(`Tokens already exchanged, skipping auth step for ${currentType}...`);
1233
+ }
1234
+ this.transportType = currentType;
1235
+ this.emitStateChange("AUTHENTICATED");
1236
+ this.emitProgress("Creating authenticated client...");
1237
+ this.client = new Client(
1238
+ {
1239
+ name: "mcp-ts-oauth-client",
1240
+ version: "2.0"
1241
+ },
1242
+ { capabilities: {} }
1243
+ );
1244
+ this.emitStateChange("CONNECTING");
1245
+ await this.client.connect(this.transport);
1246
+ this.emitStateChange("CONNECTED");
1247
+ console.log(`[MCPClient] Updating session ${this.sessionId} to 12hr TTL (OAuth complete)`);
1248
+ await this.saveSession(SESSION_TTL_SECONDS);
1249
+ return;
1250
+ } catch (error) {
1251
+ lastError = error;
1252
+ const isAuthError = error instanceof UnauthorizedError$1 || error instanceof Error && error.message.toLowerCase().includes("unauthorized");
1253
+ if (isAuthError) {
1254
+ throw error;
1255
+ }
1256
+ const errorMessage = error instanceof Error ? error.message : String(error);
1257
+ if (!tokensExchanged && errorMessage.toLowerCase().includes("invalid authorization code")) {
1258
+ const msg = error instanceof Error ? error.message : "Authentication failed";
1259
+ this.emitError(msg, "auth");
1260
+ this.emitStateChange("FAILED");
1261
+ throw error;
1262
+ }
1263
+ if (isLastAttempt) {
1264
+ const msg = error instanceof Error ? error.message : "Authentication failed";
1265
+ this.emitError(msg, "auth");
1266
+ this.emitStateChange("FAILED");
1267
+ throw error;
1268
+ }
1269
+ this.emitProgress(`Auth attempt with ${currentType} failed: ${errorMessage}. Retrying...`);
1270
+ }
1271
+ }
1272
+ if (lastError) {
1273
+ const errorMessage = lastError instanceof Error ? lastError.message : "Authentication failed";
1274
+ this.emitError(errorMessage, "auth");
1275
+ this.emitStateChange("FAILED");
1276
+ throw lastError;
1277
+ }
1278
+ }
1279
+ /**
1280
+ * Lists all available tools from the connected MCP server
1281
+ * @returns List of tools with their schemas and descriptions
1282
+ * @throws {Error} When client is not connected
1283
+ */
1284
+ async listTools() {
1285
+ if (!this.client) {
1286
+ throw new Error("Not connected to server");
1287
+ }
1288
+ this.emitStateChange("DISCOVERING");
1289
+ try {
1290
+ const request = {
1291
+ method: "tools/list",
1292
+ params: {}
1293
+ };
1294
+ const result = await this.client.request(request, ListToolsResultSchema);
1295
+ if (this.serverId) {
1296
+ this._onConnectionEvent.fire({
1297
+ type: "tools_discovered",
1298
+ sessionId: this.sessionId,
1299
+ serverId: this.serverId,
1300
+ toolCount: result.tools.length,
1301
+ tools: result.tools,
1302
+ timestamp: Date.now()
1303
+ });
1304
+ }
1305
+ this.emitStateChange("READY");
1306
+ this.emitProgress(`Discovered ${result.tools.length} tools`);
1307
+ return result;
1308
+ } catch (error) {
1309
+ const errorMessage = error instanceof Error ? error.message : "Failed to list tools";
1310
+ this.emitError(errorMessage, "validation");
1311
+ this.emitStateChange("FAILED");
1312
+ throw error;
1313
+ }
1314
+ }
1315
+ /**
1316
+ * Executes a tool on the connected MCP server
1317
+ * @param toolName - Name of the tool to execute
1318
+ * @param toolArgs - Arguments to pass to the tool
1319
+ * @returns Tool execution result
1320
+ * @throws {Error} When client is not connected
1321
+ */
1322
+ async callTool(toolName, toolArgs) {
1323
+ if (!this.client) {
1324
+ throw new Error("Not connected to server");
1325
+ }
1326
+ const request = {
1327
+ method: "tools/call",
1328
+ params: {
1329
+ name: toolName,
1330
+ arguments: toolArgs
1331
+ }
1332
+ };
1333
+ try {
1334
+ const result = await this.client.request(request, CallToolResultSchema);
1335
+ this._onObservabilityEvent.fire({
1336
+ type: "mcp:client:tool_call",
1337
+ level: "info",
1338
+ message: `Tool ${toolName} called successfully`,
1339
+ displayMessage: `Called tool ${toolName}`,
1340
+ sessionId: this.sessionId,
1341
+ serverId: this.serverId,
1342
+ payload: {
1343
+ toolName,
1344
+ args: toolArgs
1345
+ },
1346
+ timestamp: Date.now(),
1347
+ id: nanoid()
1348
+ });
1349
+ return result;
1350
+ } catch (error) {
1351
+ const errorMessage = error instanceof Error ? error.message : `Failed to call tool ${toolName}`;
1352
+ this._onObservabilityEvent.fire({
1353
+ type: "mcp:client:error",
1354
+ level: "error",
1355
+ message: errorMessage,
1356
+ displayMessage: `Failed to call tool ${toolName}`,
1357
+ sessionId: this.sessionId,
1358
+ serverId: this.serverId,
1359
+ payload: {
1360
+ errorType: "tool_execution",
1361
+ error: errorMessage,
1362
+ toolName,
1363
+ args: toolArgs
1364
+ },
1365
+ timestamp: Date.now(),
1366
+ id: nanoid()
1367
+ });
1368
+ throw error;
1369
+ }
1370
+ }
1371
+ /**
1372
+ * Lists all available prompts from the connected MCP server
1373
+ * @returns List of available prompts
1374
+ * @throws {Error} When client is not connected
1375
+ */
1376
+ async listPrompts() {
1377
+ if (!this.client) {
1378
+ throw new Error("Not connected to server");
1379
+ }
1380
+ this.emitStateChange("DISCOVERING");
1381
+ try {
1382
+ const request = {
1383
+ method: "prompts/list",
1384
+ params: {}
1385
+ };
1386
+ const result = await this.client.request(request, ListPromptsResultSchema);
1387
+ this.emitStateChange("READY");
1388
+ this.emitProgress(`Discovered ${result.prompts.length} prompts`);
1389
+ return result;
1390
+ } catch (error) {
1391
+ const errorMessage = error instanceof Error ? error.message : "Failed to list prompts";
1392
+ this.emitError(errorMessage, "validation");
1393
+ this.emitStateChange("FAILED");
1394
+ throw error;
1395
+ }
1396
+ }
1397
+ /**
1398
+ * Gets a specific prompt with arguments
1399
+ * @param name - Name of the prompt
1400
+ * @param args - Arguments for the prompt
1401
+ * @returns Prompt content
1402
+ * @throws {Error} When client is not connected
1403
+ */
1404
+ async getPrompt(name, args) {
1405
+ if (!this.client) {
1406
+ throw new Error("Not connected to server");
1407
+ }
1408
+ const request = {
1409
+ method: "prompts/get",
1410
+ params: {
1411
+ name,
1412
+ arguments: args
1413
+ }
1414
+ };
1415
+ return await this.client.request(request, GetPromptResultSchema);
1416
+ }
1417
+ /**
1418
+ * Lists all available resources from the connected MCP server
1419
+ * @returns List of available resources
1420
+ * @throws {Error} When client is not connected
1421
+ */
1422
+ async listResources() {
1423
+ if (!this.client) {
1424
+ throw new Error("Not connected to server");
1425
+ }
1426
+ this.emitStateChange("DISCOVERING");
1427
+ try {
1428
+ const request = {
1429
+ method: "resources/list",
1430
+ params: {}
1431
+ };
1432
+ const result = await this.client.request(request, ListResourcesResultSchema);
1433
+ this.emitStateChange("READY");
1434
+ this.emitProgress(`Discovered ${result.resources.length} resources`);
1435
+ return result;
1436
+ } catch (error) {
1437
+ const errorMessage = error instanceof Error ? error.message : "Failed to list resources";
1438
+ this.emitError(errorMessage, "validation");
1439
+ this.emitStateChange("FAILED");
1440
+ throw error;
1441
+ }
1442
+ }
1443
+ /**
1444
+ * Reads a specific resource
1445
+ * @param uri - URI of the resource to read
1446
+ * @returns Resource content
1447
+ * @throws {Error} When client is not connected
1448
+ */
1449
+ async readResource(uri) {
1450
+ if (!this.client) {
1451
+ throw new Error("Not connected to server");
1452
+ }
1453
+ const request = {
1454
+ method: "resources/read",
1455
+ params: {
1456
+ uri
1457
+ }
1458
+ };
1459
+ return await this.client.request(request, ReadResourceResultSchema);
1460
+ }
1461
+ /**
1462
+ * Refreshes the OAuth access token using the refresh token
1463
+ * Discovers OAuth metadata from server and exchanges refresh token for new access token
1464
+ * @returns True if refresh was successful, false otherwise
1465
+ */
1466
+ async refreshToken() {
1467
+ await this.initialize();
1468
+ if (!this.oauthProvider) {
1469
+ return false;
1470
+ }
1471
+ const tokens = await this.oauthProvider.tokens();
1472
+ if (!tokens || !tokens.refresh_token) {
1473
+ return false;
1474
+ }
1475
+ const clientInformation = await this.oauthProvider.clientInformation();
1476
+ if (!clientInformation) {
1477
+ return false;
1478
+ }
1479
+ try {
1480
+ const resourceMetadata = await discoverOAuthProtectedResourceMetadata(this.serverUrl);
1481
+ const authServerUrl = resourceMetadata?.authorization_servers?.[0] || this.serverUrl;
1482
+ const authMetadata = await discoverAuthorizationServerMetadata(authServerUrl);
1483
+ const newTokens = await refreshAuthorization(authServerUrl, {
1484
+ metadata: authMetadata,
1485
+ clientInformation,
1486
+ refreshToken: tokens.refresh_token
1487
+ });
1488
+ await this.oauthProvider.saveTokens(newTokens);
1489
+ return true;
1490
+ } catch (error) {
1491
+ console.error("[OAuth] Token refresh failed:", error);
1492
+ return false;
1493
+ }
1494
+ }
1495
+ /**
1496
+ * Ensures OAuth tokens are valid, refreshing them if expired
1497
+ * Called automatically by connect() - rarely needs to be called manually
1498
+ * @returns True if valid tokens are available, false otherwise
1499
+ */
1500
+ async getValidTokens() {
1501
+ await this.initialize();
1502
+ if (!this.oauthProvider) {
1503
+ return false;
1504
+ }
1505
+ const tokens = await this.oauthProvider.tokens();
1506
+ if (!tokens) {
1507
+ return false;
1508
+ }
1509
+ if (this.oauthProvider.isTokenExpired()) {
1510
+ return await this.refreshToken();
1511
+ }
1512
+ return true;
1513
+ }
1514
+ /**
1515
+ * Reconnects to MCP server using existing OAuth provider from Redis
1516
+ * Used for session restoration in serverless environments
1517
+ * Creates new client and transport without re-initializing OAuth provider
1518
+ * @throws {Error} When OAuth provider is not initialized
1519
+ */
1520
+ async reconnect() {
1521
+ await this.initialize();
1522
+ if (!this.oauthProvider) {
1523
+ throw new Error("OAuth provider not initialized");
1524
+ }
1525
+ this.client = new Client(
1526
+ {
1527
+ name: "mcp-ts-oauth-client",
1528
+ version: "2.0"
1529
+ },
1530
+ { capabilities: {} }
1531
+ );
1532
+ const tt = this.transportType || "streamable_http";
1533
+ this.transport = this.getTransport(tt);
1534
+ await this.client.connect(this.transport);
1535
+ }
1536
+ /**
1537
+ * Completely removes the session from Redis including all OAuth data
1538
+ * Invalidates credentials and disconnects the client
1539
+ */
1540
+ async clearSession() {
1541
+ try {
1542
+ await this.initialize();
1543
+ } catch (error) {
1544
+ console.warn("[MCPClient] Initialization failed during clearSession:", error);
1545
+ }
1546
+ if (this.oauthProvider) {
1547
+ await this.oauthProvider.invalidateCredentials("all");
1548
+ }
1549
+ await storage.removeSession(this.identity, this.sessionId);
1550
+ this.disconnect();
1551
+ }
1552
+ /**
1553
+ * Checks if the client is currently connected to an MCP server
1554
+ * @returns True if connected, false otherwise
1555
+ */
1556
+ isConnected() {
1557
+ return this.client !== null;
1558
+ }
1559
+ /**
1560
+ * Disconnects from the MCP server and cleans up resources
1561
+ * Does not remove session from Redis - use clearSession() for that
1562
+ */
1563
+ disconnect(reason) {
1564
+ if (this.client) {
1565
+ this.client.close();
1566
+ }
1567
+ this.client = null;
1568
+ this.oauthProvider = null;
1569
+ this.transport = null;
1570
+ if (this.serverId) {
1571
+ this._onConnectionEvent.fire({
1572
+ type: "disconnected",
1573
+ sessionId: this.sessionId,
1574
+ serverId: this.serverId,
1575
+ reason,
1576
+ timestamp: Date.now()
1577
+ });
1578
+ this._onObservabilityEvent.fire({
1579
+ type: "mcp:client:disconnect",
1580
+ level: "info",
1581
+ message: `Disconnected from ${this.serverId}`,
1582
+ sessionId: this.sessionId,
1583
+ serverId: this.serverId,
1584
+ payload: {
1585
+ reason: reason || "unknown"
1586
+ },
1587
+ timestamp: Date.now(),
1588
+ id: nanoid()
1589
+ });
1590
+ }
1591
+ this.emitStateChange("DISCONNECTED");
1592
+ }
1593
+ /**
1594
+ * Dispose of all event emitters
1595
+ * Call this when the client is no longer needed
1596
+ */
1597
+ dispose() {
1598
+ this._onConnectionEvent.dispose();
1599
+ this._onObservabilityEvent.dispose();
1600
+ }
1601
+ /**
1602
+ * Gets the server URL
1603
+ * @returns Server URL or empty string if not set
1604
+ */
1605
+ getServerUrl() {
1606
+ return this.serverUrl || "";
1607
+ }
1608
+ /**
1609
+ * Gets the OAuth callback URL
1610
+ * @returns Callback URL or empty string if not set
1611
+ */
1612
+ getCallbackUrl() {
1613
+ return this.callbackUrl || "";
1614
+ }
1615
+ /**
1616
+ * Gets the transport type being used
1617
+ * @returns Transport type (defaults to 'streamable_http')
1618
+ */
1619
+ getTransportType() {
1620
+ return this.transportType || "streamable_http";
1621
+ }
1622
+ /**
1623
+ * Gets the human-readable server name
1624
+ * @returns Server name or undefined
1625
+ */
1626
+ getServerName() {
1627
+ return this.serverName;
1628
+ }
1629
+ /**
1630
+ * Gets the server ID
1631
+ * @returns Server ID or undefined
1632
+ */
1633
+ getServerId() {
1634
+ return this.serverId;
1635
+ }
1636
+ /**
1637
+ * Gets the session ID
1638
+ * @returns Session ID
1639
+ */
1640
+ getSessionId() {
1641
+ return this.sessionId;
1642
+ }
1643
+ /**
1644
+ * Gets MCP server configuration for all active user sessions
1645
+ * Loads sessions from Redis, validates OAuth tokens, refreshes if expired
1646
+ * Returns ready-to-use configuration with valid auth headers
1647
+ * @param identity - User ID to fetch sessions for
1648
+ * @returns Object keyed by sanitized server labels containing transport, url, headers, etc.
1649
+ * @static
1650
+ */
1651
+ static async getMcpServerConfig(identity) {
1652
+ const mcpConfig = {};
1653
+ const sessions = await storage.getIdentitySessionsData(identity);
1654
+ await Promise.all(
1655
+ sessions.map(async (sessionData) => {
1656
+ const { sessionId } = sessionData;
1657
+ try {
1658
+ if (!sessionData.serverId || !sessionData.transportType || !sessionData.serverUrl || !sessionData.callbackUrl) {
1659
+ await storage.removeSession(identity, sessionId);
1660
+ return;
1661
+ }
1662
+ let headers;
1663
+ try {
1664
+ const client = new _MCPClient({
1665
+ identity,
1666
+ sessionId,
1667
+ serverId: sessionData.serverId,
1668
+ serverUrl: sessionData.serverUrl,
1669
+ callbackUrl: sessionData.callbackUrl,
1670
+ serverName: sessionData.serverName,
1671
+ transportType: sessionData.transportType,
1672
+ headers: sessionData.headers
1673
+ });
1674
+ await client.initialize();
1675
+ const hasValidTokens = await client.getValidTokens();
1676
+ if (hasValidTokens && client.oauthProvider) {
1677
+ const tokens = await client.oauthProvider.tokens();
1678
+ if (tokens?.access_token) {
1679
+ headers = { Authorization: `Bearer ${tokens.access_token}` };
1680
+ }
1681
+ }
1682
+ } catch (error) {
1683
+ console.warn(`[MCP] Failed to get OAuth tokens for ${sessionId}:`, error);
1684
+ }
1685
+ const label = sanitizeServerLabel(
1686
+ sessionData.serverName || sessionData.serverId || "server"
1687
+ );
1688
+ mcpConfig[label] = {
1689
+ transport: sessionData.transportType,
1690
+ url: sessionData.serverUrl,
1691
+ ...sessionData.serverName && {
1692
+ serverName: sessionData.serverName,
1693
+ serverLabel: label
1694
+ },
1695
+ ...headers && { headers }
1696
+ };
1697
+ } catch (error) {
1698
+ await storage.removeSession(identity, sessionId);
1699
+ console.warn(`[MCP] Failed to process session ${sessionId}:`, error);
1700
+ }
1701
+ })
1702
+ );
1703
+ return mcpConfig;
1704
+ }
1705
+ };
1706
+
1707
+ // src/server/mcp/multi-session-client.ts
1708
+ var MultiSessionClient = class {
1709
+ constructor(identity, options = {}) {
1710
+ __publicField(this, "clients", []);
1711
+ __publicField(this, "identity");
1712
+ __publicField(this, "options");
1713
+ this.identity = identity;
1714
+ this.options = {
1715
+ timeout: 15e3,
1716
+ maxRetries: 2,
1717
+ retryDelay: 1e3,
1718
+ ...options
1719
+ };
1720
+ }
1721
+ async getActiveSessions() {
1722
+ const sessions = await storage.getIdentitySessionsData(this.identity);
1723
+ console.log(
1724
+ `[MultiSessionClient] All sessions for ${this.identity}:`,
1725
+ sessions.map((s) => ({ sessionId: s.sessionId, serverId: s.serverId }))
1726
+ );
1727
+ const valid = sessions.filter((s) => s.serverId && s.serverUrl && s.callbackUrl);
1728
+ console.log(`[MultiSessionClient] Filtered valid sessions:`, valid.length);
1729
+ return valid;
1730
+ }
1731
+ async connectInBatches(sessions) {
1732
+ const BATCH_SIZE = 5;
1733
+ for (let i = 0; i < sessions.length; i += BATCH_SIZE) {
1734
+ const batch = sessions.slice(i, i + BATCH_SIZE);
1735
+ await Promise.all(batch.map((session) => this.connectSession(session)));
1736
+ }
1737
+ }
1738
+ async connectSession(session) {
1739
+ const existingClient = this.clients.find((c) => c.getSessionId() === session.sessionId);
1740
+ if (existingClient?.isConnected()) {
1741
+ return;
1742
+ }
1743
+ const maxRetries = this.options.maxRetries ?? 2;
1744
+ const retryDelay = this.options.retryDelay ?? 1e3;
1745
+ let lastError;
1746
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
1747
+ try {
1748
+ const client = await this.createAndConnectClient(session);
1749
+ this.clients.push(client);
1750
+ return;
1751
+ } catch (error) {
1752
+ lastError = error;
1753
+ if (attempt < maxRetries) {
1754
+ await new Promise((resolve) => setTimeout(resolve, retryDelay));
1755
+ }
1756
+ }
1757
+ }
1758
+ console.error(`[MultiSessionClient] Failed to connect to session ${session.sessionId} after ${maxRetries + 1} attempts:`, lastError);
1759
+ }
1760
+ async createAndConnectClient(session) {
1761
+ const client = new MCPClient({
1762
+ identity: this.identity,
1763
+ sessionId: session.sessionId,
1764
+ serverId: session.serverId,
1765
+ serverUrl: session.serverUrl,
1766
+ callbackUrl: session.callbackUrl,
1767
+ serverName: session.serverName,
1768
+ transportType: session.transportType,
1769
+ headers: session.headers
1770
+ });
1771
+ const timeoutMs = this.options.timeout ?? 15e3;
1772
+ const timeoutPromise = new Promise((_, reject) => {
1773
+ setTimeout(() => reject(new Error(`Connection timed out after ${timeoutMs}ms`)), timeoutMs);
1774
+ });
1775
+ await Promise.race([client.connect(), timeoutPromise]);
1776
+ return client;
1777
+ }
1778
+ async connect() {
1779
+ const sessions = await this.getActiveSessions();
1780
+ await this.connectInBatches(sessions);
1781
+ }
1782
+ /**
1783
+ * Returns the array of currently connected clients.
1784
+ */
1785
+ getClients() {
1786
+ return this.clients;
1787
+ }
1788
+ /**
1789
+ * Disconnects all clients.
1790
+ */
1791
+ disconnect() {
1792
+ this.clients.forEach((client) => client.disconnect());
1793
+ this.clients = [];
1794
+ }
1795
+ };
1796
+
1797
+ // src/server/handlers/sse-handler.ts
1798
+ var SSEConnectionManager = class {
1799
+ constructor(options, sendEvent) {
1800
+ this.options = options;
1801
+ this.sendEvent = sendEvent;
1802
+ __publicField(this, "identity");
1803
+ __publicField(this, "clients", /* @__PURE__ */ new Map());
1804
+ __publicField(this, "heartbeatTimer");
1805
+ __publicField(this, "isActive", true);
1806
+ this.identity = options.identity;
1807
+ this.startHeartbeat();
1808
+ }
1809
+ /**
1810
+ * Get resolved client metadata (dynamic > static > defaults)
1811
+ */
1812
+ async getResolvedClientMetadata(request) {
1813
+ let metadata = {};
1814
+ if (this.options.clientDefaults) {
1815
+ metadata = { ...this.options.clientDefaults };
1816
+ }
1817
+ if (this.options.getClientMetadata) {
1818
+ const dynamicMetadata = await this.options.getClientMetadata(request);
1819
+ metadata = { ...metadata, ...dynamicMetadata };
1820
+ }
1821
+ return metadata;
1822
+ }
1823
+ /**
1824
+ * Start heartbeat to keep connection alive
1825
+ */
1826
+ startHeartbeat() {
1827
+ const interval = this.options.heartbeatInterval || 3e4;
1828
+ this.heartbeatTimer = setInterval(() => {
1829
+ if (this.isActive) {
1830
+ this.sendEvent({
1831
+ level: "debug",
1832
+ message: "heartbeat",
1833
+ timestamp: Date.now()
1834
+ });
1835
+ }
1836
+ }, interval);
1837
+ }
1838
+ /**
1839
+ * Handle incoming RPC requests
1840
+ */
1841
+ async handleRequest(request) {
1842
+ try {
1843
+ let result;
1844
+ switch (request.method) {
1845
+ case "getSessions":
1846
+ result = await this.getSessions();
1847
+ break;
1848
+ case "connect":
1849
+ result = await this.connect(request.params);
1850
+ break;
1851
+ case "disconnect":
1852
+ result = await this.disconnect(request.params);
1853
+ break;
1854
+ case "listTools":
1855
+ result = await this.listTools(request.params);
1856
+ break;
1857
+ case "callTool":
1858
+ result = await this.callTool(request.params);
1859
+ break;
1860
+ case "restoreSession":
1861
+ result = await this.restoreSession(request.params);
1862
+ break;
1863
+ case "finishAuth":
1864
+ result = await this.finishAuth(request.params);
1865
+ break;
1866
+ case "listPrompts":
1867
+ result = await this.listPrompts(request.params);
1868
+ break;
1869
+ case "getPrompt":
1870
+ result = await this.getPrompt(request.params);
1871
+ break;
1872
+ case "listResources":
1873
+ result = await this.listResources(request.params);
1874
+ break;
1875
+ case "readResource":
1876
+ result = await this.readResource(request.params);
1877
+ break;
1878
+ default:
1879
+ throw new Error(`Unknown method: ${request.method}`);
1880
+ }
1881
+ this.sendEvent({
1882
+ id: request.id,
1883
+ result
1884
+ });
1885
+ } catch (error) {
1886
+ this.sendEvent({
1887
+ id: request.id,
1888
+ error: {
1889
+ code: RpcErrorCodes.EXECUTION_ERROR,
1890
+ message: error instanceof Error ? error.message : "Unknown error"
1891
+ }
1892
+ });
1893
+ }
1894
+ }
1895
+ /**
1896
+ * Get all user sessions
1897
+ */
1898
+ async getSessions() {
1899
+ const sessions = await storage.getIdentitySessionsData(this.identity);
1900
+ this.sendEvent({
1901
+ level: "debug",
1902
+ message: `Retrieved ${sessions.length} sessions for identity ${this.identity}`,
1903
+ timestamp: Date.now(),
1904
+ metadata: {
1905
+ identity: this.identity,
1906
+ sessionCount: sessions.length,
1907
+ sessions: sessions.map((s) => ({
1908
+ sessionId: s.sessionId,
1909
+ serverId: s.serverId,
1910
+ serverName: s.serverName
1911
+ }))
1912
+ }
1913
+ });
1914
+ return {
1915
+ sessions: sessions.map((s) => ({
1916
+ sessionId: s.sessionId,
1917
+ serverId: s.serverId,
1918
+ serverName: s.serverName,
1919
+ serverUrl: s.serverUrl,
1920
+ transport: s.transportType
1921
+ }))
1922
+ };
1923
+ }
1924
+ /**
1925
+ * Connect to an MCP server
1926
+ */
1927
+ async connect(params) {
1928
+ const { serverId, serverName, serverUrl, callbackUrl, transportType } = params;
1929
+ const existingSessions = await storage.getIdentitySessionsData(this.identity);
1930
+ const duplicate = existingSessions.find(
1931
+ (s) => s.serverId === serverId || s.serverUrl === serverUrl
1932
+ );
1933
+ if (duplicate) {
1934
+ throw new Error(`Connection already exists for server: ${duplicate.serverUrl || duplicate.serverId} (${duplicate.serverName})`);
1935
+ }
1936
+ const sessionId = await storage.generateSessionId();
1937
+ this.emitConnectionEvent({
1938
+ type: "state_changed",
1939
+ sessionId,
1940
+ serverId,
1941
+ serverName,
1942
+ state: "CONNECTING",
1943
+ previousState: "DISCONNECTED",
1944
+ timestamp: Date.now()
1945
+ });
1946
+ try {
1947
+ const clientMetadata = await this.getResolvedClientMetadata();
1948
+ const client = new MCPClient({
1949
+ identity: this.identity,
1950
+ sessionId,
1951
+ serverId,
1952
+ serverName,
1953
+ serverUrl,
1954
+ callbackUrl,
1955
+ transportType,
1956
+ ...clientMetadata,
1957
+ // Spread client metadata (clientName, clientUri, logoUri, policyUri)
1958
+ onRedirect: (authUrl) => {
1959
+ this.emitConnectionEvent({
1960
+ type: "auth_required",
1961
+ sessionId,
1962
+ serverId,
1963
+ authUrl,
1964
+ timestamp: Date.now()
1965
+ });
1966
+ }
1967
+ });
1968
+ this.clients.set(sessionId, client);
1969
+ client.onConnectionEvent((event) => {
1970
+ this.emitConnectionEvent(event);
1971
+ });
1972
+ client.onObservabilityEvent((event) => {
1973
+ this.sendEvent(event);
1974
+ });
1975
+ await client.connect();
1976
+ const tools = await client.listTools();
1977
+ const sessionAfterConnect = await storage.getSession(this.identity, sessionId);
1978
+ console.log(`[SSE Handler] After connect() - Session ${sessionId}:`, {
1979
+ serverId: sessionAfterConnect?.serverId
1980
+ });
1981
+ this.emitConnectionEvent({
1982
+ type: "tools_discovered",
1983
+ sessionId,
1984
+ serverId,
1985
+ toolCount: tools.tools.length,
1986
+ tools: tools.tools,
1987
+ timestamp: Date.now()
1988
+ });
1989
+ return {
1990
+ sessionId,
1991
+ success: true
1992
+ };
1993
+ } catch (error) {
1994
+ this.emitConnectionEvent({
1995
+ type: "error",
1996
+ sessionId,
1997
+ serverId,
1998
+ error: error instanceof Error ? error.message : "Connection failed",
1999
+ errorType: "connection",
2000
+ timestamp: Date.now()
2001
+ });
2002
+ this.clients.delete(sessionId);
2003
+ throw error;
2004
+ }
2005
+ }
2006
+ /**
2007
+ * Disconnect from an MCP server
2008
+ */
2009
+ async disconnect(params) {
2010
+ const { sessionId } = params;
2011
+ const client = this.clients.get(sessionId);
2012
+ if (client) {
2013
+ await client.clearSession();
2014
+ client.disconnect();
2015
+ this.clients.delete(sessionId);
2016
+ } else {
2017
+ await storage.removeSession(this.identity, sessionId);
2018
+ }
2019
+ return { success: true };
2020
+ }
2021
+ /**
2022
+ * Helper to get or restore a client
2023
+ */
2024
+ async getOrCreateClient(sessionId) {
2025
+ let client = this.clients.get(sessionId);
2026
+ if (!client) {
2027
+ client = new MCPClient({
2028
+ identity: this.identity,
2029
+ sessionId
2030
+ });
2031
+ client.onConnectionEvent((event) => {
2032
+ this.emitConnectionEvent(event);
2033
+ });
2034
+ client.onObservabilityEvent((event) => {
2035
+ this.sendEvent(event);
2036
+ });
2037
+ await client.connect();
2038
+ this.clients.set(sessionId, client);
2039
+ }
2040
+ return client;
2041
+ }
2042
+ /**
2043
+ * List tools from a session
2044
+ */
2045
+ async listTools(params) {
2046
+ const { sessionId } = params;
2047
+ const client = await this.getOrCreateClient(sessionId);
2048
+ const result = await client.listTools();
2049
+ return { tools: result.tools };
2050
+ }
2051
+ /**
2052
+ * Call a tool
2053
+ */
2054
+ async callTool(params) {
2055
+ const { sessionId, toolName, toolArgs } = params;
2056
+ const client = await this.getOrCreateClient(sessionId);
2057
+ return await client.callTool(toolName, toolArgs);
2058
+ }
2059
+ /**
2060
+ * Refresh/validate a session
2061
+ */
2062
+ async restoreSession(params) {
2063
+ const { sessionId } = params;
2064
+ this.sendEvent({
2065
+ level: "debug",
2066
+ message: `Starting session refresh for ${sessionId}`,
2067
+ timestamp: Date.now(),
2068
+ metadata: { sessionId, identity: this.identity }
2069
+ });
2070
+ const session = await storage.getSession(this.identity, sessionId);
2071
+ if (!session) {
2072
+ this.sendEvent({
2073
+ level: "error",
2074
+ message: `Session not found: ${sessionId}`,
2075
+ timestamp: Date.now(),
2076
+ metadata: { sessionId, identity: this.identity }
2077
+ });
2078
+ throw new Error("Session not found");
2079
+ }
2080
+ this.sendEvent({
2081
+ level: "debug",
2082
+ message: `Session found in Redis`,
2083
+ timestamp: Date.now(),
2084
+ metadata: {
2085
+ sessionId,
2086
+ serverId: session.serverId,
2087
+ serverName: session.serverName,
2088
+ serverUrl: session.serverUrl,
2089
+ transportType: session.transportType
2090
+ }
2091
+ });
2092
+ this.emitConnectionEvent({
2093
+ type: "state_changed",
2094
+ sessionId,
2095
+ serverId: session.serverId || "unknown",
2096
+ serverName: session.serverName || "Unknown",
2097
+ state: "VALIDATING",
2098
+ previousState: "DISCONNECTED",
2099
+ timestamp: Date.now()
2100
+ });
2101
+ try {
2102
+ const clientMetadata = await this.getResolvedClientMetadata();
2103
+ const client = new MCPClient({
2104
+ identity: this.identity,
2105
+ sessionId,
2106
+ ...clientMetadata
2107
+ // Include metadata for consistency
2108
+ });
2109
+ client.onConnectionEvent((event) => {
2110
+ this.emitConnectionEvent(event);
2111
+ });
2112
+ client.onObservabilityEvent((event) => {
2113
+ this.sendEvent(event);
2114
+ });
2115
+ await client.connect();
2116
+ this.clients.set(sessionId, client);
2117
+ const tools = await client.listTools();
2118
+ this.emitConnectionEvent({
2119
+ type: "tools_discovered",
2120
+ sessionId,
2121
+ serverId: session.serverId || "unknown",
2122
+ toolCount: tools.tools.length,
2123
+ tools: tools.tools,
2124
+ timestamp: Date.now()
2125
+ });
2126
+ return { success: true, toolCount: tools.tools.length };
2127
+ } catch (error) {
2128
+ this.emitConnectionEvent({
2129
+ type: "error",
2130
+ sessionId,
2131
+ serverId: session.serverId || "unknown",
2132
+ error: error instanceof Error ? error.message : "Validation failed",
2133
+ errorType: "validation",
2134
+ timestamp: Date.now()
2135
+ });
2136
+ throw error;
2137
+ }
2138
+ }
2139
+ /**
2140
+ * Complete OAuth authorization
2141
+ */
2142
+ async finishAuth(params) {
2143
+ const { sessionId, code } = params;
2144
+ this.sendEvent({
2145
+ level: "debug",
2146
+ message: `Completing OAuth for session ${sessionId}`,
2147
+ timestamp: Date.now(),
2148
+ metadata: { sessionId, identity: this.identity }
2149
+ });
2150
+ const session = await storage.getSession(this.identity, sessionId);
2151
+ if (!session) {
2152
+ throw new Error("Session not found");
2153
+ }
2154
+ this.emitConnectionEvent({
2155
+ type: "state_changed",
2156
+ sessionId,
2157
+ serverId: session.serverId || "unknown",
2158
+ serverName: session.serverName || "Unknown",
2159
+ state: "AUTHENTICATING",
2160
+ previousState: "DISCONNECTED",
2161
+ timestamp: Date.now()
2162
+ });
2163
+ try {
2164
+ const client = new MCPClient({
2165
+ identity: this.identity,
2166
+ sessionId
2167
+ });
2168
+ client.onConnectionEvent((event) => {
2169
+ this.emitConnectionEvent(event);
2170
+ });
2171
+ await client.finishAuth(code);
2172
+ this.clients.set(sessionId, client);
2173
+ const tools = await client.listTools();
2174
+ this.emitConnectionEvent({
2175
+ type: "tools_discovered",
2176
+ sessionId,
2177
+ serverId: session.serverId || "unknown",
2178
+ toolCount: tools.tools.length,
2179
+ tools: tools.tools,
2180
+ timestamp: Date.now()
2181
+ });
2182
+ return { success: true, toolCount: tools.tools.length };
2183
+ } catch (error) {
2184
+ this.emitConnectionEvent({
2185
+ type: "error",
2186
+ sessionId,
2187
+ serverId: session.serverId || "unknown",
2188
+ error: error instanceof Error ? error.message : "OAuth completion failed",
2189
+ errorType: "auth",
2190
+ timestamp: Date.now()
2191
+ });
2192
+ throw error;
2193
+ }
2194
+ }
2195
+ /**
2196
+ * List prompts from a session
2197
+ */
2198
+ async listPrompts(params) {
2199
+ const { sessionId } = params;
2200
+ const client = await this.getOrCreateClient(sessionId);
2201
+ const result = await client.listPrompts();
2202
+ return { prompts: result.prompts };
2203
+ }
2204
+ /**
2205
+ * Get a specific prompt
2206
+ */
2207
+ async getPrompt(params) {
2208
+ const { sessionId, name, args } = params;
2209
+ const client = await this.getOrCreateClient(sessionId);
2210
+ return await client.getPrompt(name, args);
2211
+ }
2212
+ /**
2213
+ * List resources from a session
2214
+ */
2215
+ async listResources(params) {
2216
+ const { sessionId } = params;
2217
+ const client = await this.getOrCreateClient(sessionId);
2218
+ const result = await client.listResources();
2219
+ return { resources: result.resources };
2220
+ }
2221
+ /**
2222
+ * Read a specific resource
2223
+ */
2224
+ async readResource(params) {
2225
+ const { sessionId, uri } = params;
2226
+ const client = await this.getOrCreateClient(sessionId);
2227
+ return await client.readResource(uri);
2228
+ }
2229
+ /**
2230
+ * Emit connection event
2231
+ */
2232
+ emitConnectionEvent(event) {
2233
+ this.sendEvent(event);
2234
+ }
2235
+ /**
2236
+ * Cleanup and close all connections
2237
+ */
2238
+ dispose() {
2239
+ this.isActive = false;
2240
+ if (this.heartbeatTimer) {
2241
+ clearInterval(this.heartbeatTimer);
2242
+ }
2243
+ for (const client of this.clients.values()) {
2244
+ client.disconnect();
2245
+ }
2246
+ this.clients.clear();
2247
+ }
2248
+ };
2249
+ function createSSEHandler(options) {
2250
+ return async (req, res) => {
2251
+ res.writeHead(200, {
2252
+ "Content-Type": "text/event-stream",
2253
+ "Cache-Control": "no-cache",
2254
+ "Connection": "keep-alive",
2255
+ "Access-Control-Allow-Origin": "*"
2256
+ });
2257
+ sendSSE(res, "connected", { timestamp: Date.now() });
2258
+ const manager = new SSEConnectionManager(options, (event) => {
2259
+ if ("id" in event) {
2260
+ sendSSE(res, "rpc-response", event);
2261
+ } else if ("type" in event && "sessionId" in event) {
2262
+ sendSSE(res, "connection", event);
2263
+ } else {
2264
+ sendSSE(res, "observability", event);
2265
+ }
2266
+ });
2267
+ req.on("close", () => {
2268
+ manager.dispose();
2269
+ });
2270
+ if (req.method === "POST") {
2271
+ let body = "";
2272
+ req.on("data", (chunk) => {
2273
+ body += chunk.toString();
2274
+ });
2275
+ req.on("end", async () => {
2276
+ try {
2277
+ const request = JSON.parse(body);
2278
+ await manager.handleRequest(request);
2279
+ } catch (error) {
2280
+ console.error("[SSE] Error handling request:", error);
2281
+ }
2282
+ });
2283
+ }
2284
+ };
2285
+ }
2286
+ function sendSSE(res, event, data) {
2287
+ res.write(`event: ${event}
2288
+ `);
2289
+ res.write(`data: ${JSON.stringify(data)}
2290
+
2291
+ `);
2292
+ }
2293
+
2294
+ // src/server/handlers/nextjs-handler.ts
2295
+ var managers = /* @__PURE__ */ new Map();
2296
+ function createNextMcpHandler(options = {}) {
2297
+ const {
2298
+ getIdentity = (request) => new URL(request.url).searchParams.get("identity"),
2299
+ getAuthToken = (request) => {
2300
+ const url = new URL(request.url);
2301
+ return url.searchParams.get("token") || request.headers.get("authorization");
2302
+ },
2303
+ authenticate = () => true,
2304
+ heartbeatInterval = 3e4,
2305
+ clientDefaults,
2306
+ getClientMetadata
2307
+ } = options;
2308
+ async function GET(request) {
2309
+ const identity = getIdentity(request);
2310
+ const authToken = getAuthToken(request);
2311
+ if (!identity) {
2312
+ return new Response("Missing identity", { status: 400 });
2313
+ }
2314
+ const isAuthorized = await authenticate(identity, authToken);
2315
+ if (!isAuthorized) {
2316
+ return new Response("Unauthorized", { status: 401 });
2317
+ }
2318
+ const stream = new TransformStream();
2319
+ const writer = stream.writable.getWriter();
2320
+ const encoder = new TextEncoder();
2321
+ const sendSSE2 = (event, data) => {
2322
+ const message = `event: ${event}
2323
+ data: ${JSON.stringify(data)}
2324
+
2325
+ `;
2326
+ writer.write(encoder.encode(message)).catch(() => {
2327
+ });
2328
+ };
2329
+ sendSSE2("connected", { timestamp: Date.now() });
2330
+ const previousManager = managers.get(identity);
2331
+ if (previousManager) {
2332
+ previousManager.dispose();
2333
+ }
2334
+ const resolvedClientMetadata = getClientMetadata ? await getClientMetadata(request) : clientDefaults;
2335
+ const manager = new SSEConnectionManager(
2336
+ {
2337
+ identity,
2338
+ heartbeatInterval,
2339
+ clientDefaults: resolvedClientMetadata
2340
+ // Pass resolved metadata
2341
+ },
2342
+ (event) => {
2343
+ if ("id" in event) {
2344
+ sendSSE2("rpc-response", event);
2345
+ } else if ("type" in event && "sessionId" in event) {
2346
+ sendSSE2("connection", event);
2347
+ } else {
2348
+ sendSSE2("observability", event);
2349
+ }
2350
+ }
2351
+ );
2352
+ managers.set(identity, manager);
2353
+ const abortController = new AbortController();
2354
+ request.signal?.addEventListener("abort", () => {
2355
+ manager.dispose();
2356
+ managers.delete(identity);
2357
+ writer.close().catch(() => {
2358
+ });
2359
+ abortController.abort();
2360
+ });
2361
+ return new Response(stream.readable, {
2362
+ status: 200,
2363
+ headers: {
2364
+ "Content-Type": "text/event-stream",
2365
+ "Cache-Control": "no-cache, no-transform",
2366
+ "Connection": "keep-alive",
2367
+ "X-Accel-Buffering": "no"
2368
+ }
2369
+ });
2370
+ }
2371
+ async function POST(request) {
2372
+ const identity = getIdentity(request);
2373
+ const authToken = getAuthToken(request);
2374
+ if (!identity) {
2375
+ return Response.json({ error: { code: "MISSING_IDENTITY", message: "Missing identity" } }, { status: 400 });
2376
+ }
2377
+ const isAuthorized = await authenticate(identity, authToken);
2378
+ if (!isAuthorized) {
2379
+ return Response.json({ error: { code: "UNAUTHORIZED", message: "Unauthorized" } }, { status: 401 });
2380
+ }
2381
+ try {
2382
+ const body = await request.json();
2383
+ const manager = managers.get(identity);
2384
+ if (!manager) {
2385
+ return Response.json(
2386
+ {
2387
+ error: {
2388
+ code: "NO_CONNECTION",
2389
+ message: "No SSE connection found. Please establish SSE connection first."
2390
+ }
2391
+ },
2392
+ { status: 400 }
2393
+ );
2394
+ }
2395
+ await manager.handleRequest(body);
2396
+ return Response.json({ acknowledged: true });
2397
+ } catch (error) {
2398
+ return Response.json(
2399
+ {
2400
+ error: {
2401
+ code: "EXECUTION_ERROR",
2402
+ message: error instanceof Error ? error.message : "Unknown error"
2403
+ }
2404
+ },
2405
+ { status: 500 }
2406
+ );
2407
+ }
2408
+ }
2409
+ return { GET, POST };
2410
+ }
2411
+
2412
+ export { MCPClient, MultiSessionClient, SSEConnectionManager, StorageOAuthClientProvider, UnauthorizedError, createNextMcpHandler, createSSEHandler, sanitizeServerLabel, storage };
2413
+ //# sourceMappingURL=index.mjs.map
2414
+ //# sourceMappingURL=index.mjs.map