Bug Description
hermes-mcp-closedresource-bug-report.md
Bug Description
MCP tool calls via hermes mcp fail with ClosedResourceError: (note: empty message) even when hermes mcp test paperclip passes successfully and tools are discovered. The ClosedResourceError exception message is being swallowed somewhere in the error handling path, making debugging impossible.
Key symptom: The error message is literally the empty string — ClosedResourceError: with nothing after the colon.
Steps to Reproduce
- Configure an MCP server in
~/.hermes/config.yaml (e.g., paperclip pointing to a Python stdio MCP server)
- Start
hermes gateway run
- Wait for the gateway to register the MCP tools
- Run
hermes mcp test paperclip — this passes ✓
- Attempt to call any MCP tool via the agent — it fails with
ClosedResourceError:
Expected Behavior
MCP tool calls should either succeed or return a meaningful error message with details about what went wrong (e.g., which resource was closed, why, and where in the call stack).
Actual Behavior
MCP call failed: ClosedResourceError:
The exception has an empty message. Looking at the hermes-agent source in tools/mcp_tool.py:
- The exception is caught in
_call_once() → _run_on_mcp_loop(_call(), timeout=tool_timeout)
- The
_run_on_mcp_loop function does future.result(timeout=wait_timeout)
- This raises the
concurrent.futures.CancelledError wrapped exception (if timeout) or the raw exception
- The exception propagates to
_call_once()'s except Exception as exc: handler at line 2066
- The error is logged at line 2088-2091:
MCP tool paperclip/paperclip_health call failed: with the exception repr
- The error is sanitized and returned as JSON
The ClosedResourceError class from anyio is initialized with no message argument:
# anyio._core._exceptions
class ClosedResourceError(Exception):
"""Raised when trying to use a resource that has been closed."""
When raised without an argument (e.g., raise ClosedResourceError), str(e) returns "", giving the empty error string.
Root Cause Analysis
The ClosedResourceError is raised by anyio.streams.memory when the memory object stream is closed. In the MCP Python SDK (mcp/client/stdio/__init__.py), ClosedResourceError is caught silently in the stdout_reader and stdin_writer tasks:
except anyio.ClosedResourceError: # pragma: no cover
await anyio.lowlevel.checkpoint()
This means the stream being closed is expected in normal operation (when the subprocess exits). However, when combined with hermes-agent's event loop sharing model, the ClosedResourceError can propagate up unexpectedly.
The real issue is that hermes-agent's error handler at line 2093-2094:
"error": _sanitize_error(
f"MCP call failed: {type(exc).__name__}: {exc}"
)
Uses f"{exc}" which calls str(exc). Since ClosedResourceError() has no message, this produces an empty string. The exception itself (its type and origin) is not included in the error output shown to the user.
Environment
- OS: Linux (Proxmox home lab)
- Hermes Agent: v0.12.0 (2026.4.30) — latest from origin/main
- Python: 3.11.15 (hermes venv) / 3.13.5 (system)
- MCP Python SDK: 1.27.0
- anyio: 4.13.0
- MCP Server: Custom Python stdio MCP server (paperclip_server.py)
Additional Context
Related MCP Python SDK issues:
Suggested Fix
- Better exception formatting in
tools/mcp_tool.py line 2093-2095: Include repr(exc) or at minimum the exception module/class name when str(exc) is empty:
exc_str = str(exc) if str(exc) else repr(exc)
"error": _sanitize_error(f"MCP call failed: {type(exc).__name__}: {exc_str}")
-
Also log the full traceback for ClosedResourceError specifically, since it often indicates a stream/session lifecycle issue that needs source-level debugging.
-
Consider session reconnect logic for ClosedResourceError — if the read stream was closed, the subprocess may have crashed and should be restarted.
Steps to Reproduce
Steps to Reproduce
- Configure an MCP server in
~/.hermes/config.yaml (e.g., paperclip pointing to a Python stdio MCP server)
- Start
hermes gateway run
- Wait for the gateway to register the MCP tools
- Run
hermes mcp test paperclip — this passes ✓
- Attempt to call any MCP tool via the agent — it fails with
ClosedResourceError:
Expected Behavior
Expected Behavior
MCP tool calls should either succeed or return a meaningful error message with details about what went wrong (e.g., which resource was closed, why, and where in the call stack).
Actual Behavior
Actual Behavior
MCP call failed: ClosedResourceError:
The exception has an empty message. Looking at the hermes-agent source in tools/mcp_tool.py:
- The exception is caught in
_call_once() → _run_on_mcp_loop(_call(), timeout=tool_timeout)
- The
_run_on_mcp_loop function does future.result(timeout=wait_timeout)
- This raises the
concurrent.futures.CancelledError wrapped exception (if timeout) or the raw exception
- The exception propagates to
_call_once()'s except Exception as exc: handler at line 2066
- The error is logged at line 2088-2091:
MCP tool paperclip/paperclip_health call failed: with the exception repr
- The error is sanitized and returned as JSON
The ClosedResourceError class from anyio is initialized with no message argument:
# anyio._core._exceptions
class ClosedResourceError(Exception):
"""Raised when trying to use a resource that has been closed."""
When raised without an argument (e.g., raise ClosedResourceError), str(e) returns "", giving the empty error string.
Affected Component
Tools (terminal, file ops, web, code execution, etc.)
Messaging Platform (if gateway-related)
Telegram
Debug Report
Report https://paste.rs/QpNOr
agent.log https://paste.rs/fNZug
gateway.log https://paste.rs/CARNq
Operating System
Debian
Python Version
No response
Hermes Version
No response
Additional Logs / Traceback (optional)
Root Cause Analysis (optional)
No response
Proposed Fix (optional)
No response
Are you willing to submit a PR for this?
Bug Description
hermes-mcp-closedresource-bug-report.md
Bug Description
MCP tool calls via
hermes mcpfail withClosedResourceError:(note: empty message) even whenhermes mcp test paperclippasses successfully and tools are discovered. TheClosedResourceErrorexception message is being swallowed somewhere in the error handling path, making debugging impossible.Key symptom: The error message is literally the empty string —
ClosedResourceError:with nothing after the colon.Steps to Reproduce
~/.hermes/config.yaml(e.g., paperclip pointing to a Python stdio MCP server)hermes gateway runhermes mcp test paperclip— this passes ✓ClosedResourceError:Expected Behavior
MCP tool calls should either succeed or return a meaningful error message with details about what went wrong (e.g., which resource was closed, why, and where in the call stack).
Actual Behavior
The exception has an empty message. Looking at the hermes-agent source in
tools/mcp_tool.py:_call_once()→_run_on_mcp_loop(_call(), timeout=tool_timeout)_run_on_mcp_loopfunction doesfuture.result(timeout=wait_timeout)concurrent.futures.CancelledErrorwrapped exception (if timeout) or the raw exception_call_once()'sexcept Exception as exc:handler at line 2066MCP tool paperclip/paperclip_health call failed:with the exception reprThe
ClosedResourceErrorclass fromanyiois initialized with no message argument:When raised without an argument (e.g.,
raise ClosedResourceError),str(e)returns"", giving the empty error string.Root Cause Analysis
The
ClosedResourceErroris raised byanyio.streams.memorywhen the memory object stream is closed. In the MCP Python SDK (mcp/client/stdio/__init__.py),ClosedResourceErroris caught silently in thestdout_readerandstdin_writertasks:This means the stream being closed is expected in normal operation (when the subprocess exits). However, when combined with hermes-agent's event loop sharing model, the
ClosedResourceErrorcan propagate up unexpectedly.The real issue is that hermes-agent's error handler at line 2093-2094:
Uses
f"{exc}"which callsstr(exc). SinceClosedResourceError()has no message, this produces an empty string. The exception itself (its type and origin) is not included in the error output shown to the user.Environment
Additional Context
Related MCP Python SDK issues:
Suggested Fix
tools/mcp_tool.pyline 2093-2095: Includerepr(exc)or at minimum the exception module/class name whenstr(exc)is empty:Also log the full traceback for
ClosedResourceErrorspecifically, since it often indicates a stream/session lifecycle issue that needs source-level debugging.Consider session reconnect logic for
ClosedResourceError— if the read stream was closed, the subprocess may have crashed and should be restarted.Steps to Reproduce
Steps to Reproduce
~/.hermes/config.yaml(e.g., paperclip pointing to a Python stdio MCP server)hermes gateway runhermes mcp test paperclip— this passes ✓ClosedResourceError:Expected Behavior
Expected Behavior
MCP tool calls should either succeed or return a meaningful error message with details about what went wrong (e.g., which resource was closed, why, and where in the call stack).
Actual Behavior
Actual Behavior
The exception has an empty message. Looking at the hermes-agent source in
tools/mcp_tool.py:_call_once()→_run_on_mcp_loop(_call(), timeout=tool_timeout)_run_on_mcp_loopfunction doesfuture.result(timeout=wait_timeout)concurrent.futures.CancelledErrorwrapped exception (if timeout) or the raw exception_call_once()'sexcept Exception as exc:handler at line 2066MCP tool paperclip/paperclip_health call failed:with the exception reprThe
ClosedResourceErrorclass fromanyiois initialized with no message argument:When raised without an argument (e.g.,
raise ClosedResourceError),str(e)returns"", giving the empty error string.Affected Component
Tools (terminal, file ops, web, code execution, etc.)
Messaging Platform (if gateway-related)
Telegram
Debug Report
Operating System
Debian
Python Version
No response
Hermes Version
No response
Additional Logs / Traceback (optional)
Root Cause Analysis (optional)
No response
Proposed Fix (optional)
No response
Are you willing to submit a PR for this?