{"record":{"id":"8c9209df0ec7dd65","repo":"ruvnet/ruflo","slug":"mcp-tool-not-found-toolname","errorCode":null,"errorMessage":"MCP tool not found: ${toolName}","messagePattern":"MCP tool not found: (.+?)","errorType":"exception","errorClass":"MCPClientError","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/cli/src/mcp-client.ts","lineNumber":253,"sourceCode":" * });\n *\n * // Initialize swarm\n * const swarm = await callMCPTool('swarm_init', {\n *   topology: 'hierarchical-mesh',\n *   maxAgents: 15\n * });\n * ```\n */\nexport async function callMCPTool<T = unknown>(\n  toolName: string,\n  input: Record<string, unknown> = {},\n  context?: Record<string, unknown>\n): Promise<T> {\n  // Look up tool in registry\n  const tool = TOOL_REGISTRY.get(toolName);\n\n  if (!tool) {\n    throw new MCPClientError(\n      `MCP tool not found: ${toolName}`,\n      toolName\n    );\n  }\n\n  try {\n    // ADR-324: one policy chokepoint for every local CLI/MCP invocation.\n    // Policy administration is not exempt: authorization calls the engine\n    // directly, so there is no recursive MCP dispatch. In enforce mode an\n    // administrator must explicitly allow policy.* actions or use the local\n    // CLI bootstrap path.\n    const decision = await authorizeMcpTool(toolName, input, context, classifyMcpTool(toolName));\n    if (decision.enforcedOutcome !== 'allowed') {\n      throw new Error(`policy-${decision.enforcedOutcome}:${decision.reason}; receipt=${decision.receiptId}`);\n    }\n    // Call the tool handler\n    const result = await tool.handler(input, context);\n    // ADR-146 P2: scan every tool result for indirect-injection before it","sourceCodeStart":235,"sourceCodeEnd":271,"githubUrl":"https://github.com/ruvnet/ruflo/blob/2602b642d92234c710ffbe96bfb33007d481ceab/v3/@claude-flow/cli/src/mcp-client.ts#L235-L271","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before — hallucinated/misspelled name\nawait callMCPTool('memry_search', { query: 'auth' });\n\n// after — verify against the registry, then call\nconst names = listMCPTools().map(t => t.name);\nif (!names.includes('memory_search')) {\n  throw new Error(`Unknown tool. Available: ${names.join(', ')}`);\n}\nawait callMCPTool('memory_search', { query: 'auth' });","handlingStrategy":"validation","validationCode":"import { listMCPTools } from './mcp-client';\n\nconst available = new Set(listMCPTools().map(t => t.name));\nif (!available.has(toolName)) {\n  throw new Error(\n    `Tool '${toolName}' not registered. Available: ${[...available].join(', ')}`\n  );\n}\nconst result = await callMCPTool(toolName, input);","typeGuard":"import { listMCPTools } from './mcp-client';\nconst registeredTools = new Set(listMCPTools().map(t => t.name));\nconst isRegisteredTool = (name: string): boolean => registeredTools.has(name);","tryCatchPattern":"try { await callMCPTool(name, input); }\ncatch (e) {\n  if (e instanceof MCPClientError && e.message.startsWith('MCP tool not found')) {\n    // pre-dispatch failure: surface available names, do NOT retry\n    throw new Error(`'${name}' unknown. Registered: ${listMCPTools().map(t => t.name).join(', ')}`);\n  }\n  throw e;\n}","preventionTips":["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"],"tags":["mcp","tool-registry","dispatch","typo","version-drift"],"backgroundTag":"unknown-tool-name","analyzedSha":"2602b642d92234c710ffbe96bfb33007d481ceab","analyzedAt":"2026-08-18T21:34:22.708Z","contentChangedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}