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/09/29 05:04:58 UTC
(superset) branch master updated: fix(mcp): hide internal sanitization warnings from input schemas (#44602)
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 19ea6b231c7 fix(mcp): hide internal sanitization warnings from input schemas (#44602)
19ea6b231c7 is described below
commit 19ea6b231c7e4a70adb4b0ffe0ea18aec6dcf783
Author: Amin Ghadersohi <am...@gmail.com>
AuthorDate: Tue Sep 29 15:04:50 2026 +1000
fix(mcp): hide internal sanitization warnings from input schemas (#44602)
---
docs/developer_docs/extensions/mcp.md | 4 ++
superset/mcp_service/chart/schemas.py | 3 +-
superset/mcp_service/dashboard/schemas.py | 7 +-
.../dashboard/tool/test_duplicate_dashboard.py | 21 +++++-
.../test_sanitization_warning_inputs.py | 80 ++++++++++++++++++++++
5 files changed, 110 insertions(+), 5 deletions(-)
diff --git a/docs/developer_docs/extensions/mcp.md b/docs/developer_docs/extensions/mcp.md
index 6d54f1ec0ca..84c02557eb0 100644
--- a/docs/developer_docs/extensions/mcp.md
+++ b/docs/developer_docs/extensions/mcp.md
@@ -44,6 +44,10 @@ Tools are Python functions that AI agents can call to perform specific tasks. Th
- Specialized report generation
- Business-specific operations
+Sanitization notices are returned in tool response `warnings`. The internal
+`sanitization_warnings` request attribute is not advertised in input schemas;
+caller-supplied values are discarded rather than echoed in responses.
+
### MCP Prompts
Prompts provide interactive guidance and context to AI agents. They help agents understand how to better assist users with specific workflows or domain knowledge.
diff --git a/superset/mcp_service/chart/schemas.py b/superset/mcp_service/chart/schemas.py
index ada93209cab..b3b10a53e42 100644
--- a/superset/mcp_service/chart/schemas.py
+++ b/superset/mcp_service/chart/schemas.py
@@ -41,6 +41,7 @@ from pydantic import (
StrictBool,
ValidationError,
)
+from pydantic.json_schema import SkipJsonSchema
from typing_extensions import Self, TypedDict
from superset.constants import NO_TIME_RANGE, TimeGrain
@@ -3840,7 +3841,7 @@ class GenerateChartRequest(ChartRequestNormalizerMixin, QueryCacheControl):
preview_formats: List[Literal["url", "ascii", "vega_lite", "table"]] = Field(
default_factory=lambda: ["url"],
)
- sanitization_warnings: List[str] = Field(
+ sanitization_warnings: SkipJsonSchema[List[str]] = Field(
default_factory=list,
description=(
"Internal: warnings emitted when user input was altered by "
diff --git a/superset/mcp_service/dashboard/schemas.py b/superset/mcp_service/dashboard/schemas.py
index 8b7dfee325b..54cab9a0914 100644
--- a/superset/mcp_service/dashboard/schemas.py
+++ b/superset/mcp_service/dashboard/schemas.py
@@ -80,6 +80,7 @@ from pydantic import (
model_serializer,
model_validator,
)
+from pydantic.json_schema import SkipJsonSchema
if TYPE_CHECKING:
from superset.connectors.sqla.models import SqlaTable
@@ -722,7 +723,7 @@ class GenerateDashboardRequest(BaseModel):
"dashboard's css field."
),
)
- sanitization_warnings: List[str] = Field(
+ sanitization_warnings: SkipJsonSchema[List[str]] = Field(
default_factory=list,
description=(
"Internal: warnings emitted when user input was altered by "
@@ -888,7 +889,7 @@ class UpdateDashboardRequest(BaseModel):
"the ``filter_bar_orientation`` json_metadata key."
),
)
- sanitization_warnings: List[str] = Field(
+ sanitization_warnings: SkipJsonSchema[List[str]] = Field(
default_factory=list,
description=(
"Internal: warnings emitted when user input was altered by "
@@ -1383,7 +1384,7 @@ class DuplicateDashboardRequest(BaseModel):
"source."
),
)
- sanitization_warnings: List[str] = Field(
+ sanitization_warnings: SkipJsonSchema[List[str]] = Field(
default_factory=list,
description=(
"Internal: warnings emitted when user input was altered by "
diff --git a/tests/unit_tests/mcp_service/dashboard/tool/test_duplicate_dashboard.py b/tests/unit_tests/mcp_service/dashboard/tool/test_duplicate_dashboard.py
index 5fffbc45f34..a1db92d3678 100644
--- a/tests/unit_tests/mcp_service/dashboard/tool/test_duplicate_dashboard.py
+++ b/tests/unit_tests/mcp_service/dashboard/tool/test_duplicate_dashboard.py
@@ -448,6 +448,7 @@ async def test_title_xss_is_sanitized(
"request": {
"dashboard_id": 1,
"dashboard_title": "<script>alert('x')</script>Regional Copy",
+ "sanitization_warnings": ["caller-controlled warning"],
}
},
)
@@ -457,7 +458,9 @@ async def test_title_xss_is_sanitized(
# The sanitized title — not the raw payload — is sent to the command.
_, cmd_data = mock_copy_cmd_cls.call_args.args
assert cmd_data["dashboard_title"] == "Regional Copy"
- assert content["warnings"], "expected a sanitization warning"
+ assert len(content["warnings"]) == 1
+ assert "dashboard_title" in content["warnings"][0]
+ assert "caller-controlled" not in content["warnings"][0]
@patch("superset.daos.dashboard.DashboardDAO.find_by_id")
@@ -692,3 +695,19 @@ def test_empty_title_rejected_by_schema() -> None:
with pytest.raises(ValidationError):
DuplicateDashboardRequest(dashboard_id=1, dashboard_title="")
+
+
+@pytest.mark.asyncio
+async def test_tool_input_schemas_omit_sanitization_warnings(
+ mcp_server: object,
+) -> None:
+ """Tool discovery must not advertise server-generated warning fields."""
+ async with Client(mcp_server) as client:
+ tools = {tool.name: tool for tool in await client.list_tools()}
+ for name in (
+ "generate_chart",
+ "generate_dashboard",
+ "update_dashboard",
+ "duplicate_dashboard",
+ ):
+ assert "sanitization_warnings" not in str(tools[name].inputSchema)
diff --git a/tests/unit_tests/mcp_service/test_sanitization_warning_inputs.py b/tests/unit_tests/mcp_service/test_sanitization_warning_inputs.py
new file mode 100644
index 00000000000..b7aa44cbc7a
--- /dev/null
+++ b/tests/unit_tests/mcp_service/test_sanitization_warning_inputs.py
@@ -0,0 +1,80 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+"""Regression tests for server-only MCP sanitization warnings."""
+
+from typing import Any
+
+import pytest
+from pydantic import TypeAdapter
+
+from superset.mcp_service.chart.schemas import GenerateChartRequest
+from superset.mcp_service.dashboard.schemas import (
+ DuplicateDashboardRequest,
+ GenerateDashboardRequest,
+ UpdateDashboardRequest,
+)
+from superset.utils import json
+
+
+@pytest.mark.parametrize(
+ ("model", "payload", "title_field"),
+ [
+ (GenerateDashboardRequest, {"chart_ids": [1]}, "dashboard_title"),
+ (UpdateDashboardRequest, {"identifier": 1}, "dashboard_title"),
+ (DuplicateDashboardRequest, {"dashboard_id": 1}, "dashboard_title"),
+ (
+ GenerateChartRequest,
+ {
+ "dataset_id": 1,
+ "config": {"chart_type": "table", "columns": [{"name": "a"}]},
+ },
+ "chart_name",
+ ),
+ ],
+)
+@pytest.mark.parametrize("title", ["Safe title", "Safe <b>title</b>"])
+def test_sanitization_warnings_are_server_only(
+ model: type[
+ GenerateChartRequest
+ | GenerateDashboardRequest
+ | UpdateDashboardRequest
+ | DuplicateDashboardRequest
+ ],
+ payload: dict[str, Any],
+ title_field: str,
+ title: str,
+) -> None:
+ """Hide internal warnings in schemas without losing sanitization notices."""
+ assert "sanitization_warnings" not in json.dumps(model.model_json_schema())
+ # Also cover the type-adapter schema used by tool integrations.
+ assert "sanitization_warnings" not in json.dumps(TypeAdapter(model).json_schema())
+
+ payload = {
+ **payload,
+ title_field: title,
+ "sanitization_warnings": ["caller-controlled warning"],
+ }
+ request = model.model_validate_json(json.dumps(payload))
+ warnings = request.sanitization_warnings
+ assert getattr(request, title_field) == "Safe title"
+ if "<b>" in title:
+ assert len(warnings) == 1
+ assert title_field in warnings[0]
+ assert "caller-controlled" not in warnings[0]
+ else:
+ assert warnings == []