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
@@ -661,7 +661,7 @@ var VERSION;
661
661
  var init_version = __esm({
662
662
  "src/version.ts"() {
663
663
  "use strict";
664
- VERSION = "1.34.7";
664
+ VERSION = "1.34.8";
665
665
  __name(getPackageVersion, "getPackageVersion");
666
666
  }
667
667
  });
@@ -8739,27 +8739,19 @@ function isExpressMiddleware(middleware) {
8739
8739
  }
8740
8740
  const paramCount = middleware.length;
8741
8741
  const fnString = middleware.toString();
8742
- const expressPatterns = [
8743
- /\bres\.(send|json|status|end|redirect|render|sendFile|download)\b/,
8744
- /\breq\.(body|params|query|cookies|session)\b/,
8745
- /\breq\.get\s*\(/,
8746
- /\bres\.set\s*\(/,
8747
- /\bres\.statusCode\s*=/,
8748
- /\bres\.writeHead\s*\(/
8742
+ const expressResponsePatterns = [
8743
+ /(?<![\w$.])res\.(send|json|status|end|redirect|render|sendFile|download)\b/,
8744
+ /(?<![\w$.])res\.set\s*\(/,
8745
+ /(?<![\w$.])res\.statusCode\s*=/,
8746
+ /(?<![\w$.])res\.writeHead\s*\(/
8749
8747
  ];
8750
- const hasExpressPattern = expressPatterns.some(
8748
+ const hasExpressResponsePattern = expressResponsePatterns.some(
8751
8749
  (pattern) => pattern.test(fnString)
8752
8750
  );
8753
8751
  if (paramCount === 3 || paramCount === 4) {
8754
- if (hasExpressPattern) {
8755
- return true;
8756
- }
8757
8752
  return true;
8758
8753
  }
8759
8754
  if (paramCount === 2) {
8760
- if (hasExpressPattern) {
8761
- return true;
8762
- }
8763
8755
  const honoPatterns = [
8764
8756
  /\bc\.(req|res|json|text|html|status|header)\b/,
8765
8757
  /\bc\.get\s*\(/,
@@ -8772,7 +8764,7 @@ function isExpressMiddleware(middleware) {
8772
8764
  if (hasHonoPattern) {
8773
8765
  return false;
8774
8766
  }
8775
- return false;
8767
+ return hasExpressResponsePattern;
8776
8768
  }
8777
8769
  return false;
8778
8770
  }
@@ -8817,31 +8809,51 @@ async function adaptConnectMiddleware(connectMiddleware, middlewarePath) {
8817
8809
  ...request.body && { body: request.body }
8818
8810
  });
8819
8811
  const mockResponse = createResponse();
8820
- let responseResolved = false;
8821
- const res = await new Promise((resolve) => {
8812
+ const res = await new Promise((resolve, reject2) => {
8813
+ let responseSettled = false;
8814
+ const resolveResponse = /* @__PURE__ */ __name((response) => {
8815
+ if (responseSettled) return;
8816
+ responseSettled = true;
8817
+ resolve(response);
8818
+ }, "resolveResponse");
8819
+ const rejectResponse = /* @__PURE__ */ __name((error2) => {
8820
+ if (responseSettled) return;
8821
+ responseSettled = true;
8822
+ reject2(error2);
8823
+ }, "rejectResponse");
8822
8824
  const originalEnd = mockResponse.end.bind(mockResponse);
8823
8825
  mockResponse.end = (...args) => {
8824
- const result = originalEnd(...args);
8825
- if (!responseResolved && mockResponse.writableEnded) {
8826
- responseResolved = true;
8827
- const statusCode = mockResponse.statusCode;
8828
- const noBodyStatuses = [204, 304];
8829
- const responseBody = noBodyStatuses.includes(statusCode) ? null : mockResponse._getData() || mockResponse._getBuffer() || null;
8830
- const connectResponse = new Response(responseBody, {
8831
- status: statusCode,
8832
- statusText: mockResponse.statusMessage,
8833
- headers: mockResponse.getHeaders()
8834
- });
8835
- resolve(connectResponse);
8826
+ try {
8827
+ const result = originalEnd(...args);
8828
+ if (!responseSettled && mockResponse.writableEnded) {
8829
+ const statusCode = mockResponse.statusCode;
8830
+ const noBodyStatuses = [204, 304];
8831
+ const responseBody = noBodyStatuses.includes(statusCode) ? null : mockResponse._getData() || mockResponse._getBuffer() || null;
8832
+ const connectResponse = new Response(responseBody, {
8833
+ status: statusCode,
8834
+ statusText: mockResponse.statusMessage,
8835
+ headers: mockResponse.getHeaders()
8836
+ });
8837
+ resolveResponse(connectResponse);
8838
+ }
8839
+ return result;
8840
+ } catch (error2) {
8841
+ rejectResponse(error2);
8842
+ return mockResponse;
8836
8843
  }
8837
- return result;
8838
8844
  };
8839
- connectMiddleware(mockRequest, mockResponse, () => {
8840
- if (!responseResolved && !mockResponse.writableEnded) {
8841
- responseResolved = true;
8845
+ const connectNext = /* @__PURE__ */ __name((error2) => {
8846
+ if (responseSettled) return;
8847
+ if (error2) {
8848
+ rejectResponse(error2);
8849
+ return;
8850
+ }
8851
+ if (mockResponse.writableEnded) return;
8852
+ try {
8842
8853
  const statusCode = mockResponse.statusCode;
8843
8854
  const noBodyStatuses = [204, 304];
8844
- const responseBody = noBodyStatuses.includes(statusCode) ? null : mockResponse._getData() || mockResponse._getBuffer() || null;
8855
+ const buffer = mockResponse._getBuffer();
8856
+ const responseBody = noBodyStatuses.includes(statusCode) ? null : mockResponse._getData() || (buffer.length > 0 ? buffer : null);
8845
8857
  const preparedHeaders = c.newResponse(null, 204, {}).headers;
8846
8858
  for (const key of [...preparedHeaders.keys()]) {
8847
8859
  if (preparedHeaders.has(key)) {
@@ -8862,14 +8874,23 @@ async function adaptConnectMiddleware(connectMiddleware, middlewarePath) {
8862
8874
  }
8863
8875
  c.status(statusCode);
8864
8876
  if (noBodyStatuses.includes(statusCode)) {
8865
- resolve(c.newResponse(null, statusCode));
8877
+ resolveResponse(c.newResponse(null, statusCode));
8866
8878
  } else if (responseBody) {
8867
- resolve(c.body(responseBody));
8879
+ resolveResponse(c.body(responseBody));
8868
8880
  } else {
8869
- resolve(void 0);
8881
+ resolveResponse(void 0);
8870
8882
  }
8883
+ } catch (error3) {
8884
+ rejectResponse(error3);
8871
8885
  }
8872
- });
8886
+ }, "connectNext");
8887
+ try {
8888
+ Promise.resolve(
8889
+ connectMiddleware(mockRequest, mockResponse, connectNext)
8890
+ ).catch(rejectResponse);
8891
+ } catch (error2) {
8892
+ rejectResponse(error2);
8893
+ }
8873
8894
  });
8874
8895
  if (res) {
8875
8896
  c.res = res;
@@ -12918,11 +12939,17 @@ var RedisStreamManager = class {
12918
12939
  * Key: sessionId, Value: controller
12919
12940
  */
12920
12941
  localControllers = /* @__PURE__ */ new Map();
12942
+ streamGenerations = /* @__PURE__ */ new Map();
12921
12943
  /**
12922
12944
  * Map of heartbeat intervals for keeping sessions alive
12923
12945
  * Key: sessionId, Value: interval timer
12924
12946
  */
12925
12947
  heartbeats = /* @__PURE__ */ new Map();
12948
+ // Track attempted subscriptions separately from controllers: a disconnected
12949
+ // controller or a rejected subscribe() must not lose its cleanup ownership.
12950
+ sessionSubscriptions = /* @__PURE__ */ new Map();
12951
+ sessionOperations = /* @__PURE__ */ new Map();
12952
+ closing = false;
12926
12953
  /**
12927
12954
  * Unique identifier for this server instance, used for request/response routing.
12928
12955
  */
@@ -12935,6 +12962,7 @@ var RedisStreamManager = class {
12935
12962
  * Whether the server-level response channel subscription is active.
12936
12963
  */
12937
12964
  serverChannelSubscribed = false;
12965
+ serverChannelSubscription;
12938
12966
  constructor(config) {
12939
12967
  this.pubSubClient = config.pubSubClient;
12940
12968
  this.client = config.client;
@@ -12959,90 +12987,265 @@ var RedisStreamManager = class {
12959
12987
  getActiveSessionsKey() {
12960
12988
  return `${this.prefix}active`;
12961
12989
  }
12962
- /**
12963
- * Register an active SSE stream and subscribe to Redis channel.
12964
- * Idempotent: if the session already has a subscription, it is cleaned up first.
12965
- */
12966
- async create(sessionId, controller) {
12990
+ withSessionOperation(sessionId, operation) {
12991
+ const previous = this.sessionOperations.get(sessionId) ?? Promise.resolve();
12992
+ const result = previous.then(operation);
12993
+ const settled = result.then(
12994
+ () => {
12995
+ },
12996
+ () => {
12997
+ }
12998
+ );
12999
+ this.sessionOperations.set(sessionId, settled);
13000
+ void settled.then(() => {
13001
+ if (this.sessionOperations.get(sessionId) === settled) {
13002
+ this.sessionOperations.delete(sessionId);
13003
+ }
13004
+ });
13005
+ return result;
13006
+ }
13007
+ clearLocalStream(sessionId) {
13008
+ const heartbeat = this.heartbeats.get(sessionId);
13009
+ if (heartbeat) clearInterval(heartbeat);
13010
+ this.heartbeats.delete(sessionId);
13011
+ const controller = this.localControllers.get(sessionId);
13012
+ this.localControllers.delete(sessionId);
13013
+ this.streamGenerations.delete(sessionId);
13014
+ this.closeController(controller);
13015
+ }
13016
+ closeController(controller) {
12967
13017
  try {
12968
- if (this.localControllers.has(sessionId)) {
12969
- const oldHeartbeat = this.heartbeats.get(sessionId);
12970
- if (oldHeartbeat) {
12971
- clearInterval(oldHeartbeat);
12972
- this.heartbeats.delete(sessionId);
12973
- }
12974
- const channel2 = this.getChannel(sessionId);
12975
- const deleteChannel2 = `delete:${channel2}`;
12976
- try {
12977
- await this.pubSubClient.unsubscribe?.(channel2);
12978
- await this.pubSubClient.unsubscribe?.(deleteChannel2);
12979
- } catch {
12980
- }
13018
+ controller?.close();
13019
+ } catch {
13020
+ }
13021
+ }
13022
+ async unsubscribeSession(sessionId) {
13023
+ const channels = this.sessionSubscriptions.get(sessionId);
13024
+ if (!channels?.size) return;
13025
+ if (!this.pubSubClient.unsubscribe) {
13026
+ throw new Error(
13027
+ "[RedisStreamManager] Redis client does not support unsubscribe method"
13028
+ );
13029
+ }
13030
+ const results = await Promise.allSettled(
13031
+ Array.from(channels, async (channel) => {
13032
+ await this.pubSubClient.unsubscribe(channel);
13033
+ channels.delete(channel);
13034
+ })
13035
+ );
13036
+ if (!channels.size) this.sessionSubscriptions.delete(sessionId);
13037
+ this.throwCleanupError(results);
13038
+ }
13039
+ throwCleanupError(results) {
13040
+ const failed = results.find((result) => result.status === "rejected");
13041
+ if (failed?.status === "rejected") throw failed.reason;
13042
+ }
13043
+ async markAvailable(sessionId) {
13044
+ const key = this.getAvailableKey(sessionId);
13045
+ const ttl = this.heartbeatInterval * 2;
13046
+ if (this.client.setEx) {
13047
+ await this.client.setEx(key, ttl, "active");
13048
+ } else if (this.client.setex) {
13049
+ await this.client.setex(key, ttl, "active");
13050
+ } else {
13051
+ await this.client.set(key, "active", { EX: ttl });
13052
+ if (this.client.expire) await this.client.expire(key, ttl);
13053
+ }
13054
+ if (this.client.sAdd) {
13055
+ await this.client.sAdd(this.getActiveSessionsKey(), sessionId);
13056
+ await this.client.expire?.(this.getActiveSessionsKey(), ttl);
13057
+ }
13058
+ }
13059
+ async removeAvailability(sessionId) {
13060
+ const results = await Promise.allSettled([
13061
+ Promise.resolve().then(
13062
+ () => this.client.del(this.getAvailableKey(sessionId))
13063
+ ),
13064
+ Promise.resolve().then(
13065
+ () => this.client.sRem?.(this.getActiveSessionsKey(), sessionId)
13066
+ )
13067
+ ]);
13068
+ this.throwCleanupError(results);
13069
+ }
13070
+ async removeStream(sessionId, notifyOtherServers, cleanupAvailability = true) {
13071
+ this.clearLocalStream(sessionId);
13072
+ const results = await Promise.allSettled([
13073
+ this.unsubscribeSession(sessionId),
13074
+ cleanupAvailability ? this.removeAvailability(sessionId) : Promise.resolve()
13075
+ ]);
13076
+ if (notifyOtherServers) {
13077
+ results.push(
13078
+ ...await Promise.allSettled([
13079
+ Promise.resolve().then(() => {
13080
+ if (!this.client.publish) {
13081
+ throw new Error(
13082
+ "[RedisStreamManager] Redis client does not support publish method"
13083
+ );
13084
+ }
13085
+ return this.client.publish(
13086
+ `delete:${this.getChannel(sessionId)}`,
13087
+ ""
13088
+ );
13089
+ })
13090
+ ])
13091
+ );
13092
+ }
13093
+ this.throwCleanupError(results);
13094
+ }
13095
+ // These factories have no enclosing controller or handler argument. Redis
13096
+ // may retain their callbacks after a failed unsubscribe, so the callbacks
13097
+ // must resolve controllers/handlers through the manager rather than capture
13098
+ // a registration operation's context.
13099
+ createMessageCallback(sessionId, generation) {
13100
+ return (message) => {
13101
+ if (this.streamGenerations.get(sessionId) !== generation) return;
13102
+ const controller = this.localControllers.get(sessionId);
13103
+ if (!controller) return;
13104
+ try {
13105
+ controller.enqueue(this.textEncoder.encode(message));
13106
+ } catch {
13107
+ this.clearLocalStream(sessionId);
13108
+ this.cleanupDisconnectedStream(sessionId);
12981
13109
  }
12982
- this.localControllers.set(sessionId, controller);
12983
- const availableKey = this.getAvailableKey(sessionId);
12984
- await this.client.set(availableKey, "active");
12985
- if (this.client.expire) {
12986
- await this.client.expire(availableKey, this.heartbeatInterval * 2);
13110
+ };
13111
+ }
13112
+ cleanupDisconnectedStream(sessionId) {
13113
+ void this.withSessionOperation(sessionId, async () => {
13114
+ if (!this.localControllers.has(sessionId)) {
13115
+ await this.unsubscribeSession(sessionId);
12987
13116
  }
12988
- const activeSessionsKey = this.getActiveSessionsKey();
12989
- if (this.client.sAdd) {
12990
- await this.client.sAdd(activeSessionsKey, sessionId);
13117
+ }).catch((error2) => {
13118
+ console.warn(
13119
+ `[RedisStreamManager] Failed to unsubscribe disconnected stream ${sessionId}:`,
13120
+ error2
13121
+ );
13122
+ });
13123
+ }
13124
+ createDeleteCallback(sessionId, generation) {
13125
+ return () => {
13126
+ if (this.streamGenerations.get(sessionId) !== generation) return;
13127
+ void this.withSessionOperation(
13128
+ sessionId,
13129
+ () => this.removeStream(sessionId, false)
13130
+ ).catch((error2) => {
13131
+ console.warn(
13132
+ `[RedisStreamManager] Failed to clean up deleted stream ${sessionId}:`,
13133
+ error2
13134
+ );
13135
+ });
13136
+ };
13137
+ }
13138
+ startHeartbeat(sessionId) {
13139
+ const availableKey = this.getAvailableKey(sessionId);
13140
+ const activeSessionsKey = this.getActiveSessionsKey();
13141
+ const heartbeat = setInterval(async () => {
13142
+ try {
12991
13143
  if (this.client.expire) {
13144
+ await this.client.expire(availableKey, this.heartbeatInterval * 2);
12992
13145
  await this.client.expire(
12993
13146
  activeSessionsKey,
12994
13147
  this.heartbeatInterval * 2
12995
13148
  );
12996
13149
  }
13150
+ } catch (error2) {
13151
+ console.warn(
13152
+ `[RedisStreamManager] Heartbeat failed for session ${sessionId}:`,
13153
+ error2
13154
+ );
12997
13155
  }
12998
- const heartbeat = setInterval(async () => {
12999
- try {
13000
- if (this.client.expire) {
13001
- await this.client.expire(availableKey, this.heartbeatInterval * 2);
13002
- const activeSessionsKey2 = this.getActiveSessionsKey();
13003
- await this.client.expire(
13004
- activeSessionsKey2,
13005
- this.heartbeatInterval * 2
13006
- );
13007
- }
13008
- } catch (error2) {
13009
- console.warn(
13010
- `[RedisStreamManager] Heartbeat failed for session ${sessionId}:`,
13011
- error2
13156
+ }, this.heartbeatInterval * 1e3);
13157
+ this.heartbeats.set(sessionId, heartbeat);
13158
+ }
13159
+ createForwardedResponseCallback() {
13160
+ return (raw) => {
13161
+ try {
13162
+ const { response, sessionId } = JSON.parse(raw);
13163
+ this.forwardedResponseHandler?.(response, sessionId);
13164
+ } catch (error2) {
13165
+ console.warn(
13166
+ `[RedisStreamManager] Failed to parse forwarded response:`,
13167
+ error2
13168
+ );
13169
+ }
13170
+ };
13171
+ }
13172
+ /**
13173
+ * Register an active SSE stream and subscribe to Redis channel.
13174
+ * Idempotent: if the session already has a subscription, it is cleaned up first.
13175
+ */
13176
+ async create(sessionId, controller) {
13177
+ return this.withSessionOperation(sessionId, async () => {
13178
+ if (this.closing) {
13179
+ this.closeController(controller);
13180
+ throw new Error("[RedisStreamManager] Stream manager is closed");
13181
+ }
13182
+ if (this.localControllers.get(sessionId) === controller) return;
13183
+ this.clearLocalStream(sessionId);
13184
+ let controllerRegistered = false;
13185
+ try {
13186
+ await this.unsubscribeSession(sessionId);
13187
+ if (this.closing) {
13188
+ throw new Error("[RedisStreamManager] Stream manager is closed");
13189
+ }
13190
+ this.localControllers.set(sessionId, controller);
13191
+ controllerRegistered = true;
13192
+ const generation = Symbol(sessionId);
13193
+ this.streamGenerations.set(sessionId, generation);
13194
+ if (!this.pubSubClient.subscribe) {
13195
+ throw new Error(
13196
+ "[RedisStreamManager] Redis client does not support subscribe method"
13012
13197
  );
13013
13198
  }
13014
- }, this.heartbeatInterval * 1e3);
13015
- this.heartbeats.set(sessionId, heartbeat);
13016
- const channel = this.getChannel(sessionId);
13017
- if (!this.pubSubClient.subscribe) {
13018
- throw new Error(
13019
- "[RedisStreamManager] Redis client does not support subscribe method"
13199
+ const channels = /* @__PURE__ */ new Set();
13200
+ this.sessionSubscriptions.set(sessionId, channels);
13201
+ const channel = this.getChannel(sessionId);
13202
+ channels.add(channel);
13203
+ await this.pubSubClient.subscribe(
13204
+ channel,
13205
+ this.createMessageCallback(sessionId, generation)
13020
13206
  );
13021
- }
13022
- await this.pubSubClient.subscribe(channel, (message) => {
13023
- const localController = this.localControllers.get(sessionId);
13024
- if (localController) {
13025
- try {
13026
- localController.enqueue(this.textEncoder.encode(message));
13027
- } catch {
13028
- this.localControllers.delete(sessionId);
13207
+ const deleteChannel = `delete:${channel}`;
13208
+ channels.add(deleteChannel);
13209
+ await this.pubSubClient.subscribe(
13210
+ deleteChannel,
13211
+ this.createDeleteCallback(sessionId, generation)
13212
+ );
13213
+ if (this.closing || this.streamGenerations.get(sessionId) !== generation) {
13214
+ throw new Error(
13215
+ "[RedisStreamManager] Stream closed during registration"
13216
+ );
13217
+ }
13218
+ await this.markAvailable(sessionId);
13219
+ if (this.closing || this.streamGenerations.get(sessionId) !== generation) {
13220
+ throw new Error(
13221
+ "[RedisStreamManager] Stream closed during registration"
13222
+ );
13223
+ }
13224
+ this.startHeartbeat(sessionId);
13225
+ console.log(
13226
+ `[RedisStreamManager] Created stream for session ${sessionId}`
13227
+ );
13228
+ } catch (error2) {
13229
+ this.clearLocalStream(sessionId);
13230
+ if (!controllerRegistered) this.closeController(controller);
13231
+ const cleanups = await Promise.allSettled([
13232
+ this.unsubscribeSession(sessionId)
13233
+ ]);
13234
+ for (const cleanup of cleanups) {
13235
+ if (cleanup.status === "rejected") {
13236
+ console.warn(
13237
+ `[RedisStreamManager] Failed to roll back stream ${sessionId}:`,
13238
+ cleanup.reason
13239
+ );
13029
13240
  }
13030
13241
  }
13031
- });
13032
- const deleteChannel = `delete:${this.getChannel(sessionId)}`;
13033
- await this.pubSubClient.subscribe(deleteChannel, async () => {
13034
- await this.delete(sessionId);
13035
- });
13036
- console.log(
13037
- `[RedisStreamManager] Created stream for session ${sessionId}`
13038
- );
13039
- } catch (error2) {
13040
- console.error(
13041
- `[RedisStreamManager] Error creating stream for ${sessionId}:`,
13042
- error2
13043
- );
13044
- throw error2;
13045
- }
13242
+ console.error(
13243
+ `[RedisStreamManager] Error creating stream for ${sessionId}:`,
13244
+ error2
13245
+ );
13246
+ throw error2;
13247
+ }
13248
+ });
13046
13249
  }
13047
13250
  /**
13048
13251
  * Send data to sessions via Redis Pub/Sub
@@ -13060,6 +13263,22 @@ var RedisStreamManager = class {
13060
13263
  if (this.client.sMembers) {
13061
13264
  const sessionIds2 = await this.client.sMembers(activeSessionsKey);
13062
13265
  for (const sessionId of sessionIds2) {
13266
+ const availableKey = this.getAvailableKey(sessionId);
13267
+ if (await this.client.exists(availableKey) !== 1) {
13268
+ await this.client.sRem?.(activeSessionsKey, sessionId);
13269
+ let available;
13270
+ try {
13271
+ available = await this.client.exists(availableKey);
13272
+ } catch (error2) {
13273
+ try {
13274
+ await this.client.sAdd?.(activeSessionsKey, sessionId);
13275
+ } catch {
13276
+ }
13277
+ throw error2;
13278
+ }
13279
+ if (available !== 1) continue;
13280
+ await this.client.sAdd?.(activeSessionsKey, sessionId);
13281
+ }
13063
13282
  const channel = this.getChannel(sessionId);
13064
13283
  if (!this.client.publish) {
13065
13284
  throw new Error(
@@ -13102,53 +13321,20 @@ var RedisStreamManager = class {
13102
13321
  * Remove an active SSE stream
13103
13322
  */
13104
13323
  async delete(sessionId) {
13105
- try {
13106
- const heartbeat = this.heartbeats.get(sessionId);
13107
- if (heartbeat) {
13108
- clearInterval(heartbeat);
13109
- this.heartbeats.delete(sessionId);
13110
- }
13111
- const channel = this.getChannel(sessionId);
13112
- const deleteChannel = `delete:${channel}`;
13113
- if (!this.pubSubClient.unsubscribe) {
13114
- throw new Error(
13115
- "[RedisStreamManager] Redis client does not support unsubscribe method"
13324
+ return this.withSessionOperation(sessionId, async () => {
13325
+ try {
13326
+ await this.removeStream(sessionId, true);
13327
+ console.log(
13328
+ `[RedisStreamManager] Deleted stream for session ${sessionId}`
13116
13329
  );
13117
- }
13118
- await this.pubSubClient.unsubscribe(channel);
13119
- await this.pubSubClient.unsubscribe(deleteChannel);
13120
- if (!this.client.publish) {
13121
- throw new Error(
13122
- "[RedisStreamManager] Redis client does not support publish method"
13330
+ } catch (error2) {
13331
+ console.error(
13332
+ `[RedisStreamManager] Error deleting stream for ${sessionId}:`,
13333
+ error2
13123
13334
  );
13335
+ throw error2;
13124
13336
  }
13125
- await this.client.publish(deleteChannel, "");
13126
- await this.client.del(this.getAvailableKey(sessionId));
13127
- const activeSessionsKey = this.getActiveSessionsKey();
13128
- if (this.client.sRem) {
13129
- await this.client.sRem(activeSessionsKey, sessionId);
13130
- }
13131
- const controller = this.localControllers.get(sessionId);
13132
- if (controller) {
13133
- try {
13134
- controller.close();
13135
- } catch (error2) {
13136
- console.debug(
13137
- `[RedisStreamManager] Controller already closed for ${sessionId}`
13138
- );
13139
- }
13140
- this.localControllers.delete(sessionId);
13141
- }
13142
- console.log(
13143
- `[RedisStreamManager] Deleted stream for session ${sessionId}`
13144
- );
13145
- } catch (error2) {
13146
- console.error(
13147
- `[RedisStreamManager] Error deleting stream for ${sessionId}:`,
13148
- error2
13149
- );
13150
- throw error2;
13151
- }
13337
+ });
13152
13338
  }
13153
13339
  /**
13154
13340
  * Check if a session has an active stream (on ANY server)
@@ -13170,31 +13356,42 @@ var RedisStreamManager = class {
13170
13356
  * Close all connections and cleanup
13171
13357
  */
13172
13358
  async close() {
13173
- try {
13174
- for (const heartbeat of this.heartbeats.values()) {
13175
- clearInterval(heartbeat);
13176
- }
13177
- this.heartbeats.clear();
13178
- const activeSessionsKey = this.getActiveSessionsKey();
13179
- const sessionIdsToCleanup = Array.from(this.localControllers.keys());
13180
- for (const sessionId of sessionIdsToCleanup) {
13181
- await this.client.del(this.getAvailableKey(sessionId));
13182
- if (this.client.sRem) {
13183
- await this.client.sRem(activeSessionsKey, sessionId);
13184
- }
13185
- }
13186
- for (const controller of this.localControllers.values()) {
13187
- try {
13188
- controller.close();
13189
- } catch (error2) {
13359
+ this.closing = true;
13360
+ this.forwardedResponseHandler = void 0;
13361
+ const sessionIds = /* @__PURE__ */ new Set([
13362
+ ...this.localControllers.keys(),
13363
+ ...this.heartbeats.keys(),
13364
+ ...this.sessionSubscriptions.keys(),
13365
+ ...this.sessionOperations.keys()
13366
+ ]);
13367
+ for (const sessionId of sessionIds) this.clearLocalStream(sessionId);
13368
+ const results = await Promise.allSettled([
13369
+ ...Array.from(
13370
+ sessionIds,
13371
+ (sessionId) => this.withSessionOperation(
13372
+ sessionId,
13373
+ () => (
13374
+ // Shared availability expires after shutdown; deleting it here
13375
+ // could invalidate another manager serving the same session.
13376
+ this.removeStream(sessionId, false, false)
13377
+ )
13378
+ )
13379
+ ),
13380
+ Promise.resolve().then(async () => {
13381
+ await this.serverChannelSubscription;
13382
+ if (this.serverChannelSubscribed) {
13383
+ if (!this.pubSubClient.unsubscribe) {
13384
+ throw new Error(
13385
+ "[RedisStreamManager] Redis client does not support unsubscribe method"
13386
+ );
13387
+ }
13388
+ await this.pubSubClient.unsubscribe(this.getServerChannel());
13389
+ this.serverChannelSubscribed = false;
13190
13390
  }
13191
- }
13192
- this.localControllers.clear();
13193
- console.log(`[RedisStreamManager] Closed all streams`);
13194
- } catch (error2) {
13195
- console.error(`[RedisStreamManager] Error during close:`, error2);
13196
- throw error2;
13197
- }
13391
+ })
13392
+ ]);
13393
+ this.throwCleanupError(results);
13394
+ console.log(`[RedisStreamManager] Closed all streams`);
13198
13395
  }
13199
13396
  /**
13200
13397
  * Get count of active local streams on this server instance
@@ -13259,9 +13456,9 @@ var RedisStreamManager = class {
13259
13456
  * Subscribes to this server's dedicated Pub/Sub channel.
13260
13457
  */
13261
13458
  onForwardedResponse(handler) {
13459
+ if (this.closing) return;
13262
13460
  this.forwardedResponseHandler = handler;
13263
13461
  if (this.serverChannelSubscribed) return;
13264
- this.serverChannelSubscribed = true;
13265
13462
  const serverChannel = this.getServerChannel();
13266
13463
  if (!this.pubSubClient.subscribe) {
13267
13464
  console.warn(
@@ -13269,17 +13466,21 @@ var RedisStreamManager = class {
13269
13466
  );
13270
13467
  return;
13271
13468
  }
13272
- this.pubSubClient.subscribe(serverChannel, (raw) => {
13469
+ this.serverChannelSubscribed = true;
13470
+ this.serverChannelSubscription = Promise.resolve().then(
13471
+ () => this.pubSubClient.subscribe(
13472
+ serverChannel,
13473
+ this.createForwardedResponseCallback()
13474
+ )
13475
+ ).then(() => {
13476
+ }).catch(async (error2) => {
13273
13477
  try {
13274
- const { response, sessionId } = JSON.parse(raw);
13275
- this.forwardedResponseHandler?.(response, sessionId);
13276
- } catch (error2) {
13277
- console.warn(
13278
- `[RedisStreamManager] Failed to parse forwarded response:`,
13279
- error2
13280
- );
13478
+ if (this.pubSubClient.unsubscribe) {
13479
+ await this.pubSubClient.unsubscribe(serverChannel);
13480
+ this.serverChannelSubscribed = false;
13481
+ }
13482
+ } catch {
13281
13483
  }
13282
- }).catch((error2) => {
13283
13484
  console.error(
13284
13485
  `[RedisStreamManager] Failed to subscribe to server channel:`,
13285
13486
  error2
@@ -13378,37 +13579,21 @@ async function extractResponseError(res) {
13378
13579
  return null;
13379
13580
  }
13380
13581
  if (!text2) return null;
13381
- const isSse = (res.headers.get("content-type") || "").includes(
13382
- "text/event-stream"
13383
- );
13384
- const payloads = [];
13385
- const tryParse = /* @__PURE__ */ __name((raw) => {
13386
- try {
13387
- payloads.push(JSON.parse(raw));
13388
- } catch {
13389
- }
13390
- }, "tryParse");
13391
- if (isSse) {
13392
- for (const line of text2.split(/\r?\n/)) {
13393
- if (line.startsWith("data:")) {
13394
- const data = line.slice(5).trim();
13395
- if (data) tryParse(data);
13396
- }
13397
- }
13398
- } else {
13399
- tryParse(text2);
13582
+ let payload;
13583
+ try {
13584
+ payload = JSON.parse(text2);
13585
+ } catch {
13586
+ return null;
13400
13587
  }
13401
- for (const payload of payloads) {
13402
- for (const msg of Array.isArray(payload) ? payload : [payload]) {
13403
- if (!msg || typeof msg !== "object") continue;
13404
- const m = msg;
13405
- if (typeof m.error?.message === "string") return m.error.message;
13406
- if (m.result?.isError === true) {
13407
- const textBlock = Array.isArray(m.result.content) ? m.result.content.find(
13408
- (b) => b?.type === "text" && typeof b.text === "string"
13409
- ) : null;
13410
- return textBlock ? String(textBlock.text) : "tool error";
13411
- }
13588
+ for (const msg of Array.isArray(payload) ? payload : [payload]) {
13589
+ if (!msg || typeof msg !== "object") continue;
13590
+ const m = msg;
13591
+ if (typeof m.error?.message === "string") return m.error.message;
13592
+ if (m.result?.isError === true) {
13593
+ const textBlock = Array.isArray(m.result.content) ? m.result.content.find(
13594
+ (b) => b?.type === "text" && typeof b.text === "string"
13595
+ ) : null;
13596
+ return textBlock ? String(textBlock.text) : "tool error";
13412
13597
  }
13413
13598
  }
13414
13599
  return null;
@@ -13452,6 +13637,7 @@ async function requestLogger(c, next) {
13452
13637
  await next();
13453
13638
  const durationMs = Date.now() - startedAt;
13454
13639
  const statusCode = c.res.status;
13640
+ const isStreaming = c.res.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase() === "text/event-stream";
13455
13641
  const incomingSid = c.req.header("mcp-session-id");
13456
13642
  const responseSid = c.res.headers.get("mcp-session-id");
13457
13643
  const mcpMethod = requestBody?.method;
@@ -13490,7 +13676,7 @@ async function requestLogger(c, next) {
13490
13676
  parts.push(import_chalk2.default.gray(`\u2192 session=${shortSessionId(responseSid)}`));
13491
13677
  }
13492
13678
  let outcomePart;
13493
- const errMsg = await extractResponseError(c.res);
13679
+ const errMsg = isStreaming ? null : await extractResponseError(c.res);
13494
13680
  if (errMsg) {
13495
13681
  outcomePart = import_chalk2.default.red(`ERROR ${errMsg}`);
13496
13682
  } else if (mcpMethod) {
@@ -13526,7 +13712,9 @@ async function requestLogger(c, next) {
13526
13712
  console.log(formatForLogging(responseHeaders));
13527
13713
  }
13528
13714
  try {
13529
- if (c.res.body !== null && c.res.body !== void 0) {
13715
+ if (isStreaming) {
13716
+ console.log(import_chalk2.default.yellow("Response Body:") + " (streaming; skipped)");
13717
+ } else if (c.res.body !== null && c.res.body !== void 0) {
13530
13718
  try {
13531
13719
  const clonedResponse = c.res.clone();
13532
13720
  const responseBody = await clonedResponse.text().catch(() => null);
@@ -13640,6 +13828,29 @@ async function mountMcp(app, mcpServerInstance, sessions, config, isProductionMo
13640
13828
  }));
13641
13829
  const streamManager = config.streamManager ?? new InMemoryStreamManager();
13642
13830
  const transports = /* @__PURE__ */ new Map();
13831
+ const cleanupClosedSession = /* @__PURE__ */ __name(async (sid) => {
13832
+ if (getDebugLevel() !== "info") {
13833
+ console.log(`[MCP] Session closed: ${sid}`);
13834
+ }
13835
+ transports.delete(sid);
13836
+ sessions.delete(sid);
13837
+ const cleanups = await Promise.allSettled([
13838
+ Promise.resolve().then(
13839
+ () => mcpServerInstance.cleanupSessionSubscriptions?.(sid)
13840
+ ),
13841
+ Promise.resolve().then(() => mcpServerInstance.cleanupSessionRefs?.(sid)),
13842
+ Promise.resolve().then(() => streamManager.delete(sid)),
13843
+ Promise.resolve().then(() => sessionStore.delete(sid))
13844
+ ]);
13845
+ for (const cleanup of cleanups) {
13846
+ if (cleanup.status === "rejected") {
13847
+ console.warn(
13848
+ `[MCP] Error cleaning up closed session ${sid}:`,
13849
+ cleanup.reason
13850
+ );
13851
+ }
13852
+ }
13853
+ }, "cleanupClosedSession");
13643
13854
  if (streamManager.onForwardedResponse) {
13644
13855
  streamManager.onForwardedResponse((message, sid) => {
13645
13856
  const transport = transports.get(sid);
@@ -13784,17 +13995,7 @@ async function mountMcp(app, mcpServerInstance, sessions, config, isProductionMo
13784
13995
  console.log(`[MCP] Session re-initialized: ${sid}`);
13785
13996
  }
13786
13997
  }, "onsessioninitialized"),
13787
- onsessionclosed: /* @__PURE__ */ __name(async (sid) => {
13788
- if (getDebugLevel() !== "info") {
13789
- console.log(`[MCP] Session closed: ${sid}`);
13790
- }
13791
- transports.delete(sid);
13792
- await streamManager.delete(sid);
13793
- await sessionStore.delete(sid);
13794
- sessions.delete(sid);
13795
- mcpServerInstance.cleanupSessionSubscriptions?.(sid);
13796
- mcpServerInstance.cleanupSessionRefs?.(sid);
13797
- }, "onsessionclosed")
13998
+ onsessionclosed: cleanupClosedSession
13798
13999
  });
13799
14000
  wrapTransportForStreamManager(transport, sessionId, streamManager);
13800
14001
  await server.connect(transport);
@@ -13920,17 +14121,7 @@ async function mountMcp(app, mcpServerInstance, sessions, config, isProductionMo
13920
14121
  );
13921
14122
  };
13922
14123
  }, "onsessioninitialized"),
13923
- onsessionclosed: /* @__PURE__ */ __name(async (sid) => {
13924
- if (getDebugLevel() !== "info") {
13925
- console.log(`[MCP] Session closed: ${sid}`);
13926
- }
13927
- transports.delete(sid);
13928
- await streamManager.delete(sid);
13929
- await sessionStore.delete(sid);
13930
- sessions.delete(sid);
13931
- mcpServerInstance.cleanupSessionSubscriptions?.(sid);
13932
- mcpServerInstance.cleanupSessionRefs?.(sid);
13933
- }, "onsessionclosed")
14124
+ onsessionclosed: cleanupClosedSession
13934
14125
  });
13935
14126
  allocatedTransport = transport;
13936
14127
  wrapTransportForStreamManager(transport, newSessionId, streamManager);