BonnardBonnard

Getting Started

Add interactive, agent-ready charts, dashboards, and views to your MCP server with @bonnard/mcp-charts.

@bonnard/mcp-charts adds interactive charts to any MCP server. Your agent asks for data, you run the query, and the result renders as an interactive chart inside the MCP host (Claude, ChatGPT, and other MCP Apps clients). You write no frontend code: one widget renders across every MCP Apps host.

Pre-1.0: the API may change before a 1.0 release.

Install

npm install @bonnard/mcp-charts

Two ways to add charts

There are two shapes, depending on who chooses the query:

  • Ad-hoc visualize (the agent writes SQL). Add a generic visualize tool with addCharts. The agent writes SQL against your warehouse and the rows render as a chart. Best for open-ended exploration.
  • Named views (you author the queries). Register named views with addViews. Each view returns a single chart or a full dashboard, and the agent picks a view instead of writing SQL. Best for repeatable analytics.

Both paths render through the same ui://bonnard/chart widget and the same encoding logic. Start with visualize below; jump to Named views for the authored path (and Dashboards for the DashboardSpec a dashboard view returns).

Quickstart: the visualize tool

Call addCharts on your existing MCP server and give it a read-only query callback. Use a bundled warehouse adapter so the driver's column types become chart field kinds:

import { addCharts } from "@bonnard/mcp-charts";
import { postgresRunSql } from "@bonnard/mcp-charts/postgres";

addCharts(server, {
  runSql: postgresRunSql(pool), // maps driver column types to chart field kinds
  discovery: { toolName: "explore_schema" }, // your schema-discovery tool
});

That registers a visualize tool and a ui://bonnard/chart widget resource. The agent calls visualize with SQL (and optional presentation hints); the rows render as a chart in the host, with a text fallback for non-widget clients. The agent chooses the chart type, or omit it to auto-detect from the shape of the data. See Chart Types.

Warehouse adapters

Adapters ship for Postgres, BigQuery, DuckDB, Snowflake, and Databricks. Each lives at its own import subpath and turns a native driver result into typed chart data. See Warehouse Adapters.

Writing runSql by hand

Without an adapter, return ChartData yourself. Types matter: the resolver infers field kinds from the row values, so a driver that returns numbers as strings (Postgres NUMERIC/BIGINT, for example) charts a measure as a category unless you declare typed fields. This "numbers must be numbers" story, and how to declare types, is covered in Connecting a Database.

addCharts(server, {
  runSql: async (sql) => ({ rows: await db.query(sql) }), // only if your driver returns native JS types
});

The full export surface

Beyond addCharts, the package exports the authoring surface for dashboards and views:

ExportWhat it does
addChartsRegister the ad-hoc visualize tool (agent writes SQL).
addViewsRegister explore_views + render_view over a set of named views.
chartBuild a ChartSpec from rows or typed ChartData.
chartCellBuild a dashboard chart cell from rows or typed ChartData.
explainDiagnose the encoding in a test, without rendering.
buildChartData, defaultNormalizeCell, assertReadOnlySqlAdapter authoring kit. See API Reference.

Security

visualize executes agent-written SQL against your database. Treat it as untrusted input.

The bundled adapters reject non-read SQL by default: each one requires a single SELECT or WITH statement and rejects write and DDL keywords, and the Postgres adapter also runs the query in a READ ONLY transaction. You can turn that off per adapter with { readOnly: false }. If you write your own runSql, call assertReadOnlySql to get the same check.

That check reads the SQL string; it does not sandbox execution. Connect runSql to a read-only, least-privilege role scoped to the data you want exposed. Your database permissions are what actually enforces the boundary.

On this page