oraios/serena · warning · ToolError

e.get_error_message()

Error message

e.get_error_message()

What it means

Serena's MCP adapter wraps each tool call; when tool.apply_ex raises ToolCallError (with catch_exceptions=False), the adapter converts it to an MCP ToolError whose message is the tool's user-facing error text via e.get_error_message(). This is the standard channel for surfacing tool-level failures (bad arguments, file not found, etc.) to the MCP client.

Source

Thrown at src/serena/mcp.py:101

        if docstring.returns and (docstring_returns_descr := docstring.returns.description):
            # Only add a space before "Returns" if func_doc is not empty
            prefix = " " if func_doc else ""
            func_doc = f"{func_doc}{prefix}Returns {docstring_returns_descr.strip().strip('.')}."

        # Parse the parameter descriptions from the docstring and add pass its description
        # to the parameter schema.
        docstring_params = {param.arg_name: param for param in docstring.params}
        parameters_properties: dict[str, dict[str, Any]] = parameters["properties"]
        for parameter, properties in parameters_properties.items():
            if (param_doc := docstring_params.get(parameter)) and param_doc.description:
                param_desc = f"{param_doc.description.strip().strip('.') + '.'}"
                properties["description"] = param_desc[0].upper() + param_desc[1:]

        def execute_fn(**kwargs) -> str:
            try:
                return tool.apply_ex(log_call=True, catch_exceptions=False, **kwargs)
            except ToolCallError as e:
                raise ToolError(e.get_error_message()) from e

        # Generate human-readable title from snake_case tool name
        tool_title = " ".join(word.capitalize() for word in func_name.split("_"))

        # Create annotations with appropriate hints based on tool capabilities
        can_edit = tool.can_edit()
        annotations = ToolAnnotations(
            title=tool_title,
            readOnlyHint=not can_edit,
            destructiveHint=can_edit,
        )

        super().__init__(
            fn=execute_fn,
            name=func_name,
            description=func_doc,
            parameters=parameters,
            fn_metadata=func_arg_metadata,

View on GitHub (pinned to 7fcbca7e62)

Solutions

  1. Read the ToolError message — it contains the tool's actual failure description
  2. Validate inputs (paths exist, symbol name format, regex compiles) before invoking the tool
  3. Catch tool errors on the MCP client side and retry with corrected arguments
  4. If the message is unclear, run the tool with log_level=debug to get the underlying stack trace

Example fix

// before (client passes bad path)
{"tool": "read_file", "arguments": {"relative_path": "nope.txt"}}
// after
{"tool": "read_file", "arguments": {"relative_path": "src/nope.txt"}}
// or guard on client side
if os.path.isfile(os.path.join(root, rel_path)):
    call_tool("read_file", {"relative_path": rel_path})
Defensive patterns

Strategy: try-catch

Validate before calling

from pathlib import Path
if not (Path(project_root) / relative_path).exists():
    raise FileNotFoundError(relative_path)

Try / catch

try:
    result = call_mcp_tool("read_file", {"relative_path": rel})
except ToolError as e:
    logger.warning("tool failed: %s", e)
    result = retry_with_corrected_args(e)  # message states the actual problem

Prevention

When it happens

Trigger: Any MCP tool execution whose underlying apply_ex raises ToolCallError — e.g. find_symbol on a missing symbol, read/write of a nonexistent path, invalid regex in search tools, tool-specific precondition failures.

Common situations: Client passes a nonexistent file path or symbol name; timeout/no-match conditions raised as ToolCallError; caller-side confusion when the LLM client shows a generic 'tool error' containing the real message.

Related errors


AI-assisted analysis of oraios/serena@7fcbca7e62 (2026-08-29). Data as JSON: /api/errors/1c729927c52a26a3. Report an issue: GitHub.