@chrysb/alphaclaw 0.9.0-beta.0 → 0.9.0-beta.2
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 +74 -50
- package/package.json +1 -1
package/lib/server/routes/mcp.js
CHANGED
|
@@ -26,17 +26,34 @@ const resolveGatewayWsUrl = ({ openclawDir, gatewayPort }) => {
|
|
|
26
26
|
return `${scheme}://127.0.0.1:${gatewayPort}`;
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
+
const sessions = new Map();
|
|
29
30
|
let activeTransport = null;
|
|
31
|
+
const kSessionGraceMs = 15_000;
|
|
30
32
|
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
const closeSession = (sessionId) => {
|
|
34
|
+
const t = sessions.get(sessionId);
|
|
35
|
+
if (!t) return;
|
|
36
|
+
sessions.delete(sessionId);
|
|
37
|
+
if (activeTransport === t) activeTransport = null;
|
|
38
|
+
t.close().catch(() => {});
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const closeAllSessions = () => {
|
|
42
|
+
for (const [id] of sessions) closeSession(id);
|
|
34
43
|
activeTransport = null;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const retireStaleSessions = (keepId) => {
|
|
47
|
+
const staleIds = [...sessions.keys()].filter((id) => id !== keepId);
|
|
48
|
+
if (staleIds.length === 0) return;
|
|
49
|
+
setTimeout(() => {
|
|
50
|
+
for (const id of staleIds) {
|
|
51
|
+
if (sessions.has(id) && sessions.get(id) !== activeTransport) {
|
|
52
|
+
console.log(`[mcp] Cleaning up stale session: ${id}`);
|
|
53
|
+
closeSession(id);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}, kSessionGraceMs);
|
|
40
57
|
};
|
|
41
58
|
|
|
42
59
|
const registerMcpRoutes = ({
|
|
@@ -46,7 +63,6 @@ const registerMcpRoutes = ({
|
|
|
46
63
|
gatewayEnv,
|
|
47
64
|
openclawDir,
|
|
48
65
|
}) => {
|
|
49
|
-
// Wire bridge stdout messages → active transport
|
|
50
66
|
setOnMcpMessage((message) => {
|
|
51
67
|
if (!activeTransport) return;
|
|
52
68
|
activeTransport.send(message).catch((err) => {
|
|
@@ -86,7 +102,7 @@ const registerMcpRoutes = ({
|
|
|
86
102
|
});
|
|
87
103
|
|
|
88
104
|
app.post("/api/mcp/stop", requireAuth, async (_req, res) => {
|
|
89
|
-
|
|
105
|
+
closeAllSessions();
|
|
90
106
|
const result = stopMcpBridge();
|
|
91
107
|
res.json(result);
|
|
92
108
|
});
|
|
@@ -125,38 +141,52 @@ const registerMcpRoutes = ({
|
|
|
125
141
|
const sessionId = req.headers["mcp-session-id"];
|
|
126
142
|
|
|
127
143
|
// ── Existing session ───────────────────────────────────────
|
|
128
|
-
if (sessionId
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
144
|
+
if (sessionId) {
|
|
145
|
+
const transport = sessions.get(sessionId);
|
|
146
|
+
if (transport) {
|
|
147
|
+
console.log(
|
|
148
|
+
`[mcp] ${req.method} sessionId=${sessionId} → routed to transport (sessions=${sessions.size})`,
|
|
149
|
+
);
|
|
150
|
+
try {
|
|
151
|
+
await transport.handleRequest(req, res, req.body);
|
|
152
|
+
} catch (err) {
|
|
153
|
+
console.error(
|
|
154
|
+
"[mcp] handleRequest error (existing session):",
|
|
155
|
+
err?.message,
|
|
156
|
+
);
|
|
157
|
+
if (!res.headersSent) {
|
|
158
|
+
res.status(500).json({ error: "Internal transport error" });
|
|
159
|
+
}
|
|
135
160
|
}
|
|
161
|
+
} else {
|
|
162
|
+
console.log(
|
|
163
|
+
`[mcp] ${req.method} sessionId=${sessionId} → NOT FOUND (known=[${[...sessions.keys()].join(", ")}])`,
|
|
164
|
+
);
|
|
165
|
+
res.status(404).json({
|
|
166
|
+
jsonrpc: "2.0",
|
|
167
|
+
error: {
|
|
168
|
+
code: -32001,
|
|
169
|
+
message: "Session not found. The server may have been restarted.",
|
|
170
|
+
},
|
|
171
|
+
id: null,
|
|
172
|
+
});
|
|
136
173
|
}
|
|
137
174
|
return;
|
|
138
175
|
}
|
|
139
176
|
|
|
140
|
-
// ── Stale / unknown session ────────────────────────────────
|
|
141
|
-
if (sessionId && !activeTransport) {
|
|
142
|
-
res.status(404).json({
|
|
143
|
-
jsonrpc: "2.0",
|
|
144
|
-
error: {
|
|
145
|
-
code: -32000,
|
|
146
|
-
message: "Session not found. The server may have been restarted.",
|
|
147
|
-
},
|
|
148
|
-
id: null,
|
|
149
|
-
});
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
177
|
// ── New session (POST without session ID) ────────────────
|
|
154
|
-
|
|
155
|
-
// non-initialize requests and return the proper JSON-RPC error.
|
|
156
|
-
if (!sessionId && req.method === "POST") {
|
|
178
|
+
if (req.method === "POST") {
|
|
157
179
|
const transport = new StreamableHTTPServerTransport({
|
|
158
180
|
sessionIdGenerator: () => randomUUID(),
|
|
159
181
|
enableJsonResponse: true,
|
|
182
|
+
onsessioninitialized: (newSessionId) => {
|
|
183
|
+
sessions.set(newSessionId, transport);
|
|
184
|
+
activeTransport = transport;
|
|
185
|
+
retireStaleSessions(newSessionId);
|
|
186
|
+
console.log(
|
|
187
|
+
`[mcp] Session registered: ${newSessionId} (sessions=${sessions.size})`,
|
|
188
|
+
);
|
|
189
|
+
},
|
|
160
190
|
});
|
|
161
191
|
|
|
162
192
|
transport.onmessage = (message) => {
|
|
@@ -164,10 +194,14 @@ const registerMcpRoutes = ({
|
|
|
164
194
|
};
|
|
165
195
|
|
|
166
196
|
transport.onclose = () => {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
197
|
+
for (const [id, t] of sessions) {
|
|
198
|
+
if (t === transport) {
|
|
199
|
+
sessions.delete(id);
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
170
202
|
}
|
|
203
|
+
if (activeTransport === transport) activeTransport = null;
|
|
204
|
+
console.log(`[mcp] Transport closed (sessions=${sessions.size})`);
|
|
171
205
|
};
|
|
172
206
|
|
|
173
207
|
transport.onerror = (err) => {
|
|
@@ -176,27 +210,17 @@ const registerMcpRoutes = ({
|
|
|
176
210
|
|
|
177
211
|
await transport.start();
|
|
178
212
|
|
|
179
|
-
// Set activeTransport BEFORE handleRequest so the onMcpMessage
|
|
180
|
-
// callback can forward the child's response to this transport.
|
|
181
|
-
const prev = activeTransport;
|
|
182
|
-
activeTransport = transport;
|
|
183
|
-
|
|
184
213
|
try {
|
|
185
214
|
await transport.handleRequest(req, res, req.body);
|
|
186
215
|
} catch (err) {
|
|
187
|
-
console.error(
|
|
216
|
+
console.error(
|
|
217
|
+
"[mcp] handleRequest error (new session):",
|
|
218
|
+
err?.message,
|
|
219
|
+
);
|
|
188
220
|
if (!res.headersSent) {
|
|
189
221
|
res.status(500).json({ error: "Failed to initialize MCP session" });
|
|
190
222
|
}
|
|
191
223
|
}
|
|
192
|
-
|
|
193
|
-
if (transport.sessionId) {
|
|
194
|
-
if (prev) prev.close().catch(() => {});
|
|
195
|
-
console.log(`[mcp] Session established: ${transport.sessionId}`);
|
|
196
|
-
} else {
|
|
197
|
-
// Session was not established — roll back
|
|
198
|
-
if (activeTransport === transport) activeTransport = prev;
|
|
199
|
-
}
|
|
200
224
|
return;
|
|
201
225
|
}
|
|
202
226
|
|