{"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":272,"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":254,"sourceCodeEnd":290,"githubUrl":"https://github.com/ruvnet/ruflo/blob/6b01dc5a687b26b3e218f796de45ec51f8fa9e8c/v3/@claude-flow/cli/src/mcp-client.ts#L254-L290","documentation":"Wrapping error thrown by callMCPTool() when anything inside the try block (tool.handler execution, the content-boundary guardrail, or even the authorize step) throws and is not already a more specific MCPClientError. The original error is preserved on the `.cause` property and its message is inlined so the surface message is self-contained. This is the generic 'the tool blew up' boundary for downstream consumers.","triggerScenarios":"The tool handler threw (filesystem error, downstream API 500, native crash in sql.js/better-sqlite3, OOM in embeddings generation); the ADR-146 content-boundary guardrail in strict mode rejected a field; an optional-dependency load threw a non-MODULE_NOT_FOUND error inside the handler.","commonSituations":"The backing database file is locked or corrupt; a native module (better-sqlite3) failed to load for the current Node ABI; the tool's input was accepted by validation but the underlying operation failed at runtime; out-of-memory during large vector operations.","solutions":["Read `.cause` (or `.toolName`) on the MCPClientError to find the original stack and the failing tool.","Reproduce with the same input against the tool directly (bypassing callMCPTool) to confirm the error is in the handler, not the dispatch path.","Check the backing resource: database file perms/locks, native module presence, memory headroom.","If the underlying error is transient (network, lock contention), retry with backoff; otherwise fix the handler input/state."],"exampleFix":"// before — caller loses the original error\ntry { await callMCPTool('memory_store', input); }\ncatch (e) { console.log(e.message); }\n// after — surface .cause and .toolName\ntry { await callMCPTool('memory_store', input); }\ncatch (e) {\n  if (e.name === 'MCPClientError') {\n    console.error(e.toolName, e.cause?.stack ?? e.message);\n  }\n}","handlingStrategy":"try-catch","validationCode":"null","typeGuard":"function isMCPClientError(e: unknown): e is { name: 'MCPClientError'; toolName: string; cause?: Error; message: string } {\n  return e instanceof Error && (e as any).name === 'MCPClientError' && typeof (e as any).toolName === 'string';\n}","tryCatchPattern":"try {\n  return await callMCPTool(toolName, input, ctx);\n} catch (e) {\n  if (isMCPClientError(e)) {\n    log.error({ tool: e.toolName, msg: e.message, stack: e.cause?.stack });\n    // optionally classify transient vs permanent from e.cause\n  }\n  throw e;\n}","preventionTips":["Always inspect `.cause` and `.toolName` rather than just `.message`.","Wrap each call with structured logging so handler failures are attributable to a tool.","Distinguish transient causes (locks, network) from permanent ones (bad input) before retrying."],"tags":["mcp","error-wrapping","dispatch","handler"],"backgroundTag":null,"analyzedSha":"6b01dc5a687b26b3e218f796de45ec51f8fa9e8c","analyzedAt":"2026-08-12T13:20:50.148Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}