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