feat(mcp): add display_name and native_viz_types to chart type plugins

Each ChartTypePlugin now declares:
- display_name: human-readable label for the chart_type discriminator
  (e.g. "Line / Bar / Area / Scatter Chart", "Pivot Table")
- native_viz_types: dict mapping every Superset-internal viz_type the
  plugin produces to a user-friendly name
  (e.g. {"echarts_timeseries_line": "Line Chart", "echarts_area": "Area Chart"})

The registry gains display_name_for_viz_type(viz_type) which searches
all plugins' native_viz_types maps, replacing the need for a separate
viz_type_display_names.json or viz_type_names.py module.

ChartInfo gains a chart_type_display_name field populated via the registry,
so list_charts / get_chart_info return human-readable chart type names.
The MCP system instructions now reference display names rather than
internal viz_type identifiers.
This commit is contained in:
Amin Ghadersohi
2026-05-07 00:27:36 +00:00
parent e7adf0c670
commit b09cbc80aa
11 changed files with 92 additions and 6 deletions

View File

@@ -222,10 +222,14 @@ Time grain for temporal x-axis (time_grain parameter):
- PT1H (hourly), P1D (daily), P1W (weekly), P1M (monthly), P1Y (yearly)
Chart Types in Existing Charts (viewable via list_charts/get_chart_info):
- pie, big_number, big_number_total, funnel, gauge_chart
- echarts_timeseries_line, echarts_timeseries_bar, echarts_timeseries_area
- pivot_table_v2, heatmap_v2, sankey_v2, sunburst_v2, treemap_v2
- word_cloud, world_map, box_plot, bubble, mixed_timeseries
Each chart returned by list_charts / get_chart_info includes a
chart_type_display_name field with a human-readable name. Use that name
when referring to chart types — do NOT expose raw viz_type identifiers.
Common display names: Line Chart, Bar Chart, Area Chart, Scatter Plot,
Pie Chart, Table, Interactive Table, Pivot Table, Big Number,
Big Number with Trendline, Mixed Timeseries Chart, Custom Template Chart,
Funnel Chart, Gauge Chart, Heatmap, Sankey Chart, Sunburst, Treemap,
Word Cloud, World Map, Box Plot, Bubble Chart.
Query Examples:
- List all tables:

View File

@@ -46,6 +46,15 @@ class ChartTypePlugin(Protocol):
#: Discriminator value matching ChartConfig's chart_type field.
chart_type: str
#: Human-readable name shown to users (e.g. "Line / Bar / Area / Scatter").
display_name: str
#: Maps every Superset-internal viz_type this plugin can produce to a
#: user-facing display name, e.g. {"echarts_timeseries_line": "Line Chart"}.
#: Used by the registry to resolve display names for existing charts without
#: needing a separate JSON mapping file.
native_viz_types: dict[str, str]
def pre_validate(
self,
config: dict[str, Any],
@@ -148,6 +157,8 @@ class BaseChartPlugin:
"""
chart_type: str = ""
display_name: str = ""
native_viz_types: dict[str, str] = {}
def pre_validate(
self,

View File

@@ -30,6 +30,11 @@ class BigNumberChartPlugin(BaseChartPlugin):
"""Plugin for big_number chart type."""
chart_type = "big_number"
display_name = "Big Number"
native_viz_types = {
"big_number": "Big Number with Trendline",
"big_number_total": "Big Number",
}
def pre_validate(
self,

View File

@@ -30,6 +30,10 @@ class HandlebarsChartPlugin(BaseChartPlugin):
"""Plugin for handlebars chart type (custom HTML template charts)."""
chart_type = "handlebars"
display_name = "Handlebars (Custom Template)"
native_viz_types = {
"handlebars": "Custom Template Chart",
}
def pre_validate(
self,

View File

@@ -30,6 +30,10 @@ class MixedTimeseriesChartPlugin(BaseChartPlugin):
"""Plugin for mixed_timeseries chart type."""
chart_type = "mixed_timeseries"
display_name = "Mixed Timeseries"
native_viz_types = {
"mixed_timeseries": "Mixed Timeseries Chart",
}
def pre_validate(
self,

View File

@@ -30,6 +30,10 @@ class PieChartPlugin(BaseChartPlugin):
"""Plugin for pie chart type."""
chart_type = "pie"
display_name = "Pie / Donut Chart"
native_viz_types = {
"pie": "Pie Chart",
}
def pre_validate(
self,

View File

@@ -30,6 +30,10 @@ class PivotTableChartPlugin(BaseChartPlugin):
"""Plugin for pivot_table chart type."""
chart_type = "pivot_table"
display_name = "Pivot Table"
native_viz_types = {
"pivot_table_v2": "Pivot Table",
}
def pre_validate(
self,

View File

@@ -30,6 +30,11 @@ class TableChartPlugin(BaseChartPlugin):
"""Plugin for table chart type."""
chart_type = "table"
display_name = "Table"
native_viz_types = {
"table": "Table",
"ag-grid-table": "Interactive Table",
}
def pre_validate(
self,

View File

@@ -30,6 +30,13 @@ class XYChartPlugin(BaseChartPlugin):
"""Plugin for xy chart type (line, bar, area, scatter)."""
chart_type = "xy"
display_name = "Line / Bar / Area / Scatter Chart"
native_viz_types = {
"echarts_timeseries_line": "Line Chart",
"echarts_timeseries_bar": "Bar Chart",
"echarts_area": "Area Chart",
"echarts_timeseries_scatter": "Scatter Plot",
}
def pre_validate(
self,

View File

@@ -72,6 +72,25 @@ def is_registered(chart_type: str) -> bool:
return chart_type in _REGISTRY
def display_name_for_viz_type(viz_type: str) -> str | None:
"""Return the user-facing display name for a Superset-internal viz_type.
Searches every registered plugin's ``native_viz_types`` mapping.
Returns None if no plugin recognises the viz_type.
Example::
display_name_for_viz_type("echarts_timeseries_line") # "Line Chart"
display_name_for_viz_type("pivot_table_v2") # "Pivot Table"
display_name_for_viz_type("unknown_type") # None
"""
for plugin in _REGISTRY.values():
name = plugin.native_viz_types.get(viz_type)
if name is not None:
return name
return None
def get_registry() -> "_RegistryProxy":
"""Return a proxy object for registry access (convenience wrapper)."""
return _RegistryProxy()
@@ -88,3 +107,6 @@ class _RegistryProxy:
def is_registered(self, chart_type: str) -> bool:
return chart_type in _REGISTRY
def display_name_for_viz_type(self, viz_type: str) -> str | None:
return display_name_for_viz_type(viz_type)

View File

@@ -101,7 +101,14 @@ class ChartInfo(BaseModel):
id: int | None = Field(None, description="Chart ID")
slice_name: str | None = Field(None, description="Chart name")
viz_type: str | None = Field(None, description="Visualization type")
viz_type: str | None = Field(None, description="Visualization type (internal ID)")
chart_type_display_name: str | None = Field(
None,
description=(
"User-friendly chart type name (e.g. 'Line Chart', 'Pivot Table'). "
"Use this field when referring to chart types — never expose viz_type."
),
)
datasource_name: str | None = Field(None, description="Datasource name")
datasource_type: str | None = Field(None, description="Datasource type")
url: str | None = Field(None, description="Chart explore page URL")
@@ -488,11 +495,20 @@ def serialize_chart_object(chart: ChartLike | None) -> ChartInfo | None:
# Extract structured filter information
filters_info = extract_filters_from_form_data(chart_form_data)
_viz_type = getattr(chart, "viz_type", None)
try:
from superset.mcp_service.chart.registry import display_name_for_viz_type
_display_name = display_name_for_viz_type(_viz_type) if _viz_type else None
except Exception:
_display_name = None
return sanitize_chart_info_for_llm_context(
ChartInfo(
id=chart_id,
slice_name=getattr(chart, "slice_name", None),
viz_type=getattr(chart, "viz_type", None),
viz_type=_viz_type,
chart_type_display_name=_display_name,
datasource_name=getattr(chart, "datasource_name", None),
datasource_type=getattr(chart, "datasource_type", None),
url=chart_url,