Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 41 additions & 2 deletions src/ha_mcp/tools/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@
- "all" (default): Load all tools
- "automation": Load only automation-related tools (automations, scripts, traces, blueprints)
- Comma-separated list: Load specific modules (e.g., "tools_config_automations,tools_search")

Tool proxy:
Modules listed in tool_proxy.PROXY_MODULES are NOT registered with MCP directly.
Instead, they are captured into a proxy registry and accessed via 3 meta-tools:
ha_find_tools, ha_get_tool_details, ha_execute_tool. See tool_proxy.py for details.
"""

import logging
Expand Down Expand Up @@ -136,27 +141,43 @@ def register_all_tools(self) -> None:

Tool modules are imported and registered only when this method is called,
which happens after the MCP server is ready to accept connections.

Modules listed in tool_proxy.PROXY_MODULES are NOT registered with MCP.
Instead, they are captured into a proxy registry and served via meta-tools.
"""
if self._modules_registered:
logger.debug("Tools already registered, skipping")
return

import importlib

from .tool_proxy import PROXY_MODULES, discover_proxy_tools, register_proxy_tools

# Build kwargs with all available dependencies (lazy access)
kwargs = {
"smart_tools": self.smart_tools,
"device_tools": self.device_tools,
}

registered_count = 0
proxied_modules = []

# Import and register tools_*.py modules
# Determine which discovered modules should be proxied
proxy_set = {
m for m in self._discovered_modules if m in PROXY_MODULES
}

# Import and register tools_*.py modules (skip proxied ones)
for module_name in self._discovered_modules:
# Skip explicit modules - handled separately
if module_name in EXPLICIT_MODULES:
continue

# Skip proxied modules — they'll be captured below
if module_name in proxy_set:
proxied_modules.append(module_name)
continue

try:
module = importlib.import_module(f".{module_name}", "ha_mcp.tools")

Expand Down Expand Up @@ -185,6 +206,10 @@ def register_all_tools(self) -> None:
for module_name, func_name in EXPLICIT_MODULES.items():
if module_name not in self._discovered_modules:
continue
# Skip proxied explicit modules
if module_name in proxy_set:
proxied_modules.append(module_name)
continue
try:
module = importlib.import_module(f".{module_name}", "ha_mcp.tools")
register_func = getattr(module, func_name)
Expand All @@ -195,5 +220,19 @@ def register_all_tools(self) -> None:
logger.error(f"Failed to register tools from {module_name}: {e}")
raise

# Capture proxied modules into the proxy registry and register meta-tools
if proxy_set:
proxy_registry = discover_proxy_tools(
self.mcp, self.client, proxy_set, **kwargs
)
register_proxy_tools(self.mcp, self.client, proxy_registry, **kwargs)
logger.info(
f"Tool proxy: {proxy_registry.tool_count} tools from "
f"{len(proxy_set)} modules routed through meta-tools"
)

self._modules_registered = True
logger.info(f"Auto-discovery registered tools from {registered_count} modules")
logger.info(
f"Auto-discovery registered tools from {registered_count} modules "
f"(+ {len(proxied_modules)} proxied modules)"
)
Loading
Loading