From 71f210cd8984ee463653869df1b57e64ee574fa8 Mon Sep 17 00:00:00 2001 From: Amin Ghadersohi Date: Wed, 6 May 2026 22:56:46 +0000 Subject: [PATCH 1/5] fix(mcp): clarify request wrapper pattern in list_datasets, list_charts, list_dashboards LLMs consistently passed flat kwargs (search, page, page_size) to list_* tools instead of wrapping them in the required `request` object, causing pydantic validation errors. - Add docstring usage examples to list_datasets, list_charts, and list_dashboards showing the correct `request={...}` call shape and explicitly warning against flat kwargs - Enumerate valid filter columns directly in DatasetFilter, ChartFilter, and DashboardFilter field descriptions (e.g. `created_by_fk` is not a valid dataset filter col) - Add TestListDatasetsRequestWrapper tests covering: correct request wrapper usage, default values, valid/invalid filter col validation, and the flat-kwargs rejection scenario from story #105712 - Allow E501 in list_*.py tool files (docstring examples need full request shapes to be instructive) --- pyproject.toml | 1 + superset/mcp_service/chart/schemas.py | 7 ++- .../mcp_service/chart/tool/list_charts.py | 20 ++++++- superset/mcp_service/dashboard/schemas.py | 5 +- .../dashboard/tool/list_dashboards.py | 20 ++++++- superset/mcp_service/dataset/schemas.py | 7 ++- .../mcp_service/dataset/tool/list_datasets.py | 19 +++++- .../dataset/tool/test_dataset_tools.py | 60 +++++++++++++++++++ 8 files changed, 127 insertions(+), 12 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index fbbd8d82b7a..0b586403c21 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -377,6 +377,7 @@ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" [tool.ruff.lint.per-file-ignores] "superset/mcp_service/app.py" = ["S608", "E501"] # LLM instruction text: SQL examples (S608) and long lines in multiline string (E501) +"superset/mcp_service/*/tool/list_*.py" = ["E501"] # LLM docstring examples show full request shapes which exceed line length "scripts/*" = ["TID251"] "setup.py" = ["TID251"] "superset/config.py" = ["TID251"] diff --git a/superset/mcp_service/chart/schemas.py b/superset/mcp_service/chart/schemas.py index 1fdb4f43ab6..d9f1c6b97e3 100644 --- a/superset/mcp_service/chart/schemas.py +++ b/superset/mcp_service/chart/schemas.py @@ -537,8 +537,11 @@ class ChartFilter(ColumnOperator): "datasource_name", ] = Field( ..., - description="Column to filter on. Use get_schema(model_type='chart') for " - "available filter columns.", + description=( + "Column to filter on. Valid values: 'slice_name', 'viz_type', " + "'datasource_name'. Other column names are not valid filter columns " + "and will cause a validation error." + ), ) opr: ColumnOperatorEnum = Field( ..., diff --git a/superset/mcp_service/chart/tool/list_charts.py b/superset/mcp_service/chart/tool/list_charts.py index 2d53afc8d30..66c6909f33f 100644 --- a/superset/mcp_service/chart/tool/list_charts.py +++ b/superset/mcp_service/chart/tool/list_charts.py @@ -91,8 +91,24 @@ async def list_charts( Returns chart metadata including id, name, viz_type, URL, and last modified time. - Sortable columns for order_column: id, slice_name, viz_type, description, - changed_on, created_on + **IMPORTANT**: All parameters must be wrapped in a ``request`` object. + Do NOT pass ``search``, ``page``, ``page_size``, etc. as top-level + keyword arguments — they will be rejected. Use the ``request`` wrapper:: + + # Correct usage + list_charts(request={"search": "revenue", "page": 1, "page_size": 10}) + list_charts(request={"filters": [{"col": "slice_name", "opr": "ct", "value": "sales"}]}) + list_charts() # no arguments returns first page with defaults + + # Wrong — causes pydantic validation errors + list_charts(search="revenue", page=1) # DO NOT DO THIS + + Valid filter columns for ``filters[].col``: + ``slice_name``, ``viz_type``, ``datasource_name`` + + Sortable columns for ``order_column``: + ``id``, ``slice_name``, ``viz_type``, ``description``, + ``changed_on``, ``created_on`` """ request = request or _DEFAULT_LIST_CHARTS_REQUEST.model_copy(deep=True) await ctx.info( diff --git a/superset/mcp_service/dashboard/schemas.py b/superset/mcp_service/dashboard/schemas.py index 8a92d585896..68a680863e5 100644 --- a/superset/mcp_service/dashboard/schemas.py +++ b/superset/mcp_service/dashboard/schemas.py @@ -176,8 +176,9 @@ class DashboardFilter(ColumnOperator): ] = Field( ..., description=( - "Column to filter on. Use " - "get_schema(model_type='dashboard') for available filter columns." + "Column to filter on. Valid values: 'dashboard_title', 'published', " + "'favorite'. Other column names are not valid filter columns and will " + "cause a validation error." ), ) opr: ColumnOperatorEnum = Field( diff --git a/superset/mcp_service/dashboard/tool/list_dashboards.py b/superset/mcp_service/dashboard/tool/list_dashboards.py index b1bc664c593..ed856dff47d 100644 --- a/superset/mcp_service/dashboard/tool/list_dashboards.py +++ b/superset/mcp_service/dashboard/tool/list_dashboards.py @@ -85,8 +85,24 @@ async def list_dashboards( including title, slug, URL, and last modified time. Use select_columns to request additional fields. - Sortable columns for order_column: id, dashboard_title, slug, published, - changed_on, created_on + **IMPORTANT**: All parameters must be wrapped in a ``request`` object. + Do NOT pass ``search``, ``page``, ``page_size``, etc. as top-level + keyword arguments — they will be rejected. Use the ``request`` wrapper:: + + # Correct usage + list_dashboards(request={"search": "sales", "page": 1, "page_size": 10}) + list_dashboards(request={"filters": [{"col": "dashboard_title", "opr": "ct", "value": "exec"}]}) + list_dashboards() # no arguments returns first page with defaults + + # Wrong — causes pydantic validation errors + list_dashboards(search="sales", page=1) # DO NOT DO THIS + + Valid filter columns for ``filters[].col``: + ``dashboard_title``, ``published``, ``favorite`` + + Sortable columns for ``order_column``: + ``id``, ``dashboard_title``, ``slug``, ``published``, + ``changed_on``, ``created_on`` """ request = request or _DEFAULT_LIST_DASHBOARDS_REQUEST.model_copy(deep=True) await ctx.info( diff --git a/superset/mcp_service/dataset/schemas.py b/superset/mcp_service/dataset/schemas.py index 9d706c84152..b5aa475ff2e 100644 --- a/superset/mcp_service/dataset/schemas.py +++ b/superset/mcp_service/dataset/schemas.py @@ -71,8 +71,11 @@ class DatasetFilter(ColumnOperator): "database_name", ] = Field( ..., - description="Column to filter on. Use get_schema(model_type='dataset') for " - "available filter columns.", + description=( + "Column to filter on. Valid values: 'table_name', 'schema', " + "'database_name'. Other column names (e.g. 'created_by_fk', 'id') " + "are not valid filter columns and will cause a validation error." + ), ) opr: ColumnOperatorEnum = Field( ..., diff --git a/superset/mcp_service/dataset/tool/list_datasets.py b/superset/mcp_service/dataset/tool/list_datasets.py index 84b7a71d15f..9679f658316 100644 --- a/superset/mcp_service/dataset/tool/list_datasets.py +++ b/superset/mcp_service/dataset/tool/list_datasets.py @@ -96,8 +96,23 @@ async def list_datasets( Returns dataset metadata including table name, schema, and last modified time. - Sortable columns for order_column: id, table_name, schema, changed_on, - created_on + **IMPORTANT**: All parameters must be wrapped in a ``request`` object. + Do NOT pass ``search``, ``page``, ``page_size``, etc. as top-level + keyword arguments — they will be rejected. Use the ``request`` wrapper:: + + # Correct usage + list_datasets(request={"search": "sales", "page": 1, "page_size": 10}) + list_datasets(request={"filters": [{"col": "table_name", "opr": "ct", "value": "orders"}]}) + list_datasets() # no arguments returns first page with defaults + + # Wrong — causes pydantic validation errors + list_datasets(search="sales", page=1) # DO NOT DO THIS + + Valid filter columns for ``filters[].col``: + ``table_name``, ``schema``, ``database_name`` + + Sortable columns for ``order_column``: + ``id``, ``table_name``, ``schema``, ``changed_on``, ``created_on`` """ if ctx is None: raise RuntimeError("FastMCP context is required for list_datasets") diff --git a/tests/unit_tests/mcp_service/dataset/tool/test_dataset_tools.py b/tests/unit_tests/mcp_service/dataset/tool/test_dataset_tools.py index bfe8410dde9..aa93389921f 100644 --- a/tests/unit_tests/mcp_service/dataset/tool/test_dataset_tools.py +++ b/tests/unit_tests/mcp_service/dataset/tool/test_dataset_tools.py @@ -2080,3 +2080,63 @@ def test_owned_by_me_and_created_by_me_allowed(self): request = ListDatasetsRequest(owned_by_me=True, created_by_me=True) assert request.owned_by_me is True assert request.created_by_me is True + + +class TestListDatasetsRequestWrapper: + """ + Tests verifying that list_datasets requires a ``request`` wrapper object. + + LLMs sometimes pass parameters like ``search``, ``page``, or ``page_size`` + as flat top-level kwargs instead of nesting them inside a ``request`` + object. These tests confirm the correct call shape and that the schema + rejects invalid filter column names (e.g. ``created_by_fk``). + """ + + def test_request_wrapper_with_search(self): + """Parameters passed inside request= are accepted.""" + request = ListDatasetsRequest(search="sales", page=1, page_size=10) + assert request.search == "sales" + assert request.page == 1 + assert request.page_size == 10 + + def test_request_wrapper_defaults(self): + """No-arg constructor produces valid defaults.""" + request = ListDatasetsRequest() + assert request.search is None + assert request.page == 1 + assert request.filters == [] + + def test_dataset_filter_valid_col(self): + """Valid col values are accepted by DatasetFilter.""" + for col in ("table_name", "schema", "database_name"): + f = DatasetFilter(col=col, opr="ct", value="test") + assert f.col == col + + def test_dataset_filter_invalid_col_raises(self): + """Column names not in the Literal are rejected with a validation error. + + This guards against LLMs passing ``created_by_fk`` or similar + internal column names that are not exposed as filter fields. + """ + from pydantic import ValidationError + + for bad_col in ("created_by_fk", "id", "database_id", "owner"): + with pytest.raises(ValidationError): + DatasetFilter(col=bad_col, opr="eq", value="1") + + @pytest.mark.asyncio + async def test_flat_kwargs_rejected(self, mcp_server): + """Passing search/page/page_size as top-level kwargs raises a ToolError. + + This is the exact failure pattern reported in story #105712: LLMs call + ``list_datasets(search=..., page=..., page_size=...)`` instead of + ``list_datasets(request={...})``. + """ + from fastmcp.exceptions import ToolError + + with pytest.raises(ToolError): + async with Client(mcp_server) as client: + await client.call_tool( + "list_datasets", + {"search": "sales", "page": 1, "page_size": 10}, + ) From d7205c204d19b632f31ef358a4606384874fc89f Mon Sep 17 00:00:00 2001 From: Amin Ghadersohi Date: Thu, 7 May 2026 03:13:14 +0000 Subject: [PATCH 2/5] test(mcp): fix and strengthen TestListDatasetsRequestWrapper tests - Fix test_sortable_columns_in_docstring: assertion used plain-text match but docstring now uses RST backtick format; use substring check for both 'Sortable columns for' and 'order_column' separately - Fix test_dataset_filter_valid_col: opr='ct' is not a valid ColumnOperatorEnum value; use opr='sw' (starts-with) instead - Add test_request_wrapper_enforced_by_tool: exercises the wrapper pattern through the actual FastMCP client call (not just schema instantiation), verifying the MCP tool layer accepts request={...} correctly - Strengthen test_flat_kwargs_rejected: assert that the ToolError message references the unexpected arguments ('search', 'Unexpected', or 'request') so the test cannot pass on unrelated failures --- .../dataset/tool/test_dataset_tools.py | 46 +++++++++++++++---- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/tests/unit_tests/mcp_service/dataset/tool/test_dataset_tools.py b/tests/unit_tests/mcp_service/dataset/tool/test_dataset_tools.py index aa93389921f..63e2044d100 100644 --- a/tests/unit_tests/mcp_service/dataset/tool/test_dataset_tools.py +++ b/tests/unit_tests/mcp_service/dataset/tool/test_dataset_tools.py @@ -1700,7 +1700,8 @@ def test_sortable_columns_in_docstring(self): # Check list_datasets docstring for sortable columns documentation assert list_datasets.__doc__ is not None - assert "Sortable columns for order_column:" in list_datasets.__doc__ + assert "Sortable columns for" in list_datasets.__doc__ + assert "order_column" in list_datasets.__doc__ for col in SORTABLE_DATASET_COLUMNS: assert col in list_datasets.__doc__ @@ -2088,19 +2089,20 @@ class TestListDatasetsRequestWrapper: LLMs sometimes pass parameters like ``search``, ``page``, or ``page_size`` as flat top-level kwargs instead of nesting them inside a ``request`` - object. These tests confirm the correct call shape and that the schema - rejects invalid filter column names (e.g. ``created_by_fk``). + object. These tests confirm the correct call shape through both the Pydantic + schema and the actual MCP tool layer, and verify that invalid filter column + names (e.g. ``created_by_fk``) are rejected. """ def test_request_wrapper_with_search(self): - """Parameters passed inside request= are accepted.""" + """Parameters passed inside request= are accepted by the schema.""" request = ListDatasetsRequest(search="sales", page=1, page_size=10) assert request.search == "sales" assert request.page == 1 assert request.page_size == 10 def test_request_wrapper_defaults(self): - """No-arg constructor produces valid defaults.""" + """No-arg constructor produces valid schema defaults.""" request = ListDatasetsRequest() assert request.search is None assert request.page == 1 @@ -2109,7 +2111,7 @@ def test_request_wrapper_defaults(self): def test_dataset_filter_valid_col(self): """Valid col values are accepted by DatasetFilter.""" for col in ("table_name", "schema", "database_name"): - f = DatasetFilter(col=col, opr="ct", value="test") + f = DatasetFilter(col=col, opr="sw", value="test") assert f.col == col def test_dataset_filter_invalid_col_raises(self): @@ -2124,19 +2126,45 @@ def test_dataset_filter_invalid_col_raises(self): with pytest.raises(ValidationError): DatasetFilter(col=bad_col, opr="eq", value="1") + @patch("superset.daos.dataset.DatasetDAO.list") + @pytest.mark.asyncio + async def test_request_wrapper_enforced_by_tool(self, mock_list, mcp_server): + """The MCP tool layer accepts the request wrapper and returns results. + + Verifies end-to-end that wrapping params in ``request={}`` works through + the actual FastMCP tool call, not just schema validation. + """ + mock_list.return_value = ([], 0) + async with Client(mcp_server) as client: + result = await client.call_tool( + "list_datasets", + {"request": {"search": "sales", "page": 1, "page_size": 5}}, + ) + data = json.loads(result.content[0].text) + assert data["count"] == 0 + assert data["datasets"] == [] + @pytest.mark.asyncio async def test_flat_kwargs_rejected(self, mcp_server): - """Passing search/page/page_size as top-level kwargs raises a ToolError. + """Passing search/page/page_size as top-level kwargs raises a ToolError + that specifically mentions the unexpected arguments. - This is the exact failure pattern reported in story #105712: LLMs call + This is the exact failure pattern from story #105712: LLMs call ``list_datasets(search=..., page=..., page_size=...)`` instead of ``list_datasets(request={...})``. """ from fastmcp.exceptions import ToolError - with pytest.raises(ToolError): + with pytest.raises(ToolError) as exc_info: async with Client(mcp_server) as client: await client.call_tool( "list_datasets", {"search": "sales", "page": 1, "page_size": 10}, ) + error_text = str(exc_info.value) + # The error must call out the unexpected arguments, not some unrelated failure + assert ( + "search" in error_text + or "Unexpected" in error_text + or "request" in error_text + ) From c57de66407d3aa7e243dddf18ed62921b0378107 Mon Sep 17 00:00:00 2001 From: Amin Ghadersohi Date: Thu, 7 May 2026 03:46:14 +0000 Subject: [PATCH 3/5] fix(mcp): use valid opr value in docstring filter examples; fix dashboard docstring test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace opr:"ct" with opr:"sw" in filter examples in list_datasets, list_charts, and list_dashboards docstrings — "ct" is not a valid ColumnOperatorEnum value, so examples using it produce validation errors - Fix TestDashboardSortableColumns.test_sortable_columns_in_docstring: assertion checked for "Sortable columns for order_column:" (plain text) but docstring uses RST backtick format; split into two substring checks matching "Sortable columns for" and "order_column" separately --- superset/mcp_service/chart/tool/list_charts.py | 2 +- superset/mcp_service/dashboard/tool/list_dashboards.py | 2 +- superset/mcp_service/dataset/tool/list_datasets.py | 2 +- .../mcp_service/dashboard/tool/test_dashboard_tools.py | 3 ++- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/superset/mcp_service/chart/tool/list_charts.py b/superset/mcp_service/chart/tool/list_charts.py index 66c6909f33f..bfb2c564540 100644 --- a/superset/mcp_service/chart/tool/list_charts.py +++ b/superset/mcp_service/chart/tool/list_charts.py @@ -97,7 +97,7 @@ async def list_charts( # Correct usage list_charts(request={"search": "revenue", "page": 1, "page_size": 10}) - list_charts(request={"filters": [{"col": "slice_name", "opr": "ct", "value": "sales"}]}) + list_charts(request={"filters": [{"col": "slice_name", "opr": "sw", "value": "sales"}]}) list_charts() # no arguments returns first page with defaults # Wrong — causes pydantic validation errors diff --git a/superset/mcp_service/dashboard/tool/list_dashboards.py b/superset/mcp_service/dashboard/tool/list_dashboards.py index ed856dff47d..8c479df2753 100644 --- a/superset/mcp_service/dashboard/tool/list_dashboards.py +++ b/superset/mcp_service/dashboard/tool/list_dashboards.py @@ -91,7 +91,7 @@ async def list_dashboards( # Correct usage list_dashboards(request={"search": "sales", "page": 1, "page_size": 10}) - list_dashboards(request={"filters": [{"col": "dashboard_title", "opr": "ct", "value": "exec"}]}) + list_dashboards(request={"filters": [{"col": "dashboard_title", "opr": "sw", "value": "exec"}]}) list_dashboards() # no arguments returns first page with defaults # Wrong — causes pydantic validation errors diff --git a/superset/mcp_service/dataset/tool/list_datasets.py b/superset/mcp_service/dataset/tool/list_datasets.py index 9679f658316..099c15231ea 100644 --- a/superset/mcp_service/dataset/tool/list_datasets.py +++ b/superset/mcp_service/dataset/tool/list_datasets.py @@ -102,7 +102,7 @@ async def list_datasets( # Correct usage list_datasets(request={"search": "sales", "page": 1, "page_size": 10}) - list_datasets(request={"filters": [{"col": "table_name", "opr": "ct", "value": "orders"}]}) + list_datasets(request={"filters": [{"col": "table_name", "opr": "sw", "value": "orders"}]}) list_datasets() # no arguments returns first page with defaults # Wrong — causes pydantic validation errors diff --git a/tests/unit_tests/mcp_service/dashboard/tool/test_dashboard_tools.py b/tests/unit_tests/mcp_service/dashboard/tool/test_dashboard_tools.py index 64d31eb76e1..a2b4e1e5f98 100644 --- a/tests/unit_tests/mcp_service/dashboard/tool/test_dashboard_tools.py +++ b/tests/unit_tests/mcp_service/dashboard/tool/test_dashboard_tools.py @@ -1349,7 +1349,8 @@ def test_sortable_columns_in_docstring(self): # Check list_dashboards docstring for sortable columns documentation assert list_dashboards.__doc__ is not None - assert "Sortable columns for order_column:" in list_dashboards.__doc__ + assert "Sortable columns for" in list_dashboards.__doc__ + assert "order_column" in list_dashboards.__doc__ for col in SORTABLE_DASHBOARD_COLUMNS: assert col in list_dashboards.__doc__ From c5b4fd79788885eb83e4692b91c4e443a6bbb498 Mon Sep 17 00:00:00 2001 From: Amin Ghadersohi Date: Thu, 7 May 2026 15:53:38 +0000 Subject: [PATCH 4/5] test(mcp): add -> None return types and remove redundant ToolError import All 6 new test methods in TestListDatasetsRequestWrapper were missing the required -> None return type annotation (Rule 12). Also remove the inline `from fastmcp.exceptions import ToolError` in test_flat_kwargs_rejected since ToolError is already imported at module top-level (line 27). --- .../mcp_service/dataset/tool/test_dataset_tools.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/tests/unit_tests/mcp_service/dataset/tool/test_dataset_tools.py b/tests/unit_tests/mcp_service/dataset/tool/test_dataset_tools.py index 63e2044d100..66a7f3fba80 100644 --- a/tests/unit_tests/mcp_service/dataset/tool/test_dataset_tools.py +++ b/tests/unit_tests/mcp_service/dataset/tool/test_dataset_tools.py @@ -2094,27 +2094,27 @@ class TestListDatasetsRequestWrapper: names (e.g. ``created_by_fk``) are rejected. """ - def test_request_wrapper_with_search(self): + def test_request_wrapper_with_search(self) -> None: """Parameters passed inside request= are accepted by the schema.""" request = ListDatasetsRequest(search="sales", page=1, page_size=10) assert request.search == "sales" assert request.page == 1 assert request.page_size == 10 - def test_request_wrapper_defaults(self): + def test_request_wrapper_defaults(self) -> None: """No-arg constructor produces valid schema defaults.""" request = ListDatasetsRequest() assert request.search is None assert request.page == 1 assert request.filters == [] - def test_dataset_filter_valid_col(self): + def test_dataset_filter_valid_col(self) -> None: """Valid col values are accepted by DatasetFilter.""" for col in ("table_name", "schema", "database_name"): f = DatasetFilter(col=col, opr="sw", value="test") assert f.col == col - def test_dataset_filter_invalid_col_raises(self): + def test_dataset_filter_invalid_col_raises(self) -> None: """Column names not in the Literal are rejected with a validation error. This guards against LLMs passing ``created_by_fk`` or similar @@ -2128,7 +2128,7 @@ def test_dataset_filter_invalid_col_raises(self): @patch("superset.daos.dataset.DatasetDAO.list") @pytest.mark.asyncio - async def test_request_wrapper_enforced_by_tool(self, mock_list, mcp_server): + async def test_request_wrapper_enforced_by_tool(self, mock_list, mcp_server) -> None: """The MCP tool layer accepts the request wrapper and returns results. Verifies end-to-end that wrapping params in ``request={}`` works through @@ -2145,7 +2145,7 @@ async def test_request_wrapper_enforced_by_tool(self, mock_list, mcp_server): assert data["datasets"] == [] @pytest.mark.asyncio - async def test_flat_kwargs_rejected(self, mcp_server): + async def test_flat_kwargs_rejected(self, mcp_server) -> None: """Passing search/page/page_size as top-level kwargs raises a ToolError that specifically mentions the unexpected arguments. @@ -2153,8 +2153,6 @@ async def test_flat_kwargs_rejected(self, mcp_server): ``list_datasets(search=..., page=..., page_size=...)`` instead of ``list_datasets(request={...})``. """ - from fastmcp.exceptions import ToolError - with pytest.raises(ToolError) as exc_info: async with Client(mcp_server) as client: await client.call_tool( From 0ea59cbd54f09ce7dc774b74f27592aaebb393bf Mon Sep 17 00:00:00 2001 From: Amin Ghadersohi Date: Thu, 7 May 2026 16:50:23 +0000 Subject: [PATCH 5/5] test(mcp): apply ruff format to test_dataset_tools --- .../unit_tests/mcp_service/dataset/tool/test_dataset_tools.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/unit_tests/mcp_service/dataset/tool/test_dataset_tools.py b/tests/unit_tests/mcp_service/dataset/tool/test_dataset_tools.py index 66a7f3fba80..25a8a4c0fc6 100644 --- a/tests/unit_tests/mcp_service/dataset/tool/test_dataset_tools.py +++ b/tests/unit_tests/mcp_service/dataset/tool/test_dataset_tools.py @@ -2128,7 +2128,9 @@ def test_dataset_filter_invalid_col_raises(self) -> None: @patch("superset.daos.dataset.DatasetDAO.list") @pytest.mark.asyncio - async def test_request_wrapper_enforced_by_tool(self, mock_list, mcp_server) -> None: + async def test_request_wrapper_enforced_by_tool( + self, mock_list, mcp_server + ) -> None: """The MCP tool layer accepts the request wrapper and returns results. Verifies end-to-end that wrapping params in ``request={}`` works through