Skip to main content

maple-dashboard-widgets

Build, repair, or review Maple dashboard widgets via the MCP. Triggers on phrases like 'create_dashboard', 'add_dashboard_widget', 'update_dashboard_widget', 'dashboard widget JSON', 'panel_type', 'QueryDraft', or any session that submits widget JSON to the maple MCP. Covers the panel-type table, the kind-discriminated data source, the percent vs percent_100 unit rule, valid aggregations and group-by tokens per source, the custom whereClause grammar, the scalar reduceToValue transform, and the verification step (MCP success != chart correctness).

Quellinformationen

Repository
MapleTechLabs/maple
Letzte Quellaktivität
25. September 2026 um 17:54
Erkannte Sprache von SKILL.md
Englisch
Sterne
1.925
Forks
124

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
maple-dashboard-widgets
description
Build, repair, or review Maple dashboard widgets via the MCP. Triggers on phrases like 'create_dashboard', 'add_dashboard_widget', 'update_dashboard_widget', 'dashboard widget JSON', 'panel_type', 'QueryDraft', or any session that submits widget JSON to the maple MCP. Covers the panel-type table, the kind-discriminated data source, the percent vs percent_100 unit rule, valid aggregations and group-by tokens per source, the custom whereClause grammar, the scalar reduceToValue transform, and the verification step (MCP success != chart correctness).
# Maple dashboard widgets via MCP Everything below is generated from the live widget schema by `bun run --cwd apps/ai mcp:docs`. **Do not edit this file by hand.** Edit `apps/ai/src/mcp/lib/dashboard-schema-doc.ts` and regenerate. The same module backs the `describe_dashboard_schema` MCP tool, so an agent at runtime and a reader here see one truth. ## When to use this skill When constructing widget JSON for `mcp__maple__create_dashboard`, `mcp__maple__add_dashboard_widget`, `mcp__maple__update_dashboard_widget` or `mcp__maple__replace_dashboard_widgets`. For a brand-new dashboard, prefer the simplified `widgets` array on `create_dashboard` (`{ title, source, metric, group_by?, service_name?, unit? }`). It fills in the traps below. Reach for raw JSON when the simplified spec can't express what you need: multi-query charts, formulas, hidden series, non-default transforms. ## The three silent failures 1. **A data source is a `kind`-discriminated union.** `{ "endpoint": …, "params": … }` is the retired v2 shape and will not decode. 2. **`percent` means a 0–1 fraction; `percent_100` means 0–100.** Inverted from Grafana. 3. **`groupBy` is ignored unless `addOns.groupBy` is `true`.** No error, just an ungrouped total. ## Verification MCP success is not chart correctness. The mutation tools reject queries the engine can't honor and widgets that cannot render, and return an automatic `inspect_chart_data` summary for everything else. Read the verdict: `suspicious` or `broken` means fix and resubmit. ## Panel types `panel_type` is the whole answer to “what kind of widget is this”. Pass it and the persisted `visualization`, the `display.chartId` and the raw-SQL display type are all derived for you. The legacy `visualization` parameter is still accepted, but it collapses line/bar/area into `chart` and then needs a `display.chartId` to tell them apart. The two columns below are what `panel_type` resolves to, and are what you write directly when authoring an assembled widget rather than calling `add_dashboard_widget`. A panel whose `chartId` is `-` takes none: the `visualization` alone identifies it. | panel_type | Label | `visualization` | `display.chartId` | Raw-SQL type | Default w×h | Requirements | |---|---|---|---|---|---|---| | `line` | Line | `chart` | `query-builder-line` | `line` | 4×6 | - | | `bar` | Bar | `chart` | `query-builder-bar` | `bar` | 4×6 | - | | `hbar` | Horizontal Bar | `hbar` | `query-builder-hbar` | `hbar` | 4×6 | needs a group-by | | `area` | Area | `chart` | `query-builder-area` | `area` | 4×6 | - | | `pie` | Pie | `pie` | `query-builder-pie` | `pie` | 4×6 | needs a group-by | | `stat` | Stat | `stat` | - | `stat` | 3×4 | needs `transform.reduceToValue` | | `gauge` | Gauge | `gauge` | - | `stat` | 4×6 | needs `transform.reduceToValue` | | `table` | Table | `table` | - | `table` | 6×5 | - | | `list` | List | `list` | - | - | 6×5 | **no raw-SQL support** | | `histogram` | Histogram | `histogram` | `query-builder-histogram` | `histogram` | 4×6 | - | | `heatmap` | Heatmap | `heatmap` | `query-builder-heatmap` | `heatmap` | 4×6 | needs a group-by | | `funnel` | Funnel | `funnel` | `query-builder-funnel` | `funnel` | 4×6 | needs a group-by | | `paths` | Paths | `paths` | `query-builder-paths` | - | 6×5 | **no raw-SQL support** | | `markdown` | Note | `markdown` | - | - | 4×5 | **no raw-SQL support** | ### Choosing one - **line / area / bar**: a value over time. `area` and `bar` accept `display.stacked`; `line` does not. - **hbar**: a ranked “top N by volume”. Each row is labelled with its share of the **total**. - **funnel**: sequential stages with a drop-off. Labels each bar as a share of the *largest*, so an unranked breakdown of four equal things reads “100%” four times. Use `hbar` for that. `display.funnel.variant: "dropoff"` draws the same funnel as one column per step with the loss between steps, the median time between them, and where the leavers went. - **paths**: what people do in the steps after (or before) one event or page, as a flow. Defined by `display.paths` alone; no query set. - **pie**: composition, few slices. Collapses a long tail into “Other”. - **stat / gauge**: one number. A gauge adds an arc; set `display.gauge.min`/`max` to match the unit. - **table**: rows and columns; set `display.columns` for headers and per-column units. - **list**: recent traces/logs. Configured by `display.listDataSource`, never by SQL. - **heatmap / histogram**: a distribution. A histogram over traces can bucket raw values client-side. - **markdown**: a static note. Takes no query at all. ## Data sources A widget's `dataSource` is a discriminated union over `kind`: `query`, `raw_sql`, `route`, `static`. Every arm requires its `kind`. > **If you have seen `{ "endpoint": …, "params": … }` anywhere, that is the retired v2 shape > and it will not decode.** A `query` source spreads `queries`/`formulas` at the TOP LEVEL, > not under `params`, and requires `resultShape`. ### `kind: "query"`: the query builder `resultShape` is required and is one of `timeseries` (a value over time), `breakdown` (one row per group) or `list` (raw rows). Optional: `formulas`, `comparison`, `limit`, `defaultLimit`, `columns`, `transform`. ```json { "kind": "query", "resultShape": "timeseries", "queries": [ { "id": "q1", "name": "A", "enabled": true, "whereClause": "service.name = \"api\"", "aggregation": "error_rate", "stepInterval": "", "orderByDirection": "desc", "addOns": { "groupBy": true, "having": false, "orderBy": false, "limit": false, "legend": false }, "groupBy": [ "service.name" ], "having": "", "orderBy": "", "limit": "", "legend": "", "dataSource": "traces" } ] } ``` ### `kind: "raw_sql"`: your own ClickHouse SQL ```json { "kind": "raw_sql", "sql": "SELECT count() AS value FROM logs WHERE $__orgFilter", "displayType": "stat" } ``` ### `kind: "static"`: a markdown note, no request ```json { "kind": "static" } ``` ### `kind: "route"`: a curated built-in panel `{ "kind": "route", "endpoint": "service_overview", "params": { … } }`. These back the prebuilt panels; you rarely author one by hand. ### Scalar panels need a reduction A `stat` or `gauge` reads `data[0].value`. Without `transform.reduceToValue` it renders `[object Object]`. `add_dashboard_widget` injects `{ field: "value", aggregate: "first" }` when you omit it; set it explicitly to choose a different reducer. Valid aggregates: `sum`, `first`, `count`, `avg`, `max`, `min`: **there is no `last`**. Which one depends on what the query returns, because the query is bucketed over time and the reducer collapses those buckets into one number: - **A rate or a count** (`count`, a metrics `rate`) → `sum`, for a window total. - **A latency percentile or an average** (`p95_duration`, `avg_duration`, a gauge metric) → `avg` for the typical value over the window, or `max` for the worst bucket. **Not** `sum`: adding percentiles together is meaningless, and it is the common wrong choice. - **A current reading**, where only the newest bucket matters → `first`. ```json { "kind": "query", "resultShape": "timeseries", "queries": [ { "id": "q1", "name": "A", "enabled": true, "whereClause": "", "aggregation": "count", "stepInterval": "", "orderByDirection": "desc", "addOns": { "groupBy": false, "having": false, "orderBy": false, "limit": false, "legend": false }, "groupBy": [], "having": "", "orderBy": "", "limit": "", "legend": "", "dataSource": "traces" } ], "transform": { "reduceToValue": { "field": "value", "aggregate": "sum" } } } ``` ### The breakdown shape `resultShape: "breakdown"` returns one row per group instead of a series over time, the shape `pie`, `hbar`, `funnel` and `heatmap` need. It requires a group-by, and `limit` caps the rows (honoured for 1–100). ```json { "kind": "query", "resultShape": "breakdown", "queries": [ { "id": "q1", "name": "A", "enabled": true, "whereClause": "", "aggregation": "count", "stepInterval": "", "orderByDirection": "desc", "addOns": { "groupBy": true, "having": false, "orderBy": false, "limit": false, "legend": false }, "groupBy": [ "service.name" ], "having": "", "orderBy": "", "limit": "", "legend": "", "dataSource": "traces" } ], "limit": 10 } ``` ### Product-event funnels (`panel_type: "funnel"` + `display.funnel.steps`) A funnel widget has two modes. Without `display.funnel.steps` it draws a group-by breakdown as descending stages (the shape above). With them it is a **conversion funnel over product events** (page views, `track()` events and server-side events, stitched per person), and the query set is not used at all. Set the definition on `display_json.funnel` and `add_dashboard_widget` derives the data source (`kind: "route"`, `endpoint: "product_events_funnel"`) for you; do not pass `data_source_json`. - `steps`: 1–10, in order. `{ kind: "event", eventName, attributeEquals? }`, `{ kind: "page", pagePath, host? }`, or (**step 1 only**) `{ kind: "session", dimension, value }` with `dimension` one of `referrerHost`, `utmSource`, `utmMedium`, `utmCampaign`, `country`, `host`. - `keyBy`: `person` (default; user id, else the visitor's linked user, else the visitor), `visitor`, `user`, or `session`. - `windowSeconds`: the whole chain must complete within this many seconds of step 1 (default 86400). Must be positive. - `breakdownBy`: split the funnel by a session dimension (`referrerHost`, `utmSource`, `utmMedium`, `utmCampaign`, `country`, `host`) or `attribute:<key>`; the widget draws one bar per group per step for the top 6 groups by step-1 count. - `filters`: the population filter: only persons with a session matching these dimensions take part. `{ host?, pagePath?, referrerHost?, country?, deviceType?, browserName?, osName?, language?, utmSource?, utmMedium?, utmCampaign?, visitorType?: "new" | "returning" }`. They are spread FLAT into the derived route params. The step count, the step-1-only session rule and a positive `windowSeconds` are enforced on write: a definition that breaks one of them is rejected rather than saved, because the query engine would reject it again on every render. Use `list_product_events` to see which event names exist, and `query_funnel` to try a definition before pinning it to a board. ```json { "id": "w-signup-funnel", "visualization": "funnel", "dataSource": { "kind": "route", "endpoint": "product_events_funnel",
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen