crewAIInc/crewAI · error · MergeAgentHandlerToolError

Tool execution failed: {e!s}

Error message

Tool execution failed: {e!s}

What it means

The catch-all in MergeAgentHandlerTool._run: any exception that is not already a MergeAgentHandlerToolError (those are re-raised untouched) gets logged and re-wrapped as "Tool execution failed: ..." with the original as __cause__. It fires during parsing of the MCP response content or while navigating the result structure — e.g. unexpected content shapes, missing keys, or JSON shape surprises.

Source

Thrown at lib/crewai-tools/src/crewai_tools/tools/merge_agent_handler_tool/merge_agent_handler_tool.py:142

                params={"name": self.tool_name, "arguments": kwargs},
            )

            if "result" in result and "content" in result["result"]:
                content = result["result"]["content"]
                if content and len(content) > 0:
                    text_content = content[0].get("text", "")
                    try:
                        return json.loads(text_content)
                    except json.JSONDecodeError:
                        return text_content

            return result

        except MergeAgentHandlerToolError:
            raise
        except Exception as e:
            logger.error(f"Unexpected error executing tool {self.tool_name}: {e!s}")
            raise MergeAgentHandlerToolError(f"Tool execution failed: {e!s}") from e

    @classmethod
    def from_tool_name(
        cls,
        tool_name: str,
        tool_pack_id: str,
        registered_user_id: str,
        base_url: str = "https://ah-api.merge.dev",
        **kwargs: Any,
    ) -> te.Self:
        """
        Create a MergeAgentHandlerTool from a tool name.

        Args:
            tool_name: Name of the tool (e.g., "linear__create_issue")
            tool_pack_id: UUID of the Tool Pack
            registered_user_id: UUID of the registered user
            base_url: Base URL for Agent Handler API (defaults to production)

View on GitHub (pinned to 754d7323be)

Solutions

  1. Inspect e.__cause__ (and the log line) — it names the real underlying exception
  2. Call the same tool via a raw tools/call request and print the full JSON to see the actual content shape
  3. Pin/verify the tool pack version if the response format regressed; report schema changes to the pack owner
  4. Handle unexpected content types gracefully on your side (e.g. default to str(result) instead of assuming text)

Example fix

# before
result = tool._run(query="acme")  # Tool execution failed: KeyError 'text'

# after
try:
    result = tool._run(query="acme")
except MergeAgentHandlerToolError as e:
    logging.error("underlying cause: %r", e.__cause__)
    raise
Defensive patterns

Strategy: try-catch

Validate before calling

def parse_mcp_content(result: dict):
    content = result.get("result", {}).get("content", [])
    if content and isinstance(content[0], dict) and "text" in content[0]:
        import json
        try:
            return json.loads(content[0]["text"])
        except json.JSONDecodeError:
            return content[0]["text"]
    return result

Try / catch

try:
    result = tool._run(**kwargs)
except MergeAgentHandlerToolError as e:
    cause = e.__cause__
    logging.error("tool call failed; cause=%r", cause)
    if isinstance(cause, (KeyError, TypeError)):
        # response shape changed — inspect raw tools/call output
        dump_raw_response()
    raise

Prevention

When it happens

Trigger: The remote tool returns a content array whose first element has no 'text'; tools/call returns an unexpected result envelope; KeyErrors while walking result["result"]["content"]; unexpected types from json.loads.

Common situations: Remote tool pack updated and changed its response format; a remote tool returning error content instead of text; edge-case responses (empty content list).

Related errors


AI-assisted analysis of crewAIInc/crewAI@754d7323be (2026-08-15). Data as JSON: /api/errors/f6c20dceb33e936a. Report an issue: GitHub.