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).

소스 정보

저장소
MapleTechLabs/maple
최근 소스 활동
2026년 9월 25일 17:54
감지된 SKILL.md 언어
영어
스타
1,838
포크
119

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
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",
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기