- schema_validator.py: add circular-import comment to both local registry
imports (H1); extract valid_types before the conditional so all_types()
is called once instead of in each error branch (N1)
- plugin.py: expand BaseChartPlugin docstring to list all default method
behaviours including schema_error_hint (N3); add comment warning that
native_viz_types is a class-level shared dict — subclasses must override
as a class attribute, not mutate in place (M1)
- registry.py: expand _reset_for_testing() docstring with explicit warning
that direct global assignment is not reverted by pytest monkeypatch —
callers must restore state in teardown (M2)
- mcp_config.py: replace Dict/Optional from typing with dict/X|None modern
syntax; remove now-unused Optional and Dict imports (N2)
- initialization/__init__.py: add docstring to configure_mcp_chart_registry()
explaining the known two-call pattern in MCP-standalone startup and why
the stale-config window between the two calls is benign in practice (H2)
Introduces a dynamic filter layer in the chart type registry so operators can
disable individual plugins (e.g. `handlebars`) without a code deploy:
- `MCP_DISABLED_CHART_PLUGINS: frozenset[str]` — static deny-list in mcp_config.py
- `MCP_CHART_PLUGIN_ENABLED_FUNC: Callable[[str], bool] | None` — dynamic hook
for Harness/Split/per-user targeting; takes precedence over the deny-list
- Both keys are propagated through `get_mcp_config()` defaults
registry.py changes:
- `_PluginFilterConfig` frozen dataclass replaces two bare globals so
configure() replaces them atomically (no torn reads under concurrency)
- `configure(disabled, enabled_func)` — called at app init; accepts any
iterable for `disabled`; validates `enabled_func` is callable
- `_is_plugin_enabled()` — reads config once, fails closed on callable exception
- `get()` / `all_types()` / `is_enabled()` apply the filter at lookup time;
`is_registered()` and `display_name_for_viz_type()` intentionally bypass it
so callers can distinguish "unknown" vs "disabled" and existing charts still
resolve display names for disabled viz types
schema_validator.py: two-step pre-check — `is_registered()` for unknown types,
`is_enabled()` for disabled ones, with distinct `DISABLED_CHART_TYPE` error code.
Wiring:
- `SupersetAppInitializer.configure_mcp_chart_registry()` called after
`configure_feature_flags()` in `init_app()`
- `flask_singleton.py` re-calls `registry.configure()` after the MCP config
overlay so MCP-specific overrides in `superset_config.py` take effect in
standalone MCP mode
Tests: 28 cases in test_registry_filters.py covering deny-list, callable hook,
fail-closed on exception, all_types() filtering, display_name bypass, atomic
reconfigure, and configure() with list/tuple/frozenset inputs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>