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