# How MCP Tool Registration Works in mcp_main.py: A Complete Guide

> Discover how MCP tool registration works in mcp_main.py. Learn to instantiate FastMCP, decorate async functions with mcp.tool(), and run() tools via stdio or HTTP.

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

---

**MCP tool registration in [`mcp_main.py`](https://github.com/call518/mcp-ambari-api/blob/main/mcp_main.py) works by instantiating a singleton `FastMCP` object, decorating async functions with `@mcp.tool()`, and calling `mcp.run()` to expose those tools over stdio or HTTP transports.**

The [`mcp_main.py`](https://github.com/call518/mcp-ambari-api/blob/main/mcp_main.py) file in the `call518/mcp-ambari-api` repository serves as the central registration hub for all Model Context Protocol (MCP) tools. Understanding how MCP tool registration works in this file is essential for developers extending the Ambari API integration or building their own MCP servers. This guide breaks down the exact mechanism, from the initial `FastMCP` instantiation to the final server startup.

## The Three-Step MCP Tool Registration Process

The registration architecture follows a strict three-phase lifecycle defined in [`src/mcp_ambari_api/mcp_main.py`](https://github.com/call518/mcp-ambari-api/blob/main/src/mcp_ambari_api/mcp_main.py).

### Step 1: Instantiate the FastMCP Server

First, the code creates a singleton `FastMCP` instance at import time. This object acts as the central registry.

```python

# src/mcp_ambari_api/mcp_main.py#L76-L80

logger.info("Initializing MCP instance")
mcp = FastMCP("mcp-ambari-api")

```

Because this instantiation happens at the module level, the `mcp` object is available immediately for decorators to bind to. The singleton pattern ensures that all subsequent tool registrations target the same internal registry.

### Step 2: Register Tools with the @mcp.tool Decorator

Each public async function that should be callable by an LLM is prefixed with `@mcp.tool()`. The decorator registers the function name, its signature, and optional metadata inside the `FastMCP` registry.

```python

# src/mcp_ambari_api/mcp_main.py#L139

@mcp.tool(title="Dump Configurations")
@log_tool
async def dump_configurations(
    config_type: str = "all",
    summarize: bool = True,
    limit: int = 10
) -> str:
    # Business logic implementation

    return formatted_config

```

The `@log_tool` wrapper adds timing and logging but does **not** affect the registration itself. The `FastMCP` library introspects the function signature to generate the tool schema that LLMs use to understand available capabilities.

### Step 3: Launch the Server with mcp.run()

Finally, the `main()` function selects the transport protocol and starts the server.

```python

# src/mcp_ambari_api/mcp_main.py#L3838-L3844

if transport_type == "streamable-http":
    mcp.run(transport="streamable-http", host=host, port=port)
else:
    mcp.run(transport='stdio')

```

At this point, all previously registered tools are exposed over the selected transport. The server listens for incoming MCP calls and dispatches them to the registered coroutines.

## Deep Dive: How the @mcp.tool Decorator Works

The `@mcp.tool()` decorator is defined inside the `fastmcp` library (declared in [`pyproject.toml`](https://github.com/call518/mcp-ambari-api/blob/main/pyproject.toml)). When applied to a function in [`mcp_main.py`](https://github.com/call518/mcp-ambari-api/blob/main/mcp_main.py), it performs three critical actions:

1. **Schema Extraction**: Introspects the Python function signature to build a JSON Schema describing parameters (names, types, defaults, and descriptions).
2. **Registry Insertion**: Stores the function object and its metadata in the `FastMCP` instance's internal dictionary, keyed by the function name.
3. **Wrapper Preservation**: Returns the original function (or a thin wrapper) so that subsequent decorators like `@log_tool` can still access it without breaking the registration chain.

This mechanism allows the `call518/mcp-ambari-api` repository to expose over a dozen Ambari operations—such as `dump_configurations`, `get_cluster_hosts`, and `restart_services`—as individual MCP tools without writing boilerplate registration code for each function.

## Registering MCP Resources

In addition to tools, [`mcp_main.py`](https://github.com/call518/mcp-ambari-api/blob/main/mcp_main.py) registers exactly one **MCP resource** using the `@mcp.resource` decorator. Resources provide data rather than executable actions.

```python

# src/mcp_ambari_api/mcp_main.py#L2837

@mcp.resource("ambari-metrics://catalog/{selector}")
async def ambari_metrics_catalog_resource(selector: str) -> str:
    # Returns metric metadata based on selector

    return json.dumps(catalog_data)

```

When a client requests the URI `ambari-metrics://catalog/apps`, FastMCP resolves the template, extracts the `selector` variable, and invokes the registered coroutine. This pattern is ideal for exposing read-only catalogs or configuration data that LLMs need to reference before invoking tools.

## Complete Code Examples

### Invoking a Registered Tool from an LLM

When an LLM client connects to the server, it can invoke the `dump_configurations` tool using this JSON-RPC style payload:

```json
{
  "type": "tool",
  "name": "dump_configurations",
  "arguments": {
    "config_type": "hdfs-site",
    "summarize": true,
    "limit": 5
  }
}

```

FastMCP receives the request, looks up `"dump_configurations"` in its internal registry (populated by the decorator in [`src/mcp_ambari_api/mcp_main.py`](https://github.com/call518/mcp-ambari-api/blob/main/src/mcp_ambari_api/mcp_main.py)), validates the arguments against the extracted schema, and executes the coroutine.

### Accessing the Metrics Catalog Resource

For HTTP transport mode, clients can fetch resource data using a GET request:

```http
GET http://localhost:8000/mcp?resource=ambari-metrics://catalog/apps

```

FastMCP parses the URI template registered at line 2837, extracts `selector="apps"`, and calls `ambari_metrics_catalog_resource(selector="apps")`. The JSON response is forwarded to the client.

### Adding a New Tool to the Server

To expose a new operation, define an async 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) and apply the decorator:

```python
@mcp.tool(title="Get Hadoop Queue Info")
@log_tool
async def get_queue_info(queue_name: str) -> str:
    """
    Retrieves detailed information about a specific YARN queue.
    """
    # Implementation from functions.py or inline

    data = await fetch_queue_data(queue_name)
    return f"Queue {queue_name}: {data}"

```

Because `mcp` is a module-level singleton instantiated at lines 76-80, the decorator immediately registers the new tool. No additional registration boilerplate is required.

## Key Files in the Registration Architecture

Understanding the complete registration flow requires familiarity with these files in the `call518/mcp-ambari-api` repository:

- **[`src/mcp_ambari_api/mcp_main.py`](https://github.com/call518/mcp-ambari-api/blob/main/src/mcp_ambari_api/mcp_main.py)**: The central registration hub containing the `FastMCP` singleton, all `@mcp.tool` and `@mcp.resource` decorators, and the `main()` bootstrap function.
- **[`src/mcp_ambari_api/functions.py`](https://github.com/call518/mcp-ambari-api/blob/main/src/mcp_ambari_api/functions.py)**: Implements the business logic and HTTP helpers invoked by the registered tools. Tool functions in [`mcp_main.py`](https://github.com/call518/mcp-ambari-api/blob/main/mcp_main.py) typically delegate to utilities here.
- **[`pyproject.toml`](https://github.com/call518/mcp-ambari-api/blob/main/pyproject.toml)**: Declares the `fastmcp` dependency that provides the `FastMCP` class and decorator implementations used throughout the registration process.
- **[`run-mcp-inspector-local.sh`](https://github.com/call518/mcp-ambari-api/blob/main/run-mcp-inspector-local.sh)**: Convenience script that launches the server in `stdio` mode for local testing and inspection of registered tools.

## Summary

- **Singleton Pattern**: [`mcp_main.py`](https://github.com/call518/mcp-ambari-api/blob/main/mcp_main.py) instantiates one `FastMCP` object at module import time (lines 76-80), enabling immediate decorator-based registration.
- **Decorator Registration**: The `@mcp.tool()` decorator introspects function signatures and stores metadata in the FastMCP registry, while `@mcp.resource()` handles URI templates for data access.
- **Transport Agnostic**: Registration occurs independently of transport; `mcp.run()` (lines 3838-3844) exposes registered tools via `stdio` or `streamable-http` based on CLI arguments.
- **Extensibility**: Adding new tools requires only defining an async function and applying the decorator—no additional registration boilerplate is needed.

## Frequently Asked Questions

### What is the difference between @mcp.tool and @mcp.resource in mcp_main.py?

The `@mcp.tool` decorator registers **executable functions** that LLMs can invoke to perform actions, such as `dump_configurations` or `restart_services`. These accept arguments and return results. The `@mcp.resource` decorator registers **data endpoints** identified by URI templates, such as `ambari-metrics://catalog/{selector}`, which provide read-only data that LLMs can reference before invoking tools.

### How do I add a new MCP tool to the call518/mcp-ambari-api server?

Define an async 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) and decorate it with `@mcp.tool()`, optionally including a `title` parameter for metadata. You can also chain the `@log_tool` decorator for automatic logging. Because the `FastMCP` instance is created at module level, registration happens immediately upon import; no additional registry calls are required.

### Can the mcp-ambari-api server use multiple transports simultaneously?

No, the current implementation in [`mcp_main.py`](https://github.com/call518/mcp-ambari-api/blob/main/mcp_main.py) selects a single transport during startup. The `main()` function checks the `transport_type` variable and calls `mcp.run()` with either `transport="stdio"` or `transport="streamable-http"` along with host/port parameters. To support multiple transports simultaneously, you would need to modify the bootstrap logic to run separate server instances.

### What happens if I register two MCP tools with the same name?

The `FastMCP` registry uses the function name as the key. If you decorate two different functions with `@mcp.tool()` and give them identical Python function names (or explicitly override the name parameter), the second registration will overwrite the first in the internal dictionary. This can cause unexpected behavior where only the second implementation is callable, so ensure unique function names or explicitly specify distinct names in the decorator.