Skip to content

Commit 0c5a898

Browse files
kingpanther13claude
andcommitted
feat: add tool search proxy for context reduction (phase 1, 10 tools)
Implements the server-side Tool Search Proxy pattern recommended by Anthropic (code execution with MCP blog) and validated by Speakeasy (100x token reduction). Instead of registering all tools with MCP, niche tools are served through 3 meta-tools: ha_find_tools, ha_get_tool_details, ha_execute_tool. Phase 1 proxies 10 tools from 5 modules (zones, labels, addons, voice_assistant, traces). Net tool count decreases by 7 (10 removed, 3 meta-tools added). Tool descriptions and implementations are completely unchanged — they're stored server-side and returned on demand. Schema enforcement via required tool_schema hash prevents LLMs from calling tools without reading documentation first. Migration roadmap in tool_proxy.py PROXY_MODULES comments. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 99cec2f commit 0c5a898

3 files changed

Lines changed: 1089 additions & 2 deletions

File tree

src/ha_mcp/tools/registry.py

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@
1616
- "all" (default): Load all tools
1717
- "automation": Load only automation-related tools (automations, scripts, traces, blueprints)
1818
- Comma-separated list: Load specific modules (e.g., "tools_config_automations,tools_search")
19+
20+
Tool proxy:
21+
Modules listed in tool_proxy.PROXY_MODULES are NOT registered with MCP directly.
22+
Instead, they are captured into a proxy registry and accessed via 3 meta-tools:
23+
ha_find_tools, ha_get_tool_details, ha_execute_tool. See tool_proxy.py for details.
1924
"""
2025

2126
import logging
@@ -136,27 +141,43 @@ def register_all_tools(self) -> None:
136141
137142
Tool modules are imported and registered only when this method is called,
138143
which happens after the MCP server is ready to accept connections.
144+
145+
Modules listed in tool_proxy.PROXY_MODULES are NOT registered with MCP.
146+
Instead, they are captured into a proxy registry and served via meta-tools.
139147
"""
140148
if self._modules_registered:
141149
logger.debug("Tools already registered, skipping")
142150
return
143151

144152
import importlib
145153

154+
from .tool_proxy import PROXY_MODULES, discover_proxy_tools, register_proxy_tools
155+
146156
# Build kwargs with all available dependencies (lazy access)
147157
kwargs = {
148158
"smart_tools": self.smart_tools,
149159
"device_tools": self.device_tools,
150160
}
151161

152162
registered_count = 0
163+
proxied_modules = []
153164

154-
# Import and register tools_*.py modules
165+
# Determine which discovered modules should be proxied
166+
proxy_set = {
167+
m for m in self._discovered_modules if m in PROXY_MODULES
168+
}
169+
170+
# Import and register tools_*.py modules (skip proxied ones)
155171
for module_name in self._discovered_modules:
156172
# Skip explicit modules - handled separately
157173
if module_name in EXPLICIT_MODULES:
158174
continue
159175

176+
# Skip proxied modules — they'll be captured below
177+
if module_name in proxy_set:
178+
proxied_modules.append(module_name)
179+
continue
180+
160181
try:
161182
module = importlib.import_module(f".{module_name}", "ha_mcp.tools")
162183

@@ -185,6 +206,10 @@ def register_all_tools(self) -> None:
185206
for module_name, func_name in EXPLICIT_MODULES.items():
186207
if module_name not in self._discovered_modules:
187208
continue
209+
# Skip proxied explicit modules
210+
if module_name in proxy_set:
211+
proxied_modules.append(module_name)
212+
continue
188213
try:
189214
module = importlib.import_module(f".{module_name}", "ha_mcp.tools")
190215
register_func = getattr(module, func_name)
@@ -195,5 +220,19 @@ def register_all_tools(self) -> None:
195220
logger.error(f"Failed to register tools from {module_name}: {e}")
196221
raise
197222

223+
# Capture proxied modules into the proxy registry and register meta-tools
224+
if proxy_set:
225+
proxy_registry = discover_proxy_tools(
226+
self.mcp, self.client, proxy_set, **kwargs
227+
)
228+
register_proxy_tools(self.mcp, self.client, proxy_registry, **kwargs)
229+
logger.info(
230+
f"Tool proxy: {proxy_registry.tool_count} tools from "
231+
f"{len(proxy_set)} modules routed through meta-tools"
232+
)
233+
198234
self._modules_registered = True
199-
logger.info(f"Auto-discovery registered tools from {registered_count} modules")
235+
logger.info(
236+
f"Auto-discovery registered tools from {registered_count} modules "
237+
f"(+ {len(proxied_modules)} proxied modules)"
238+
)

0 commit comments

Comments
 (0)