ruvnet/ruflo · error · MCPClientError
MCP tool not found: ${toolName}
Error message
MCP tool not found: ${toolName} What it means
Thrown by callMCPTool() when TOOL_REGISTRY.get(toolName) returns undefined — i.e. no MCP tool has been registered under that exact name. The registry is populated at module load by the MCP tool definitions (agentdb, agentbbs, agenticow, etc.), so an unknown name means either a typo, a tool that was filtered out by the `mcp start --tools` selection, or a tool whose owning package/feature flag is disabled. The error is wrapped as MCPClientError so callers can read `.toolName`.
Source
Thrown at v3/@claude-flow/cli/src/mcp-client.ts:245
* });
*
* // 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 6b01dc5a68)
Solutions
- List the actually-registered tools: `TOOL_REGISTRY` keys, or `npx @claude-flow/cli mcp tools` / the `mcp list-tools` command, and copy the exact name.
- Check that the tool's owning module is imported — for tools behind optional deps verify the package is installed and the tool module was loaded.
- Inspect CLAUDE_FLOW_MCP_TOOLS and the `--tools` flag passed to `mcp start`; widen the selection or remove the env var.
- Align the integrator's version of the CLI with the docs (pin `@claude-flow/cli` to the same minor).
Example fix
// before — typo
callMCPTool('agent_spaawn', { agentType: 'coder' });
// after
callMCPTool('agent_spawn', { agentType: 'coder' }); Defensive patterns
Strategy: validation
Validate before calling
import { TOOL_REGISTRY } from '@claude-flow/cli/mcp-client';
function isRegisteredTool(name: string): boolean {
return TOOL_REGISTRY.has(name);
}
if (!isRegisteredTool(toolName)) {
throw new Error(`unknown tool '${toolName}'; registered: ${[...TOOL_REGISTRY.keys()].join(', ')}`);
} Type guard
const isMcpToolName = (s: string, registry: ReadonlyMap<string, unknown>): s is string => typeof s === 'string' && registry.has(s);
Try / catch
try {
return await callMCPTool(toolName, input, ctx);
} catch (e) {
if (e?.name === 'MCPClientError' && /^MCP tool not found:/.test(e.message)) {
// surface the list of registered tools for the caller to pick from
return { error: 'unknown-tool', available: [...TOOL_REGISTRY.keys()] };
}
throw e;
} Prevention
- Centralize toolName strings in a typed const/enum so typos fail at compile time.
- Print TOOL_REGISTRY keys once at startup and assert your call set is a subset.
- Pin the CLI version so a tool removal in a minor bump surfaces in your upgrade tests.
When it happens
Trigger: Calling `callMCPTool('agent_spaawn', ...)` (typo); calling a tool name that exists only when a feature flag or CLAUDE_FLOW_MCP_TOOLS env entry is set; calling before the tool registry module has been imported in the current process; calling a tool removed in the current minor version.
Common situations: Typo in the toolName string; using a tool gated behind an optional dependency that was filtered out; version skew between the docs an integrator copied and the installed CLI; calling a tool whose registration import was tree-shaken by a bundler.
Related errors
- unknown game "${key}". Known: ${Object.keys(GAMES).join(', '
- browser/eval: script must not be empty
- unknown strategy "${name}". Available: ${roster.map((r) => r
- Invalid completion type
- Invalid hostname
AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12).
Data as JSON: /api/errors/8c9209df0ec7dd65.
Report an issue: GitHub.