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

> Discover MCP tools error handling patterns. Learn about @log_tool decorator, try-except blocks, API error detection, and uniform prefixes for LLM clients in mcp-ambari-api.

- Repository: [JungJungIn/mcp-ambari-api](https://github.com/call518/mcp-ambari-api)
- Tags: deep-dive
- Published: 2026-02-26

---

**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`](https://github.com/call518/mcp-ambari-api/blob/main/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:

```python
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`](https://github.com/call518/mcp-ambari-api/blob/main/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`](https://github.com/call518/mcp-ambari-api/blob/main/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:

```python
@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`](https://github.com/call518/mcp-ambari-api/blob/main/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:

```python
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`](https://github.com/call518/mcp-ambari-api/blob/main/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:

```python
@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`](https://github.com/call518/mcp-ambari-api/blob/main/functions.py) (lines 47-52):

```python
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`](https://github.com/call518/mcp-ambari-api/blob/main/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`](https://github.com/call518/mcp-ambari-api/blob/main/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`](https://github.com/call518/mcp-ambari-api/blob/main/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`](https://github.com/call518/mcp-ambari-api/blob/main/src/mcp_ambari_api/functions.py) (lines 25-53) and applied to every public tool in [`src/mcp_ambari_api/mcp_main.py`](https://github.com/call518/mcp-ambari-api/blob/main/src/mcp_ambari_api/mcp_main.py), ensuring uniform logging of tool starts, successes, and errors across the entire API surface.