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/04/13 19:05:15 UTC
(superset) 10/45: feat(mcp): add get_chart_type_schema tool for on-demand schema discovery (#39142)
This is an automated email from the ASF dual-hosted git repository.
michaelsmolina pushed a commit to branch 6.1
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 1423387010b2ee331cde3eeb9b8c0822b87d8e6e
Author: Amin Ghadersohi <am...@gmail.com>
AuthorDate: Tue Apr 7 12:07:45 2026 -0400
feat(mcp): add get_chart_type_schema tool for on-demand schema discovery (#39142)
(cherry picked from commit 5f9fc31ae23cc25e7892780ccdcc17ab29bcc61a)
---
superset/mcp_service/app.py | 1 +
superset/mcp_service/chart/tool/__init__.py | 2 +
.../chart/tool/get_chart_type_schema.py | 174 +++++++++++++++++++++
.../chart/tool/test_get_chart_type_schema.py | 86 ++++++++++
4 files changed, 263 insertions(+)
diff --git a/superset/mcp_service/app.py b/superset/mcp_service/app.py
index 54c19c1dab4..c38f88e19ee 100644
--- a/superset/mcp_service/app.py
+++ b/superset/mcp_service/app.py
@@ -412,6 +412,7 @@ from superset.mcp_service.chart.tool import ( # noqa: F401, E402
get_chart_data,
get_chart_info,
get_chart_preview,
+ get_chart_type_schema,
list_charts,
update_chart,
update_chart_preview,
diff --git a/superset/mcp_service/chart/tool/__init__.py b/superset/mcp_service/chart/tool/__init__.py
index a3c8388a75c..0ad42852869 100644
--- a/superset/mcp_service/chart/tool/__init__.py
+++ b/superset/mcp_service/chart/tool/__init__.py
@@ -19,6 +19,7 @@ from .generate_chart import generate_chart
from .get_chart_data import get_chart_data
from .get_chart_info import get_chart_info
from .get_chart_preview import get_chart_preview
+from .get_chart_type_schema import get_chart_type_schema
from .list_charts import list_charts
from .update_chart import update_chart
from .update_chart_preview import update_chart_preview
@@ -31,4 +32,5 @@ __all__ = [
"update_chart_preview",
"get_chart_preview",
"get_chart_data",
+ "get_chart_type_schema",
]
diff --git a/superset/mcp_service/chart/tool/get_chart_type_schema.py b/superset/mcp_service/chart/tool/get_chart_type_schema.py
new file mode 100644
index 00000000000..dc21f3c6203
--- /dev/null
+++ b/superset/mcp_service/chart/tool/get_chart_type_schema.py
@@ -0,0 +1,174 @@
+# 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.
+
+"""
+MCP tool: get_chart_type_schema
+"""
+
+from __future__ import annotations
+
+import logging
+from typing import Any, Dict
+
+from pydantic import TypeAdapter
+from superset_core.mcp.decorators import tool, ToolAnnotations
+
+from superset.mcp_service.chart.schemas import (
+ BigNumberChartConfig,
+ HandlebarsChartConfig,
+ MixedTimeseriesChartConfig,
+ PieChartConfig,
+ PivotTableChartConfig,
+ TableChartConfig,
+ XYChartConfig,
+)
+
+logger = logging.getLogger(__name__)
+
+# Module-level TypeAdapters — one per chart type, compiled once.
+_CHART_TYPE_ADAPTERS: Dict[str, TypeAdapter[Any]] = {
+ "xy": TypeAdapter(XYChartConfig),
+ "table": TypeAdapter(TableChartConfig),
+ "pie": TypeAdapter(PieChartConfig),
+ "pivot_table": TypeAdapter(PivotTableChartConfig),
+ "mixed_timeseries": TypeAdapter(MixedTimeseriesChartConfig),
+ "handlebars": TypeAdapter(HandlebarsChartConfig),
+ "big_number": TypeAdapter(BigNumberChartConfig),
+}
+
+VALID_CHART_TYPES = sorted(_CHART_TYPE_ADAPTERS.keys())
+
+# Per-type examples — lightweight inline examples for each chart type.
+_CHART_EXAMPLES: Dict[str, list[Dict[str, Any]]] = {
+ "xy": [
+ {
+ "chart_type": "xy",
+ "kind": "line",
+ "x": {"name": "order_date"},
+ "y": [{"name": "revenue", "aggregate": "SUM"}],
+ "time_grain": "P1D",
+ },
+ {
+ "chart_type": "xy",
+ "kind": "bar",
+ "x": {"name": "category"},
+ "y": [{"name": "sales", "aggregate": "SUM"}],
+ },
+ ],
+ "table": [
+ {
+ "chart_type": "table",
+ "columns": [
+ {"name": "customer_name"},
+ {"name": "revenue", "aggregate": "SUM"},
+ ],
+ },
+ ],
+ "pie": [
+ {
+ "chart_type": "pie",
+ "dimension": {"name": "region"},
+ "metric": {"name": "revenue", "aggregate": "SUM"},
+ },
+ ],
+ "pivot_table": [
+ {
+ "chart_type": "pivot_table",
+ "rows": [{"name": "region"}],
+ "metrics": [{"name": "revenue", "aggregate": "SUM"}],
+ "columns": [{"name": "quarter"}],
+ },
+ ],
+ "mixed_timeseries": [
+ {
+ "chart_type": "mixed_timeseries",
+ "x": {"name": "order_date"},
+ "y": [{"name": "revenue", "aggregate": "SUM"}],
+ "y_secondary": [{"name": "orders", "aggregate": "COUNT"}],
+ "time_grain": "P1M",
+ },
+ ],
+ "handlebars": [
+ {
+ "chart_type": "handlebars",
+ "query_mode": "raw",
+ "columns": [{"name": "customer_name"}, {"name": "email"}],
+ "handlebars_template": "{{#each data}}<p>{{customer_name}}</p>{{/each}}",
+ },
+ ],
+ "big_number": [
+ {
+ "chart_type": "big_number",
+ "metric": {"name": "revenue", "aggregate": "SUM"},
+ },
+ ],
+}
+
+
+def _get_chart_type_schema_impl(
+ chart_type: str,
+ include_examples: bool = True,
+) -> Dict[str, Any]:
+ """Pure logic for chart type schema lookup — no auth, no decorators."""
+ adapter = _CHART_TYPE_ADAPTERS.get(chart_type)
+ if adapter is None:
+ return {
+ "error": f"Unknown chart_type: {chart_type!r}",
+ "valid_chart_types": VALID_CHART_TYPES,
+ "hint": (
+ "Use one of the valid chart_type values listed above. "
+ "Call this tool again with a valid chart_type to see "
+ "its schema and examples."
+ ),
+ }
+
+ schema = adapter.json_schema()
+ result: Dict[str, Any] = {
+ "chart_type": chart_type,
+ "schema": schema,
+ }
+
+ if include_examples:
+ result["examples"] = _CHART_EXAMPLES.get(chart_type, [])
+
+ return result
+
+
+@tool(
+ tags=["discovery"],
+ annotations=ToolAnnotations(
+ title="Get chart type schema",
+ readOnlyHint=True,
+ destructiveHint=False,
+ ),
+)
+def get_chart_type_schema(
+ chart_type: str,
+ include_examples: bool = True,
+) -> Dict[str, Any]:
+ """Get the full JSON Schema and examples for a specific chart type.
+
+ Use this tool to discover the exact fields, types, and constraints
+ for a chart configuration before calling generate_chart or update_chart.
+
+ Valid chart_type values: xy, table, pie, pivot_table,
+ mixed_timeseries, handlebars, big_number.
+
+ Returns the JSON Schema for the requested chart type, optionally
+ with working examples.
+ """
+ return _get_chart_type_schema_impl(chart_type, include_examples)
diff --git a/tests/unit_tests/mcp_service/chart/tool/test_get_chart_type_schema.py b/tests/unit_tests/mcp_service/chart/tool/test_get_chart_type_schema.py
new file mode 100644
index 00000000000..f7f2d166a5a
--- /dev/null
+++ b/tests/unit_tests/mcp_service/chart/tool/test_get_chart_type_schema.py
@@ -0,0 +1,86 @@
+# 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.
+
+"""Tests for get_chart_type_schema tool logic."""
+
+import pytest
+
+from superset.mcp_service.chart.tool.get_chart_type_schema import (
+ _CHART_EXAMPLES,
+ _get_chart_type_schema_impl as _call_schema,
+ VALID_CHART_TYPES,
+)
+
+
+class TestGetChartTypeSchema:
+ @pytest.mark.parametrize("chart_type", VALID_CHART_TYPES)
+ def test_valid_chart_type_returns_schema(self, chart_type: str) -> None:
+ result = _call_schema(chart_type)
+ assert "schema" in result
+ assert result["chart_type"] == chart_type
+ assert isinstance(result["schema"], dict)
+ assert "properties" in result["schema"]
+ assert "examples" in result
+
+ def test_xy_schema_has_expected_fields(self) -> None:
+ result = _call_schema("xy")
+ props = result["schema"]["properties"]
+ assert "x" in props
+ assert "y" in props
+ assert "kind" in props
+
+ def test_table_schema_has_columns(self) -> None:
+ result = _call_schema("table")
+ props = result["schema"]["properties"]
+ assert "columns" in props
+
+ def test_pie_schema_has_dimension_metric(self) -> None:
+ result = _call_schema("pie")
+ props = result["schema"]["properties"]
+ assert "dimension" in props
+ assert "metric" in props
+
+ def test_big_number_schema_has_metric(self) -> None:
+ result = _call_schema("big_number")
+ props = result["schema"]["properties"]
+ assert "metric" in props
+
+ def test_include_examples_false_omits_examples(self) -> None:
+ result = _call_schema("xy", include_examples=False)
+ assert "schema" in result
+ assert "examples" not in result
+
+ def test_invalid_chart_type_returns_error(self) -> None:
+ result = _call_schema("nonexistent")
+ assert "error" in result
+ assert "valid_chart_types" in result
+ assert result["valid_chart_types"] == VALID_CHART_TYPES
+
+ def test_examples_match_chart_type(self) -> None:
+ result = _call_schema("pie")
+ for example in result["examples"]:
+ assert example["chart_type"] == "pie"
+
+ def test_valid_chart_types_constant(self) -> None:
+ assert len(VALID_CHART_TYPES) == 7
+ assert "xy" in VALID_CHART_TYPES
+ assert "table" in VALID_CHART_TYPES
+
+ def test_all_chart_types_have_examples(self) -> None:
+ for chart_type in VALID_CHART_TYPES:
+ assert chart_type in _CHART_EXAMPLES
+ assert len(_CHART_EXAMPLES[chart_type]) >= 1