BonnardBonnard

Named views

Expose a set of named views behind explore_views + render_view with addViews.

Register a set of named views when you want the agent to render repeatable analytics instead of writing ad-hoc SQL. addViews is the one authored entrypoint. It registers two tools over a registry:

  • explore_views lists what's available (id, title, description, params). The agent calls this first to discover what it can render.
  • render_view renders one view by view_id, bound to the chart widget.

A view is not a new kind of spec. It returns either a ChartSpec (via chart(rows, opts)) or a composed DashboardSpec, so a view can be a single chart or a full dashboard. addViews changes how the agent finds your spec (by id, through explore_views and render_view), not what it renders.

Quickstart

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

addViews(server, {
  views: [
    {
      id: "revenue_trend",
      title: "Revenue trend",
      description: "Monthly revenue",
      kind: "chart",
      render: () => chart(monthlyRows, { chartType: "line", title: "Revenue by month" }),
    },
    {
      id: "sales_overview",
      title: "Sales overview",
      description: "KPIs + charts, optionally per region",
      kind: "dashboard",
      params: { region: z.enum(["EU", "US", "APAC"]).optional() },
      render: ({ region }) => buildDashboard(region), // your function; returns a DashboardSpec
    },
  ],
});

Single chart or dashboard

addViews is the authored surface for both single charts and dashboards. Each view declares its shape with kind and returns the matching spec from render:

  • A chart-kind view returns a ChartSpec (one preset chart), built with chart(rows, opts).
  • A dashboard-kind view returns a DashboardSpec (a grid of KPI tiles, chart cells, and text blocks), with cells built by chartCell(rows, opts).

render_view returns whichever the chosen view produced; the widget discriminates and renders the right one. kind is an author hint for the catalog, not a separate code path.

The ViewDef

Each view in the registry is a ViewDef:

FieldTypeNotes
idstringThe render_view view_id enum value. Stable, snake/kebab-case.
titlestringShown in the catalog.
descriptionstringOne line, shown in explore_views and the render_view catalog.
kind"chart" / "dashboard"Optional author hint for the catalog.
paramszod raw shapeOptional per-view input params, validated on each render.
render(args) => ViewResultProduce the spec(s). May be async. Return a ChartSpec, a DashboardSpec, or { spec, summary }.

Per-view params

A view can declare params as a zod raw shape. render_view validates the caller's params against that shape per view (strict: unknown keys are rejected) and passes the parsed values to render:

{
  id: "sales_overview",
  title: "Sales overview",
  description: "KPIs + revenue-by-region, optionally filtered",
  kind: "dashboard",
  params: { region: z.enum(["EU", "US", "APAC"]).optional() },
  render: ({ region }) => buildSalesOverview(region), // your function; returns a DashboardSpec
}

explore_views reports each param's name, type, and whether it's required, so the agent knows what it can pass. Validation failures come back as a clear MCP error (invalid param "region": ...), not a broken render.

Rendering one cell

Give a dashboard's chart cells a stable id and the agent can re-render one cell on its own as a standalone chart, without a new tool:

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

The agent then calls render_view with an item_id:

{ "view_id": "sales_overview", "item_id": "revenue_by_month" }

Params and selection compose in a fixed order: params always apply to the whole view first, then the named cell is projected out of the resulting dashboard and returned as a ChartSpec. So { "view_id": "sales_overview", "params": { "region": "EU" }, "item_id": "revenue_by_month" } renders the EU-filtered month chart alone.

The cell ids surface in the dashboard's summary text (Revenue by month [id: revenue_by_month]: ...), so the agent learns them from the natural first step of rendering the whole dashboard. A few rules keep the behavior predictable:

  • item_id on a chart-kind view is an error (a single chart has no cells to pick from).
  • An unknown item_id errors and lists the selectable chart-cell ids.
  • Selecting a KPI tile or text block errors: only chart cells render alone. (KpiTile and TextBlock accept an id for addressability, but v1 selection renders chart cells only.)

How the agent uses it

  1. The agent calls explore_views and gets the catalog: each view's id, title, description, kind, and params.
  2. It calls render_view with a view_id (and params if the view declares any).
  3. The chosen view renders into the ui://bonnard/chart widget, a single chart or a full dashboard, with a text fallback for non-widget clients.

render_view returns either a ChartSpec or a DashboardSpec, so its outputSchema accepts both shapes; the widget discriminates and renders the right one.

Options

addViews(server, options):

OptionDefaultNotes
viewsRequired, non-empty. Duplicate ids throw.
exploreToolName"explore_views"Rename the discovery tool.
renderToolName"render_view"Rename the execute tool.
renderDescriptionExtra text appended to render_view's description.

Live example

examples/dashboard is a runnable six-view server (sales_overview, exec_summary, revenue_trend, region_breakdown, order_funnel, department_spend) mixing single charts and dashboards over Streamable HTTP.

Next

  • Connecting a Database: return typed ChartData from a view's render so the encoding rides driver types, not a value sniff.
  • Dashboards: the DashboardSpec a dashboard-kind view returns.

On this page