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/08/05 23:25:01 UTC
(superset) branch master updated: fix(mcp): reject unknown fields in nested chart-config models too (#42626) (#42732)
This is an automated email from the ASF dual-hosted git repository.
rusackas 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 1de35d13611 fix(mcp): reject unknown fields in nested chart-config models too (#42626) (#42732)
1de35d13611 is described below
commit 1de35d136117e171c92acaf6cf898ab8ab395a42
Author: Evan Rusackas <ev...@preset.io>
AuthorDate: Wed Aug 5 16:24:55 2026 -0700
fix(mcp): reject unknown fields in nested chart-config models too (#42626) (#42732)
Co-authored-by: Claude Code <no...@anthropic.com>
---
superset/mcp_service/chart/schemas.py | 24 +++++++++++--------
.../mcp_service/chart/test_chart_schemas.py | 27 ++++++++++++++++++++--
2 files changed, 39 insertions(+), 12 deletions(-)
diff --git a/superset/mcp_service/chart/schemas.py b/superset/mcp_service/chart/schemas.py
index f58da946088..12bf88e769a 100644
--- a/superset/mcp_service/chart/schemas.py
+++ b/superset/mcp_service/chart/schemas.py
@@ -771,8 +771,8 @@ class UnknownFieldCheckMixin(BaseModel):
return _check_unknown_fields(data, cls)
-class ColumnRef(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
+class ColumnRef(UnknownFieldCheckMixin):
+ model_config = ConfigDict(extra="ignore", populate_by_name=True)
name: str | None = Field(
None,
@@ -892,21 +892,25 @@ class ColumnRef(BaseModel):
)
-class AxisConfig(BaseModel):
+class AxisConfig(UnknownFieldCheckMixin):
+ model_config = ConfigDict(extra="ignore")
+
title: str | None = Field(None, max_length=200)
scale: Literal["linear", "log"] | None = "linear"
format: str | None = Field(None, description="e.g. '$,.2f'", max_length=50)
-class LegendConfig(BaseModel):
+class LegendConfig(UnknownFieldCheckMixin):
+ model_config = ConfigDict(extra="ignore")
+
show: bool = True
position: Literal["top", "bottom", "left", "right"] | None = "right"
-class CurrencyFormat(BaseModel):
+class CurrencyFormat(UnknownFieldCheckMixin):
"""Currency symbol and placement applied to numeric values."""
- model_config = ConfigDict(populate_by_name=True)
+ model_config = ConfigDict(extra="ignore", populate_by_name=True)
symbol: str = Field(
...,
@@ -926,8 +930,8 @@ class CurrencyFormat(BaseModel):
LEGEND_POSITION_LITERAL = Literal["top", "bottom", "left", "right"]
-class FilterConfig(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
+class FilterConfig(UnknownFieldCheckMixin):
+ model_config = ConfigDict(extra="ignore", populate_by_name=True)
column: str = Field(
...,
@@ -994,7 +998,7 @@ class FilterConfig(BaseModel):
return self
-class SortByConfig(BaseModel):
+class SortByConfig(UnknownFieldCheckMixin):
"""Sort specification with explicit direction.
Accepts either this object or a bare column-name string in `sort_by`
@@ -1002,7 +1006,7 @@ class SortByConfig(BaseModel):
sort-by-metric "top N" pattern most commonly used for tables.
"""
- model_config = ConfigDict(populate_by_name=True)
+ model_config = ConfigDict(extra="ignore", populate_by_name=True)
column: str = Field(
...,
diff --git a/tests/unit_tests/mcp_service/chart/test_chart_schemas.py b/tests/unit_tests/mcp_service/chart/test_chart_schemas.py
index 5290d44042f..7f42c1f0760 100644
--- a/tests/unit_tests/mcp_service/chart/test_chart_schemas.py
+++ b/tests/unit_tests/mcp_service/chart/test_chart_schemas.py
@@ -23,6 +23,7 @@ import pytest
from pydantic import ValidationError
from superset.mcp_service.chart.schemas import (
+ AxisConfig,
BigNumberChartConfig,
ColumnRef,
FilterConfig,
@@ -705,11 +706,20 @@ class TestUnknownFieldDetection:
)
def test_known_aliases_not_flagged_as_unknown(self) -> None:
- """Test that known aliases pass validation without errors."""
+ """Test that known aliases pass validation without errors.
+
+ Uses ``x_column`` rather than ``x_axis`` to set the X column:
+ ``x_axis`` is ambiguous between that alias on ``x`` and the
+ unrelated ``x_axis: AxisConfig`` field of the same name, and
+ pydantic resolves the collision in favor of the real field name.
+ Now that nested models reject unknown fields (#42626), routing a
+ column dict through ``x_axis`` here would raise, not silently
+ no-op into ``AxisConfig`` the way it used to.
+ """
config = XYChartConfig.model_validate(
{
"chart_type": "xy",
- "x_axis": {"name": "category"},
+ "x_column": {"name": "category"},
"metrics": [{"name": "sales", "aggregate": "SUM"}],
"groupby": [{"name": "region"}],
"stack": True,
@@ -720,6 +730,19 @@ class TestUnknownFieldDetection:
assert config.row_limit == 10000
assert config.group_by is not None
+ def test_unknown_field_nested_one_level_down_is_rejected(self) -> None:
+ """
+ Regression for #42626: only the top-level chart config models
+ inherit UnknownFieldCheckMixin. Nested models like AxisConfig are
+ plain BaseModel, so a typo'd field one level down (e.g. inside
+ x_axis) is silently dropped by pydantic's default extra="ignore"
+ instead of raising the same "did you mean?" error a top-level typo
+ gets -- an MCP client (or the LLM driving it) gets no signal that
+ its setting was ignored.
+ """
+ with pytest.raises(ValidationError, match="Unknown field"):
+ AxisConfig.model_validate({"title": "State", "sort_by": "metric"})
+
class TestColumnRefSavedMetric:
"""Test ColumnRef saved_metric support."""