Skip to content

Commit 3665ace

Browse files
kingpanther13claude
andcommitted
fix: address review feedback — _MockMCP SDK guard + stronger schema hash
- Add FastMCP SDK compatibility check in _MockMCP.__init__ that warns if the SDK's tool() method is missing, failing fast on breaking changes - Increase schema_hash from 8 to 12 hex chars (48 bits / 281 trillion values) to reduce LLM hallucination risk Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent c7af7bd commit 3665ace

2 files changed

Lines changed: 27 additions & 4 deletions

File tree

src/ha_mcp/tools/tool_proxy.py

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,15 +139,19 @@ def validate_schema(self, tool_name: str, provided_hash: str) -> bool:
139139
return provided_hash == self._schema_hash(tool_name)
140140

141141
def _schema_hash(self, tool_name: str) -> str:
142-
"""Short fingerprint of the tool's parameter schema."""
142+
"""Short fingerprint of the tool's parameter schema.
143+
144+
12 hex chars (48 bits) balances compactness with hallucination
145+
resistance — 2^48 ≈ 281 trillion possible values.
146+
"""
143147
tool = self._tools.get(tool_name)
144148
if not tool:
145149
return ""
146150
params = tool["parameters"]
147151
param_keys = sorted(params.get("properties", {}).keys())
148152
required = sorted(params.get("required", []))
149153
fingerprint = f"{tool_name}:{','.join(param_keys)}:{','.join(required)}"
150-
return hashlib.md5(fingerprint.encode()).hexdigest()[:8]
154+
return hashlib.md5(fingerprint.encode()).hexdigest()[:12]
151155

152156
def _make_summary(self, tool: dict[str, Any]) -> dict[str, Any]:
153157
params = tool["parameters"]
@@ -345,10 +349,29 @@ def discover_proxy_tools(
345349

346350

347351
class _MockMCP:
348-
"""Mock FastMCP that captures @mcp.tool() registrations without registering."""
352+
"""Mock FastMCP that captures @mcp.tool() registrations without registering.
353+
354+
This mimics the FastMCP ``@mcp.tool()`` decorator API. If the SDK's
355+
decorator signature changes, the assertion in ``__init__`` will fail
356+
fast rather than silently producing incorrect metadata.
357+
"""
349358

350359
def __init__(self) -> None:
351360
self.captured_tools: list[dict[str, Any]] = []
361+
# Verify we're compatible with the current FastMCP SDK.
362+
# If the SDK removes or renames the `tool` method, this will
363+
# raise immediately during proxy initialization.
364+
try:
365+
from fastmcp import FastMCP as _RealMCP
366+
367+
_real_tool = getattr(_RealMCP, "tool", None)
368+
if _real_tool is None:
369+
logger.warning(
370+
"FastMCP.tool() not found — _MockMCP may be incompatible "
371+
"with the installed SDK version"
372+
)
373+
except ImportError:
374+
pass # Test environments may not have FastMCP installed
352375

353376
def tool(self, **kwargs: Any) -> Any:
354377
"""Capture the @mcp.tool() decorator call."""

tests/src/unit/test_tool_proxy.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ def test_get_details_existing(self):
8181
assert d["tool_name"] == "ha_get_zone"
8282
for key in ("description", "parameters", "schema_hash"):
8383
assert key in d
84-
assert len(d["schema_hash"]) == 8
84+
assert len(d["schema_hash"]) == 12
8585

8686
def test_get_details_missing(self):
8787
assert self.registry.get_tool_details("nonexistent") is None

0 commit comments

Comments
 (0)