BonnardBonnard

Dashboards

The DashboardSpec a dashboard view returns, a grid of KPI tiles, chart cells, and text blocks.

A dashboard is what a dashboard-kind named view returns: several cells (KPI tiles, charts, and text) rendered in one ui:// view. You author it as a DashboardSpec and return it from a view's render; render_view binds it to the widget.

Unlike the ad-hoc visualize tool, a dashboard is authored by you: you choose the queries and the layout, and the agent renders it by view_id.

Quickstart

chartCell builds a cell from rows. Return a DashboardSpec from a view's render:

import { addViews, chartCell } from "@bonnard/mcp-charts";

addViews(server, {
  views: [
    {
      id: "sales_dashboard",
      title: "Sales dashboard",
      description: "Revenue overview",
      kind: "dashboard",
      render: () => ({
        title: "Sales Dashboard",
        columns: 2,
        items: [
          { type: "kpi", label: "Revenue", value: 336800, format: "currency", currency: "USD", delta: 61800 },
          chartCell(monthlyRows, { chartType: "line", title: "Revenue by month", span: 2, id: "revenue_by_month" }),
          chartCell(regionRows, { chartType: "bar", title: "Revenue by region", id: "revenue_by_region" }),
          { type: "text", heading: "Summary", text: "Trending up, led by EU and US." },
        ],
      }),
    },
  ],
});

The render can be async and can take per-view params (a zod raw shape declared on the view), so you can compose the spec from live data. See Named views for params and the explore_views / render_view flow, including re-rendering one cell by its id.

The DashboardSpec

A dashboard is a { title?, columns?, items, notes? } grid:

FieldTypeNotes
titlestringOptional dashboard heading.
columnsnumberGrid width, default 2. The renderer clamps to 1..4.
itemsDashboardItem[]The cells: KPI tiles, chart cells, and text blocks.
notesstring[]Non-fatal advisories, shown on the dashboard.

Each item is one of three shapes, discriminated by its fields.

KPI tile

A headline number with an optional signed delta and caption.

{
  type: "kpi",
  label: "Total revenue",
  value: 336800,          // number | string | null  (null renders as "—")
  format: "currency",     // "number" | "currency" | "percent"
  currency: "USD",
  delta: 61800,           // signed change vs prior period, same unit as value
  caption: "vs prior period",
  span: 1,                // grid columns this tile spans
}

A percent KPI whose value is a 0..1 fraction sets fraction: true (the renderer scales by 100); same for deltaFraction on the delta. A zero delta is suppressed; positive/negative deltas are coloured and arrowed.

Chart cell

A cell holding one resolved chart. Build it with chartCell(rowsOrChartData, options) rather than authoring a ChartSpec by hand. It runs the same inference as chart:

chartCell(monthlyRows, { chartType: "line", title: "Revenue by month", span: 2, id: "revenue_by_month" });

chartCell accepts every chart option (chartType, title, encode, stacking, horizontal, reference), plus span for the grid, an optional id, and the fields/encode escape hatches for typed data. Its first argument is raw rows or a typed ChartData from an adapter, the same as chart.

A cell's id makes it addressable: with it, render_view can re-render this one cell as a standalone chart via item_id. KPI tiles and text blocks also accept an id for uniform addressability, but only chart cells render alone today.

Text block

Plain text (escaped by the renderer), with an optional heading.

{ type: "text", heading: "Summary", text: "Revenue is trending up, led by EU and US.", span: 2 }

How it renders

render_view returns structuredContent = the DashboardSpec and binds it to the widget via _meta.ui.resourceUri (with the openai/outputTemplate alias for the ChatGPT Apps SDK). One cacheable ui://bonnard/chart resource serves the widget; it discriminates a DashboardSpec (has items) from a single ChartSpec (has data) and renders the grid. A permissive outputSchema declares the envelope so hosts that gate structuredContent on a schema still forward it.

For non-widget clients, render_view also returns a compact text summary (one line per item: KPI values, chart titles/types/row counts, headings, and each chart cell's id). Override it by returning { spec, summary } from the view's render instead of a bare spec.

If you register a dashboard-returning tool by hand instead of via addViews, these build the same envelope:

  • dashboardResult(spec, { summary? }): build the widget-linked result envelope yourself.
  • summarizeDashboard(spec): the default text summary.
  • DASHBOARD_OUTPUT_SCHEMA: the permissive outputSchema for a DashboardSpec-returning tool.
  • isDashboardSpec / isChartSpec: runtime guards to tell the two spec shapes apart.

Next

  • Named views: expose named views (charts and dashboards) behind an explore_views + render_view pair, so the agent discovers and renders them by id.
  • Connecting a Database: feed dashboard cells from live, typed warehouse data.

On this page