# How to Add Custom MCP Tools to Extend Server Functionality in MCP Ambari API

> Extend MCP Ambari API server functionality by creating custom async Python tools. Learn how FastMCP automatically registers @mcp.tool() decorated functions for immediate client access.

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

---

**You can extend the MCP Ambari API server by creating async Python functions decorated with `@mcp.tool()` and optionally `@log_tool`, which FastMCP registers automatically at import time and exposes to all MCP clients without additional configuration.**

The MCP Ambari API server is built on FastMCP to provide programmatic access to Apache Ambari cluster operations. To add custom MCP tools that extend server functionality, you write async functions in [`src/mcp_ambari_api/mcp_main.py`](https://github.com/call518/mcp-ambari-api/blob/main/src/mcp_ambari_api/mcp_main.py) (or any module imported before `main()` executes) and apply the `@mcp.tool` decorator, reusing helpers like `make_ambari_request` from [`functions.py`](https://github.com/call518/mcp-ambari-api/blob/main/functions.py) for consistent HTTP handling and structured logging.

## How FastMCP Tool Registration Works

In [`src/mcp_ambari_api/mcp_main.py`](https://github.com/call518/mcp-ambari-api/blob/main/src/mcp_ambari_api/mcp_main.py), the global `FastMCP` instance is instantiated at lines 79-80:

```python
mcp = FastMCP("mcp-ambari-api")

```

When the module is imported, FastMCP collects every function decorated with `@mcp.tool()`. The server then starts via `mcp.run()` at line 3731 inside the `main()` function. Consequently, any async function decorated before `main()` executes becomes instantly available to MCP clients (CLI, OpenWebUI, or MCPO proxy) without further configuration.

## Step-by-Step Guide to Adding Custom Tools

Follow these steps to implement new functionality:

### 1. Create an Async Function

Define a new `async def` function in [`src/mcp_ambari_api/mcp_main.py`](https://github.com/call518/mcp-ambari-api/blob/main/src/mcp_ambari_api/mcp_main.py) or a separate module that is imported before `main()` runs. FastMCP registers tools at import time, so the function must be defined before the server starts.

### 2. Apply the `@mcp.tool` Decorator

Use `@mcp.tool(title="Your Tool Name")` to register the callable with the global FastMCP instance. The title parameter specifies the name shown to clients.

### 3. Add Optional `@log_tool` Wrapper

Apply `@log_tool` to enable automatic start/stop timing and consistent log formatting. Existing tools like `dump_configurations` use this wrapper at lines 39-42 in [`mcp_main.py`](https://github.com/call518/mcp-ambari-api/blob/main/mcp_main.py).

### 4. Implement Business Logic

Write the async body using helpers from [`src/mcp_ambari_api/functions.py`](https://github.com/call518/mcp-ambari-api/blob/main/src/mcp_ambari_api/functions.py). The `make_ambari_request` function handles HTTP transport, error handling, and response parsing uniformly.

### 5. Export Public API (Optional)

If you want the tool importable from the package root, append its name to `__all__` in [`src/mcp_ambari_api/__init__.py`](https://github.com/call518/mcp-ambari-api/blob/main/src/mcp_ambari_api/__init__.py).

### 6. Test Locally

Run the server in stdio mode to verify registration:

```bash
PYTHONPATH=./src uv run python -m mcp_ambari_api --type stdio

```

Then invoke the tool via the MCP inspector or a simple client to validate output formatting.

## Complete Example: Adding a "Ping Ambari" Tool

Here is a production-ready example that adds a lightweight health-check tool to [`src/mcp_ambari_api/mcp_main.py`](https://github.com/call518/mcp-ambari-api/blob/main/src/mcp_ambari_api/mcp_main.py):

```python
from .functions import make_ambari_request, AMBARI_CLUSTER_NAME

@mcp.tool(title="Ping Ambari")
@log_tool
async def ping_ambari() -> str:
    """
    Very lightweight health-check: tries to fetch the cluster summary.
    Returns a one-line status that LLM agents can use in decision trees.
    """
    endpoint = f"/clusters/{AMBARI_CLUSTER_NAME}"
    resp = await make_ambari_request(endpoint)

    if resp is None:
        return "❌ Unable to contact Ambari – no response."

    if "error" in resp:
        return f"❌ Ambari returned an error: {resp['error']}"

    version = resp.get("Clusters", {}).get("version", "unknown")
    return f"✅ Ambari reachable – cluster version: {version}"

```

This tool is now listed alongside built-in tools like `get_cluster_info` and can be invoked from any MCP client using the command `ping_ambari`.

## Core Files and Helper Functions

When adding custom MCP tools, reference these key files:

- **[`src/mcp_ambari_api/mcp_main.py`](https://github.com/call518/mcp-ambari-api/blob/main/src/mcp_ambari_api/mcp_main.py)**: Contains the `FastMCP` instance creation (lines 79-80), the `mcp.run()` call (line 3731), and existing tool implementations like `dump_configurations` and `start_service`.

- **[`src/mcp_ambari_api/functions.py`](https://github.com/call518/mcp-ambari-api/blob/main/src/mcp_ambari_api/functions.py)**: Provides `make_ambari_request` for Ambari HTTP calls, timestamp handling, and metric caching. Reuse these helpers to ensure consistent error handling and response parsing.

- **[`src/mcp_ambari_api/__init__.py`](https://github.com/call518/mcp-ambari-api/blob/main/src/mcp_ambari_api/__init__.py)**: Package initializer where you can add tool names to `__all__` for public API exports.

## Best Practices for Custom MCP Tools

- **Keep tools atomic**: Each tool should perform one logical operation. Compose complex workflows from the client side by chaining multiple tool calls.

- **Reuse existing infrastructure**: Always use `make_ambari_request` for HTTP transport and `@log_tool` for uniform logging rather than implementing custom network or logging code.

- **Document thoroughly**: Include docstrings following the repository's existing format (sections like `[Tool Role]`, `[Core Functions]`, `[Required Usage Scenarios]`) to enable automatic help generation.

- **Avoid blocking I/O**: Never use synchronous libraries like `requests` inside tools. All network calls must be awaited to maintain server responsiveness.

- **Validate with MCP Inspector**: Test new tools using [`./run-mcp-inspector-local.sh`](https://github.com/call518/mcp-ambari-api/blob/main/./run-mcp-inspector-local.sh) to verify JSON schema compliance and output formatting before deployment.

## Summary

- **Write async functions** decorated with `@mcp.tool()` anywhere imported before `main()` executes in [`mcp_main.py`](https://github.com/call518/mcp-ambari-api/blob/main/mcp_main.py).

- **Apply `@log_tool`** for automatic timing and consistent log formatting across all custom tools.

- **Leverage `make_ambari_request`** from [`functions.py`](https://github.com/call518/mcp-ambari-api/blob/main/functions.py) to ensure uniform HTTP handling and error management.

- **Optionally export** the tool name in [`__init__.py`](https://github.com/call518/mcp-ambari-api/blob/main/__init__.py) to maintain a tidy public API.

- **Test locally** using stdio mode and the MCP inspector before committing changes.

## Frequently Asked Questions

### How does FastMCP know which functions to expose as tools?

FastMCP scans for the `@mcp.tool()` decorator at import time. When you decorate an async function with `@mcp.tool(title="Name")`, the decorator registers the callable with the global `FastMCP` instance created at lines 79-80 in [`src/mcp_ambari_api/mcp_main.py`](https://github.com/call518/mcp-ambari-api/blob/main/src/mcp_ambari_api/mcp_main.py). The server then exposes these functions through `mcp.run()` at line 3731 without requiring explicit registration calls.

### Can I place custom tools in separate files instead of mcp_main.py?

Yes. You can define custom tools in any Python module provided it is imported before `main()` executes in [`mcp_main.py`](https://github.com/call518/mcp-ambari-api/blob/main/mcp_main.py). Add an import statement at the top of [`mcp_main.py`](https://github.com/call518/mcp-ambari-api/blob/main/mcp_main.py) to ensure FastMCP registers the decorated functions during module initialization. The tools become available immediately without modifying the server startup code.

### What is the purpose of the `@log_tool` decorator?

The `@log_tool` wrapper provides automatic start/stop timing and consistent log formatting for every tool invocation. According to the source code in [`mcp_main.py`](https://github.com/call518/mcp-ambari-api/blob/main/mcp_main.py) (see lines 39-42 in the `dump_configurations` implementation), this decorator ensures all tools follow the same logging standards, making debugging and performance monitoring easier across custom and built-in tools alike.

### Should I use `make_ambari_request` or write my own HTTP client?

Always use `make_ambari_request` from [`src/mcp_ambari_api/functions.py`](https://github.com/call518/mcp-ambari-api/blob/main/src/mcp_ambari_api/functions.py). This helper handles authentication, error parsing, timeout management, and response formatting consistently across the codebase. Writing a custom HTTP client risks inconsistent error handling and bypasses the centralized logging and retry logic implemented in the helper function.