mcp-use 1.34.7 → 1.34.8

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 (38) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/{chunk-6ERGZHCN.js → chunk-4KPCPZWE.js} +1 -1
  3. package/dist/{chunk-XRLI6ZYX.js → chunk-5ZFGXHAU.js} +1 -1
  4. package/dist/{chunk-PJARRPKP.js → chunk-ENBB6Z2J.js} +5 -5
  5. package/dist/{chunk-YGL37XKG.js → chunk-FXO3CJ7N.js} +2 -2
  6. package/dist/{chunk-XILMNNJF.js → chunk-HIRI2UY6.js} +7 -7
  7. package/dist/{chunk-LOSPF4H5.js → chunk-I3P2KX5U.js} +1 -1
  8. package/dist/{chunk-SGT4D4LO.js → chunk-MG47YFCC.js} +1 -1
  9. package/dist/{chunk-QEYINRZL.js → chunk-NCK7SUJD.js} +4 -4
  10. package/dist/{chunk-RRCHCSPQ.js → chunk-RDJKEIOW.js} +2 -2
  11. package/dist/{chunk-E7PWRUQZ.js → chunk-SZYMINCU.js} +1 -1
  12. package/dist/{chunk-ESSZPKPU.js → chunk-TYVQPHZW.js} +1 -1
  13. package/dist/{client-Y2ZVEDQC.js → client-YE5FSLWK.js} +4 -4
  14. package/dist/index.cjs +1 -1
  15. package/dist/index.js +33 -33
  16. package/dist/src/agents/index.cjs +1 -1
  17. package/dist/src/agents/index.js +6 -6
  18. package/dist/src/browser-agent.cjs +1 -1
  19. package/dist/src/browser-agent.js +2 -2
  20. package/dist/src/browser.cjs +1 -1
  21. package/dist/src/browser.js +4 -4
  22. package/dist/src/client.cjs +1 -1
  23. package/dist/src/client.js +6 -6
  24. package/dist/src/react/index.cjs +1 -1
  25. package/dist/src/react/index.js +4 -4
  26. package/dist/src/server/connect-adapter.d.ts.map +1 -1
  27. package/dist/src/server/endpoints/mount-mcp.d.ts.map +1 -1
  28. package/dist/src/server/index.cjs +431 -240
  29. package/dist/src/server/index.js +438 -247
  30. package/dist/src/server/logging.d.ts +2 -1
  31. package/dist/src/server/logging.d.ts.map +1 -1
  32. package/dist/src/server/sessions/streams/redis.d.ts +18 -0
  33. package/dist/src/server/sessions/streams/redis.d.ts.map +1 -1
  34. package/dist/src/version.d.ts +1 -1
  35. package/dist/{stdio-BJKCQSAY.js → stdio-5L34RAVM.js} +4 -4
  36. package/dist/{stdio-3JX4DSGH.js → stdio-RCVXPUI4.js} +2 -2
  37. package/dist/{tool-execution-helpers-YOE4JC6H.js → tool-execution-helpers-IJTPZL55.js} +3 -3
  38. package/package.json +3 -3
@@ -17,7 +17,7 @@ import {
17
17
  findSessionContext,
18
18
  isValidLogLevel,
19
19
  supportsApps
20
- } from "../../chunk-E7PWRUQZ.js";
20
+ } from "../../chunk-SZYMINCU.js";
21
21
  import {
22
22
  getRequestContext,
23
23
  hasRequestContext,
@@ -59,7 +59,8 @@ import {
59
59
  Telemetry,
60
60
  VERSION,
61
61
  getPackageVersion
62
- } from "../../chunk-QEYINRZL.js";
62
+ } from "../../chunk-NCK7SUJD.js";
63
+ import "../../chunk-BXZHMYMM.js";
63
64
  import {
64
65
  fsHelpers,
65
66
  generateUUID,
@@ -69,7 +70,6 @@ import {
69
70
  pathHelpers
70
71
  } from "../../chunk-MTHLLDCX.js";
71
72
  import "../../chunk-QWQYAQCK.js";
72
- import "../../chunk-BXZHMYMM.js";
73
73
  import {
74
74
  __name
75
75
  } from "../../chunk-3GQAWCBQ.js";
@@ -942,27 +942,19 @@ function isExpressMiddleware(middleware) {
942
942
  }
943
943
  const paramCount = middleware.length;
944
944
  const fnString = middleware.toString();
945
- const expressPatterns = [
946
- /\bres\.(send|json|status|end|redirect|render|sendFile|download)\b/,
947
- /\breq\.(body|params|query|cookies|session)\b/,
948
- /\breq\.get\s*\(/,
949
- /\bres\.set\s*\(/,
950
- /\bres\.statusCode\s*=/,
951
- /\bres\.writeHead\s*\(/
945
+ const expressResponsePatterns = [
946
+ /(?<![\w$.])res\.(send|json|status|end|redirect|render|sendFile|download)\b/,
947
+ /(?<![\w$.])res\.set\s*\(/,
948
+ /(?<![\w$.])res\.statusCode\s*=/,
949
+ /(?<![\w$.])res\.writeHead\s*\(/
952
950
  ];
953
- const hasExpressPattern = expressPatterns.some(
951
+ const hasExpressResponsePattern = expressResponsePatterns.some(
954
952
  (pattern) => pattern.test(fnString)
955
953
  );
956
954
  if (paramCount === 3 || paramCount === 4) {
957
- if (hasExpressPattern) {
958
- return true;
959
- }
960
955
  return true;
961
956
  }
962
957
  if (paramCount === 2) {
963
- if (hasExpressPattern) {
964
- return true;
965
- }
966
958
  const honoPatterns = [
967
959
  /\bc\.(req|res|json|text|html|status|header)\b/,
968
960
  /\bc\.get\s*\(/,
@@ -975,7 +967,7 @@ function isExpressMiddleware(middleware) {
975
967
  if (hasHonoPattern) {
976
968
  return false;
977
969
  }
978
- return false;
970
+ return hasExpressResponsePattern;
979
971
  }
980
972
  return false;
981
973
  }
@@ -1020,31 +1012,51 @@ async function adaptConnectMiddleware(connectMiddleware, middlewarePath) {
1020
1012
  ...request.body && { body: request.body }
1021
1013
  });
1022
1014
  const mockResponse = createResponse();
1023
- let responseResolved = false;
1024
- const res = await new Promise((resolve) => {
1015
+ const res = await new Promise((resolve, reject) => {
1016
+ let responseSettled = false;
1017
+ const resolveResponse = /* @__PURE__ */ __name((response) => {
1018
+ if (responseSettled) return;
1019
+ responseSettled = true;
1020
+ resolve(response);
1021
+ }, "resolveResponse");
1022
+ const rejectResponse = /* @__PURE__ */ __name((error2) => {
1023
+ if (responseSettled) return;
1024
+ responseSettled = true;
1025
+ reject(error2);
1026
+ }, "rejectResponse");
1025
1027
  const originalEnd = mockResponse.end.bind(mockResponse);
1026
1028
  mockResponse.end = (...args) => {
1027
- const result = originalEnd(...args);
1028
- if (!responseResolved && mockResponse.writableEnded) {
1029
- responseResolved = true;
1030
- const statusCode = mockResponse.statusCode;
1031
- const noBodyStatuses = [204, 304];
1032
- const responseBody = noBodyStatuses.includes(statusCode) ? null : mockResponse._getData() || mockResponse._getBuffer() || null;
1033
- const connectResponse = new Response(responseBody, {
1034
- status: statusCode,
1035
- statusText: mockResponse.statusMessage,
1036
- headers: mockResponse.getHeaders()
1037
- });
1038
- resolve(connectResponse);
1029
+ try {
1030
+ const result = originalEnd(...args);
1031
+ if (!responseSettled && mockResponse.writableEnded) {
1032
+ const statusCode = mockResponse.statusCode;
1033
+ const noBodyStatuses = [204, 304];
1034
+ const responseBody = noBodyStatuses.includes(statusCode) ? null : mockResponse._getData() || mockResponse._getBuffer() || null;
1035
+ const connectResponse = new Response(responseBody, {
1036
+ status: statusCode,
1037
+ statusText: mockResponse.statusMessage,
1038
+ headers: mockResponse.getHeaders()
1039
+ });
1040
+ resolveResponse(connectResponse);
1041
+ }
1042
+ return result;
1043
+ } catch (error2) {
1044
+ rejectResponse(error2);
1045
+ return mockResponse;
1039
1046
  }
1040
- return result;
1041
1047
  };
1042
- connectMiddleware(mockRequest, mockResponse, () => {
1043
- if (!responseResolved && !mockResponse.writableEnded) {
1044
- responseResolved = true;
1048
+ const connectNext = /* @__PURE__ */ __name((error2) => {
1049
+ if (responseSettled) return;
1050
+ if (error2) {
1051
+ rejectResponse(error2);
1052
+ return;
1053
+ }
1054
+ if (mockResponse.writableEnded) return;
1055
+ try {
1045
1056
  const statusCode = mockResponse.statusCode;
1046
1057
  const noBodyStatuses = [204, 304];
1047
- const responseBody = noBodyStatuses.includes(statusCode) ? null : mockResponse._getData() || mockResponse._getBuffer() || null;
1058
+ const buffer = mockResponse._getBuffer();
1059
+ const responseBody = noBodyStatuses.includes(statusCode) ? null : mockResponse._getData() || (buffer.length > 0 ? buffer : null);
1048
1060
  const preparedHeaders = c.newResponse(null, 204, {}).headers;
1049
1061
  for (const key of [...preparedHeaders.keys()]) {
1050
1062
  if (preparedHeaders.has(key)) {
@@ -1065,14 +1077,23 @@ async function adaptConnectMiddleware(connectMiddleware, middlewarePath) {
1065
1077
  }
1066
1078
  c.status(statusCode);
1067
1079
  if (noBodyStatuses.includes(statusCode)) {
1068
- resolve(c.newResponse(null, statusCode));
1080
+ resolveResponse(c.newResponse(null, statusCode));
1069
1081
  } else if (responseBody) {
1070
- resolve(c.body(responseBody));
1082
+ resolveResponse(c.body(responseBody));
1071
1083
  } else {
1072
- resolve(void 0);
1084
+ resolveResponse(void 0);
1073
1085
  }
1086
+ } catch (error3) {
1087
+ rejectResponse(error3);
1074
1088
  }
1075
- });
1089
+ }, "connectNext");
1090
+ try {
1091
+ Promise.resolve(
1092
+ connectMiddleware(mockRequest, mockResponse, connectNext)
1093
+ ).catch(rejectResponse);
1094
+ } catch (error2) {
1095
+ rejectResponse(error2);
1096
+ }
1076
1097
  });
1077
1098
  if (res) {
1078
1099
  c.res = res;
@@ -3332,7 +3353,7 @@ function registerPrompt(promptDefinition, callback) {
3332
3353
  }
3333
3354
  const wrappedCallback = /* @__PURE__ */ __name(async (params, extra) => {
3334
3355
  const { getRequestContext: getRequestContext2, runWithContext: runWithContext2 } = await import("../../context-storage-KMOAHEL7.js");
3335
- const { findSessionContext: findSessionContext2, createClientCapabilityChecker } = await import("../../tool-execution-helpers-YOE4JC6H.js");
3356
+ const { findSessionContext: findSessionContext2, createClientCapabilityChecker } = await import("../../tool-execution-helpers-IJTPZL55.js");
3336
3357
  const initialRequestContext = getRequestContext2();
3337
3358
  const sessions = this.sessions || /* @__PURE__ */ new Map();
3338
3359
  const { requestContext, session } = findSessionContext2(
@@ -3537,7 +3558,7 @@ function registerResource(resourceDefinition, callback) {
3537
3558
  const explicitMimeType = resourceDefinition.mimeType;
3538
3559
  const wrappedCallback = /* @__PURE__ */ __name(async () => {
3539
3560
  const { getRequestContext: getRequestContext2, runWithContext: runWithContext2 } = await import("../../context-storage-KMOAHEL7.js");
3540
- const { findSessionContext: findSessionContext2, createClientCapabilityChecker } = await import("../../tool-execution-helpers-YOE4JC6H.js");
3561
+ const { findSessionContext: findSessionContext2, createClientCapabilityChecker } = await import("../../tool-execution-helpers-IJTPZL55.js");
3541
3562
  const initialRequestContext = getRequestContext2();
3542
3563
  const sessions = this.sessions || /* @__PURE__ */ new Map();
3543
3564
  const { requestContext, session } = findSessionContext2(
@@ -3625,7 +3646,7 @@ function registerResourceTemplate(resourceTemplateDefinition, callback) {
3625
3646
  async (uri) => {
3626
3647
  const params = this.parseTemplateUri(uriTemplate, uri.toString());
3627
3648
  const { getRequestContext: getRequestContext2, runWithContext: runWithContext2 } = await import("../../context-storage-KMOAHEL7.js");
3628
- const { findSessionContext: findSessionContext2 } = await import("../../tool-execution-helpers-YOE4JC6H.js");
3649
+ const { findSessionContext: findSessionContext2 } = await import("../../tool-execution-helpers-IJTPZL55.js");
3629
3650
  const initialRequestContext = getRequestContext2();
3630
3651
  const sessions = this.sessions || /* @__PURE__ */ new Map();
3631
3652
  const { requestContext } = findSessionContext2(
@@ -5099,11 +5120,17 @@ var RedisStreamManager = class {
5099
5120
  * Key: sessionId, Value: controller
5100
5121
  */
5101
5122
  localControllers = /* @__PURE__ */ new Map();
5123
+ streamGenerations = /* @__PURE__ */ new Map();
5102
5124
  /**
5103
5125
  * Map of heartbeat intervals for keeping sessions alive
5104
5126
  * Key: sessionId, Value: interval timer
5105
5127
  */
5106
5128
  heartbeats = /* @__PURE__ */ new Map();
5129
+ // Track attempted subscriptions separately from controllers: a disconnected
5130
+ // controller or a rejected subscribe() must not lose its cleanup ownership.
5131
+ sessionSubscriptions = /* @__PURE__ */ new Map();
5132
+ sessionOperations = /* @__PURE__ */ new Map();
5133
+ closing = false;
5107
5134
  /**
5108
5135
  * Unique identifier for this server instance, used for request/response routing.
5109
5136
  */
@@ -5116,6 +5143,7 @@ var RedisStreamManager = class {
5116
5143
  * Whether the server-level response channel subscription is active.
5117
5144
  */
5118
5145
  serverChannelSubscribed = false;
5146
+ serverChannelSubscription;
5119
5147
  constructor(config) {
5120
5148
  this.pubSubClient = config.pubSubClient;
5121
5149
  this.client = config.client;
@@ -5140,90 +5168,265 @@ var RedisStreamManager = class {
5140
5168
  getActiveSessionsKey() {
5141
5169
  return `${this.prefix}active`;
5142
5170
  }
5143
- /**
5144
- * Register an active SSE stream and subscribe to Redis channel.
5145
- * Idempotent: if the session already has a subscription, it is cleaned up first.
5146
- */
5147
- async create(sessionId, controller) {
5171
+ withSessionOperation(sessionId, operation) {
5172
+ const previous = this.sessionOperations.get(sessionId) ?? Promise.resolve();
5173
+ const result = previous.then(operation);
5174
+ const settled = result.then(
5175
+ () => {
5176
+ },
5177
+ () => {
5178
+ }
5179
+ );
5180
+ this.sessionOperations.set(sessionId, settled);
5181
+ void settled.then(() => {
5182
+ if (this.sessionOperations.get(sessionId) === settled) {
5183
+ this.sessionOperations.delete(sessionId);
5184
+ }
5185
+ });
5186
+ return result;
5187
+ }
5188
+ clearLocalStream(sessionId) {
5189
+ const heartbeat = this.heartbeats.get(sessionId);
5190
+ if (heartbeat) clearInterval(heartbeat);
5191
+ this.heartbeats.delete(sessionId);
5192
+ const controller = this.localControllers.get(sessionId);
5193
+ this.localControllers.delete(sessionId);
5194
+ this.streamGenerations.delete(sessionId);
5195
+ this.closeController(controller);
5196
+ }
5197
+ closeController(controller) {
5148
5198
  try {
5149
- if (this.localControllers.has(sessionId)) {
5150
- const oldHeartbeat = this.heartbeats.get(sessionId);
5151
- if (oldHeartbeat) {
5152
- clearInterval(oldHeartbeat);
5153
- this.heartbeats.delete(sessionId);
5154
- }
5155
- const channel2 = this.getChannel(sessionId);
5156
- const deleteChannel2 = `delete:${channel2}`;
5157
- try {
5158
- await this.pubSubClient.unsubscribe?.(channel2);
5159
- await this.pubSubClient.unsubscribe?.(deleteChannel2);
5160
- } catch {
5161
- }
5199
+ controller?.close();
5200
+ } catch {
5201
+ }
5202
+ }
5203
+ async unsubscribeSession(sessionId) {
5204
+ const channels = this.sessionSubscriptions.get(sessionId);
5205
+ if (!channels?.size) return;
5206
+ if (!this.pubSubClient.unsubscribe) {
5207
+ throw new Error(
5208
+ "[RedisStreamManager] Redis client does not support unsubscribe method"
5209
+ );
5210
+ }
5211
+ const results = await Promise.allSettled(
5212
+ Array.from(channels, async (channel) => {
5213
+ await this.pubSubClient.unsubscribe(channel);
5214
+ channels.delete(channel);
5215
+ })
5216
+ );
5217
+ if (!channels.size) this.sessionSubscriptions.delete(sessionId);
5218
+ this.throwCleanupError(results);
5219
+ }
5220
+ throwCleanupError(results) {
5221
+ const failed = results.find((result) => result.status === "rejected");
5222
+ if (failed?.status === "rejected") throw failed.reason;
5223
+ }
5224
+ async markAvailable(sessionId) {
5225
+ const key = this.getAvailableKey(sessionId);
5226
+ const ttl = this.heartbeatInterval * 2;
5227
+ if (this.client.setEx) {
5228
+ await this.client.setEx(key, ttl, "active");
5229
+ } else if (this.client.setex) {
5230
+ await this.client.setex(key, ttl, "active");
5231
+ } else {
5232
+ await this.client.set(key, "active", { EX: ttl });
5233
+ if (this.client.expire) await this.client.expire(key, ttl);
5234
+ }
5235
+ if (this.client.sAdd) {
5236
+ await this.client.sAdd(this.getActiveSessionsKey(), sessionId);
5237
+ await this.client.expire?.(this.getActiveSessionsKey(), ttl);
5238
+ }
5239
+ }
5240
+ async removeAvailability(sessionId) {
5241
+ const results = await Promise.allSettled([
5242
+ Promise.resolve().then(
5243
+ () => this.client.del(this.getAvailableKey(sessionId))
5244
+ ),
5245
+ Promise.resolve().then(
5246
+ () => this.client.sRem?.(this.getActiveSessionsKey(), sessionId)
5247
+ )
5248
+ ]);
5249
+ this.throwCleanupError(results);
5250
+ }
5251
+ async removeStream(sessionId, notifyOtherServers, cleanupAvailability = true) {
5252
+ this.clearLocalStream(sessionId);
5253
+ const results = await Promise.allSettled([
5254
+ this.unsubscribeSession(sessionId),
5255
+ cleanupAvailability ? this.removeAvailability(sessionId) : Promise.resolve()
5256
+ ]);
5257
+ if (notifyOtherServers) {
5258
+ results.push(
5259
+ ...await Promise.allSettled([
5260
+ Promise.resolve().then(() => {
5261
+ if (!this.client.publish) {
5262
+ throw new Error(
5263
+ "[RedisStreamManager] Redis client does not support publish method"
5264
+ );
5265
+ }
5266
+ return this.client.publish(
5267
+ `delete:${this.getChannel(sessionId)}`,
5268
+ ""
5269
+ );
5270
+ })
5271
+ ])
5272
+ );
5273
+ }
5274
+ this.throwCleanupError(results);
5275
+ }
5276
+ // These factories have no enclosing controller or handler argument. Redis
5277
+ // may retain their callbacks after a failed unsubscribe, so the callbacks
5278
+ // must resolve controllers/handlers through the manager rather than capture
5279
+ // a registration operation's context.
5280
+ createMessageCallback(sessionId, generation) {
5281
+ return (message) => {
5282
+ if (this.streamGenerations.get(sessionId) !== generation) return;
5283
+ const controller = this.localControllers.get(sessionId);
5284
+ if (!controller) return;
5285
+ try {
5286
+ controller.enqueue(this.textEncoder.encode(message));
5287
+ } catch {
5288
+ this.clearLocalStream(sessionId);
5289
+ this.cleanupDisconnectedStream(sessionId);
5162
5290
  }
5163
- this.localControllers.set(sessionId, controller);
5164
- const availableKey = this.getAvailableKey(sessionId);
5165
- await this.client.set(availableKey, "active");
5166
- if (this.client.expire) {
5167
- await this.client.expire(availableKey, this.heartbeatInterval * 2);
5291
+ };
5292
+ }
5293
+ cleanupDisconnectedStream(sessionId) {
5294
+ void this.withSessionOperation(sessionId, async () => {
5295
+ if (!this.localControllers.has(sessionId)) {
5296
+ await this.unsubscribeSession(sessionId);
5168
5297
  }
5169
- const activeSessionsKey = this.getActiveSessionsKey();
5170
- if (this.client.sAdd) {
5171
- await this.client.sAdd(activeSessionsKey, sessionId);
5298
+ }).catch((error2) => {
5299
+ console.warn(
5300
+ `[RedisStreamManager] Failed to unsubscribe disconnected stream ${sessionId}:`,
5301
+ error2
5302
+ );
5303
+ });
5304
+ }
5305
+ createDeleteCallback(sessionId, generation) {
5306
+ return () => {
5307
+ if (this.streamGenerations.get(sessionId) !== generation) return;
5308
+ void this.withSessionOperation(
5309
+ sessionId,
5310
+ () => this.removeStream(sessionId, false)
5311
+ ).catch((error2) => {
5312
+ console.warn(
5313
+ `[RedisStreamManager] Failed to clean up deleted stream ${sessionId}:`,
5314
+ error2
5315
+ );
5316
+ });
5317
+ };
5318
+ }
5319
+ startHeartbeat(sessionId) {
5320
+ const availableKey = this.getAvailableKey(sessionId);
5321
+ const activeSessionsKey = this.getActiveSessionsKey();
5322
+ const heartbeat = setInterval(async () => {
5323
+ try {
5172
5324
  if (this.client.expire) {
5325
+ await this.client.expire(availableKey, this.heartbeatInterval * 2);
5173
5326
  await this.client.expire(
5174
5327
  activeSessionsKey,
5175
5328
  this.heartbeatInterval * 2
5176
5329
  );
5177
5330
  }
5331
+ } catch (error2) {
5332
+ console.warn(
5333
+ `[RedisStreamManager] Heartbeat failed for session ${sessionId}:`,
5334
+ error2
5335
+ );
5178
5336
  }
5179
- const heartbeat = setInterval(async () => {
5180
- try {
5181
- if (this.client.expire) {
5182
- await this.client.expire(availableKey, this.heartbeatInterval * 2);
5183
- const activeSessionsKey2 = this.getActiveSessionsKey();
5184
- await this.client.expire(
5185
- activeSessionsKey2,
5186
- this.heartbeatInterval * 2
5187
- );
5188
- }
5189
- } catch (error2) {
5190
- console.warn(
5191
- `[RedisStreamManager] Heartbeat failed for session ${sessionId}:`,
5192
- error2
5337
+ }, this.heartbeatInterval * 1e3);
5338
+ this.heartbeats.set(sessionId, heartbeat);
5339
+ }
5340
+ createForwardedResponseCallback() {
5341
+ return (raw) => {
5342
+ try {
5343
+ const { response, sessionId } = JSON.parse(raw);
5344
+ this.forwardedResponseHandler?.(response, sessionId);
5345
+ } catch (error2) {
5346
+ console.warn(
5347
+ `[RedisStreamManager] Failed to parse forwarded response:`,
5348
+ error2
5349
+ );
5350
+ }
5351
+ };
5352
+ }
5353
+ /**
5354
+ * Register an active SSE stream and subscribe to Redis channel.
5355
+ * Idempotent: if the session already has a subscription, it is cleaned up first.
5356
+ */
5357
+ async create(sessionId, controller) {
5358
+ return this.withSessionOperation(sessionId, async () => {
5359
+ if (this.closing) {
5360
+ this.closeController(controller);
5361
+ throw new Error("[RedisStreamManager] Stream manager is closed");
5362
+ }
5363
+ if (this.localControllers.get(sessionId) === controller) return;
5364
+ this.clearLocalStream(sessionId);
5365
+ let controllerRegistered = false;
5366
+ try {
5367
+ await this.unsubscribeSession(sessionId);
5368
+ if (this.closing) {
5369
+ throw new Error("[RedisStreamManager] Stream manager is closed");
5370
+ }
5371
+ this.localControllers.set(sessionId, controller);
5372
+ controllerRegistered = true;
5373
+ const generation = Symbol(sessionId);
5374
+ this.streamGenerations.set(sessionId, generation);
5375
+ if (!this.pubSubClient.subscribe) {
5376
+ throw new Error(
5377
+ "[RedisStreamManager] Redis client does not support subscribe method"
5193
5378
  );
5194
5379
  }
5195
- }, this.heartbeatInterval * 1e3);
5196
- this.heartbeats.set(sessionId, heartbeat);
5197
- const channel = this.getChannel(sessionId);
5198
- if (!this.pubSubClient.subscribe) {
5199
- throw new Error(
5200
- "[RedisStreamManager] Redis client does not support subscribe method"
5380
+ const channels = /* @__PURE__ */ new Set();
5381
+ this.sessionSubscriptions.set(sessionId, channels);
5382
+ const channel = this.getChannel(sessionId);
5383
+ channels.add(channel);
5384
+ await this.pubSubClient.subscribe(
5385
+ channel,
5386
+ this.createMessageCallback(sessionId, generation)
5201
5387
  );
5202
- }
5203
- await this.pubSubClient.subscribe(channel, (message) => {
5204
- const localController = this.localControllers.get(sessionId);
5205
- if (localController) {
5206
- try {
5207
- localController.enqueue(this.textEncoder.encode(message));
5208
- } catch {
5209
- this.localControllers.delete(sessionId);
5388
+ const deleteChannel = `delete:${channel}`;
5389
+ channels.add(deleteChannel);
5390
+ await this.pubSubClient.subscribe(
5391
+ deleteChannel,
5392
+ this.createDeleteCallback(sessionId, generation)
5393
+ );
5394
+ if (this.closing || this.streamGenerations.get(sessionId) !== generation) {
5395
+ throw new Error(
5396
+ "[RedisStreamManager] Stream closed during registration"
5397
+ );
5398
+ }
5399
+ await this.markAvailable(sessionId);
5400
+ if (this.closing || this.streamGenerations.get(sessionId) !== generation) {
5401
+ throw new Error(
5402
+ "[RedisStreamManager] Stream closed during registration"
5403
+ );
5404
+ }
5405
+ this.startHeartbeat(sessionId);
5406
+ console.log(
5407
+ `[RedisStreamManager] Created stream for session ${sessionId}`
5408
+ );
5409
+ } catch (error2) {
5410
+ this.clearLocalStream(sessionId);
5411
+ if (!controllerRegistered) this.closeController(controller);
5412
+ const cleanups = await Promise.allSettled([
5413
+ this.unsubscribeSession(sessionId)
5414
+ ]);
5415
+ for (const cleanup of cleanups) {
5416
+ if (cleanup.status === "rejected") {
5417
+ console.warn(
5418
+ `[RedisStreamManager] Failed to roll back stream ${sessionId}:`,
5419
+ cleanup.reason
5420
+ );
5210
5421
  }
5211
5422
  }
5212
- });
5213
- const deleteChannel = `delete:${this.getChannel(sessionId)}`;
5214
- await this.pubSubClient.subscribe(deleteChannel, async () => {
5215
- await this.delete(sessionId);
5216
- });
5217
- console.log(
5218
- `[RedisStreamManager] Created stream for session ${sessionId}`
5219
- );
5220
- } catch (error2) {
5221
- console.error(
5222
- `[RedisStreamManager] Error creating stream for ${sessionId}:`,
5223
- error2
5224
- );
5225
- throw error2;
5226
- }
5423
+ console.error(
5424
+ `[RedisStreamManager] Error creating stream for ${sessionId}:`,
5425
+ error2
5426
+ );
5427
+ throw error2;
5428
+ }
5429
+ });
5227
5430
  }
5228
5431
  /**
5229
5432
  * Send data to sessions via Redis Pub/Sub
@@ -5241,6 +5444,22 @@ var RedisStreamManager = class {
5241
5444
  if (this.client.sMembers) {
5242
5445
  const sessionIds2 = await this.client.sMembers(activeSessionsKey);
5243
5446
  for (const sessionId of sessionIds2) {
5447
+ const availableKey = this.getAvailableKey(sessionId);
5448
+ if (await this.client.exists(availableKey) !== 1) {
5449
+ await this.client.sRem?.(activeSessionsKey, sessionId);
5450
+ let available;
5451
+ try {
5452
+ available = await this.client.exists(availableKey);
5453
+ } catch (error2) {
5454
+ try {
5455
+ await this.client.sAdd?.(activeSessionsKey, sessionId);
5456
+ } catch {
5457
+ }
5458
+ throw error2;
5459
+ }
5460
+ if (available !== 1) continue;
5461
+ await this.client.sAdd?.(activeSessionsKey, sessionId);
5462
+ }
5244
5463
  const channel = this.getChannel(sessionId);
5245
5464
  if (!this.client.publish) {
5246
5465
  throw new Error(
@@ -5283,53 +5502,20 @@ var RedisStreamManager = class {
5283
5502
  * Remove an active SSE stream
5284
5503
  */
5285
5504
  async delete(sessionId) {
5286
- try {
5287
- const heartbeat = this.heartbeats.get(sessionId);
5288
- if (heartbeat) {
5289
- clearInterval(heartbeat);
5290
- this.heartbeats.delete(sessionId);
5291
- }
5292
- const channel = this.getChannel(sessionId);
5293
- const deleteChannel = `delete:${channel}`;
5294
- if (!this.pubSubClient.unsubscribe) {
5295
- throw new Error(
5296
- "[RedisStreamManager] Redis client does not support unsubscribe method"
5505
+ return this.withSessionOperation(sessionId, async () => {
5506
+ try {
5507
+ await this.removeStream(sessionId, true);
5508
+ console.log(
5509
+ `[RedisStreamManager] Deleted stream for session ${sessionId}`
5297
5510
  );
5298
- }
5299
- await this.pubSubClient.unsubscribe(channel);
5300
- await this.pubSubClient.unsubscribe(deleteChannel);
5301
- if (!this.client.publish) {
5302
- throw new Error(
5303
- "[RedisStreamManager] Redis client does not support publish method"
5511
+ } catch (error2) {
5512
+ console.error(
5513
+ `[RedisStreamManager] Error deleting stream for ${sessionId}:`,
5514
+ error2
5304
5515
  );
5516
+ throw error2;
5305
5517
  }
5306
- await this.client.publish(deleteChannel, "");
5307
- await this.client.del(this.getAvailableKey(sessionId));
5308
- const activeSessionsKey = this.getActiveSessionsKey();
5309
- if (this.client.sRem) {
5310
- await this.client.sRem(activeSessionsKey, sessionId);
5311
- }
5312
- const controller = this.localControllers.get(sessionId);
5313
- if (controller) {
5314
- try {
5315
- controller.close();
5316
- } catch (error2) {
5317
- console.debug(
5318
- `[RedisStreamManager] Controller already closed for ${sessionId}`
5319
- );
5320
- }
5321
- this.localControllers.delete(sessionId);
5322
- }
5323
- console.log(
5324
- `[RedisStreamManager] Deleted stream for session ${sessionId}`
5325
- );
5326
- } catch (error2) {
5327
- console.error(
5328
- `[RedisStreamManager] Error deleting stream for ${sessionId}:`,
5329
- error2
5330
- );
5331
- throw error2;
5332
- }
5518
+ });
5333
5519
  }
5334
5520
  /**
5335
5521
  * Check if a session has an active stream (on ANY server)
@@ -5351,31 +5537,42 @@ var RedisStreamManager = class {
5351
5537
  * Close all connections and cleanup
5352
5538
  */
5353
5539
  async close() {
5354
- try {
5355
- for (const heartbeat of this.heartbeats.values()) {
5356
- clearInterval(heartbeat);
5357
- }
5358
- this.heartbeats.clear();
5359
- const activeSessionsKey = this.getActiveSessionsKey();
5360
- const sessionIdsToCleanup = Array.from(this.localControllers.keys());
5361
- for (const sessionId of sessionIdsToCleanup) {
5362
- await this.client.del(this.getAvailableKey(sessionId));
5363
- if (this.client.sRem) {
5364
- await this.client.sRem(activeSessionsKey, sessionId);
5365
- }
5366
- }
5367
- for (const controller of this.localControllers.values()) {
5368
- try {
5369
- controller.close();
5370
- } catch (error2) {
5540
+ this.closing = true;
5541
+ this.forwardedResponseHandler = void 0;
5542
+ const sessionIds = /* @__PURE__ */ new Set([
5543
+ ...this.localControllers.keys(),
5544
+ ...this.heartbeats.keys(),
5545
+ ...this.sessionSubscriptions.keys(),
5546
+ ...this.sessionOperations.keys()
5547
+ ]);
5548
+ for (const sessionId of sessionIds) this.clearLocalStream(sessionId);
5549
+ const results = await Promise.allSettled([
5550
+ ...Array.from(
5551
+ sessionIds,
5552
+ (sessionId) => this.withSessionOperation(
5553
+ sessionId,
5554
+ () => (
5555
+ // Shared availability expires after shutdown; deleting it here
5556
+ // could invalidate another manager serving the same session.
5557
+ this.removeStream(sessionId, false, false)
5558
+ )
5559
+ )
5560
+ ),
5561
+ Promise.resolve().then(async () => {
5562
+ await this.serverChannelSubscription;
5563
+ if (this.serverChannelSubscribed) {
5564
+ if (!this.pubSubClient.unsubscribe) {
5565
+ throw new Error(
5566
+ "[RedisStreamManager] Redis client does not support unsubscribe method"
5567
+ );
5568
+ }
5569
+ await this.pubSubClient.unsubscribe(this.getServerChannel());
5570
+ this.serverChannelSubscribed = false;
5371
5571
  }
5372
- }
5373
- this.localControllers.clear();
5374
- console.log(`[RedisStreamManager] Closed all streams`);
5375
- } catch (error2) {
5376
- console.error(`[RedisStreamManager] Error during close:`, error2);
5377
- throw error2;
5378
- }
5572
+ })
5573
+ ]);
5574
+ this.throwCleanupError(results);
5575
+ console.log(`[RedisStreamManager] Closed all streams`);
5379
5576
  }
5380
5577
  /**
5381
5578
  * Get count of active local streams on this server instance
@@ -5440,9 +5637,9 @@ var RedisStreamManager = class {
5440
5637
  * Subscribes to this server's dedicated Pub/Sub channel.
5441
5638
  */
5442
5639
  onForwardedResponse(handler) {
5640
+ if (this.closing) return;
5443
5641
  this.forwardedResponseHandler = handler;
5444
5642
  if (this.serverChannelSubscribed) return;
5445
- this.serverChannelSubscribed = true;
5446
5643
  const serverChannel = this.getServerChannel();
5447
5644
  if (!this.pubSubClient.subscribe) {
5448
5645
  console.warn(
@@ -5450,17 +5647,21 @@ var RedisStreamManager = class {
5450
5647
  );
5451
5648
  return;
5452
5649
  }
5453
- this.pubSubClient.subscribe(serverChannel, (raw) => {
5650
+ this.serverChannelSubscribed = true;
5651
+ this.serverChannelSubscription = Promise.resolve().then(
5652
+ () => this.pubSubClient.subscribe(
5653
+ serverChannel,
5654
+ this.createForwardedResponseCallback()
5655
+ )
5656
+ ).then(() => {
5657
+ }).catch(async (error2) => {
5454
5658
  try {
5455
- const { response, sessionId } = JSON.parse(raw);
5456
- this.forwardedResponseHandler?.(response, sessionId);
5457
- } catch (error2) {
5458
- console.warn(
5459
- `[RedisStreamManager] Failed to parse forwarded response:`,
5460
- error2
5461
- );
5659
+ if (this.pubSubClient.unsubscribe) {
5660
+ await this.pubSubClient.unsubscribe(serverChannel);
5661
+ this.serverChannelSubscribed = false;
5662
+ }
5663
+ } catch {
5462
5664
  }
5463
- }).catch((error2) => {
5464
5665
  console.error(
5465
5666
  `[RedisStreamManager] Failed to subscribe to server channel:`,
5466
5667
  error2
@@ -5528,37 +5729,21 @@ async function extractResponseError(res) {
5528
5729
  return null;
5529
5730
  }
5530
5731
  if (!text2) return null;
5531
- const isSse = (res.headers.get("content-type") || "").includes(
5532
- "text/event-stream"
5533
- );
5534
- const payloads = [];
5535
- const tryParse = /* @__PURE__ */ __name((raw) => {
5536
- try {
5537
- payloads.push(JSON.parse(raw));
5538
- } catch {
5539
- }
5540
- }, "tryParse");
5541
- if (isSse) {
5542
- for (const line of text2.split(/\r?\n/)) {
5543
- if (line.startsWith("data:")) {
5544
- const data = line.slice(5).trim();
5545
- if (data) tryParse(data);
5546
- }
5547
- }
5548
- } else {
5549
- tryParse(text2);
5732
+ let payload;
5733
+ try {
5734
+ payload = JSON.parse(text2);
5735
+ } catch {
5736
+ return null;
5550
5737
  }
5551
- for (const payload of payloads) {
5552
- for (const msg of Array.isArray(payload) ? payload : [payload]) {
5553
- if (!msg || typeof msg !== "object") continue;
5554
- const m = msg;
5555
- if (typeof m.error?.message === "string") return m.error.message;
5556
- if (m.result?.isError === true) {
5557
- const textBlock = Array.isArray(m.result.content) ? m.result.content.find(
5558
- (b) => b?.type === "text" && typeof b.text === "string"
5559
- ) : null;
5560
- return textBlock ? String(textBlock.text) : "tool error";
5561
- }
5738
+ for (const msg of Array.isArray(payload) ? payload : [payload]) {
5739
+ if (!msg || typeof msg !== "object") continue;
5740
+ const m = msg;
5741
+ if (typeof m.error?.message === "string") return m.error.message;
5742
+ if (m.result?.isError === true) {
5743
+ const textBlock = Array.isArray(m.result.content) ? m.result.content.find(
5744
+ (b) => b?.type === "text" && typeof b.text === "string"
5745
+ ) : null;
5746
+ return textBlock ? String(textBlock.text) : "tool error";
5562
5747
  }
5563
5748
  }
5564
5749
  return null;
@@ -5602,6 +5787,7 @@ async function requestLogger(c, next) {
5602
5787
  await next();
5603
5788
  const durationMs = Date.now() - startedAt;
5604
5789
  const statusCode = c.res.status;
5790
+ const isStreaming = c.res.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase() === "text/event-stream";
5605
5791
  const incomingSid = c.req.header("mcp-session-id");
5606
5792
  const responseSid = c.res.headers.get("mcp-session-id");
5607
5793
  const mcpMethod = requestBody?.method;
@@ -5640,7 +5826,7 @@ async function requestLogger(c, next) {
5640
5826
  parts.push(chalk2.gray(`\u2192 session=${shortSessionId(responseSid)}`));
5641
5827
  }
5642
5828
  let outcomePart;
5643
- const errMsg = await extractResponseError(c.res);
5829
+ const errMsg = isStreaming ? null : await extractResponseError(c.res);
5644
5830
  if (errMsg) {
5645
5831
  outcomePart = chalk2.red(`ERROR ${errMsg}`);
5646
5832
  } else if (mcpMethod) {
@@ -5676,7 +5862,9 @@ async function requestLogger(c, next) {
5676
5862
  console.log(formatForLogging(responseHeaders));
5677
5863
  }
5678
5864
  try {
5679
- if (c.res.body !== null && c.res.body !== void 0) {
5865
+ if (isStreaming) {
5866
+ console.log(chalk2.yellow("Response Body:") + " (streaming; skipped)");
5867
+ } else if (c.res.body !== null && c.res.body !== void 0) {
5680
5868
  try {
5681
5869
  const clonedResponse = c.res.clone();
5682
5870
  const responseBody = await clonedResponse.text().catch(() => null);
@@ -5789,6 +5977,29 @@ async function mountMcp(app, mcpServerInstance, sessions, config, isProductionMo
5789
5977
  }));
5790
5978
  const streamManager = config.streamManager ?? new InMemoryStreamManager();
5791
5979
  const transports = /* @__PURE__ */ new Map();
5980
+ const cleanupClosedSession = /* @__PURE__ */ __name(async (sid) => {
5981
+ if (getDebugLevel() !== "info") {
5982
+ console.log(`[MCP] Session closed: ${sid}`);
5983
+ }
5984
+ transports.delete(sid);
5985
+ sessions.delete(sid);
5986
+ const cleanups = await Promise.allSettled([
5987
+ Promise.resolve().then(
5988
+ () => mcpServerInstance.cleanupSessionSubscriptions?.(sid)
5989
+ ),
5990
+ Promise.resolve().then(() => mcpServerInstance.cleanupSessionRefs?.(sid)),
5991
+ Promise.resolve().then(() => streamManager.delete(sid)),
5992
+ Promise.resolve().then(() => sessionStore.delete(sid))
5993
+ ]);
5994
+ for (const cleanup of cleanups) {
5995
+ if (cleanup.status === "rejected") {
5996
+ console.warn(
5997
+ `[MCP] Error cleaning up closed session ${sid}:`,
5998
+ cleanup.reason
5999
+ );
6000
+ }
6001
+ }
6002
+ }, "cleanupClosedSession");
5792
6003
  if (streamManager.onForwardedResponse) {
5793
6004
  streamManager.onForwardedResponse((message, sid) => {
5794
6005
  const transport = transports.get(sid);
@@ -5933,17 +6144,7 @@ async function mountMcp(app, mcpServerInstance, sessions, config, isProductionMo
5933
6144
  console.log(`[MCP] Session re-initialized: ${sid}`);
5934
6145
  }
5935
6146
  }, "onsessioninitialized"),
5936
- onsessionclosed: /* @__PURE__ */ __name(async (sid) => {
5937
- if (getDebugLevel() !== "info") {
5938
- console.log(`[MCP] Session closed: ${sid}`);
5939
- }
5940
- transports.delete(sid);
5941
- await streamManager.delete(sid);
5942
- await sessionStore.delete(sid);
5943
- sessions.delete(sid);
5944
- mcpServerInstance.cleanupSessionSubscriptions?.(sid);
5945
- mcpServerInstance.cleanupSessionRefs?.(sid);
5946
- }, "onsessionclosed")
6147
+ onsessionclosed: cleanupClosedSession
5947
6148
  });
5948
6149
  wrapTransportForStreamManager(transport, sessionId, streamManager);
5949
6150
  await server.connect(transport);
@@ -6069,17 +6270,7 @@ async function mountMcp(app, mcpServerInstance, sessions, config, isProductionMo
6069
6270
  );
6070
6271
  };
6071
6272
  }, "onsessioninitialized"),
6072
- onsessionclosed: /* @__PURE__ */ __name(async (sid) => {
6073
- if (getDebugLevel() !== "info") {
6074
- console.log(`[MCP] Session closed: ${sid}`);
6075
- }
6076
- transports.delete(sid);
6077
- await streamManager.delete(sid);
6078
- await sessionStore.delete(sid);
6079
- sessions.delete(sid);
6080
- mcpServerInstance.cleanupSessionSubscriptions?.(sid);
6081
- mcpServerInstance.cleanupSessionRefs?.(sid);
6082
- }, "onsessionclosed")
6273
+ onsessionclosed: cleanupClosedSession
6083
6274
  });
6084
6275
  allocatedTransport = transport;
6085
6276
  wrapTransportForStreamManager(transport, newSessionId, streamManager);
@@ -7021,7 +7212,7 @@ var MCPServerClass = class _MCPServerClass {
7021
7212
  onSampling: /* @__PURE__ */ __name(async (params) => {
7022
7213
  try {
7023
7214
  const { getRequestContext: getRequestContext2 } = await import("../../context-storage-KMOAHEL7.js");
7024
- const { findSessionContext: findSessionContext2 } = await import("../../tool-execution-helpers-YOE4JC6H.js");
7215
+ const { findSessionContext: findSessionContext2 } = await import("../../tool-execution-helpers-IJTPZL55.js");
7025
7216
  const ctx = getRequestContext2();
7026
7217
  if (!ctx) throw new Error("No request context");
7027
7218
  const { session } = findSessionContext2(
@@ -7049,7 +7240,7 @@ var MCPServerClass = class _MCPServerClass {
7049
7240
  onElicitation: /* @__PURE__ */ __name(async (params) => {
7050
7241
  try {
7051
7242
  const { getRequestContext: getRequestContext2 } = await import("../../context-storage-KMOAHEL7.js");
7052
- const { findSessionContext: findSessionContext2 } = await import("../../tool-execution-helpers-YOE4JC6H.js");
7243
+ const { findSessionContext: findSessionContext2 } = await import("../../tool-execution-helpers-IJTPZL55.js");
7053
7244
  const ctx = getRequestContext2();
7054
7245
  if (!ctx) throw new Error("No request context");
7055
7246
  const { session } = findSessionContext2(