Skip to content

Commit 84279ac

Browse files
feat(mcp): add unified get_schema tool for schema discovery (#36458)
Co-authored-by: Claude Opus 4.5 <[email protected]>
1 parent 03caa7b commit 84279ac

26 files changed

+1850
-395
lines changed

superset/mcp_service/CLAUDE.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ The `mcp_core.py` module provides reusable patterns:
176176

177177
- **`ModelListCore`**: For listing resources (dashboards, charts, datasets)
178178
- **`ModelGetInfoCore`**: For getting resource details by ID/UUID
179-
- **`ModelGetAvailableFiltersCore`**: For retrieving filterable columns
179+
- **`ModelGetSchemaCore`**: For retrieving comprehensive schema metadata (columns, filters, sortable columns)
180180

181181
**Example**:
182182
```python
@@ -502,8 +502,8 @@ def my_function(param: Optional[str] = None) -> Optional[int]:
502502
**Solution**: Use DAO classes (ChartDAO, DashboardDAO, etc.).
503503

504504
### 7. ❌ Not Using Core Classes
505-
**Problem**: Duplicating list/get_info/filter logic across tools.
506-
**Solution**: Use ModelListCore, ModelGetInfoCore, ModelGetAvailableFiltersCore.
505+
**Problem**: Duplicating list/get_info/schema logic across tools.
506+
**Solution**: Use ModelListCore, ModelGetInfoCore, ModelGetSchemaCore.
507507

508508
### 8. ❌ Missing Apache License Headers
509509
**Problem**: CI fails on license check.

superset/mcp_service/app.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,21 +51,18 @@ def get_default_instructions(branding: str = "Apache Superset") -> str:
5151
Dashboard Management:
5252
- list_dashboards: List dashboards with advanced filters (1-based pagination)
5353
- get_dashboard_info: Get detailed dashboard information by ID
54-
- get_dashboard_available_filters: List available dashboard filter fields/operators
5554
- generate_dashboard: Automatically create a dashboard from datasets with AI
5655
- add_chart_to_existing_dashboard: Add a chart to an existing dashboard
5756
5857
Dataset Management:
5958
- list_datasets: List datasets with advanced filters (1-based pagination)
6059
- get_dataset_info: Get detailed dataset information by ID
61-
- get_dataset_available_filters: List available dataset filter fields/operators
6260
6361
Chart Management:
6462
- list_charts: List charts with advanced filters (1-based pagination)
6563
- get_chart_info: Get detailed chart information by ID
6664
- get_chart_preview: Get a visual preview of a chart with image URL
6765
- get_chart_data: Get underlying chart data in text-friendly format
68-
- get_chart_available_filters: List available chart filter fields/operators
6966
- generate_chart: Create a new chart with AI assistance
7067
- update_chart: Update existing chart configuration
7168
- update_chart_preview: Update chart and get preview in one operation
@@ -77,6 +74,9 @@ def get_default_instructions(branding: str = "Apache Superset") -> str:
7774
Explore & Analysis:
7875
- generate_explore_link: Create pre-configured explore URL with dataset/metrics/filters
7976
77+
Schema Discovery:
78+
- get_schema: Get schema metadata for chart/dataset/dashboard (columns, filters)
79+
8080
System Information:
8181
- get_instance_info: Get instance-wide statistics and metadata
8282
- health_check: Simple health check tool (takes NO parameters, call without arguments)
@@ -125,7 +125,8 @@ def get_default_instructions(branding: str = "Apache Superset") -> str:
125125
126126
General usage tips:
127127
- All listing tools use 1-based pagination (first page is 1)
128-
- Use 'filters' parameter for advanced queries (see *_available_filters tools)
128+
- Use get_schema to discover filterable columns, sortable columns, and default columns
129+
- Use 'filters' parameter for advanced queries with filter columns from get_schema
129130
- IDs can be integer or UUID format where supported
130131
- All tools return structured, Pydantic-typed responses
131132
- Chart previews are served as PNG images via custom screenshot endpoints
@@ -289,7 +290,6 @@ def create_mcp_app(
289290
)
290291
from superset.mcp_service.chart.tool import ( # noqa: F401, E402
291292
generate_chart,
292-
get_chart_available_filters,
293293
get_chart_data,
294294
get_chart_info,
295295
get_chart_preview,
@@ -300,12 +300,10 @@ def create_mcp_app(
300300
from superset.mcp_service.dashboard.tool import ( # noqa: F401, E402
301301
add_chart_to_existing_dashboard,
302302
generate_dashboard,
303-
get_dashboard_available_filters,
304303
get_dashboard_info,
305304
list_dashboards,
306305
)
307306
from superset.mcp_service.dataset.tool import ( # noqa: F401, E402
308-
get_dataset_available_filters,
309307
get_dataset_info,
310308
list_datasets,
311309
)
@@ -322,6 +320,7 @@ def create_mcp_app(
322320
)
323321
from superset.mcp_service.system.tool import ( # noqa: F401, E402
324322
get_instance_info,
323+
get_schema,
325324
health_check,
326325
)
327326

superset/mcp_service/chart/schemas.py

Lines changed: 21 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -131,25 +131,6 @@ def _filter_fields_by_context(self, serializer: Any, info: Any) -> Dict[str, Any
131131
return data
132132

133133

134-
class GetChartAvailableFiltersRequest(BaseModel):
135-
"""
136-
Request schema for get_chart_available_filters tool.
137-
138-
Currently has no parameters but provides consistent API for future extensibility.
139-
"""
140-
141-
model_config = ConfigDict(
142-
extra="forbid",
143-
str_strip_whitespace=True,
144-
)
145-
146-
147-
class ChartAvailableFiltersResponse(BaseModel):
148-
column_operators: Dict[str, Any] = Field(
149-
..., description="Available filter operators and metadata for each column"
150-
)
151-
152-
153134
class ChartError(BaseModel):
154135
error: str = Field(..., description="Error message")
155136
error_type: str = Field(..., description="Type of error")
@@ -288,13 +269,13 @@ class ChartFilter(ColumnOperator):
288269
"datasource_name",
289270
] = Field(
290271
...,
291-
description="Column to filter on. See get_chart_available_filters for "
292-
"allowed values.",
272+
description="Column to filter on. Use get_schema(model_type='chart') for "
273+
"available filter columns.",
293274
)
294275
opr: ColumnOperatorEnum = Field(
295276
...,
296-
description="Operator to use. See get_chart_available_filters for "
297-
"allowed values.",
277+
description="Operator to use. Use get_schema(model_type='chart') for "
278+
"available operators.",
298279
)
299280
value: str | int | float | bool | List[str | int | float | bool] = Field(
300281
..., description="Value to filter by (type depends on col and opr)"
@@ -310,8 +291,22 @@ class ChartList(BaseModel):
310291
total_pages: int
311292
has_previous: bool
312293
has_next: bool
313-
columns_requested: List[str] | None = None
314-
columns_loaded: List[str] | None = None
294+
columns_requested: List[str] = Field(
295+
default_factory=list,
296+
description="Requested columns for the response",
297+
)
298+
columns_loaded: List[str] = Field(
299+
default_factory=list,
300+
description="Columns that were actually loaded for each chart",
301+
)
302+
columns_available: List[str] = Field(
303+
default_factory=list,
304+
description="All columns available for selection via select_columns parameter",
305+
)
306+
sortable_columns: List[str] = Field(
307+
default_factory=list,
308+
description="Columns that can be used with order_column parameter",
309+
)
315310
filters_applied: List[ChartFilter] = Field(
316311
default_factory=list,
317312
description="List of advanced filter dicts applied to the query.",
@@ -749,18 +744,7 @@ class ListChartsRequest(MetadataCacheControl):
749744
select_columns: Annotated[
750745
List[str],
751746
Field(
752-
default_factory=lambda: [
753-
"id",
754-
"slice_name",
755-
"viz_type",
756-
"datasource_name",
757-
"description",
758-
"changed_by_name",
759-
"created_by_name",
760-
"changed_on",
761-
"created_on",
762-
"uuid",
763-
],
747+
default_factory=list,
764748
description="List of columns to select. Defaults to common columns if not "
765749
"specified.",
766750
),

superset/mcp_service/chart/tool/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
# under the License.
1717

1818
from .generate_chart import generate_chart
19-
from .get_chart_available_filters import get_chart_available_filters
2019
from .get_chart_data import get_chart_data
2120
from .get_chart_info import get_chart_info
2221
from .get_chart_preview import get_chart_preview
@@ -27,7 +26,6 @@
2726
__all__ = [
2827
"list_charts",
2928
"get_chart_info",
30-
"get_chart_available_filters",
3129
"generate_chart",
3230
"update_chart",
3331
"update_chart_preview",

superset/mcp_service/chart/tool/get_chart_available_filters.py

Lines changed: 0 additions & 50 deletions
This file was deleted.

superset/mcp_service/chart/tool/list_charts.py

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -41,17 +41,12 @@
4141

4242
logger = logging.getLogger(__name__)
4343

44+
# Minimal defaults for reduced token usage - users can request more via select_columns
4445
DEFAULT_CHART_COLUMNS = [
4546
"id",
4647
"slice_name",
4748
"viz_type",
4849
"uuid",
49-
"datasource_name",
50-
"description",
51-
"changed_by_name",
52-
"created_by_name",
53-
"changed_on",
54-
"created_on",
5550
]
5651

5752
SORTABLE_CHART_COLUMNS = [
@@ -93,6 +88,14 @@ async def list_charts(request: ListChartsRequest, ctx: Context) -> ChartList:
9388
)
9489

9590
from superset.daos.chart import ChartDAO
91+
from superset.mcp_service.common.schema_discovery import (
92+
CHART_SORTABLE_COLUMNS,
93+
get_all_column_names,
94+
get_chart_columns,
95+
)
96+
97+
# Get all column names dynamically from the model
98+
all_columns = get_all_column_names(get_chart_columns())
9699

97100
def _serialize_chart(
98101
obj: "Slice | None", cols: list[str] | None
@@ -112,6 +115,8 @@ def _serialize_chart(
112115
],
113116
list_field_name="charts",
114117
output_list_schema=ChartList,
118+
all_columns=all_columns,
119+
sortable_columns=CHART_SORTABLE_COLUMNS,
115120
logger=logger,
116121
)
117122

@@ -132,20 +137,17 @@ def _serialize_chart(
132137
% (count, total_pages)
133138
)
134139

135-
# Apply field filtering via serialization context if select_columns specified
136-
# This triggers ChartInfo._filter_fields_by_context for each chart
137-
if request.select_columns:
138-
await ctx.debug(
139-
"Applying field filtering via serialization context: select_columns=%s"
140-
% (request.select_columns,)
141-
)
142-
# Return dict with context - FastMCP will serialize it
143-
return result.model_dump(
144-
mode="json", context={"select_columns": request.select_columns}
145-
)
146-
147-
# No filtering - return full result as dict
148-
return result.model_dump(mode="json")
140+
# Apply field filtering via serialization context
141+
# Use columns_requested from result (already resolved by ModelListCore)
142+
columns_to_filter = result.columns_requested
143+
await ctx.debug(
144+
"Applying field filtering via serialization context: select_columns=%s"
145+
% (columns_to_filter,)
146+
)
147+
filtered = result.model_dump(
148+
mode="json", context={"select_columns": columns_to_filter}
149+
)
150+
return ChartList.model_validate(filtered)
149151
except Exception as e:
150152
await ctx.error("Failed to list charts: %s" % (str(e),))
151153
raise

0 commit comments

Comments
 (0)