You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by ru...@apache.org on 2026/10/20 04:05:26 UTC

(superset) 01/01: fix(mcp): clean up nits from PNG chart preview review (#44558)

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

rusackas pushed a commit to branch fix/mcp-png-preview-nits
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 18eb97edb55535f343eb8f395b4a82ad7841734a
Author: Evan Rusackas <ev...@rusackas.com>
AuthorDate: Mon Oct 19 21:04:57 2026 -0700

    fix(mcp): clean up nits from PNG chart preview review (#44558)
    
    Small follow-ups from #44558's review that weren't worth holding the
    merge for: reuse the existing PNG_MAGIC_BYTES constant instead of a
    second hardcoded magic-byte check, avoid constructing
    PreviewFormatGenerator on the png branch where it's never used, log
    mcp_response_size_exceeded on PNG rejections the same way the text
    path does, and name the PNG-guarded tool set as a constant instead of
    a bare string literal.
    
    Co-Authored-By: Evan Rusackas <ev...@preset.io>
    Co-Authored-By: Claude Sonnet 5 <no...@anthropic.com>
---
 .../mcp_service/chart/tool/get_chart_preview.py    | 10 +++++----
 superset/mcp_service/middleware.py                 | 25 +++++++++++++++++++++-
 superset/mcp_service/utils/response_size_utils.py  |  4 ++++
 3 files changed, 34 insertions(+), 5 deletions(-)

diff --git a/superset/mcp_service/chart/tool/get_chart_preview.py b/superset/mcp_service/chart/tool/get_chart_preview.py
index 9f9aed4fd0c..300928deba2 100644
--- a/superset/mcp_service/chart/tool/get_chart_preview.py
+++ b/superset/mcp_service/chart/tool/get_chart_preview.py
@@ -1257,7 +1257,10 @@ async def _generate_png_preview(
         from superset import is_feature_enabled
         from superset.mcp_service.auth import _mcp_tool_call_context
         from superset.utils.core import override_user
-        from superset.utils.screenshots import validate_screenshot_image
+        from superset.utils.screenshots import (
+            PNG_MAGIC_BYTES,
+            validate_screenshot_image,
+        )
         from superset.utils.urls import get_url_path
         from superset.utils.webdriver import (
             _PlaywrightBrowserManager,
@@ -1297,7 +1300,7 @@ async def _generate_png_preview(
                     image = driver.get_screenshot(url, "chart-container", user=user)
                     if (
                         image is None
-                        or not image.startswith(b"\x89PNG\r\n\x1a\n")
+                        or not image.startswith(PNG_MAGIC_BYTES)
                         or validate_screenshot_image(image)
                     ):
                         return ChartError(
@@ -1604,11 +1607,10 @@ async def _get_chart_preview_internal(  # noqa: C901
         with event_logger.log_context(
             action="mcp.get_chart_preview.preview_generation"
         ):
-            preview_generator = PreviewFormatGenerator(chart, request)
             content = (
                 await _generate_png_preview(chart.id, request)
                 if request.format == "png"
-                else preview_generator.generate()
+                else PreviewFormatGenerator(chart, request).generate()
             )
 
         if isinstance(content, ChartError):
diff --git a/superset/mcp_service/middleware.py b/superset/mcp_service/middleware.py
index fca8966d66b..199cb693f95 100644
--- a/superset/mcp_service/middleware.py
+++ b/superset/mcp_service/middleware.py
@@ -71,6 +71,7 @@ from superset.mcp_service.utils.response_size_utils import (
     format_size_limit_error,
     get_response_size_bytes,
     INFO_TOOLS,
+    PNG_PREVIEW_TOOLS,
     RESPONSE_AWARE_SUGGESTION_TOOLS,
     string_clip_chars,
     STRING_FIELD_TRUNCATION_TOOLS,
@@ -2220,11 +2221,33 @@ class ResponseSizeGuardMiddleware(Middleware):
             else None
         )
         if (
-            tool_name == "get_chart_preview"
+            tool_name in PNG_PREVIEW_TOOLS
             and isinstance(content, dict)
             and content.get("type") == "png"
         ):
             if actual_bytes > self.png_max_bytes:
+                logger.warning(
+                    "Response blocked for %s: %d bytes exceeds png_max_bytes of %d",
+                    tool_name,
+                    actual_bytes,
+                    self.png_max_bytes,
+                )
+                try:
+                    event_logger.log(
+                        user_id=get_user_id(),
+                        action="mcp_response_size_exceeded",
+                        dashboard_id=None,
+                        duration_ms=None,
+                        slice_id=None,
+                        referrer=None,
+                        curated_payload={
+                            "tool": tool_name,
+                            "actual_bytes": actual_bytes,
+                            "max_bytes": self.png_max_bytes,
+                        },
+                    )
+                except Exception as log_error:  # noqa: BLE001
+                    logger.warning("Failed to log size exceeded event: %s", log_error)
                 raise ToolError(
                     f"PNG preview is {actual_bytes} bytes, exceeding the "
                     f"{self.png_max_bytes}-byte limit. Request smaller width and "
diff --git a/superset/mcp_service/utils/response_size_utils.py b/superset/mcp_service/utils/response_size_utils.py
index d217fd65a74..38b9614fc1c 100644
--- a/superset/mcp_service/utils/response_size_utils.py
+++ b/superset/mcp_service/utils/response_size_utils.py
@@ -295,6 +295,10 @@ def _identify_large_fields(response: ToolResponse) -> List[str]:
 # the size guard passes the parsed payload to format_size_limit_error.
 RESPONSE_AWARE_SUGGESTION_TOOLS = frozenset({"get_dashboard_layout"})
 
+# Tools whose PNG-formatted responses are checked against png_max_bytes
+# instead of the normal text-response budget.
+PNG_PREVIEW_TOOLS = frozenset({"get_chart_preview"})
+
 _MAX_SUGGESTED_TAB_IDS = 5
 _MAX_SUGGESTED_TAB_ID_CHARS = 64