How MCP Tool Registration Works in mcp_main.py: A Complete Guide
MCP tool registration in 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 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.
Step 1: Instantiate the FastMCP Server
First, the code creates a singleton FastMCP instance at import time. This object acts as the central registry.
# 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.
# 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.
# 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). When applied to a function in mcp_main.py, it performs three critical actions:
- Schema Extraction: Introspects the Python function signature to build a JSON Schema describing parameters (names, types, defaults, and descriptions).
- Registry Insertion: Stores the function object and its metadata in the
FastMCPinstance's internal dictionary, keyed by the function name. - Wrapper Preservation: Returns the original function (or a thin wrapper) so that subsequent decorators like
@log_toolcan 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 registers exactly one MCP resource using the @mcp.resource decorator. Resources provide data rather than executable actions.
# 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:
{
"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), 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:
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 and apply the decorator:
@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: The central registration hub containing theFastMCPsingleton, all@mcp.tooland@mcp.resourcedecorators, and themain()bootstrap function.src/mcp_ambari_api/functions.py: Implements the business logic and HTTP helpers invoked by the registered tools. Tool functions inmcp_main.pytypically delegate to utilities here.pyproject.toml: Declares thefastmcpdependency that provides theFastMCPclass and decorator implementations used throughout the registration process.run-mcp-inspector-local.sh: Convenience script that launches the server instdiomode for local testing and inspection of registered tools.
Summary
- Singleton Pattern:
mcp_main.pyinstantiates oneFastMCPobject 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 viastdioorstreamable-httpbased 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 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 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.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →