ruflo 3.16.3 → 3.17.0
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ruflo",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.17.0",
|
|
4
4
|
"description": "Ruflo - Enterprise AI agent orchestration platform. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration",
|
|
5
5
|
"main": "bin/ruflo.js",
|
|
6
6
|
"type": "module",
|
|
@@ -40,14 +40,14 @@
|
|
|
40
40
|
"package:rvf": "bash src/scripts/package-rvf.sh"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@claude-flow/cli": "^3.
|
|
43
|
+
"@claude-flow/cli": "^3.17.0"
|
|
44
44
|
},
|
|
45
45
|
"optionalDependencies": {
|
|
46
|
-
"metaharness": "~0.2.
|
|
46
|
+
"metaharness": "~0.2.8",
|
|
47
47
|
"@metaharness/router": "~0.3.2",
|
|
48
|
-
"@metaharness/kernel": "~0.1.
|
|
49
|
-
"@metaharness/darwin": "~0.
|
|
50
|
-
"@metaharness/redblue": "~0.1.
|
|
48
|
+
"@metaharness/kernel": "~0.1.2",
|
|
49
|
+
"@metaharness/darwin": "~0.8.0",
|
|
50
|
+
"@metaharness/redblue": "~0.1.4"
|
|
51
51
|
},
|
|
52
52
|
"overrides": {
|
|
53
53
|
"ruvector": "^0.2.27",
|
package/src/mcp-bridge/index.js
CHANGED
|
@@ -882,6 +882,19 @@ const GROUP_DISPLAY_NAMES = {
|
|
|
882
882
|
const app = express();
|
|
883
883
|
app.use(express.json({ limit: "10mb" }));
|
|
884
884
|
|
|
885
|
+
// ---------- MCP Streamable HTTP session (#2425 djimit) ----------
|
|
886
|
+
// Streamable-HTTP clients (Codex/RMCP) send `DELETE /mcp` with an
|
|
887
|
+
// `Mcp-Session-Id` header at shutdown. We echo a stable session id back
|
|
888
|
+
// on every /mcp* response so those clients can attach it to the DELETE
|
|
889
|
+
// and to `notifications/initialized` handshakes.
|
|
890
|
+
const MCP_SESSION_ID = randomUUID();
|
|
891
|
+
app.use((req, res, next) => {
|
|
892
|
+
if (req.path.startsWith("/mcp")) {
|
|
893
|
+
res.setHeader("Mcp-Session-Id", MCP_SESSION_ID);
|
|
894
|
+
}
|
|
895
|
+
next();
|
|
896
|
+
});
|
|
897
|
+
|
|
885
898
|
// ---------- CORS middleware (ADR-166 §6 Phase 3b) ----------
|
|
886
899
|
const CORS_ALLOWLIST = (process.env.MCP_CORS_ORIGIN || "*")
|
|
887
900
|
.split(",").map(s => s.trim()).filter(Boolean);
|
|
@@ -894,8 +907,8 @@ app.use((req, res, next) => {
|
|
|
894
907
|
res.setHeader("Access-Control-Allow-Origin", origin);
|
|
895
908
|
res.setHeader("Vary", "Origin");
|
|
896
909
|
}
|
|
897
|
-
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
898
|
-
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
|
910
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
|
|
911
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, Mcp-Session-Id");
|
|
899
912
|
if (req.method === "OPTIONS") return res.sendStatus(204);
|
|
900
913
|
next();
|
|
901
914
|
});
|
|
@@ -944,7 +957,9 @@ function createMcpHandler(groupName) {
|
|
|
944
957
|
return res.json({ jsonrpc: "2.0", id, result: mcpResult });
|
|
945
958
|
}
|
|
946
959
|
case "notifications/initialized":
|
|
947
|
-
|
|
960
|
+
// MCP streamable-HTTP spec: notifications must return 202 Accepted
|
|
961
|
+
// with an empty body (no jsonrpc envelope).
|
|
962
|
+
return res.status(202).end();
|
|
948
963
|
default:
|
|
949
964
|
return res.json({ jsonrpc: "2.0", id, error: { code: -32601, message: `Method not found: ${method}` } });
|
|
950
965
|
}
|
|
@@ -968,6 +983,8 @@ function createMcpSseHandler(groupName) {
|
|
|
968
983
|
for (const groupName of Object.keys(TOOL_GROUPS)) {
|
|
969
984
|
app.post(`/mcp/${groupName}`, createMcpHandler(groupName));
|
|
970
985
|
app.get(`/mcp/${groupName}`, createMcpSseHandler(groupName));
|
|
986
|
+
// #2425 djimit — streamable-HTTP session cleanup
|
|
987
|
+
app.delete(`/mcp/${groupName}`, (_, res) => res.sendStatus(204));
|
|
971
988
|
}
|
|
972
989
|
|
|
973
990
|
// ---------- Catch-all /mcp — serves ALL enabled tools (backwards-compatible) ----------
|
|
@@ -998,7 +1015,7 @@ app.post("/mcp", async (req, res) => {
|
|
|
998
1015
|
return res.json({ jsonrpc: "2.0", id, result: mcpResult });
|
|
999
1016
|
}
|
|
1000
1017
|
case "notifications/initialized":
|
|
1001
|
-
return res.
|
|
1018
|
+
return res.status(202).end();
|
|
1002
1019
|
default:
|
|
1003
1020
|
return res.json({ jsonrpc: "2.0", id, error: { code: -32601, message: `Method not found: ${method}` } });
|
|
1004
1021
|
}
|
|
@@ -1015,6 +1032,9 @@ app.get("/mcp", (req, res) => {
|
|
|
1015
1032
|
res.write(`data: ${JSON.stringify({ type: "endpoint", url: "/mcp" })}\n\n`);
|
|
1016
1033
|
});
|
|
1017
1034
|
|
|
1035
|
+
// #2425 djimit — streamable-HTTP session cleanup on the catch-all route.
|
|
1036
|
+
app.delete("/mcp", (_, res) => res.sendStatus(204));
|
|
1037
|
+
|
|
1018
1038
|
// ---------- GET /mcp-servers — returns MCP_SERVERS JSON for Chat UI config ----------
|
|
1019
1039
|
app.get("/mcp-servers", (_, res) => {
|
|
1020
1040
|
const servers = [];
|
|
@@ -135,6 +135,37 @@ async function testBridge(label, entry) {
|
|
|
135
135
|
} catch (e) {
|
|
136
136
|
assert(false, `R4. terminal_execute threw: ${e.message}`);
|
|
137
137
|
}
|
|
138
|
+
|
|
139
|
+
// R6 — #2425 djimit streamable-HTTP session cleanup: DELETE /mcp → 204
|
|
140
|
+
try {
|
|
141
|
+
const r = await fetch(`http://127.0.0.1:${port}/mcp`, {
|
|
142
|
+
method: "DELETE",
|
|
143
|
+
headers: {
|
|
144
|
+
"Authorization": `Bearer ${TOKEN}`,
|
|
145
|
+
"Mcp-Session-Id": "test-cleanup",
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
assert(r.status === 204, `R6. DELETE /mcp → 204 (got ${r.status})`);
|
|
149
|
+
assert(!!r.headers.get("Mcp-Session-Id"), "R6b. Mcp-Session-Id header echoed on /mcp*");
|
|
150
|
+
} catch (e) {
|
|
151
|
+
assert(false, `R6. DELETE /mcp threw: ${e.message}`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// R7 — #2425 notifications/initialized returns 202 Accepted with empty body
|
|
155
|
+
try {
|
|
156
|
+
const r = await fetch(`http://127.0.0.1:${port}/mcp`, {
|
|
157
|
+
method: "POST",
|
|
158
|
+
headers: {
|
|
159
|
+
"Content-Type": "application/json",
|
|
160
|
+
"Authorization": `Bearer ${TOKEN}`,
|
|
161
|
+
},
|
|
162
|
+
body: JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" }),
|
|
163
|
+
});
|
|
164
|
+
assert(r.status === 202, `R7. notifications/initialized → 202 (got ${r.status})`);
|
|
165
|
+
assert((await r.text()) === "", "R7b. notifications/initialized has empty body");
|
|
166
|
+
} catch (e) {
|
|
167
|
+
assert(false, `R7. notifications/initialized threw: ${e.message}`);
|
|
168
|
+
}
|
|
138
169
|
}
|
|
139
170
|
await killAndWait(b.child);
|
|
140
171
|
}
|
|
@@ -58,6 +58,16 @@ const CHECKS = [
|
|
|
58
58
|
rx: /MCP_CORS_ORIGIN[\s\S]*?CORS_ALLOWLIST/,
|
|
59
59
|
hint: "CORS must respect MCP_CORS_ORIGIN allowlist (Phase 3b)",
|
|
60
60
|
},
|
|
61
|
+
{
|
|
62
|
+
id: "7. streamable-http-delete-handler (#2425)",
|
|
63
|
+
rx: /app\.delete\(\s*["']\/mcp["']\s*,/,
|
|
64
|
+
hint: "DELETE /mcp must be handled for streamable-HTTP session cleanup (#2425)",
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: "8. mcp-session-id-header (#2425)",
|
|
68
|
+
rx: /Mcp-Session-Id/,
|
|
69
|
+
hint: "Mcp-Session-Id header must be echoed on /mcp* responses (#2425)",
|
|
70
|
+
},
|
|
61
71
|
];
|
|
62
72
|
|
|
63
73
|
let hardFail = false;
|
|
@@ -1057,6 +1057,19 @@ const GROUP_DISPLAY_NAMES = {
|
|
|
1057
1057
|
const app = express();
|
|
1058
1058
|
app.use(express.json({ limit: "10mb" }));
|
|
1059
1059
|
|
|
1060
|
+
// ---------- MCP Streamable HTTP session (#2425 djimit) ----------
|
|
1061
|
+
// Streamable-HTTP clients (Codex/RMCP) send `DELETE /mcp` with an
|
|
1062
|
+
// `Mcp-Session-Id` header at shutdown. We echo a stable session id back
|
|
1063
|
+
// on every /mcp* response so those clients can attach it to the DELETE
|
|
1064
|
+
// and to `notifications/initialized` handshakes.
|
|
1065
|
+
const MCP_SESSION_ID = randomUUID();
|
|
1066
|
+
app.use((req, res, next) => {
|
|
1067
|
+
if (req.path.startsWith("/mcp")) {
|
|
1068
|
+
res.setHeader("Mcp-Session-Id", MCP_SESSION_ID);
|
|
1069
|
+
}
|
|
1070
|
+
next();
|
|
1071
|
+
});
|
|
1072
|
+
|
|
1060
1073
|
// ---------- CORS middleware (ADR-166 §6 Phase 3b) ----------
|
|
1061
1074
|
// MCP_CORS_ORIGIN: comma-separated allowlist (e.g. "https://a.example,https://b.example").
|
|
1062
1075
|
// unset → "*" for back-compat (loopback default is same-origin anyway)
|
|
@@ -1074,8 +1087,8 @@ app.use((req, res, next) => {
|
|
|
1074
1087
|
res.setHeader("Vary", "Origin");
|
|
1075
1088
|
}
|
|
1076
1089
|
// If no match, do NOT set the header — browser will block the request.
|
|
1077
|
-
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
1078
|
-
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
|
1090
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
|
|
1091
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, Mcp-Session-Id");
|
|
1079
1092
|
if (req.method === "OPTIONS") return res.sendStatus(204);
|
|
1080
1093
|
next();
|
|
1081
1094
|
});
|
|
@@ -1125,7 +1138,9 @@ function createMcpHandler(groupName) {
|
|
|
1125
1138
|
});
|
|
1126
1139
|
}
|
|
1127
1140
|
case "notifications/initialized":
|
|
1128
|
-
|
|
1141
|
+
// MCP streamable-HTTP spec: notifications must return 202 Accepted
|
|
1142
|
+
// with an empty body (no jsonrpc envelope).
|
|
1143
|
+
return res.status(202).end();
|
|
1129
1144
|
default:
|
|
1130
1145
|
return res.json({ jsonrpc: "2.0", id, error: { code: -32601, message: `Method not found: ${method}` } });
|
|
1131
1146
|
}
|
|
@@ -1149,6 +1164,8 @@ function createMcpSseHandler(groupName) {
|
|
|
1149
1164
|
for (const groupName of Object.keys(TOOL_GROUPS)) {
|
|
1150
1165
|
app.post(`/mcp/${groupName}`, createMcpHandler(groupName));
|
|
1151
1166
|
app.get(`/mcp/${groupName}`, createMcpSseHandler(groupName));
|
|
1167
|
+
// #2425 djimit — streamable-HTTP session cleanup
|
|
1168
|
+
app.delete(`/mcp/${groupName}`, (_, res) => res.sendStatus(204));
|
|
1152
1169
|
}
|
|
1153
1170
|
|
|
1154
1171
|
// ---------- Catch-all /mcp — serves ALL enabled tools (backwards-compatible) ----------
|
|
@@ -1180,7 +1197,7 @@ app.post("/mcp", async (req, res) => {
|
|
|
1180
1197
|
});
|
|
1181
1198
|
}
|
|
1182
1199
|
case "notifications/initialized":
|
|
1183
|
-
return res.
|
|
1200
|
+
return res.status(202).end();
|
|
1184
1201
|
default:
|
|
1185
1202
|
return res.json({ jsonrpc: "2.0", id, error: { code: -32601, message: `Method not found: ${method}` } });
|
|
1186
1203
|
}
|
|
@@ -1197,6 +1214,9 @@ app.get("/mcp", (req, res) => {
|
|
|
1197
1214
|
res.write(`data: ${JSON.stringify({ type: "endpoint", url: "/mcp" })}\n\n`);
|
|
1198
1215
|
});
|
|
1199
1216
|
|
|
1217
|
+
// #2425 djimit — streamable-HTTP session cleanup on the catch-all route.
|
|
1218
|
+
app.delete("/mcp", (_, res) => res.sendStatus(204));
|
|
1219
|
+
|
|
1200
1220
|
// ---------- GET /mcp-servers — returns MCP_SERVERS JSON for Chat UI config ----------
|
|
1201
1221
|
app.get("/mcp-servers", (_, res) => {
|
|
1202
1222
|
const servers = [];
|