ruvnet/ruflo · error · Error
${toolsJson} must contain a JSON array of {name, description
Error message
${toolsJson} must contain a JSON array of {name, description} What it means
Thrown by the security command when the --toolsJson file parses successfully but its top-level value is not a JSON array. The fragment-overlap scanner expects an array of {name, description} tool descriptors; an object or scalar is rejected so the subsequent `.filter().map()` does not silently produce an empty tool set.
Source
Thrown at v3/@claude-flow/cli/src/commands/security.ts:1154
{ name: 'tools-json', type: 'string', description: 'Path to a JSON file of {name, description}[] to scan (default: scan the CLI\'s own registered MCP tools)' },
],
examples: [
{ command: 'claude-flow security composition-scan', description: 'Scan the CLI\'s own registered MCP tool descriptions' },
{ command: 'claude-flow security composition-scan --tools-json ./external-mcp-registry.json --top 50', description: 'Scan a third-party MCP registry' },
],
action: async (ctx: CommandContext): Promise<CommandResult> => {
const minFragment = (ctx.flags.minFragment as number) || 20;
const top = (ctx.flags.top as number) || 20;
const toolsJson = ctx.flags.toolsJson as string | undefined;
let tools: Array<{ name: string; description: string }> = [];
try {
if (toolsJson) {
const fs = await import('node:fs');
const path = await import('node:path');
const raw = fs.readFileSync(path.resolve(toolsJson), 'utf-8');
const parsed = JSON.parse(raw);
if (!Array.isArray(parsed)) throw new Error(`${toolsJson} must contain a JSON array of {name, description}`);
tools = parsed
.filter((t: unknown): t is { name: string; description: string } =>
typeof t === 'object' && t !== null &&
typeof (t as { name?: unknown }).name === 'string' &&
typeof (t as { description?: unknown }).description === 'string')
.map((t) => ({ name: t.name, description: t.description }));
} else {
// Scan the CLI's own registered MCP tools via the client registry.
const { listMCPTools } = await import('../mcp-client.js');
tools = listMCPTools().map((t) => ({ name: t.name, description: t.description }));
}
} catch (err) {
output.printError(`Failed to load tools: ${err instanceof Error ? err.message : String(err)}`);
return { success: false, exitCode: 1 };
}
const { scanToolDescriptions } = await import('../security/mcp-composition-inspector.js');
const result = scanToolDescriptions(tools, { minFragment });View on GitHub (pinned to 6b01dc5a68)
Solutions
- Wrap the tool list in an array at the top level of the JSON file.
- If the source is `{"tools":[...]}`, unwrap it: `jq '.tools' manifest.json > tools.json` before passing.
- Validate with `jq type tools.json` — it should print `array`.
Example fix
// before — tools.json contains {"name":"x","description":"y"}
// after — tools.json contains [{"name":"x","description":"y"}] Defensive patterns
Strategy: type-guard
Validate before calling
function loadToolsArray(filePath: string): Array<{name:string;description:string}> {
const parsed = JSON.parse(require('fs').readFileSync(filePath, 'utf-8'));
const arr = Array.isArray(parsed) ? parsed : (parsed?.tools ?? null);
if (!Array.isArray(arr)) {
throw new Error(`${filePath} must contain a JSON array of {name, description}`);
}
return arr;
} Type guard
const isToolArray = (v: unknown): v is Array<{name:string;description:string}> =>
Array.isArray(v) && v.every(t =>
typeof t === 'object' && t !== null &&
typeof (t as any).name === 'string' &&
typeof (t as any).description === 'string'); Try / catch
try {
await runSecurityScan({ toolsJson });
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
if (msg.endsWith('must contain a JSON array of {name, description}')) {
console.error('Wrap the tool list in [ ] or unwrap a {"tools":[...]} envelope.');
process.exit(2);
}
throw e;
} Prevention
- Validate the file with `jq type` (should print array) before passing.
- Unwrap `{"tools":[...]}` envelopes before writing the file.
- Generate the manifest from a trusted exporter.
When it happens
Trigger: Pointing --toolsJson at a file that contains a single tool object (`{...}`) rather than an array (`[{...}]`), or a file wrapping tools under a key (`{"tools":[...]}`) without unwrapping.
Common situations: Exporting one MCP server's tool list as an object, a manifest format that nests tools under a property, or hand-editing the file and forgetting the outer `[ ]`.
Related errors
- ${label} requires a JSON argument
- ${label} must be valid JSON
- Invalid container name: ${containerName}
- Invalid target format: ${target}. Use agent:<id> or human:<i
- Validation failed: ${result.error}
AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12).
Data as JSON: /api/errors/a8f49d65f962cb90.
Report an issue: GitHub.