Feature Request: Support Grafana 12 Schema v2 Dashboards (TabsLayout / Dynamic Dashboards)
Problem
Grafana 12 introduced a new dashboard schema (v2) with a layout property that supports TabsLayout, RowsLayout, GridLayout, and AutoGridLayout. When a user converts a dashboard from the legacy row-based layout to TabsLayout in the Grafana UI, the layout is stored in the dashboard JSON under spec.layout with panels referenced from spec.elements.
The current mcp-grafana tools have two issues with this:
1. Read-side helpers hardcode $.panels[]
The following functions assume the legacy flat panels[] array and return empty/incorrect results for schema v2 dashboards:
collectAllPanels (tools/dashboard_helpers.go) — uses safeArray(db, "panels")
findPanelByID (tools/dashboard_helpers.go) — uses safeArray(db, "panels")
getDashboardSummary (tools/dashboard.go) — uses safeArray(db, "panels")
extractPanelSummary (tools/dashboard.go) — looks for type == "row" panels
For a schema v2 dashboard, these return zero panels since panels live in the elements map, not in a top-level panels array.
2. Patch operation examples assume legacy paths
The update_dashboard tool description references paths like $.panels[0].targets[0].expr. For schema v2, the equivalent paths would be $.elements.<element-id>.spec.targets[0].expr or $.layout.spec.tabs[0].spec.layout.spec.items[0]. The existing JSONPath engine would work on these paths, but users have no guidance.
What works today
- Full JSON pass-through: The
update_dashboard tool's full-JSON mode (dashboard parameter) uses interface{} / map[string]interface{} throughout the pipeline, so it passes the layout property to Grafana's API without stripping it.
- The JSONPath patch engine can operate on any
map[string]interface{}, so $.layout.spec.tabs[0] would technically work today — but users don't know this.
Schema v2 structure (for reference)
Legacy (v1):
{
"panels": [
{"id": 1, "type": "row", "title": "Tab Name", ...},
{"id": 2, "type": "timeseries", "targets": [...], ...}
]
}
Schema v2 (with TabsLayout):
{
"elements": {
"panel-1": {
"kind": "Panel",
"spec": {
"title": "My Panel",
"vizConfig": { "kind": "timeseries", ... },
"data": { "spec": { "queries": [...] } }
}
}
},
"layout": {
"kind": "TabsLayout",
"spec": {
"tabs": [
{
"kind": "TabsLayoutTab",
"spec": {
"title": "Tab Name",
"layout": {
"kind": "GridLayout",
"spec": {
"items": [
{ "kind": "GridLayoutItem", "spec": { "element": { "kind": "ref", "name": "panel-1" }, "x": 0, "y": 0, "width": 12, "height": 8 } }
]
}
}
}
}
]
}
}
}
Reproduction
- Create a dashboard via
update_dashboard with row panels (schema v1)
- In Grafana 12 UI, switch the dashboard layout from "Rows" to "Tabs" and save
- Call
get_dashboard_summary or get_dashboard_panel_queries — returns zero panels
- Call
update_dashboard with operations using $.panels[0] paths — fails since panels array no longer exists
Proposed solution
Minimal (small PR, high value):
Detect schema version by checking for the layout property, then branch in read-side helpers:
collectAllPanels: if layout exists, iterate elements map instead of panels[]
findPanelByID: search elements map by key
getDashboardSummary: traverse layout tree to build panel list with tab/section context
extractPanelSummary: handle v2 panel structure (vizConfig.kind instead of type, data.spec.queries instead of targets)
Update update_dashboard tool description with v2 path examples.
Full (larger effort):
Add optional support for the v2beta1 k8s-style API (/apis/dashboard.grafana.app/v2beta1/namespaces/{ns}/dashboards) using the existing KubernetesClient in k8s_client.go. Auto-detect which API to use based on Grafana instance capabilities.
Environment
- Grafana version: 12.4.2
- mcp-grafana version: v0.11.4 (also reproduced on commit
9971fcaf from 2026-02-19)
- Feature toggles:
dynamicDashboards enabled
References
Feature Request: Support Grafana 12 Schema v2 Dashboards (TabsLayout / Dynamic Dashboards)
Problem
Grafana 12 introduced a new dashboard schema (v2) with a
layoutproperty that supportsTabsLayout,RowsLayout,GridLayout, andAutoGridLayout. When a user converts a dashboard from the legacy row-based layout toTabsLayoutin the Grafana UI, the layout is stored in the dashboard JSON underspec.layoutwith panels referenced fromspec.elements.The current mcp-grafana tools have two issues with this:
1. Read-side helpers hardcode
$.panels[]The following functions assume the legacy flat
panels[]array and return empty/incorrect results for schema v2 dashboards:collectAllPanels(tools/dashboard_helpers.go) — usessafeArray(db, "panels")findPanelByID(tools/dashboard_helpers.go) — usessafeArray(db, "panels")getDashboardSummary(tools/dashboard.go) — usessafeArray(db, "panels")extractPanelSummary(tools/dashboard.go) — looks fortype == "row"panelsFor a schema v2 dashboard, these return zero panels since panels live in the
elementsmap, not in a top-levelpanelsarray.2. Patch operation examples assume legacy paths
The
update_dashboardtool description references paths like$.panels[0].targets[0].expr. For schema v2, the equivalent paths would be$.elements.<element-id>.spec.targets[0].expror$.layout.spec.tabs[0].spec.layout.spec.items[0]. The existing JSONPath engine would work on these paths, but users have no guidance.What works today
update_dashboardtool's full-JSON mode (dashboardparameter) usesinterface{}/map[string]interface{}throughout the pipeline, so it passes thelayoutproperty to Grafana's API without stripping it.map[string]interface{}, so$.layout.spec.tabs[0]would technically work today — but users don't know this.Schema v2 structure (for reference)
Legacy (v1):
{ "panels": [ {"id": 1, "type": "row", "title": "Tab Name", ...}, {"id": 2, "type": "timeseries", "targets": [...], ...} ] }Schema v2 (with TabsLayout):
{ "elements": { "panel-1": { "kind": "Panel", "spec": { "title": "My Panel", "vizConfig": { "kind": "timeseries", ... }, "data": { "spec": { "queries": [...] } } } } }, "layout": { "kind": "TabsLayout", "spec": { "tabs": [ { "kind": "TabsLayoutTab", "spec": { "title": "Tab Name", "layout": { "kind": "GridLayout", "spec": { "items": [ { "kind": "GridLayoutItem", "spec": { "element": { "kind": "ref", "name": "panel-1" }, "x": 0, "y": 0, "width": 12, "height": 8 } } ] } } } } ] } } }Reproduction
update_dashboardwith row panels (schema v1)get_dashboard_summaryorget_dashboard_panel_queries— returns zero panelsupdate_dashboardwithoperationsusing$.panels[0]paths — fails sincepanelsarray no longer existsProposed solution
Minimal (small PR, high value):
Detect schema version by checking for the
layoutproperty, then branch in read-side helpers:collectAllPanels: iflayoutexists, iterateelementsmap instead ofpanels[]findPanelByID: searchelementsmap by keygetDashboardSummary: traverselayouttree to build panel list with tab/section contextextractPanelSummary: handle v2 panel structure (vizConfig.kindinstead oftype,data.spec.queriesinstead oftargets)Update
update_dashboardtool description with v2 path examples.Full (larger effort):
Add optional support for the
v2beta1k8s-style API (/apis/dashboard.grafana.app/v2beta1/namespaces/{ns}/dashboards) using the existingKubernetesClientink8s_client.go. Auto-detect which API to use based on Grafana instance capabilities.Environment
9971fcaffrom 2026-02-19)dynamicDashboardsenabledReferences