@chrysb/alphaclaw 0.9.0-beta.5 → 0.9.0-beta.7
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/lib/server/routes/mcp.js +63 -38
- package/package.json +1 -1
package/lib/server/routes/mcp.js
CHANGED
|
@@ -28,33 +28,55 @@ const resolveGatewayWsUrl = ({ openclawDir, gatewayPort }) => {
|
|
|
28
28
|
|
|
29
29
|
const sessions = new Map();
|
|
30
30
|
let activeTransport = null;
|
|
31
|
-
const kSessionGraceMs = 15_000;
|
|
32
31
|
const kSseKeepAliveMs = 15_000;
|
|
32
|
+
const kMaxSessions = 8;
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
34
|
+
let nextBridgeId = 1;
|
|
35
|
+
const pendingRequests = new Map();
|
|
36
|
+
|
|
37
|
+
const adoptSession = (sessionId, transport) => {
|
|
38
|
+
sessions.set(sessionId, transport);
|
|
39
|
+
activeTransport = transport;
|
|
40
|
+
|
|
41
|
+
if (sessions.size > kMaxSessions) {
|
|
42
|
+
const oldestId = sessions.keys().next().value;
|
|
43
|
+
if (oldestId !== sessionId) {
|
|
44
|
+
const old = sessions.get(oldestId);
|
|
45
|
+
sessions.delete(oldestId);
|
|
46
|
+
old.close().catch(() => {});
|
|
47
|
+
console.log(`[mcp] Evicted oldest session: ${oldestId}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
40
50
|
};
|
|
41
51
|
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
52
|
+
const forwardToBridge = (message, transport) => {
|
|
53
|
+
if (message.id != null) {
|
|
54
|
+
const bridgeId = nextBridgeId++;
|
|
55
|
+
pendingRequests.set(bridgeId, { originalId: message.id, transport });
|
|
56
|
+
writeToMcpBridge({ ...message, id: bridgeId });
|
|
57
|
+
} else {
|
|
58
|
+
writeToMcpBridge(message);
|
|
59
|
+
}
|
|
45
60
|
};
|
|
46
61
|
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if (sessions.has(id) && sessions.get(id) !== activeTransport) {
|
|
53
|
-
console.log(`[mcp] Cleaning up stale session: ${id}`);
|
|
54
|
-
closeSession(id);
|
|
55
|
-
}
|
|
62
|
+
const cleanupTransport = (transport) => {
|
|
63
|
+
for (const [id, t] of sessions) {
|
|
64
|
+
if (t === transport) {
|
|
65
|
+
sessions.delete(id);
|
|
66
|
+
break;
|
|
56
67
|
}
|
|
57
|
-
}
|
|
68
|
+
}
|
|
69
|
+
for (const [bridgeId, pending] of pendingRequests) {
|
|
70
|
+
if (pending.transport === transport) pendingRequests.delete(bridgeId);
|
|
71
|
+
}
|
|
72
|
+
if (activeTransport === transport) activeTransport = null;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const closeAllSessions = () => {
|
|
76
|
+
for (const [, t] of sessions) t.close().catch(() => {});
|
|
77
|
+
sessions.clear();
|
|
78
|
+
pendingRequests.clear();
|
|
79
|
+
activeTransport = null;
|
|
58
80
|
};
|
|
59
81
|
|
|
60
82
|
const registerMcpRoutes = ({
|
|
@@ -65,10 +87,21 @@ const registerMcpRoutes = ({
|
|
|
65
87
|
openclawDir,
|
|
66
88
|
}) => {
|
|
67
89
|
setOnMcpMessage((message) => {
|
|
68
|
-
if (
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
90
|
+
if (message.id != null) {
|
|
91
|
+
const pending = pendingRequests.get(message.id);
|
|
92
|
+
if (pending) {
|
|
93
|
+
pendingRequests.delete(message.id);
|
|
94
|
+
pending.transport
|
|
95
|
+
.send({ ...message, id: pending.originalId })
|
|
96
|
+
.catch((err) => {
|
|
97
|
+
console.error("[mcp] Failed to forward response:", err?.message);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (activeTransport) {
|
|
103
|
+
activeTransport.send(message).catch(() => {});
|
|
104
|
+
}
|
|
72
105
|
});
|
|
73
106
|
|
|
74
107
|
// ── Internal API (session auth) ────────────────────────────────
|
|
@@ -156,7 +189,7 @@ const registerMcpRoutes = ({
|
|
|
156
189
|
const transport = sessions.get(sessionId);
|
|
157
190
|
if (transport) {
|
|
158
191
|
console.log(
|
|
159
|
-
`[mcp] ${req.method} sessionId=${sessionId} → routed
|
|
192
|
+
`[mcp] ${req.method} sessionId=${sessionId} → routed (sessions=${sessions.size})`,
|
|
160
193
|
);
|
|
161
194
|
try {
|
|
162
195
|
await transport.handleRequest(req, res, req.body);
|
|
@@ -171,7 +204,7 @@ const registerMcpRoutes = ({
|
|
|
171
204
|
}
|
|
172
205
|
} else {
|
|
173
206
|
console.log(
|
|
174
|
-
`[mcp] ${req.method} sessionId=${sessionId} → NOT FOUND (
|
|
207
|
+
`[mcp] ${req.method} sessionId=${sessionId} → NOT FOUND (sessions=${sessions.size})`,
|
|
175
208
|
);
|
|
176
209
|
res.status(404).json({
|
|
177
210
|
jsonrpc: "2.0",
|
|
@@ -191,27 +224,19 @@ const registerMcpRoutes = ({
|
|
|
191
224
|
sessionIdGenerator: () => randomUUID(),
|
|
192
225
|
enableJsonResponse: true,
|
|
193
226
|
onsessioninitialized: (newSessionId) => {
|
|
194
|
-
|
|
195
|
-
activeTransport = transport;
|
|
196
|
-
retireStaleSessions(newSessionId);
|
|
227
|
+
adoptSession(newSessionId, transport);
|
|
197
228
|
console.log(
|
|
198
|
-
`[mcp] Session
|
|
229
|
+
`[mcp] Session adopted: ${newSessionId} (sessions=${sessions.size})`,
|
|
199
230
|
);
|
|
200
231
|
},
|
|
201
232
|
});
|
|
202
233
|
|
|
203
234
|
transport.onmessage = (message) => {
|
|
204
|
-
|
|
235
|
+
forwardToBridge(message, transport);
|
|
205
236
|
};
|
|
206
237
|
|
|
207
238
|
transport.onclose = () => {
|
|
208
|
-
|
|
209
|
-
if (t === transport) {
|
|
210
|
-
sessions.delete(id);
|
|
211
|
-
break;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
if (activeTransport === transport) activeTransport = null;
|
|
239
|
+
cleanupTransport(transport);
|
|
215
240
|
console.log(`[mcp] Transport closed (sessions=${sessions.size})`);
|
|
216
241
|
};
|
|
217
242
|
|