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.
- package/dist/.tsbuildinfo +1 -1
- package/dist/{chunk-6ERGZHCN.js → chunk-4KPCPZWE.js} +1 -1
- package/dist/{chunk-XRLI6ZYX.js → chunk-5ZFGXHAU.js} +1 -1
- package/dist/{chunk-PJARRPKP.js → chunk-ENBB6Z2J.js} +5 -5
- package/dist/{chunk-YGL37XKG.js → chunk-FXO3CJ7N.js} +2 -2
- package/dist/{chunk-XILMNNJF.js → chunk-HIRI2UY6.js} +7 -7
- package/dist/{chunk-LOSPF4H5.js → chunk-I3P2KX5U.js} +1 -1
- package/dist/{chunk-SGT4D4LO.js → chunk-MG47YFCC.js} +1 -1
- package/dist/{chunk-QEYINRZL.js → chunk-NCK7SUJD.js} +4 -4
- package/dist/{chunk-RRCHCSPQ.js → chunk-RDJKEIOW.js} +2 -2
- package/dist/{chunk-E7PWRUQZ.js → chunk-SZYMINCU.js} +1 -1
- package/dist/{chunk-ESSZPKPU.js → chunk-TYVQPHZW.js} +1 -1
- package/dist/{client-Y2ZVEDQC.js → client-YE5FSLWK.js} +4 -4
- package/dist/index.cjs +1 -1
- package/dist/index.js +33 -33
- package/dist/src/agents/index.cjs +1 -1
- package/dist/src/agents/index.js +6 -6
- package/dist/src/browser-agent.cjs +1 -1
- package/dist/src/browser-agent.js +2 -2
- package/dist/src/browser.cjs +1 -1
- package/dist/src/browser.js +4 -4
- package/dist/src/client.cjs +1 -1
- package/dist/src/client.js +6 -6
- package/dist/src/react/index.cjs +1 -1
- package/dist/src/react/index.js +4 -4
- package/dist/src/server/connect-adapter.d.ts.map +1 -1
- package/dist/src/server/endpoints/mount-mcp.d.ts.map +1 -1
- package/dist/src/server/index.cjs +431 -240
- package/dist/src/server/index.js +438 -247
- package/dist/src/server/logging.d.ts +2 -1
- package/dist/src/server/logging.d.ts.map +1 -1
- package/dist/src/server/sessions/streams/redis.d.ts +18 -0
- package/dist/src/server/sessions/streams/redis.d.ts.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/{stdio-BJKCQSAY.js → stdio-5L34RAVM.js} +4 -4
- package/dist/{stdio-3JX4DSGH.js → stdio-RCVXPUI4.js} +2 -2
- package/dist/{tool-execution-helpers-YOE4JC6H.js → tool-execution-helpers-IJTPZL55.js} +3 -3
- 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.
|
|
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
|
|
8743
|
-
|
|
8744
|
-
|
|
8745
|
-
|
|
8746
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
8821
|
-
|
|
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
|
-
|
|
8825
|
-
|
|
8826
|
-
|
|
8827
|
-
|
|
8828
|
-
|
|
8829
|
-
|
|
8830
|
-
|
|
8831
|
-
|
|
8832
|
-
|
|
8833
|
-
|
|
8834
|
-
|
|
8835
|
-
|
|
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
|
-
|
|
8840
|
-
if (
|
|
8841
|
-
|
|
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
|
|
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
|
-
|
|
8877
|
+
resolveResponse(c.newResponse(null, statusCode));
|
|
8866
8878
|
} else if (responseBody) {
|
|
8867
|
-
|
|
8879
|
+
resolveResponse(c.body(responseBody));
|
|
8868
8880
|
} else {
|
|
8869
|
-
|
|
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
|
-
|
|
12964
|
-
|
|
12965
|
-
|
|
12966
|
-
|
|
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
|
-
|
|
12969
|
-
|
|
12970
|
-
|
|
12971
|
-
|
|
12972
|
-
|
|
12973
|
-
|
|
12974
|
-
|
|
12975
|
-
|
|
12976
|
-
|
|
12977
|
-
|
|
12978
|
-
|
|
12979
|
-
|
|
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
|
-
|
|
12983
|
-
|
|
12984
|
-
|
|
12985
|
-
|
|
12986
|
-
|
|
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
|
-
|
|
12989
|
-
|
|
12990
|
-
|
|
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
|
-
|
|
12999
|
-
|
|
13000
|
-
|
|
13001
|
-
|
|
13002
|
-
|
|
13003
|
-
|
|
13004
|
-
|
|
13005
|
-
|
|
13006
|
-
|
|
13007
|
-
|
|
13008
|
-
|
|
13009
|
-
|
|
13010
|
-
|
|
13011
|
-
|
|
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
|
-
|
|
13015
|
-
|
|
13016
|
-
|
|
13017
|
-
|
|
13018
|
-
|
|
13019
|
-
|
|
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
|
-
|
|
13023
|
-
|
|
13024
|
-
|
|
13025
|
-
|
|
13026
|
-
|
|
13027
|
-
|
|
13028
|
-
|
|
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
|
-
|
|
13033
|
-
|
|
13034
|
-
|
|
13035
|
-
|
|
13036
|
-
|
|
13037
|
-
|
|
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
|
-
|
|
13106
|
-
|
|
13107
|
-
|
|
13108
|
-
|
|
13109
|
-
|
|
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
|
-
|
|
13119
|
-
|
|
13120
|
-
|
|
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
|
-
|
|
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
|
-
|
|
13174
|
-
|
|
13175
|
-
|
|
13176
|
-
|
|
13177
|
-
this.heartbeats.
|
|
13178
|
-
|
|
13179
|
-
|
|
13180
|
-
|
|
13181
|
-
|
|
13182
|
-
|
|
13183
|
-
|
|
13184
|
-
|
|
13185
|
-
|
|
13186
|
-
|
|
13187
|
-
|
|
13188
|
-
|
|
13189
|
-
|
|
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
|
-
|
|
13193
|
-
|
|
13194
|
-
|
|
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.
|
|
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
|
-
|
|
13275
|
-
|
|
13276
|
-
|
|
13277
|
-
|
|
13278
|
-
|
|
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
|
-
|
|
13382
|
-
|
|
13383
|
-
|
|
13384
|
-
|
|
13385
|
-
|
|
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
|
|
13402
|
-
|
|
13403
|
-
|
|
13404
|
-
|
|
13405
|
-
|
|
13406
|
-
|
|
13407
|
-
|
|
13408
|
-
|
|
13409
|
-
|
|
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 (
|
|
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:
|
|
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:
|
|
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);
|