You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by jl...@apache.org on 2026/10/13 18:06:13 UTC

(superset) branch ultraset-mcp-chart-data-stale-query-context created (now c8c40182d91)

This is an automated email from the ASF dual-hosted git repository.

sadpandajoe pushed a change to branch ultraset-mcp-chart-data-stale-query-context
in repository https://gitbox.apache.org/repos/asf/superset.git


      at c8c40182d91 Merge remote-tracking branch 'origin/master' into ultraset-mcp-chart-data-stale-query-context

This branch includes the following new commits:

     new 6e0efe69002 fix(mcp): surface missing/stale query_context accuracy warnings in get_chart_data
     new c8c40182d91 Merge remote-tracking branch 'origin/master' into ultraset-mcp-chart-data-stale-query-context

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(superset) 02/02: Merge remote-tracking branch 'origin/master' into ultraset-mcp-chart-data-stale-query-context

Posted by jl...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

sadpandajoe pushed a commit to branch ultraset-mcp-chart-data-stale-query-context
in repository https://gitbox.apache.org/repos/asf/superset.git

commit c8c40182d91c371e3acb58f758ce4940e90a3c8a
Merge: 6e0efe69002 718572cf29a
Author: Joe Li <47...@users.noreply.github.com>
AuthorDate: Tue Oct 13 17:57:36 2026 +0000

    Merge remote-tracking branch 'origin/master' into ultraset-mcp-chart-data-stale-query-context

 docs/src/components/databases/DatabaseLogo.tsx     |  62 ++++++++++++++++
 docs/src/components/databases/DatabaseLogoWall.tsx |   9 ++-
 docs/src/components/databases/DatabasePage.tsx     |  32 +++++++--
 docs/src/components/databases/types.ts             |   2 +
 docs/src/data/databases.json                       |  53 +++++++++-----
 docs/src/pages/index.tsx                           |  11 ++-
 docs/static/img/databases/clickhouse-dark.svg      | Bin 0 -> 4480 bytes
 docs/static/img/databases/clickhouse-mark-dark.svg | Bin 0 -> 960 bytes
 docs/static/img/databases/clickhouse-mark.svg      | Bin 0 -> 963 bytes
 docs/static/img/databases/clickhouse.svg           | Bin 0 -> 4483 bytes
 pyproject.toml                                     |   2 +-
 superset/db_engine_specs/README.md                 |   1 +
 superset/db_engine_specs/base.py                   |   2 +
 superset/db_engine_specs/clickhouse.py             |  78 +++++++++++++++++----
 superset/db_engine_specs/postgres.py               |   3 +-
 15 files changed, 210 insertions(+), 45 deletions(-)


(superset) 01/02: fix(mcp): surface missing/stale query_context accuracy warnings in get_chart_data

Posted by jl...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

sadpandajoe pushed a commit to branch ultraset-mcp-chart-data-stale-query-context
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 6e0efe69002b20814926e90fd80fea4696f6d1bf
Author: Joe Li <47...@users.noreply.github.com>
AuthorDate: Tue Oct 13 17:57:30 2026 +0000

    fix(mcp): surface missing/stale query_context accuracy warnings in get_chart_data
    
    Chart has no saved query_context or the saved one no longer matches its
    current params, get_chart_data only called ctx.warning(), an MCP log
    notification that does not reach the tool's structured result. An LLM
    client had no way to know the returned numbers skip Explore's buildQuery
    logic (time grain, trend line, period comparison) or came from a stale
    saved query_context. Thread the same warning into the response's
    insights field so it travels with the data.
    
    Fixes #45237
    
    Co-Authored-By: Claude Sonnet 5 <no...@anthropic.com>
---
 superset/mcp_service/chart/tool/get_chart_data.py  |  24 +++-
 .../mcp_service/chart/tool/test_get_chart_data.py  | 148 +++++++++++++++++++++
 2 files changed, 171 insertions(+), 1 deletion(-)

diff --git a/superset/mcp_service/chart/tool/get_chart_data.py b/superset/mcp_service/chart/tool/get_chart_data.py
index 26d6d578082..af22b3082b3 100644
--- a/superset/mcp_service/chart/tool/get_chart_data.py
+++ b/superset/mcp_service/chart/tool/get_chart_data.py
@@ -699,6 +699,14 @@ async def _get_chart_data(  # noqa: C901
             # Use the chart's saved query_context - this is the key!
             # The query_context contains all the information needed to reproduce
             # the chart's data exactly as shown in the visualization
+            #
+            # Surfaced in the response's ``insights`` (not only via
+            # ctx.warning, which is an MCP log notification and does not
+            # reach the client's tool result) whenever the returned data may
+            # not match what Explore/the dashboard would show: no saved
+            # query_context to begin with, or one that no longer matches the
+            # chart's current configuration.
+            query_context_accuracy_warning: str | None = None
             query_context_json = None
             if using_unsaved_state and cached_form_data_dict is not None:
                 form_data = cached_form_data_dict
@@ -783,6 +791,17 @@ async def _get_chart_data(  # noqa: C901
                     await ctx.debug(
                         "Using chart's saved query_context for data retrieval"
                     )
+                    if (
+                        isinstance(query_form_data, dict)
+                        and query_form_data != form_data
+                    ):
+                        query_context_accuracy_warning = (
+                            "Chart's saved query_context is stale: it no longer "
+                            "matches the chart's current configuration. Data may "
+                            "not reflect the chart's latest settings. Re-open and "
+                            "re-save the chart in Explore to refresh it."
+                        )
+                        await ctx.warning(query_context_accuracy_warning)
                 except (TypeError, ValueError) as exc:
                     if not _is_expected_json_load_error(exc):
                         raise
@@ -791,11 +810,12 @@ async def _get_chart_data(  # noqa: C901
             if query_context_json is None and not using_unsaved_state:
                 # Fallback: Chart has no saved query_context
                 # This can happen with older charts that haven't been re-saved
-                await ctx.warning(
+                query_context_accuracy_warning = (
                     "Chart has no saved query_context. "
                     "Data may not match the chart visualization exactly. "
                     "Consider re-saving the chart to enable full data retrieval."
                 )
+                await ctx.warning(query_context_accuracy_warning)
                 # Try to construct from form_data as a fallback
                 effective_form_data = canonicalize_operation_form_data(
                     effective_form_data,
@@ -1049,6 +1069,8 @@ async def _get_chart_data(  # noqa: C901
 
             # Generate insights and recommendations
             insights = []
+            if query_context_accuracy_warning:
+                insights.append(query_context_accuracy_warning)
             if len(data) > 100:
                 insights.append(
                     "Large dataset - consider filtering for better performance"
diff --git a/tests/unit_tests/mcp_service/chart/tool/test_get_chart_data.py b/tests/unit_tests/mcp_service/chart/tool/test_get_chart_data.py
index 300f1df8abf..cd04e73b16b 100644
--- a/tests/unit_tests/mcp_service/chart/tool/test_get_chart_data.py
+++ b/tests/unit_tests/mcp_service/chart/tool/test_get_chart_data.py
@@ -5276,6 +5276,154 @@ class TestSavedDataFallbackSortDirection:
         assert captured["order_desc"] is False
 
 
+class TestQueryContextAccuracyWarning:
+    """When get_chart_data cannot use a fresh, saved query_context, the
+    structured response must say so. ``ctx.warning`` alone is an MCP log
+    notification, not part of the tool result, so an LLM client has no way
+    to know the returned numbers skip the frontend's buildQuery logic
+    (time grain, trend line, period comparison) or came from a query_context
+    that no longer matches the chart's current params (#45237).
+    """
+
+    @pytest.mark.asyncio
+    async def test_missing_query_context_fallback_is_visible_in_response(
+        self, mcp_server: Any, mock_auth: Any, monkeypatch: pytest.MonkeyPatch
+    ) -> None:
+        module = importlib.import_module(
+            "superset.mcp_service.chart.tool.get_chart_data"
+        )
+        chart = SimpleNamespace(
+            id=201,
+            slice_name="QoQ growth",
+            viz_type="big_number",
+            datasource_id=1,
+            datasource_type="table",
+            query_context=None,
+            params=json.dumps(
+                {
+                    "viz_type": "big_number",
+                    "metric": {"label": "SUM(revenue)"},
+                    "time_grain_sqla": "P3M",
+                    "compare_lag": 1,
+                    "show_trend_line": True,
+                }
+            ),
+        )
+
+        class Command:
+            def __init__(self, _context: Any) -> None: ...
+            def validate(self) -> None: ...
+            def run(self) -> dict[str, Any]:
+                return chart_data_command_result(
+                    [{"SUM(revenue)": 100}],
+                    columns=["SUM(revenue)"],
+                    coltypes=[GenericDataType.NUMERIC],
+                )
+
+        class _Factory:
+            def create(self, **_kwargs: Any) -> object:
+                return object()
+
+        monkeypatch.setattr(module, "find_chart_by_identifier", lambda *a, **k: chart)
+        monkeypatch.setattr(
+            module,
+            "validate_chart_dataset",
+            lambda *a, **k: SimpleNamespace(is_valid=True, warnings=[], error=None),
+        )
+        monkeypatch.setattr(
+            "superset.commands.chart.data.get_data_command.ChartDataCommand", Command
+        )
+        monkeypatch.setattr(
+            "superset.common.query_context_factory.QueryContextFactory", _Factory
+        )
+        monkeypatch.setattr(module, "set_query_context_form_data", lambda *_a: None)
+
+        async with Client(mcp_server) as client:
+            response = await client.call_tool(
+                "get_chart_data", {"request": {"identifier": 201}}
+            )
+
+        data = json.loads(response.content[0].text)
+        assert any(
+            "query_context" in insight.lower() for insight in data["insights"]
+        ), data["insights"]
+
+    @pytest.mark.asyncio
+    async def test_stale_saved_query_context_is_flagged_in_response(
+        self, mcp_server: Any, mock_auth: Any, monkeypatch: pytest.MonkeyPatch
+    ) -> None:
+        module = importlib.import_module(
+            "superset.mcp_service.chart.tool.get_chart_data"
+        )
+        chart = SimpleNamespace(
+            id=202,
+            slice_name="QoQ growth",
+            viz_type="big_number",
+            datasource_id=1,
+            datasource_type="table",
+            # The saved query_context's embedded form_data still reflects the
+            # chart's old configuration (a plain sum, no comparison), while
+            # chart.params has since been edited (e.g. via the REST API) to
+            # add a period comparison. The saved context no longer matches.
+            query_context=json.dumps(
+                {
+                    "datasource": {"id": 1, "type": "table"},
+                    "form_data": {
+                        "viz_type": "big_number",
+                        "metric": {"label": "SUM(revenue)"},
+                    },
+                    "queries": [{"metrics": [{"label": "SUM(revenue)"}]}],
+                }
+            ),
+            params=json.dumps(
+                {
+                    "viz_type": "big_number",
+                    "metric": {"label": "SUM(revenue)"},
+                    "time_grain_sqla": "P3M",
+                    "compare_lag": 1,
+                    "show_trend_line": True,
+                }
+            ),
+        )
+
+        class Command:
+            def __init__(self, _context: Any) -> None: ...
+            def validate(self) -> None: ...
+            def run(self) -> dict[str, Any]:
+                return chart_data_command_result(
+                    [{"SUM(revenue)": 100}],
+                    columns=["SUM(revenue)"],
+                    coltypes=[GenericDataType.NUMERIC],
+                )
+
+        monkeypatch.setattr(module, "find_chart_by_identifier", lambda *a, **k: chart)
+        monkeypatch.setattr(
+            module,
+            "validate_chart_dataset",
+            lambda *a, **k: SimpleNamespace(is_valid=True, warnings=[], error=None),
+        )
+        monkeypatch.setattr(
+            "superset.commands.chart.data.get_data_command.ChartDataCommand", Command
+        )
+        monkeypatch.setattr(
+            "superset.charts.schemas.ChartDataQueryContextSchema.load",
+            lambda self, payload: SimpleNamespace(
+                queries=payload.get("queries", []), form_data=payload.get("form_data")
+            ),
+        )
+        monkeypatch.setattr(module, "set_query_context_form_data", lambda *_a: None)
+
+        async with Client(mcp_server) as client:
+            response = await client.call_tool(
+                "get_chart_data", {"request": {"identifier": 202}}
+            )
+
+        data = json.loads(response.content[0].text)
+        assert any("stale" in insight.lower() for insight in data["insights"]), data[
+            "insights"
+        ]
+
+
 @pytest.mark.asyncio
 @pytest.mark.parametrize(
     "invalid_fields, message",