ruvnet/ruflo · error · MCPClientError
MCP tool not found
Error message
MCP tool not found: ${toolName} What it means
callMCPTool() resolves toolName against the in-process TOOL_REGISTRY populated at CLI startup; if the map has no entry under that exact name it throws MCPClientError('MCP tool not found'). This is a pre-dispatch failure — the tool never ran — and it is distinct from policy denial (224) and handler execution failure (225). The registry only contains tools registered locally, not every tool that exists on remote MCP servers.
Solutions
- Print the exact registered names with listMCPTools() (mcp-client.ts:374) or `npx @claude-flow/cli@latest mcp tools` and copy the name verbatim
- Check casing and separators — the registry is a Map keyed by exact string ('memory_search' not 'MemorySearch' or 'memorySearch')
- Confirm the plugin/extension that registers the tool is installed and enabled before the call (`plugins list`)
- Align versions: update @claude-flow/cli to the release whose docs you are following, since tool names change across major versions
Example fix
// before — hallucinated/misspelled name
await callMCPTool('memry_search', { query: 'auth' });
// after — verify against the registry, then call
const names = listMCPTools().map(t => t.name);
if (!names.includes('memory_search')) {
throw new Error(`Unknown tool. Available: ${names.join(', ')}`);
}
await callMCPTool('memory_search', { query: 'auth' }); Defensive patterns
Strategy: validation
Validate before calling
import { listMCPTools } from './mcp-client';
const available = new Set(listMCPTools().map(t => t.name));
if (!available.has(toolName)) {
throw new Error(
`Tool '${toolName}' not registered. Available: ${[...available].join(', ')}`
);
}
const result = await callMCPTool(toolName, input); Type guard
import { listMCPTools } from './mcp-client';
const registeredTools = new Set(listMCPTools().map(t => t.name));
const isRegisteredTool = (name: string): boolean => registeredTools.has(name); Try / catch
try { await callMCPTool(name, input); }
catch (e) {
if (e instanceof MCPClientError && e.message.startsWith('MCP tool not found')) {
// pre-dispatch failure: surface available names, do NOT retry
throw new Error(`'${name}' unknown. Registered: ${listMCPTools().map(t => t.name).join(', ')}`);
}
throw e;
} Prevention
- Derive tool names from listMCPTools() output instead of hardcoding strings
- When LLMs compose tool calls, validate the name against the registry before dispatch
- Keep plugin sets and CLI versions aligned across environments so registrations match
When it happens
Trigger: Calling callMCPTool('memory_serch') with a typo or wrong casing; invoking a tool that lives on a separately configured remote MCP server instead of the local registry; calling a tool provided by a plugin that failed to load or was disabled at startup; using a tool name from newer docs while running an older CLI where the tool was renamed or did not exist yet.
Common situations: Agents composing tool names dynamically from LLM output that hallucinates or miscapitalizes; version drift between the installed @claude-flow/cli and tutorial docs; plugin registration failing silently so the tool count is lower than expected; migrating scripts between environments where a different plugin set is installed.
Related errors
- at least one candidate is required
- candidate must ingest at least one vector
- Connection pool is shutting down
- Dangerous key segment rejected
- each record requires a non-empty numeric vector
AI-assisted analysis of ruvnet/ruflo@2602b642d9 (2026-08-18).
Data as JSON: /api/errors/8c9209df0ec7dd65.
Report an issue: GitHub.
Appendix: source
Thrown at v3/@claude-flow/cli/src/mcp-client.ts:253
* });
*
* // Initialize swarm
* const swarm = await callMCPTool('swarm_init', {
* topology: 'hierarchical-mesh',
* maxAgents: 15
* });
* ```
*/
export async function callMCPTool<T = unknown>(
toolName: string,
input: Record<string, unknown> = {},
context?: Record<string, unknown>
): Promise<T> {
// Look up tool in registry
const tool = TOOL_REGISTRY.get(toolName);
if (!tool) {
throw new MCPClientError(
`MCP tool not found: ${toolName}`,
toolName
);
}
try {
// ADR-324: one policy chokepoint for every local CLI/MCP invocation.
// Policy administration is not exempt: authorization calls the engine
// directly, so there is no recursive MCP dispatch. In enforce mode an
// administrator must explicitly allow policy.* actions or use the local
// CLI bootstrap path.
const decision = await authorizeMcpTool(toolName, input, context, classifyMcpTool(toolName));
if (decision.enforcedOutcome !== 'allowed') {
throw new Error(`policy-${decision.enforcedOutcome}:${decision.reason}; receipt=${decision.receiptId}`);
}
// Call the tool handler
const result = await tool.handler(input, context);
// ADR-146 P2: scan every tool result for indirect-injection before itView on GitHub (pinned to 2602b642d9)