From 21aa900d64ae9d548bb237101420113e1c8bacd9 Mon Sep 17 00:00:00 2001 From: chengyongru <2755839590@qq.com> Date: Wed, 1 Jul 2026 00:40:17 +0800 Subject: [PATCH] fix: honor MCP tool error results --- nanobot/agent/tools/mcp.py | 7 +++++-- tests/tools/test_mcp_tool.py | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/nanobot/agent/tools/mcp.py b/nanobot/agent/tools/mcp.py index 11fe1243..579005dd 100644 --- a/nanobot/agent/tools/mcp.py +++ b/nanobot/agent/tools/mcp.py @@ -14,7 +14,7 @@ from weakref import WeakKeyDictionary import httpx from loguru import logger -from nanobot.agent.tools.base import Tool +from nanobot.agent.tools.base import Tool, ToolResult from nanobot.agent.tools.registry import ToolRegistry from nanobot.bus.events import ( INBOUND_META_RUNTIME_CONTROL, @@ -461,7 +461,10 @@ class MCPToolWrapper(_MCPWrapperBase): return f"(MCP tool call failed: {type(exc).__name__})" else: # Success — extract text and persist any image content as artifacts. - return self._render_call_result(result.content, kwargs) + rendered = self._render_call_result(result.content, kwargs) + if getattr(result, "isError", False): + return ToolResult.error(rendered) + return rendered return "(MCP tool call failed)" # Unreachable, but satisfies type checkers diff --git a/tests/tools/test_mcp_tool.py b/tests/tools/test_mcp_tool.py index acbb673f..7fe486d0 100644 --- a/tests/tools/test_mcp_tool.py +++ b/tests/tools/test_mcp_tool.py @@ -19,7 +19,7 @@ from nanobot.agent.tools.mcp import ( _sanitize_name, connect_mcp_servers, ) -from nanobot.agent.tools.registry import ToolRegistry +from nanobot.agent.tools.registry import ToolRegistry, is_tool_error_result from nanobot.config.schema import MCPServerConfig @@ -304,6 +304,38 @@ async def test_execute_returns_text_blocks() -> None: assert result == "hello\n42" +@pytest.mark.asyncio +async def test_execute_wraps_mcp_is_error_result() -> None: + async def call_tool(_name: str, arguments: dict) -> object: + return SimpleNamespace( + content=[_FakeTextContent("Error: server-side MCP failure")], + isError=True, + ) + + wrapper = _make_wrapper(SimpleNamespace(call_tool=call_tool)) + + result = await wrapper.execute() + + assert result == "Error: server-side MCP failure" + assert is_tool_error_result(wrapper.name, result) + + +@pytest.mark.asyncio +async def test_execute_preserves_success_text_that_starts_with_error() -> None: + async def call_tool(_name: str, arguments: dict) -> object: + return SimpleNamespace( + content=[_FakeTextContent("Error: generated report successfully")], + isError=False, + ) + + wrapper = _make_wrapper(SimpleNamespace(call_tool=call_tool)) + + result = await wrapper.execute() + + assert result == "Error: generated report successfully" + assert not is_tool_error_result(wrapper.name, result) + + # Smallest valid 1x1 PNG, base64 without the data: prefix. _PNG_B64 = ( "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8"