Error Handling Patterns Used in MCP Tools in the mcp-ambari-api Repository

MCP tools in mcp-ambari-api use a layered error handling strategy combining the @log_tool decorator for centralized logging, try-except blocks for exception safety, API-level error detection via "error" key inspection, and uniform "Error:" string prefixes to signal failures to LLM clients.

The call518/mcp-ambari-api repository exposes Apache Ambari REST operations as MCP (Model Context Protocol) tools for LLM agents. Understanding the error handling patterns used in MCP tools is essential for building reliable integrations, as every tool follows a predictable contract that transforms both API failures and Python exceptions into machine-readable error strings.

The @log_tool Decorator for Centralized Observability

All MCP tools in this repository are decorated with @log_tool, defined in src/mcp_ambari_api/functions.py (lines 25-53). This decorator provides automatic logging and result categorization without cluttering the business logic of each tool.

The decorator wraps each tool call to record start times, durations, and outcomes. It specifically inspects return values to distinguish successes from failures:

def log_tool(func):
    """Decorator for uniform tool call logging with timing and result categorization."""
    tool_name = func.__name__
    @wraps(func)
    async def wrapper(*args, **kwargs):
        start = time.monotonic()
        logger.info(f"TOOL START {tool_name} ...")
        try:
            result = await func(*args, **kwargs)
            duration_ms = (time.monotonic() - start) * 1000
            
            # Categorize result based on its prefix

            if isinstance(result, str) and result.startswith("Error:"):
                logger.warning(f"TOOL ERROR_RETURN {tool_name} ...")
            else:
                logger.info(f"TOOL SUCCESS {tool_name} ...")
            return result
        except Exception:
            # Unexpected exception – always logged and re-raised

            logger.exception(f"TOOL EXCEPTION {tool_name} failed")
            raise
    return wrapper

Every public tool in src/mcp_ambari_api/mcp_main.py (e.g., dump_configurations, get_cluster_info) applies this decorator, ensuring that error paths are consistently logged as warnings while successes are logged as info-level events.

Try-Except Wrappers and API Error Detection

Each MCP tool follows a standardized skeleton that combines defensive exception handling with API-level error validation. This pattern appears throughout src/mcp_ambari_api/mcp_main.py, where tools wrap their core logic in try-except blocks and inspect responses from make_ambari_request for error indicators.

Detecting API-Level Failures

After calling make_ambari_request, tools check for the presence of an "error" key in the response dictionary. This pattern, visible in get_cluster_info (lines 99-104) and dump_configurations (lines 66-75), converts API errors into user-friendly strings:

@mcp.tool()
@log_tool
async def get_cluster_info() -> str:
    """Return cluster name and version information."""
    try:
        endpoint = f"/clusters/{AMBARI_CLUSTER_NAME}"
        response_data = await make_ambari_request(endpoint)
        
        # API-level error detection

        if response_data is None or response_data.get("error"):
            return f"Error: Unable to retrieve cluster information - {response_data.get('error', 'Unknown error')}"
        
        # Normal processing

        cluster_info = response_data.get("Clusters", {})
        return f"Cluster: {cluster_info.get('cluster_name')}\nVersion: {cluster_info.get('version')}"
    except Exception as e:
        # Exception fallback

        return f"Error: Exception occurred while retrieving cluster information - {str(e)}"

The make_ambari_request helper in functions.py (lines 188-196) returns dictionaries containing an "error" key when HTTP requests fail, allowing tools to handle Ambari-specific failures separately from Python exceptions.

Handling Unexpected Exceptions

When unexpected exceptions bubble up—such as network timeouts or parsing errors—the tool-level except block catches them and returns a formatted error string rather than propagating raw stack traces. This pattern appears in start_service (lines 1020-1022) and other service management tools:

except Exception as e:
    return f"Error: Exception occurred while starting service - {str(e)}"

This ensures that LLM clients receive actionable error messages instead of encountering unhandled exceptions that would break the conversation flow.

Graceful Degradation and Fallback Strategies

Some tools implement fallback logic to handle cases where specific API endpoints are unavailable or return errors. The get_active_requests tool in src/mcp_ambari_api/mcp_main.py (lines 55-63) demonstrates this pattern by attempting a filtered request first, then falling back to an unfiltered endpoint if the primary request fails:

@mcp.tool()
@log_tool
async def get_active_requests() -> str:
    """List all IN-PROGRESS requests; fall back if the filtered endpoint is unavailable."""
    try:
        # Primary request (filtered by status)

        endpoint = f"/clusters/{AMBARI_CLUSTER_NAME}/requests?fields=Requests/request_status&Requests/request_status=IN_PROGRESS"
        data = await make_ambari_request(endpoint)
        
        # If the filtered call fails, retry without the filter

        if data.get("error"):
            endpoint = f"/clusters/{AMBARI_CLUSTER_NAME}/requests?fields=Requests/request_status&sortBy=Requests/id.desc"
            data = await make_ambari_request(endpoint)
        
        if data.get("error"):
            return f"Error: Unable to retrieve active requests - {data['error']}"
        
        # Normal processing continues...

    except Exception as e:
        return f"Error: Exception occurred while retrieving active requests - {str(e)}"

If both attempts fail, the tool still returns a single "Error:" string, maintaining the uniform contract while providing multiple opportunities for success.

The Uniform Error Contract

A critical convention across the repository is the uniform error string format. Every error message starts with the prefix Error: (or [ERROR] in legacy low-level helpers), creating a machine-readable contract that allows both the logging system and MCP clients to detect failures reliably.

The log_tool decorator explicitly checks for this prefix to categorize results, as shown in functions.py (lines 47-52):

if isinstance(result, str) and result.startswith("Error:"):
    logger.warning(f"TOOL ERROR_RETURN {tool_name} ...")
elif isinstance(result, str) and result.startswith("[ERROR]"):
    logger.warning(f"TOOL ERROR_RETURN {tool_name} ...")
else:
    logger.info(f"TOOL SUCCESS {tool_name} ...")

This convention ensures that:

  • Success returns multi-line human-readable strings without the "Error:" prefix
  • Failure returns strings beginning with "Error:" that can be parsed by downstream systems without complex object inspection

Summary

  • The @log_tool decorator in src/mcp_ambari_api/functions.py provides automatic logging and result categorization for all MCP tools based on the "Error:" prefix convention.
  • Every tool wraps its logic in try-except blocks to catch unexpected exceptions and return formatted error strings rather than propagating raw stack traces.
  • API-level errors are detected by checking for the "error" key in response dictionaries returned by make_ambari_request.
  • The repository implements graceful degradation, such as in get_active_requests, which falls back to alternative endpoints when primary requests return errors.
  • All error messages start with the "Error:" prefix, creating a predictable contract that allows LLM clients to distinguish success from failure using simple string parsing.

Frequently Asked Questions

How does the mcp-ambari-api repository detect API errors from Ambari?

Tools inspect the response dictionary from make_ambari_request for an "error" key. If present, the tool returns a formatted string beginning with "Error:" followed by the API error details, as implemented in get_cluster_info and dump_configurations within src/mcp_ambari_api/mcp_main.py.

What happens when an unexpected exception occurs in an MCP tool?

The exception is caught in the tool-level try-except block, logged via the @log_tool decorator in src/mcp_ambari_api/functions.py, and converted into a user-friendly error message starting with "Error:" rather than propagating raw stack traces to the LLM client.

Why do all error messages start with "Error:" in this MCP implementation?

This convention allows the @log_tool decorator to categorize results by checking result.startswith("Error:") and enables downstream MCP clients to reliably detect failures using simple string parsing rather than complex error object inspection or HTTP status code analysis.

Where is the logging logic centralized for MCP tools in this repository?

The log_tool decorator is defined in src/mcp_ambari_api/functions.py (lines 25-53) and applied to every public tool in src/mcp_ambari_api/mcp_main.py, ensuring uniform logging of tool starts, successes, and errors across the entire API surface.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →