@claude-flow/cli 3.57.2 → 3.58.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.
Files changed (55) hide show
  1. package/.claude/helpers/helpers.manifest.json +2 -2
  2. package/README.md +1 -0
  3. package/catalog-manifest.json +2 -2
  4. package/dist/src/commands/agent.js +4 -1
  5. package/dist/src/commands/mcp.js +66 -6
  6. package/dist/src/commands/memory.js +53 -2
  7. package/dist/src/mcp-server.d.ts +5 -0
  8. package/dist/src/mcp-server.js +39 -0
  9. package/dist/src/mcp-tools/agent-tools.js +28 -0
  10. package/dist/src/mcp-tools/claims-tools.js +25 -15
  11. package/dist/src/memory/memory-initializer.js +43 -4
  12. package/dist/src/memory/memory-peek.d.ts +28 -0
  13. package/dist/src/memory/memory-peek.js +222 -0
  14. package/dist/src/memory/model-cache-dir.d.ts +11 -0
  15. package/dist/src/memory/model-cache-dir.js +17 -0
  16. package/dist/src/ruvector/task-embedder.js +38 -4
  17. package/node_modules/@claude-flow/codex/README.md +78 -0
  18. package/node_modules/@claude-flow/codex/dist/activity/cli.d.ts +3 -0
  19. package/node_modules/@claude-flow/codex/dist/activity/cli.d.ts.map +1 -0
  20. package/node_modules/@claude-flow/codex/dist/activity/cli.js +115 -0
  21. package/node_modules/@claude-flow/codex/dist/activity/cli.js.map +1 -0
  22. package/node_modules/@claude-flow/codex/dist/activity/files.d.ts +14 -0
  23. package/node_modules/@claude-flow/codex/dist/activity/files.d.ts.map +1 -0
  24. package/node_modules/@claude-flow/codex/dist/activity/files.js +142 -0
  25. package/node_modules/@claude-flow/codex/dist/activity/files.js.map +1 -0
  26. package/node_modules/@claude-flow/codex/dist/activity/parser.d.ts +21 -0
  27. package/node_modules/@claude-flow/codex/dist/activity/parser.d.ts.map +1 -0
  28. package/node_modules/@claude-flow/codex/dist/activity/parser.js +179 -0
  29. package/node_modules/@claude-flow/codex/dist/activity/parser.js.map +1 -0
  30. package/node_modules/@claude-flow/codex/dist/activity/reader.d.ts +20 -0
  31. package/node_modules/@claude-flow/codex/dist/activity/reader.d.ts.map +1 -0
  32. package/node_modules/@claude-flow/codex/dist/activity/reader.js +106 -0
  33. package/node_modules/@claude-flow/codex/dist/activity/reader.js.map +1 -0
  34. package/node_modules/@claude-flow/codex/dist/activity/sanitize.d.ts +4 -0
  35. package/node_modules/@claude-flow/codex/dist/activity/sanitize.d.ts.map +1 -0
  36. package/node_modules/@claude-flow/codex/dist/activity/sanitize.js +27 -0
  37. package/node_modules/@claude-flow/codex/dist/activity/sanitize.js.map +1 -0
  38. package/node_modules/@claude-flow/codex/dist/activity/types.d.ts +31 -0
  39. package/node_modules/@claude-flow/codex/dist/activity/types.d.ts.map +1 -0
  40. package/node_modules/@claude-flow/codex/dist/activity/types.js +5 -0
  41. package/node_modules/@claude-flow/codex/dist/activity/types.js.map +1 -0
  42. package/node_modules/@claude-flow/codex/dist/activity/viewer.d.ts +21 -0
  43. package/node_modules/@claude-flow/codex/dist/activity/viewer.d.ts.map +1 -0
  44. package/node_modules/@claude-flow/codex/dist/activity/viewer.js +168 -0
  45. package/node_modules/@claude-flow/codex/dist/activity/viewer.js.map +1 -0
  46. package/node_modules/@claude-flow/codex/dist/cli.js +3 -0
  47. package/node_modules/@claude-flow/codex/dist/cli.js.map +1 -1
  48. package/node_modules/@claude-flow/mcp/dist/server.d.ts.map +1 -1
  49. package/node_modules/@claude-flow/mcp/dist/server.js +5 -3
  50. package/node_modules/@claude-flow/mcp/dist/server.js.map +1 -1
  51. package/node_modules/@claude-flow/mcp/dist/types.d.ts +11 -0
  52. package/node_modules/@claude-flow/mcp/dist/types.d.ts.map +1 -1
  53. package/node_modules/@claude-flow/mcp/dist/types.js.map +1 -1
  54. package/package.json +1 -1
  55. package/plugins/ruflo-console/dist/tui.mjs +124 -124
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "manifest": {
3
- "version": "3.57.2",
3
+ "version": "3.58.0",
4
4
  "files": {
5
5
  "auto-memory-hook.mjs": "631fac9d518835e2852a406888316bbbddddb49e7ed8e406d2e971f46833df9a",
6
6
  "hook-handler.cjs": "bc12b3cae92d5a7c8deef699fa00d0ef07b57625c18dbeac4387f15725b8e09f",
@@ -9,6 +9,6 @@
9
9
  "router.cjs": "16648b3c0d73b6681ac2d8d13def6a68286c57f5a11004de5b19a0fa088c833d"
10
10
  }
11
11
  },
12
- "signature": "1NC9a0vJ78KCh8TIZfhExMq0923sXMkAej7Dxg4hlTqmOBzS1Gn9ex7z1lpkCTOlVafXVVXXMsEa1BSdsvQ7CQ==",
12
+ "signature": "xqclyZcKpZly9uW8nOyUEwAcR+R/7JlzjHdKXYJ5I2lWeBWYWogZZjrijQEQMGSNvN61cVs3Xtyob91K7mxIBQ==",
13
13
  "algorithm": "ed25519"
14
14
  }
package/README.md CHANGED
@@ -240,6 +240,7 @@ This adds slash commands and agent definitions. `ruflo-core` (installed above) d
240
240
  | [**ruflo-browser**](plugins/ruflo-browser/README.md) | Automate browser testing with Playwright |
241
241
  | [**ruflo-jujutsu**](plugins/ruflo-jujutsu/README.md) | Analyze git diffs, score risk, suggest reviewers |
242
242
  | [**ruflo-docs**](plugins/ruflo-docs/README.md) | Generate and maintain documentation automatically |
243
+ | [**agentic-qe-fleet**](https://github.com/proffesor-for-testing/agentic-qe/tree/v3.15.10/plugins/agentic-qe-fleet) | External, maintained by [Agentic QE](https://github.com/proffesor-for-testing/agentic-qe): 11 QE agents, test generation, coverage, chaos and TDD skills, the `agentic-qe` MCP server, and a guard for AQE learning data (`/plugin install agentic-qe-fleet@ruflo`). Install it from one marketplace only, `agentic-qe` or `ruflo`: enabled from both, its mod and commands load twice |
243
244
 
244
245
  #### Security & Compliance
245
246
 
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "generation": 7,
4
- "generatedAt": "2026-10-19T23:34:04.095Z",
5
- "gitSha": "7be330bc",
4
+ "generatedAt": "2026-10-20T06:14:05.915Z",
5
+ "gitSha": "d529a38a",
6
6
  "catalog": {
7
7
  "agents": 176,
8
8
  "tools": 423,
@@ -136,6 +136,7 @@ const spawnCommand = {
136
136
  const result = await callMCPTool('agent_spawn', {
137
137
  agentType,
138
138
  id: agentName,
139
+ name: agentName,
139
140
  config: {
140
141
  provider: ctx.flags.provider || 'anthropic',
141
142
  model: ctx.flags.model,
@@ -214,7 +215,9 @@ const listCommand = {
214
215
  try {
215
216
  // Call MCP tool to list agents
216
217
  const result = await callMCPTool('agent_list', {
217
- status: ctx.flags.all ? 'all' : ctx.flags.status || undefined,
218
+ // agent_list has no 'all' status: --all means "don't hide terminated agents".
219
+ status: ctx.flags.status || undefined,
220
+ includeTerminated: ctx.flags.all ? true : undefined,
218
221
  agentType: ctx.flags.type || undefined,
219
222
  limit: 100,
220
223
  });
@@ -34,6 +34,14 @@ function formatUptime(seconds) {
34
34
  const mins = Math.floor((seconds % 3600) / 60);
35
35
  return `${hours}h ${mins}m`;
36
36
  }
37
+ function readIntegerOption(flags, name, envName) {
38
+ const camelName = name.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
39
+ const input = flags[camelName] ?? flags[name] ?? process.env[envName];
40
+ return input === undefined ? undefined : Number(input);
41
+ }
42
+ function isOutOfRange(value, min, max) {
43
+ return value !== undefined && (!Number.isSafeInteger(value) || value < min || value > max);
44
+ }
37
45
  // Start MCP server
38
46
  const startCommand = {
39
47
  name: 'start',
@@ -71,6 +79,31 @@ const startCommand = {
71
79
  description: 'HTTP request timeout in milliseconds (default 30000; env RUFLO_MCP_REQUEST_TIMEOUT_MS)',
72
80
  type: 'number'
73
81
  },
82
+ {
83
+ name: 'rate-limit-per-ip',
84
+ description: 'HTTP requests allowed per client IP per window (env RUFLO_MCP_RATE_LIMIT_PER_IP)',
85
+ type: 'number'
86
+ },
87
+ {
88
+ name: 'rate-limit-window-ms',
89
+ description: 'HTTP rate limit window in milliseconds (env RUFLO_MCP_RATE_LIMIT_WINDOW_MS)',
90
+ type: 'number'
91
+ },
92
+ {
93
+ name: 'rate-limit-per-session',
94
+ description: 'Request burst allowed per MCP session, refilled over 10s; also bounded by the server-wide bucket (env RUFLO_MCP_RATE_LIMIT_PER_SESSION)',
95
+ type: 'number'
96
+ },
97
+ {
98
+ name: 'rate-limit-global-rps',
99
+ description: 'Server-wide requests per second across all sessions (env RUFLO_MCP_RATE_LIMIT_GLOBAL_RPS)',
100
+ type: 'number'
101
+ },
102
+ {
103
+ name: 'rate-limit-global-burst',
104
+ description: 'Server-wide request burst across all sessions (env RUFLO_MCP_RATE_LIMIT_GLOBAL_BURST)',
105
+ type: 'number'
106
+ },
74
107
  {
75
108
  name: 'auth-token-file',
76
109
  description: 'Read the HTTP bearer token from this file (http transport). Preferred over --auth-token; env RUFLO_MCP_HTTP_TOKEN also works',
@@ -100,6 +133,7 @@ const startCommand = {
100
133
  { command: 'claude-flow mcp start', description: 'Start with defaults (stdio)' },
101
134
  { command: 'claude-flow mcp start -p 8080 -t http', description: 'Start HTTP server' },
102
135
  { command: 'claude-flow mcp start -t http --request-timeout-ms 120000', description: 'Allow slower cold-start HTTP tools' },
136
+ { command: 'claude-flow mcp start -t http --rate-limit-per-ip 600 --rate-limit-per-session 500 --rate-limit-global-burst 1000', description: 'Raise rate limits for a shared control plane' },
103
137
  { command: 'RUFLO_MCP_HTTP_TOKEN=<16+ chars> claude-flow mcp start -t http', description: 'HTTP server requiring "Authorization: Bearer <token>" on every request (except GET /health)' },
104
138
  { command: 'RUFLO_MCP_HTTP_TOKEN=<16+ chars> claude-flow mcp start -t http --host 0.0.0.0', description: 'Non-loopback bind: allowed only with a token (or RUFLO_MCP_ALLOW_UNAUTHENTICATED_HTTP=1)' },
105
139
  { command: 'claude-flow mcp start -d', description: 'Start as daemon' },
@@ -112,15 +146,36 @@ const startCommand = {
112
146
  const tools = ctx.flags.tools
113
147
  || process.env.CLAUDE_FLOW_MCP_TOOLS
114
148
  || 'all';
115
- const timeoutInput = ctx.flags.requestTimeoutMs
116
- ?? ctx.flags['request-timeout-ms']
117
- ?? process.env.RUFLO_MCP_REQUEST_TIMEOUT_MS;
118
- const requestTimeoutMs = timeoutInput === undefined ? undefined : Number(timeoutInput);
119
- if (requestTimeoutMs !== undefined &&
120
- (!Number.isSafeInteger(requestTimeoutMs) || requestTimeoutMs < 1 || requestTimeoutMs > 3_600_000)) {
149
+ const requestTimeoutMs = readIntegerOption(ctx.flags, 'request-timeout-ms', 'RUFLO_MCP_REQUEST_TIMEOUT_MS');
150
+ if (isOutOfRange(requestTimeoutMs, 1, 3_600_000)) {
121
151
  output.printError('Request timeout must be an integer from 1 to 3600000 milliseconds');
122
152
  return { success: false, exitCode: 1 };
123
153
  }
154
+ const rateLimitPerIp = readIntegerOption(ctx.flags, 'rate-limit-per-ip', 'RUFLO_MCP_RATE_LIMIT_PER_IP');
155
+ if (isOutOfRange(rateLimitPerIp, 1, 100_000)) {
156
+ output.printError('HTTP rate limit must be an integer from 1 to 100000 requests per window');
157
+ return { success: false, exitCode: 1 };
158
+ }
159
+ const rateLimitWindowMs = readIntegerOption(ctx.flags, 'rate-limit-window-ms', 'RUFLO_MCP_RATE_LIMIT_WINDOW_MS');
160
+ if (isOutOfRange(rateLimitWindowMs, 1000, 3_600_000)) {
161
+ output.printError('Rate limit window must be an integer from 1000 to 3600000 milliseconds');
162
+ return { success: false, exitCode: 1 };
163
+ }
164
+ const rateLimitPerSession = readIntegerOption(ctx.flags, 'rate-limit-per-session', 'RUFLO_MCP_RATE_LIMIT_PER_SESSION');
165
+ if (isOutOfRange(rateLimitPerSession, 1, 10_000)) {
166
+ output.printError('Session rate limit must be an integer from 1 to 10000 requests');
167
+ return { success: false, exitCode: 1 };
168
+ }
169
+ const rateLimitGlobalRps = readIntegerOption(ctx.flags, 'rate-limit-global-rps', 'RUFLO_MCP_RATE_LIMIT_GLOBAL_RPS');
170
+ if (isOutOfRange(rateLimitGlobalRps, 1, 10_000)) {
171
+ output.printError('Global rate limit must be an integer from 1 to 10000 requests per second');
172
+ return { success: false, exitCode: 1 };
173
+ }
174
+ const rateLimitGlobalBurst = readIntegerOption(ctx.flags, 'rate-limit-global-burst', 'RUFLO_MCP_RATE_LIMIT_GLOBAL_BURST');
175
+ if (isOutOfRange(rateLimitGlobalBurst, 1, 20_000)) {
176
+ output.printError('Global rate limit burst must be an integer from 1 to 20000 requests');
177
+ return { success: false, exitCode: 1 };
178
+ }
124
179
  let authToken;
125
180
  try {
126
181
  authToken = resolveMcpHttpAuthToken({
@@ -189,6 +244,11 @@ const startCommand = {
189
244
  port,
190
245
  tools: !tools || tools === 'all' ? 'all' : tools.split(','),
191
246
  ...(requestTimeoutMs === undefined ? {} : { requestTimeoutMs }),
247
+ ...(rateLimitPerIp === undefined ? {} : { rateLimitPerIp }),
248
+ ...(rateLimitWindowMs === undefined ? {} : { rateLimitWindowMs }),
249
+ ...(rateLimitPerSession === undefined ? {} : { rateLimitPerSession }),
250
+ ...(rateLimitGlobalRps === undefined ? {} : { rateLimitGlobalRps }),
251
+ ...(rateLimitGlobalBurst === undefined ? {} : { rateLimitGlobalBurst }),
192
252
  ...(authToken ? { authToken } : {}),
193
253
  daemonize: daemon,
194
254
  };
@@ -16,6 +16,7 @@ import { existsSync } from 'node:fs';
16
16
  import { siblingAgentDbPath } from '../memory/memory-bridge.js';
17
17
  import { validateIdentifier } from '../mcp-tools/validate-input.js';
18
18
  import { memoryKeyError } from '../mcp-tools/memory-tools.js';
19
+ import { memoryPeek } from '../memory/memory-peek.js';
19
20
  /**
20
21
  * #3228: a miss in one store is not a miss in the memory.
21
22
  *
@@ -1223,6 +1224,54 @@ const statsCommand = {
1223
1224
  }
1224
1225
  }
1225
1226
  };
1227
+ // #3508: read-only route/peek — which store files this cwd resolves to,
1228
+ // their row counts and last write, without creating or initializing
1229
+ // anything. Unlike `stats`, this never calls into the MCP tool layer (no
1230
+ // `authorizeMcpTool` receipt, no `ensureInitialized()`): it reads both
1231
+ // files directly, in-process, read-only.
1232
+ const peekCommand = {
1233
+ name: 'peek',
1234
+ description: 'Report which memory store files this cwd resolves to, their row counts and last write — read-only, creates nothing',
1235
+ options: [DB_PATH_OPTION],
1236
+ action: async (ctx) => {
1237
+ try {
1238
+ const result = await memoryPeek({ path: ctx.flags.path });
1239
+ if (ctx.flags.format === 'json') {
1240
+ output.printJson(result);
1241
+ return { success: true, data: result };
1242
+ }
1243
+ output.writeln();
1244
+ output.writeln(output.bold('Memory Peek'));
1245
+ output.writeln();
1246
+ output.writeln(`cwd: ${result.cwd}`);
1247
+ output.writeln(`root: ${result.root.path} (source: ${result.root.source})`);
1248
+ output.writeln();
1249
+ output.printTable({
1250
+ columns: [
1251
+ { key: 'role', header: 'Role', width: 6 },
1252
+ { key: 'path', header: 'Path', width: 40 },
1253
+ { key: 'exists', header: 'Exists', width: 8 },
1254
+ { key: 'rows', header: 'Rows', width: 8, align: 'right' },
1255
+ { key: 'lastWrite', header: 'Last Write', width: 24 },
1256
+ ],
1257
+ data: result.stores.map((store) => ({
1258
+ role: store.role,
1259
+ path: store.path,
1260
+ exists: store.exists ? 'yes' : 'no',
1261
+ rows: store.encrypted ? 'encrypted' : (store.rows === null ? 'unknown' : String(store.rows)),
1262
+ lastWrite: store.lastWrite ?? 'N/A',
1263
+ })),
1264
+ });
1265
+ output.writeln();
1266
+ output.writeln(`Shared keys (same namespace+key in both stores): ${result.sharedKeys === null ? 'unknown' : result.sharedKeys}`);
1267
+ return { success: true, data: result };
1268
+ }
1269
+ catch (error) {
1270
+ output.printError(`Failed to peek: ${error instanceof Error ? error.message : 'Unknown error'}`);
1271
+ return { success: false, exitCode: 1 };
1272
+ }
1273
+ }
1274
+ };
1226
1275
  // Configure command
1227
1276
  const configureCommand = {
1228
1277
  name: 'configure',
@@ -2038,12 +2087,13 @@ const classifyCommand = {
2038
2087
  export const memoryCommand = {
2039
2088
  name: 'memory',
2040
2089
  description: 'Memory management commands',
2041
- subcommands: [initMemoryCommand, storeCommand, retrieveCommand, searchCommand, listCommand, deleteCommand, purgeCommand, statsCommand, configureCommand, cleanupCommand, compressCommand, exportCommand, importCommand, distillCommand, backupCommand, classifyCommand, selectOperatorCommand],
2090
+ subcommands: [initMemoryCommand, storeCommand, retrieveCommand, searchCommand, listCommand, deleteCommand, purgeCommand, statsCommand, peekCommand, configureCommand, cleanupCommand, compressCommand, exportCommand, importCommand, distillCommand, backupCommand, classifyCommand, selectOperatorCommand],
2042
2091
  options: [],
2043
2092
  examples: [
2044
2093
  { command: 'claude-flow memory store -k "key" -v "value"', description: 'Store data' },
2045
2094
  { command: 'claude-flow memory search -q "auth patterns"', description: 'Search memory' },
2046
- { command: 'claude-flow memory stats', description: 'Show statistics' }
2095
+ { command: 'claude-flow memory stats', description: 'Show statistics' },
2096
+ { command: 'claude-flow memory peek', description: 'Read-only: which store files this cwd resolves to, rows, last write' }
2047
2097
  ],
2048
2098
  action: async (ctx) => {
2049
2099
  output.writeln();
@@ -2060,6 +2110,7 @@ export const memoryCommand = {
2060
2110
  `${output.highlight('list')} - List memory entries`,
2061
2111
  `${output.highlight('delete')} - Delete memory entry`,
2062
2112
  `${output.highlight('stats')} - Show statistics`,
2113
+ `${output.highlight('peek')} - Read-only: resolved store files, rows, last write (creates nothing)`,
2063
2114
  `${output.highlight('configure')} - Configure backend`,
2064
2115
  `${output.highlight('cleanup')} - Clean expired entries`,
2065
2116
  `${output.highlight('compress')} - Compress database`,
@@ -30,6 +30,11 @@ export interface MCPServerOptions {
30
30
  daemonize?: boolean;
31
31
  timeout?: number;
32
32
  requestTimeoutMs?: number;
33
+ rateLimitPerIp?: number;
34
+ rateLimitWindowMs?: number;
35
+ rateLimitPerSession?: number;
36
+ rateLimitGlobalRps?: number;
37
+ rateLimitGlobalBurst?: number;
33
38
  /**
34
39
  * Bearer token every HTTP request must present (`Authorization: Bearer <token>`).
35
40
  * http transport only. Prefer RUFLO_MCP_HTTP_TOKEN / a token file over this
@@ -103,6 +103,10 @@ function processStartToken(pid) {
103
103
  return undefined;
104
104
  }
105
105
  }
106
+ function definedFields(fields) {
107
+ const entries = Object.entries(fields).filter(([, value]) => value !== undefined);
108
+ return entries.length === 0 ? undefined : Object.fromEntries(entries);
109
+ }
106
110
  /**
107
111
  * Hosts treated as same-machine-only for MCP HTTP tool-call authorization
108
112
  * purposes (see startHttpServer's non-loopback authorization gate below).
@@ -134,6 +138,26 @@ export function isUnauthenticatedHttpAllowed(env = process.env) {
134
138
  export function shouldRefuseUnauthenticatedHttp(host, env = process.env, authenticated = false) {
135
139
  return !isLoopbackHost(host) && !authenticated && !isUnauthenticatedHttpAllowed(env);
136
140
  }
141
+ /** The limits @claude-flow/mcp applies when a rate limit option is unset. */
142
+ const DEFAULT_RATE_LIMITS = {
143
+ rateLimitPerIp: 120,
144
+ rateLimitPerSession: 50,
145
+ rateLimitGlobalRps: 100,
146
+ rateLimitGlobalBurst: 200,
147
+ };
148
+ const DEFAULT_RATE_LIMIT_WINDOW_MS = 60_000;
149
+ /**
150
+ * Rate limit options that are looser than the @claude-flow/mcp defaults. A
151
+ * shorter window counts as looser.
152
+ */
153
+ function raisedRateLimits(options) {
154
+ const raised = Object.keys(DEFAULT_RATE_LIMITS)
155
+ .filter((name) => (options[name] ?? 0) > DEFAULT_RATE_LIMITS[name]);
156
+ if ((options.rateLimitWindowMs ?? DEFAULT_RATE_LIMIT_WINDOW_MS) < DEFAULT_RATE_LIMIT_WINDOW_MS) {
157
+ raised.push('rateLimitWindowMs');
158
+ }
159
+ return raised;
160
+ }
137
161
  /** Printable ASCII without space (RFC 6750 token-ish), 16-512 chars. */
138
162
  const AUTH_TOKEN_PATTERN = /^[\x21-\x7e]{16,512}$/;
139
163
  /**
@@ -822,6 +846,15 @@ export class MCPServerManager extends EventEmitter {
822
846
  'RUFLO_MCP_ALLOW_UNAUTHENTICATED_HTTP=1 to acknowledge the risk and proceed ' +
823
847
  '(e.g. when a trusted reverse proxy or network boundary already enforces auth).');
824
848
  }
849
+ const raised = raisedRateLimits(this.options);
850
+ if (raised.length > 0 && !isLoopbackHost(this.options.host) && !authToken) {
851
+ // RUFLO_MCP_ALLOW_UNAUTHENTICATED_HTTP=1 lets an open server start, but
852
+ // anyone who can reach it should not also get more than the default
853
+ // request budget.
854
+ throw new Error(`Refusing to raise MCP rate limits (${raised.join(', ')}) above the defaults on non-loopback ` +
855
+ `host "${this.options.host}" without a bearer token. Set RUFLO_MCP_HTTP_TOKEN (or ` +
856
+ '--auth-token-file), or bind to a loopback host (127.0.0.1, ::1, or localhost).');
857
+ }
825
858
  // Dynamically import the MCP server package
826
859
  // FIX for issue #942: Use proper package import instead of broken relative path
827
860
  const { createMCPServer } = await import('@claude-flow/mcp');
@@ -867,6 +900,12 @@ export class MCPServerManager extends EventEmitter {
867
900
  enableMetrics: true,
868
901
  enableCaching: true,
869
902
  requestTimeout: this.options.requestTimeoutMs,
903
+ rateLimit: definedFields({ windowMs: this.options.rateLimitWindowMs, limit: this.options.rateLimitPerIp }),
904
+ sessionRateLimit: definedFields({
905
+ requestsPerSecond: this.options.rateLimitGlobalRps,
906
+ burstSize: this.options.rateLimitGlobalBurst,
907
+ perSessionLimit: this.options.rateLimitPerSession,
908
+ }),
870
909
  auth: authToken
871
910
  ? { enabled: true, method: 'token', tokens: [authToken] }
872
911
  : undefined,
@@ -216,6 +216,7 @@ export const agentTools = [
216
216
  properties: {
217
217
  agentType: { type: 'string', description: 'Type of agent to spawn' },
218
218
  agentId: { type: 'string', description: 'Optional custom agent ID' },
219
+ name: { type: 'string', description: 'Optional display name for the agent (shown by agent_list, agent_status and ruflo-console)' },
219
220
  // #2085 — accept swarmId so spawned agents register in the
220
221
  // swarm.agents array that swarm_status reports. Omit to register
221
222
  // with the most-recently-created swarm.
@@ -241,6 +242,18 @@ export const agentTools = [
241
242
  }
242
243
  const agentId = input.agentId || `agent-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
243
244
  const agentType = input.agentType;
245
+ // #4300 — older callers (including `agent spawn` before this fix) send
246
+ // the name only as metadata.name.
247
+ const metadata = input.metadata;
248
+ const rawName = input.name ?? metadata?.name;
249
+ let name;
250
+ if (rawName !== undefined && rawName !== '') {
251
+ const vName = validateText(rawName, 'name', 200);
252
+ if (!vName.valid) {
253
+ return { success: false, error: `Input validation failed: ${vName.error}` };
254
+ }
255
+ name = vName.sanitized;
256
+ }
244
257
  const config = input.config || {};
245
258
  // Add explicit model to config if provided
246
259
  if (input.model) {
@@ -262,6 +275,7 @@ export const agentTools = [
262
275
  const agent = {
263
276
  agentId,
264
277
  agentType,
278
+ ...(name ? { name } : {}),
265
279
  status: 'idle',
266
280
  health: 1.0,
267
281
  taskCount: 0,
@@ -347,6 +361,7 @@ export const agentTools = [
347
361
  success: true,
348
362
  agentId,
349
363
  agentType: agent.agentType,
364
+ ...(agent.name ? { name: agent.name } : {}),
350
365
  model: agent.model,
351
366
  modelRoutedBy: routingResult.routedBy,
352
367
  ...(routingResult.modelId ? { modelId: routingResult.modelId } : {}),
@@ -501,6 +516,7 @@ export const agentTools = [
501
516
  return {
502
517
  agentId: agent.agentId,
503
518
  agentType: agent.agentType,
519
+ ...(agent.name ? { name: agent.name } : {}),
504
520
  status: agent.status,
505
521
  health: agent.health,
506
522
  taskCount: agent.taskCount,
@@ -525,6 +541,7 @@ export const agentTools = [
525
541
  properties: {
526
542
  status: { type: 'string', description: 'Filter by status' },
527
543
  domain: { type: 'string', description: 'Filter by domain' },
544
+ agentType: { type: 'string', description: 'Filter by agent type' },
528
545
  includeTerminated: { type: 'boolean', description: 'Include terminated agents' },
529
546
  },
530
547
  },
@@ -539,6 +556,11 @@ export const agentTools = [
539
556
  if (!v.valid)
540
557
  return { agents: [], total: 0, error: `Input validation failed: ${v.error}` };
541
558
  }
559
+ if (input.agentType) {
560
+ const v = validateIdentifier(input.agentType, 'agentType');
561
+ if (!v.valid)
562
+ return { agents: [], total: 0, error: `Input validation failed: ${v.error}` };
563
+ }
542
564
  let agents = Object.values(loadAllAgents()); // #1916: includes hive-mind-spawned workers
543
565
  // Filter by status
544
566
  if (input.status) {
@@ -551,10 +573,15 @@ export const agentTools = [
551
573
  if (input.domain) {
552
574
  agents = agents.filter(a => a.domain === input.domain);
553
575
  }
576
+ // Filter by agent type
577
+ if (input.agentType) {
578
+ agents = agents.filter(a => a.agentType === input.agentType);
579
+ }
554
580
  return {
555
581
  agents: agents.map(a => ({
556
582
  agentId: a.agentId,
557
583
  agentType: a.agentType,
584
+ ...(a.name ? { name: a.name } : {}),
558
585
  status: a.status,
559
586
  health: a.health,
560
587
  taskCount: a.taskCount,
@@ -565,6 +592,7 @@ export const agentTools = [
565
592
  filters: {
566
593
  status: input.status,
567
594
  domain: input.domain,
595
+ agentType: input.agentType,
568
596
  includeTerminated: input.includeTerminated,
569
597
  },
570
598
  };
@@ -13,6 +13,7 @@ import { existsSync, writeFileSync, mkdirSync } from 'fs';
13
13
  import { join, resolve } from 'path';
14
14
  const CLAIMS_DIR = '.claude-flow/claims';
15
15
  const CLAIMS_FILE = 'claims.json';
16
+ const DEFAULT_MAX_CLAIMS = 5;
16
17
  function getClaimsPath() {
17
18
  return resolve(join(CLAIMS_DIR, CLAIMS_FILE));
18
19
  }
@@ -47,7 +48,7 @@ function parseClaimant(str) {
47
48
  export const claimsTools = [
48
49
  {
49
50
  name: 'claims_claim',
50
- description: 'Claim an issue for work (human or agent) Use when nothing native covers per-agent capability gating — Claude Code agents have file-system access by default. Pair claims_grant + claims_check before letting an agent run privileged ops. For trusted in-session work, no claims call is needed.',
51
+ description: 'Claim an issue for work (human or agent). Use when multiple agents might pick up the same issue — claiming makes ownership visible via claims_list before work starts.',
51
52
  category: 'claims',
52
53
  inputSchema: {
53
54
  type: 'object',
@@ -64,6 +65,10 @@ export const claimsTools = [
64
65
  type: 'string',
65
66
  description: 'Optional context about the work approach',
66
67
  },
68
+ ttlMs: {
69
+ type: 'number',
70
+ description: 'Optional time-to-live in milliseconds; when set, the claim records an expiresAt timestamp. Informational only today — no handler currently enforces it (e.g. claims_steal still requires claims_mark-stealable rather than checking expiry).',
71
+ },
67
72
  },
68
73
  required: ['issueId', 'claimant'],
69
74
  },
@@ -71,6 +76,7 @@ export const claimsTools = [
71
76
  const issueId = input.issueId;
72
77
  const claimantStr = input.claimant;
73
78
  const context = input.context;
79
+ const ttlMs = input.ttlMs;
74
80
  {
75
81
  const v = validateIdentifier(issueId, 'issueId');
76
82
  if (!v.valid)
@@ -86,6 +92,9 @@ export const claimsTools = [
86
92
  if (!v.valid)
87
93
  return { success: false, error: v.error };
88
94
  }
95
+ if (ttlMs !== undefined && (typeof ttlMs !== 'number' || !Number.isFinite(ttlMs) || ttlMs <= 0)) {
96
+ return { success: false, error: 'ttlMs must be a positive number' };
97
+ }
89
98
  const claimant = parseClaimant(claimantStr);
90
99
  if (!claimant) {
91
100
  return { success: false, error: 'Invalid claimant format. Use "human:userId:name" or "agent:agentId:agentType"' };
@@ -109,6 +118,7 @@ export const claimsTools = [
109
118
  statusChangedAt: now,
110
119
  progress: 0,
111
120
  context,
121
+ expiresAt: ttlMs !== undefined ? new Date(Date.now() + ttlMs).toISOString() : undefined,
112
122
  };
113
123
  store.claims[issueId] = claim;
114
124
  saveClaims(store);
@@ -121,7 +131,7 @@ export const claimsTools = [
121
131
  },
122
132
  {
123
133
  name: 'claims_release',
124
- description: 'Release a claim on an issue Use when nothing native covers per-agent capability gating — Claude Code agents have file-system access by default. Pair claims_grant + claims_check before letting an agent run privileged ops. For trusted in-session work, no claims call is needed.',
134
+ description: 'Release a claim on an issue. Use when the claimant stops working on it, freeing it for another claims_claim instead of leaving it claimed indefinitely.',
125
135
  category: 'claims',
126
136
  inputSchema: {
127
137
  type: 'object',
@@ -186,7 +196,7 @@ export const claimsTools = [
186
196
  },
187
197
  {
188
198
  name: 'claims_handoff',
189
- description: 'Request handoff of an issue to another claimant Use when nothing native covers per-agent capability gating — Claude Code agents have file-system access by default. Pair claims_grant + claims_check before letting an agent run privileged ops. For trusted in-session work, no claims call is needed.',
199
+ description: 'Request handoff of an issue to another claimant. Use when transferring in-progress work to a different agent, instead of releasing and having them claim it fresh.',
190
200
  category: 'claims',
191
201
  inputSchema: {
192
202
  type: 'object',
@@ -270,7 +280,7 @@ export const claimsTools = [
270
280
  },
271
281
  {
272
282
  name: 'claims_accept-handoff',
273
- description: 'Accept a pending handoff Use when nothing native covers per-agent capability gating — Claude Code agents have file-system access by default. Pair claims_grant + claims_check before letting an agent run privileged ops. For trusted in-session work, no claims call is needed.',
283
+ description: 'Accept a pending handoff. Use when the receiving claimant confirms a claims_handoff request, completing the ownership transfer.',
274
284
  category: 'claims',
275
285
  inputSchema: {
276
286
  type: 'object',
@@ -333,7 +343,7 @@ export const claimsTools = [
333
343
  },
334
344
  {
335
345
  name: 'claims_status',
336
- description: 'Update claim status Use when nothing native covers per-agent capability gating — Claude Code agents have file-system access by default. Pair claims_grant + claims_check before letting an agent run privileged ops. For trusted in-session work, no claims call is needed.',
346
+ description: 'Update claim status. Use when progress changes (active, blocked, review-requested, etc.) so claims_list and claims_load reflect current state.',
337
347
  category: 'claims',
338
348
  inputSchema: {
339
349
  type: 'object',
@@ -398,7 +408,7 @@ export const claimsTools = [
398
408
  },
399
409
  {
400
410
  name: 'claims_list',
401
- description: 'List all claims or filter by criteria Use when nothing native covers per-agent capability gating — Claude Code agents have file-system access by default. Pair claims_grant + claims_check before letting an agent run privileged ops. For trusted in-session work, no claims call is needed.',
411
+ description: 'List all claims or filter by criteria. Use when checking what is already claimed before starting new work, instead of guessing from issue comments.',
402
412
  category: 'claims',
403
413
  inputSchema: {
404
414
  type: 'object',
@@ -453,7 +463,7 @@ export const claimsTools = [
453
463
  },
454
464
  {
455
465
  name: 'claims_mark-stealable',
456
- description: 'Mark an issue as stealable by other agents Use when nothing native covers per-agent capability gating — Claude Code agents have file-system access by default. Pair claims_grant + claims_check before letting an agent run privileged ops. For trusted in-session work, no claims call is needed.',
466
+ description: 'Mark an issue as stealable by other agents. Use when a claimant is overloaded or stalled and wants another agent to pick it up via claims_steal.',
457
467
  category: 'claims',
458
468
  inputSchema: {
459
469
  type: 'object',
@@ -521,7 +531,7 @@ export const claimsTools = [
521
531
  },
522
532
  {
523
533
  name: 'claims_steal',
524
- description: 'Steal a stealable issue Use when nothing native covers per-agent capability gating — Claude Code agents have file-system access by default. Pair claims_grant + claims_check before letting an agent run privileged ops. For trusted in-session work, no claims call is needed.',
534
+ description: 'Steal a stealable issue. Use when picking up work already marked stealable via claims_mark-stealable, rather than claiming an issue someone else still owns.',
525
535
  category: 'claims',
526
536
  inputSchema: {
527
537
  type: 'object',
@@ -592,7 +602,7 @@ export const claimsTools = [
592
602
  },
593
603
  {
594
604
  name: 'claims_stealable',
595
- description: 'List all stealable issues Use when nothing native covers per-agent capability gating — Claude Code agents have file-system access by default. Pair claims_grant + claims_check before letting an agent run privileged ops. For trusted in-session work, no claims call is needed.',
605
+ description: 'List all stealable issues. Use when looking for available work that has been freed up via claims_mark-stealable.',
596
606
  category: 'claims',
597
607
  inputSchema: {
598
608
  type: 'object',
@@ -628,7 +638,7 @@ export const claimsTools = [
628
638
  },
629
639
  {
630
640
  name: 'claims_load',
631
- description: 'Get agent load information Use when nothing native covers per-agent capability gating — Claude Code agents have file-system access by default. Pair claims_grant + claims_check before letting an agent run privileged ops. For trusted in-session work, no claims call is needed.',
641
+ description: 'Get agent load information. Use when deciding which agent should take new work by comparing claim counts against claims_rebalance targets.',
632
642
  category: 'claims',
633
643
  inputSchema: {
634
644
  type: 'object',
@@ -689,8 +699,8 @@ export const claimsTools = [
689
699
  agentId: l.agentId,
690
700
  agentType: l.agentType,
691
701
  claimCount: l.claims.length,
692
- maxClaims: 5, // Default max
693
- utilization: l.claims.length / 5,
702
+ maxClaims: DEFAULT_MAX_CLAIMS,
703
+ utilization: l.claims.length / DEFAULT_MAX_CLAIMS,
694
704
  blockedCount: l.blockedCount,
695
705
  claims: l.claims.map(c => ({
696
706
  issueId: c.issueId,
@@ -711,7 +721,7 @@ export const claimsTools = [
711
721
  },
712
722
  {
713
723
  name: 'claims_board',
714
- description: 'Get a visual board view of all claims Use when nothing native covers per-agent capability gating — Claude Code agents have file-system access by default. Pair claims_grant + claims_check before letting an agent run privileged ops. For trusted in-session work, no claims call is needed.',
724
+ description: 'Get a visual board view of all claims. Use when scanning overall claim status across issues and agents instead of composing the view from claims_list yourself.',
715
725
  category: 'claims',
716
726
  inputSchema: {
717
727
  type: 'object',
@@ -760,7 +770,7 @@ export const claimsTools = [
760
770
  },
761
771
  {
762
772
  name: 'claims_rebalance',
763
- description: 'Suggest or apply load rebalancing across agents Use when nothing native covers per-agent capability gating — Claude Code agents have file-system access by default. Pair claims_grant + claims_check before letting an agent run privileged ops. For trusted in-session work, no claims call is needed.',
773
+ description: 'Suggest or apply load rebalancing across agents. Use when some agents are overloaded and others idle, instead of manually reassigning claims one at a time.',
764
774
  category: 'claims',
765
775
  inputSchema: {
766
776
  type: 'object',
@@ -794,7 +804,7 @@ export const claimsTools = [
794
804
  agentLoads.get(key).claims.push(claim);
795
805
  }
796
806
  const loads = Array.from(agentLoads.values());
797
- const maxClaims = 5;
807
+ const maxClaims = DEFAULT_MAX_CLAIMS;
798
808
  const avgLoad = loads.length > 0
799
809
  ? loads.reduce((sum, l) => sum + l.claims.length, 0) / loads.length
800
810
  : 0;