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