From bfee8958adc95ec7a2db291a8c881eade8d5c22e Mon Sep 17 00:00:00 2001 From: phernandez Date: Sun, 31 May 2026 11:14:39 -0500 Subject: [PATCH] docs(integrations): document the defensive getattr in the Hermes provider MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses the remaining review finding (deferred while #859 was open; now merged, so the hermes module is on main and the comment lands cleanly here). The two getattr sites read MCP SDK result/tool fields; `mcp` is an unpinned dependency (plugin.yaml), and its CallToolResult / ListToolsResult / Tool shapes have varied across SDK versions — so the defensive getattr is intentional, not the speculative kind CLAUDE.md warns against. Added constraint comments at both sites explaining why. No behavior change. `just package-check-hermes` passes (244 passed, 12 integration tests skipped without BM_INTEGRATION). Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: phernandez --- integrations/hermes/__init__.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/integrations/hermes/__init__.py b/integrations/hermes/__init__.py index b82501a4..f3ecf485 100644 --- a/integrations/hermes/__init__.py +++ b/integrations/hermes/__init__.py @@ -449,6 +449,9 @@ def _extract_mcp_text(result: Any) -> str: Returns a JSON string for the agent. If the result is itself JSON, returns it as-is. Otherwise wraps the text in `{"text": "..."}` for downstream parsing. """ + # The `mcp` SDK is an unpinned dependency (see plugin.yaml), and its result + # shapes (CallToolResult, content blocks) have shifted across versions — read + # these fields defensively with getattr rather than direct attribute access. is_error = bool(getattr(result, "isError", False)) parts: list[str] = [] for c in getattr(result, "content", None) or []: @@ -594,6 +597,9 @@ class _BmMcpActor: self._stop_future = asyncio.get_running_loop().create_future() try: listing = await session.list_tools() + # Same unpinned-`mcp`-SDK reason as parse_result above: + # ListToolsResult / Tool field shapes vary across SDK versions, + # so read them defensively rather than by direct attribute access. self._tools_cache = [ { "name": getattr(t, "name", ""),