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.16.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.10.3"
43
+ "@claude-flow/cli": "^3.17.0"
44
44
  },
45
45
  "optionalDependencies": {
46
- "metaharness": "~0.2.6",
46
+ "metaharness": "~0.2.8",
47
47
  "@metaharness/router": "~0.3.2",
48
- "@metaharness/kernel": "~0.1.0",
49
- "@metaharness/darwin": "~0.3.1",
50
- "@metaharness/redblue": "~0.1.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",
@@ -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
- return res.json({ jsonrpc: "2.0", id, result: {} });
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.json({ jsonrpc: "2.0", id, result: {} });
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
- return res.json({ jsonrpc: "2.0", id, result: {} });
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.json({ jsonrpc: "2.0", id, result: {} });
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 = [];