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_viewslists what's available (id, title, description, params). The agent calls this first to discover what it can render.render_viewrenders one view byview_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 aChartSpec(one preset chart), built withchart(rows, opts). - A
dashboard-kind view returns aDashboardSpec(a grid of KPI tiles, chart cells, and text blocks), with cells built bychartCell(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:
| Field | Type | Notes |
|---|---|---|
id | string | The render_view view_id enum value. Stable, snake/kebab-case. |
title | string | Shown in the catalog. |
description | string | One line, shown in explore_views and the render_view catalog. |
kind | "chart" / "dashboard" | Optional author hint for the catalog. |
params | zod raw shape | Optional per-view input params, validated on each render. |
render | (args) => ViewResult | Produce 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_idon achart-kind view is an error (a single chart has no cells to pick from).- An unknown
item_iderrors and lists the selectable chart-cell ids. - Selecting a KPI tile or text block errors: only chart cells render alone. (
KpiTileandTextBlockaccept anidfor addressability, but v1 selection renders chart cells only.)
How the agent uses it
- The agent calls
explore_viewsand gets the catalog: each view's id, title, description, kind, and params. - It calls
render_viewwith aview_id(andparamsif the view declares any). - The chosen view renders into the
ui://bonnard/chartwidget, 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):
| Option | Default | Notes |
|---|---|---|
views | — | Required, non-empty. Duplicate ids throw. |
exploreToolName | "explore_views" | Rename the discovery tool. |
renderToolName | "render_view" | Rename the execute tool. |
renderDescription | — | Extra 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
ChartDatafrom a view'srenderso the encoding rides driver types, not a value sniff. - Dashboards: the
DashboardSpecadashboard-kind view returns.