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

aminghadersohi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/master by this push:
     new 873566c8272 fix(mcp): stop masking dashboard lookup DB errors as 
not-found (#41919)
873566c8272 is described below

commit 873566c8272cd76e19fe2b5001e5beed597f5cd2
Author: Amin Ghadersohi <[email protected]>
AuthorDate: Mon Jul 13 13:32:35 2026 -0400

    fix(mcp): stop masking dashboard lookup DB errors as not-found (#41919)
---
 .../mcp_service/dashboard/tool/update_dashboard.py | 13 ++++++++++-
 .../dashboard/tool/test_update_dashboard.py        | 27 ++++++++++++++++++++++
 2 files changed, 39 insertions(+), 1 deletion(-)

diff --git a/superset/mcp_service/dashboard/tool/update_dashboard.py 
b/superset/mcp_service/dashboard/tool/update_dashboard.py
index d014de78694..0c423e763fc 100644
--- a/superset/mcp_service/dashboard/tool/update_dashboard.py
+++ b/superset/mcp_service/dashboard/tool/update_dashboard.py
@@ -73,11 +73,22 @@ def _find_and_authorize_dashboard(
 
     try:
         dashboard = DashboardDAO.get_by_id_or_slug(identifier)
-    except (DashboardNotFoundError, SQLAlchemyError):
+    except DashboardNotFoundError:
         return None, DashboardError(
             error=f"Dashboard not found: {identifier!r}",
             error_type="DashboardNotFound",
         )
+    except SQLAlchemyError:
+        # ``str(exc)`` on SQLAlchemyError frequently contains table/column/
+        # constraint names that should not leak to the MCP response. The raw
+        # exception is captured here via ``logger.exception``; the response
+        # surfaces a generic message (mirrors generate_dashboard.py's
+        # rollback/error handling).
+        logger.exception("Database error looking up dashboard %r", identifier)
+        return None, DashboardError(
+            error="Failed to look up dashboard due to a database error.",
+            error_type="DatabaseError",
+        )
 
     if dashboard is None:
         return None, DashboardError(
diff --git 
a/tests/unit_tests/mcp_service/dashboard/tool/test_update_dashboard.py 
b/tests/unit_tests/mcp_service/dashboard/tool/test_update_dashboard.py
index 83072f045bf..b53095948d1 100644
--- a/tests/unit_tests/mcp_service/dashboard/tool/test_update_dashboard.py
+++ b/tests/unit_tests/mcp_service/dashboard/tool/test_update_dashboard.py
@@ -183,6 +183,33 @@ class TestUpdateDashboard:
         payload = json.loads(result.content[0].text)
         assert "not found" in (payload.get("error") or "").lower()
 
+    @patch("superset.daos.dashboard.DashboardDAO.get_by_id_or_slug")
+    @pytest.mark.asyncio
+    async def test_update_lookup_database_error_is_not_masked_as_not_found(
+        self, mock_get: Mock, mcp_server: object
+    ) -> None:
+        """A real DB/infra failure during lookup must surface as a distinct
+        ``DatabaseError``, not be collapsed into ``DashboardNotFound`` — and
+        must be logged server-side, since ``ctx.*`` calls never reach ops."""
+        from sqlalchemy.exc import SQLAlchemyError
+
+        mock_get.side_effect = SQLAlchemyError("connection to server lost")
+
+        with patch(
+            "superset.mcp_service.dashboard.tool.update_dashboard.logger"
+        ) as mock_logger:
+            async with Client(mcp_server) as client:
+                result = await client.call_tool(
+                    "update_dashboard", {"request": {"identifier": 999999}}
+                )
+
+        payload = json.loads(result.content[0].text)
+        assert payload.get("error_type") == "DatabaseError"
+        assert "not found" not in (payload.get("error") or "").lower()
+        # The raw exception text must never reach the LLM-facing response.
+        assert "connection to server lost" not in (payload.get("error") or "")
+        mock_logger.exception.assert_called_once()
+
     @patch("superset.daos.dashboard.DashboardDAO.get_by_id_or_slug")
     @patch("superset.extensions.db.session")
     @pytest.mark.asyncio

Reply via email to