This is an automated email from the ASF dual-hosted git repository. aminghadersohi pushed a commit to branch mcp-rls-plugins-99978 in repository https://gitbox.apache.org/repos/asf/superset.git
commit 95b9efe1a3d973545a0edffd2077e037ff18ca36 Author: Amin Ghadersohi <[email protected]> AuthorDate: Wed May 27 18:33:25 2026 +0000 fix(mcp): prevent ValueError when select_columns contains only USER_DIRECTORY_FIELDS in list_rls_filters When the caller passes select_columns that consists entirely of USER_DIRECTORY_FIELDS columns (e.g. ["roles"]), ModelListCore raises ValueError because its privacy filter strips all columns, leaving an empty list. Strip USER_DIRECTORY_FIELDS from select_columns before passing to run_tool (falling back to None/defaults when the filtered list is empty). The existing bypass mechanism already restores these fields in the final serialized output using ALL_RLS_COLUMNS. Adds a regression test for the ["roles"]-only select_columns edge case. --- superset/mcp_service/rls/tool/list_rls_filters.py | 15 ++++++++++++++- .../mcp_service/rls/tool/test_rls_tools.py | 22 ++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/superset/mcp_service/rls/tool/list_rls_filters.py b/superset/mcp_service/rls/tool/list_rls_filters.py index 998033f90ad..ae92dcf56df 100644 --- a/superset/mcp_service/rls/tool/list_rls_filters.py +++ b/superset/mcp_service/rls/tool/list_rls_filters.py @@ -26,6 +26,7 @@ from superset_core.mcp.decorators import tool, ToolAnnotations from superset.extensions import event_logger from superset.mcp_service.mcp_core import ModelListCore +from superset.mcp_service.privacy import USER_DIRECTORY_FIELDS from superset.mcp_service.rls.schemas import ( ALL_RLS_COLUMNS, DEFAULT_RLS_COLUMNS, @@ -92,11 +93,23 @@ async def list_rls_filters( logger=logger, ) + # RLS 'roles' is valid filter data but lives in USER_DIRECTORY_FIELDS, + # so ModelListCore would raise ValueError for a column list that reduces + # to empty after privacy filtering (e.g. select_columns=["roles"]). + # Strip directory-field columns here; the bypass below restores them in + # the final serialized output from ALL_RLS_COLUMNS. + run_tool_columns = None + if request.select_columns: + non_directory = [ + c for c in request.select_columns if c not in USER_DIRECTORY_FIELDS + ] + run_tool_columns = non_directory if non_directory else None + with event_logger.log_context(action="mcp.list_rls_filters.query"): result = list_tool.run_tool( filters=request.filters, search=request.search, - select_columns=request.select_columns, + select_columns=run_tool_columns, order_column=request.order_column, order_direction=request.order_direction, page=max(request.page - 1, 0), diff --git a/tests/unit_tests/mcp_service/rls/tool/test_rls_tools.py b/tests/unit_tests/mcp_service/rls/tool/test_rls_tools.py index ab45317fab4..1a05dddd37f 100644 --- a/tests/unit_tests/mcp_service/rls/tool/test_rls_tools.py +++ b/tests/unit_tests/mcp_service/rls/tool/test_rls_tools.py @@ -157,6 +157,28 @@ async def test_list_rls_filters_returns_tables_and_roles(mock_list, mcp_server): assert item["roles"][0]["name"] == "Alpha" +@patch("superset.daos.security.RLSDAO.list") [email protected] +async def test_list_rls_filters_roles_only_select_columns(mock_list, mcp_server): + """Requesting only 'roles' must not raise ValueError from the privacy filter.""" + rls_filter = create_mock_rls_filter() + mock_list.return_value = ([rls_filter], 1) + + async with Client(mcp_server) as client: + request = ListRlsFiltersRequest( + page=1, + page_size=10, + select_columns=["roles"], + ) + result = await client.call_tool( + "list_rls_filters", {"request": request.model_dump()} + ) + data = json.loads(result.content[0].text) + item = data["rls_filters"][0] + assert "roles" in item + assert item["roles"][0]["name"] == "Alpha" + + @patch("superset.daos.security.RLSDAO.list") @pytest.mark.asyncio async def test_list_rls_filters_empty(mock_list, mcp_server):
