diff --git a/src/strands/tools/mcp/mcp_client.py b/src/strands/tools/mcp/mcp_client.py index 1fd2990ec..11fa80f14 100644 --- a/src/strands/tools/mcp/mcp_client.py +++ b/src/strands/tools/mcp/mcp_client.py @@ -732,6 +732,8 @@ def _handle_tool_result(self, tool_use_id: str, call_tool_result: MCPCallToolRes content=mapped_contents, ) + if call_tool_result.isError: + result["isError"] = True if call_tool_result.structuredContent: result["structuredContent"] = call_tool_result.structuredContent if call_tool_result.meta: diff --git a/src/strands/tools/mcp/mcp_types.py b/src/strands/tools/mcp/mcp_types.py index 8fbf573be..a172d9ee9 100644 --- a/src/strands/tools/mcp/mcp_types.py +++ b/src/strands/tools/mcp/mcp_types.py @@ -61,7 +61,12 @@ class MCPToolResult(ToolResult): metadata: Optional arbitrary metadata returned by the MCP tool. This field allows MCP servers to attach custom metadata to tool results (e.g., token usage, performance metrics, or business-specific tracking information). + isError: Whether the MCP tool itself reported an application-level error. + True means the tool executed but returned a failure result (application error). + Absent means the call succeeded or that an exception was raised before the + tool could return a result (protocol or transport error). """ structuredContent: NotRequired[dict[str, Any]] metadata: NotRequired[dict[str, Any]] + isError: NotRequired[bool] diff --git a/tests/strands/tools/mcp/test_mcp_client.py b/tests/strands/tools/mcp/test_mcp_client.py index 057c41a95..1ed200cf0 100644 --- a/tests/strands/tools/mcp/test_mcp_client.py +++ b/tests/strands/tools/mcp/test_mcp_client.py @@ -167,6 +167,21 @@ def test_call_tool_sync_with_structured_content(mock_transport, mock_session): assert result["structuredContent"]["status"] == "completed" +@pytest.mark.parametrize("is_error,expect_is_error_field", [(True, True), (False, False)]) +def test_call_tool_sync_preserves_is_error(mock_transport, mock_session, is_error, expect_is_error_field): + """Test that call_tool_sync exposes CallToolResult.isError in MCPToolResult.""" + mock_content = MCPTextContent(type="text", text="Tool output") + mock_session.call_tool.return_value = MCPCallToolResult(isError=is_error, content=[mock_content]) + + with MCPClient(mock_transport["transport_callable"]) as client: + result = client.call_tool_sync(tool_use_id="test-123", name="test_tool", arguments={}) + + if expect_is_error_field: + assert result.get("isError") is True + else: + assert "isError" not in result + + def test_call_tool_sync_exception(mock_transport, mock_session): """Test that call_tool_sync correctly handles exceptions.""" mock_session.call_tool.side_effect = Exception("Test exception")