You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by mi...@apache.org on 2026/06/04 17:39:36 UTC
(superset) 03/03: fix(mcp): ensure filter_state in response when permalink_key is present
This is an automated email from the ASF dual-hosted git repository.
aminghadersohi pushed a commit to branch aminghadersohi/fix/mcp-info-tools-select-columns
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 769aa7e695ebc7de0da41b1406b6585b5dbe0b3a
Author: Amin Ghadersohi <am...@gmail.com>
AuthorDate: Thu Jun 4 17:39:06 2026 +0000
fix(mcp): ensure filter_state in response when permalink_key is present
When `permalink_key` is supplied without explicit `select_columns`,
guard that `filter_state` is always included in the serialization
context. Adds `test_get_dashboard_info_permalink_key_includes_filter_state`
to cover the fix and adds `TestGetDatasetInfoRequestValidators` for
`GetDatasetInfoRequest.column_fields` validator coverage (JSON string,
empty list, None fallback).
---
.../dashboard/tool/get_dashboard_info.py | 14 ++++-
.../dashboard/tool/test_dashboard_tools.py | 72 ++++++++++++++++++++++
.../mcp_service/dataset/tool/test_dataset_tools.py | 41 ++++++++++++
3 files changed, 126 insertions(+), 1 deletion(-)
diff --git a/superset/mcp_service/dashboard/tool/get_dashboard_info.py b/superset/mcp_service/dashboard/tool/get_dashboard_info.py
index d47e4caa9ac..f294477e507 100644
--- a/superset/mcp_service/dashboard/tool/get_dashboard_info.py
+++ b/superset/mcp_service/dashboard/tool/get_dashboard_info.py
@@ -39,6 +39,7 @@ from superset.mcp_service.dashboard.schemas import (
dashboard_serializer,
DashboardError,
DashboardInfo,
+ DEFAULT_GET_DASHBOARD_INFO_COLUMNS,
GetDashboardInfoRequest,
redact_filter_state_data_model_metadata,
)
@@ -248,9 +249,20 @@ async def get_dashboard_info(
result.is_permalink_state,
)
)
+ # When permalink_key is supplied and the caller did not explicitly
+ # override select_columns, ensure filter_state is present so the
+ # caller gets the data they came for.
+ effective_select_columns = list(request.select_columns)
+ if (
+ request.permalink_key
+ and effective_select_columns == list(DEFAULT_GET_DASHBOARD_INFO_COLUMNS)
+ and "filter_state" not in effective_select_columns
+ ):
+ effective_select_columns.append("filter_state")
+
return result.model_dump(
mode="json",
- context={"select_columns": request.select_columns},
+ context={"select_columns": effective_select_columns},
)
else:
await ctx.warning(
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 a2b4e1e5f98..3cabe42f471 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
@@ -489,6 +489,78 @@ async def test_get_dashboard_info_permalink_does_not_double_sanitize(
assert result.data["filter_state"]["activeTabs"][0] == _wrapped("TAB-1")
+@patch("superset.daos.dashboard.DashboardDAO.find_by_id")
+@pytest.mark.asyncio
+async def test_get_dashboard_info_permalink_key_includes_filter_state(
+ mock_info, mcp_server
+):
+ """When permalink_key is provided without explicit select_columns, filter_state
+ must be present in the response."""
+ dashboard = Mock()
+ dashboard.id = 42
+ dashboard.dashboard_title = "Sales Dashboard"
+ dashboard.slug = "sales"
+ dashboard.description = None
+ dashboard.css = None
+ dashboard.certified_by = None
+ dashboard.certification_details = None
+ dashboard.json_metadata = None
+ dashboard.published = True
+ dashboard.is_managed_externally = False
+ dashboard.external_url = None
+ dashboard.created_on = None
+ dashboard.changed_on = None
+ dashboard.created_by = None
+ dashboard.changed_by = None
+ dashboard.uuid = "dashboard-uuid-42"
+ dashboard.url = "/dashboard/42"
+ dashboard.thumbnail_url = None
+ dashboard.created_on_humanized = None
+ dashboard.changed_on_humanized = None
+ dashboard.slices = []
+ dashboard.owners = []
+ dashboard.tags = []
+ dashboard.roles = []
+ dashboard.charts = []
+ mock_info.return_value = dashboard
+
+ permalink_value = {
+ "dashboardId": "42",
+ "state": {
+ "dataMask": {},
+ "activeTabs": ["TAB-A"],
+ },
+ }
+
+ with (
+ patch(
+ "superset.mcp_service.dashboard.schemas.user_can_view_data_model_metadata",
+ return_value=True,
+ ),
+ patch.object(
+ get_dashboard_info_module,
+ "user_can_view_data_model_metadata",
+ return_value=True,
+ ),
+ patch.object(
+ get_dashboard_info_module,
+ "_get_permalink_state",
+ return_value=permalink_value,
+ ),
+ ):
+ async with Client(mcp_server) as client:
+ # No explicit select_columns — tool should use its default which
+ # includes filter_state when permalink_key is supplied.
+ result = await client.call_tool(
+ "get_dashboard_info",
+ {"request": {"identifier": 42, "permalink_key": "some-key"}},
+ )
+
+ assert "filter_state" in result.data
+ assert result.data["is_permalink_state"] is True
+ assert result.data["permalink_key"] == "some-key"
+
+
def test_refresh_request_user_for_permalink_access(
app,
):
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 adc6e73ad03..6b8f5d02087 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
@@ -1641,6 +1641,47 @@ class TestDatasetDefaultColumnFiltering:
)
+class TestGetDatasetInfoRequestValidators:
+ """Unit tests for GetDatasetInfoRequest field validators."""
+
+ def test_column_fields_json_string_parses_to_list(self):
+ """JSON string input for column_fields is decoded into a list."""
+ from superset.mcp_service.dataset.schemas import GetDatasetInfoRequest
+
+ request = GetDatasetInfoRequest(
+ identifier=1,
+ column_fields='["column_name","type"]',
+ )
+ assert request.column_fields == ["column_name", "type"]
+
+ def test_column_fields_empty_list_stays_empty(self):
+ """An explicit empty list for column_fields is preserved as-is."""
+ from superset.mcp_service.dataset.schemas import GetDatasetInfoRequest
+
+ request = GetDatasetInfoRequest(identifier=1, column_fields=[])
+ assert request.column_fields == []
+
+ def test_column_fields_none_falls_back_to_default(self):
+ """When column_fields is None (not provided), the default columns are used."""
+ from superset.mcp_service.dataset.schemas import (
+ DEFAULT_GET_DATASET_INFO_COLUMN_FIELDS,
+ GetDatasetInfoRequest,
+ )
+
+ request = GetDatasetInfoRequest(identifier=1, column_fields=None)
+ assert request.column_fields == list(DEFAULT_GET_DATASET_INFO_COLUMN_FIELDS)
+
+ def test_column_fields_default_when_omitted(self):
+ """When column_fields is omitted entirely, the default columns are used."""
+ from superset.mcp_service.dataset.schemas import (
+ DEFAULT_GET_DATASET_INFO_COLUMN_FIELDS,
+ GetDatasetInfoRequest,
+ )
+
+ request = GetDatasetInfoRequest(identifier=1)
+ assert request.column_fields == list(DEFAULT_GET_DATASET_INFO_COLUMN_FIELDS)
+
+
class TestDatasetSortableColumns:
"""Test sortable columns configuration for dataset tools."""