{"record":{"id":"4128f16674f3d75e","repo":"ruvnet/ruflo","slug":"failed-to-execute-mcp-tool-toolname-error","errorCode":null,"errorMessage":"Failed to execute MCP tool '${toolName}': ${error instanceof Error ? error.message : String(error)}","messagePattern":"Failed to execute MCP tool '(.+?)': (.+?)","errorType":"exception","errorClass":"MCPClientError","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/cli/src/mcp-client.ts","lineNumber":280,"sourceCode":"    // 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\n    // returns to the caller. The screen is opt-in via env (default off in\n    // 3.10.34 — flip to default in v4) so existing pipelines keep their\n    // exact behaviour while the call site is exercised by tests and\n    // adopters. Telemetry from the screen lands in the shared\n    // GuardrailEvent sink (P5).\n    return applyContentBoundaryGuardrail(toolName, result) as T;\n  } catch (error) {\n    // Wrap and re-throw with context\n    throw new MCPClientError(\n      `Failed to execute MCP tool '${toolName}': ${error instanceof Error ? error.message : String(error)}`,\n      toolName,\n      error instanceof Error ? error : undefined\n    );\n  }\n}\n\n/**\n * ADR-146 P2 — content-boundary screen on the MCP tool dispatch path.\n *\n * Default behaviour (3.10.34, legacy mode): returns the result unchanged.\n * With `CLAUDE_FLOW_STRICT_GUARDRAIL=true`, scans every string field of the\n * result; `reject` substitutes the field with a typed marker so the caller\n * can surface the rejection. The class itself (`ToolOutputGuardrail`)\n * shipped in ADR-131 P1; this call site is what closes #2149.\n *\n * Implementation note: we resolve the guardrail lazily so the cold-import\n * cost of `@claude-flow/security` does not hit every CLI invocation. Once","sourceCodeStart":262,"sourceCodeEnd":298,"githubUrl":"https://github.com/ruvnet/ruflo/blob/2602b642d92234c710ffbe96bfb33007d481ceab/v3/@claude-flow/cli/src/mcp-client.ts#L262-L298","documentation":"callMCPTool() wraps any rejection from a registered tool's handler in MCPClientError(\"Failed to execute MCP tool 'X': <inner message>\") and preserves the original error as `cause`. It means the tool was found and policy allowed it, but execution inside the handler failed — the wrapper adds context, the root cause is one level down. This is the generic counterpart to the pre-dispatch failures 223 (not found) and 224 (policy denied).","triggerScenarios":"Any handler-level fault: arguments that passed schema but violate runtime expectations (e.g. bad file paths), downstream service outages (AgentDB/ONNX unavailable), filesystem permission errors, JSON parse errors on tool input, or nested throws from libraries the tool calls. Because the catch is around handler + guardrail, applyContentBoundaryGuardrail rejections also surface through this wrapper in strict mode.","commonSituations":"Debugging stalls at the wrapper message because logs truncate the chain; users retry the same call hoping it is transient when the cause is deterministic (bad path); strict-guardrail mode (CLAUDE_FLOW_STRICT_GUARDRAIL=true) newly rejecting results that contain injection-looking text, appearing as 'execution failed' after upgrade.","solutions":["Inspect error.cause (Node 16.9+) or the embedded inner message — fix the root error, not the wrapper","Reproduce the handler directly with the same input (call the tool's exported handler or the CLI subcommand) to remove dispatch layers","If the inner message mentions content/guardrail boundaries, check CLAUDE_FLOW_STRICT_GUARDRAIL and sanitize the data feeding the tool","Log the full chain (console.error(err, { cause: err.cause })) so the root error reaches your issue tracker"],"exampleFix":"// before — only the wrapper message is seen\ntry { await callMCPTool('memory_store', input); }\ncatch (e) { console.log(e.message); } // 'Failed to execute...: EACCES'\n\n// after — surface the root cause\ntry { await callMCPTool('memory_store', input); }\ncatch (e) {\n  const root = e.cause ?? e;\n  console.error('tool failed:', root.message, root.stack);\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"const isMCPClientError = (e: unknown): e is MCPClientError =>\n  e instanceof MCPClientError ||\n  (e instanceof Error && (e as MCPClientError).toolName !== undefined);","tryCatchPattern":"try {\n  return await callMCPTool(toolName, input, context);\n} catch (e) {\n  if (e instanceof MCPClientError) {\n    const root = e.cause ?? e; // the real failure is one level down\n    console.error(`tool ${e.toolName} failed:`, root.message, root.stack);\n    // deterministic causes (bad input) should not be retried blindly\n  }\n  throw e;\n}","preventionTips":["Always inspect .cause — the wrapper message alone hides the root error","Log the full chain (err + err.cause) to your tracker","Distinguish pre-dispatch failures (223/224) from handler failures before choosing retry vs fix"],"tags":["mcp","error-wrapping","cause-chain","debugging","guardrail"],"backgroundTag":"tool-execution-failed","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"}