Repository navigation
feat: interactive MCP Apps charts for create_chart in Claude and ChatGPT, compiled with Flint - #10016
nishantmonu51 wants to merge 5 commits into
Conversation
djbarnwal
left a comment
There was a problem hiding this comment.
Architectural review findings
1. One tool name, two schemas
MCPSpecProvider lets create_chart advertise a different input schema on MCP than in-app, under the same name. Those arguments are persisted to the same session tables, so every consumer of stored messages has to handle both shapes. Two separate tool names, or one schema, would remove that problem.
2. Feature gating is spread across four mechanisms
chat_chartsgates the tools.mcp_appsand themcp_apps_enabledannotation gate the widget on the runtime endpoint.RILL_ADMIN_MCP_APPS_ENABLEDgates the widget on the unified admin endpoint.- The hidden
chart_datagate is the user-agent prefix plus host-side enforcement of visibility.
The admin and runtime endpoints already disagree about when chart_data exists
3. Widget-driven query cost
Calls to chart_data aren't counted as billable tool calls, aren't persisted, and have no rate limit. A user flipping controls quickly, or a non-UI client calling the tool directly, can generate unlimited metrics queries. Queries are still tagged with the mcp source, but it's worth making a deliberate decision about metering and throttling here.
| func (s *Server) mcpHandler() (http.Handler, error) { | ||
| // The MCP Apps widget is served when enabled and the binary carries the widget bundle (see runtime/ai/mcpapp). | ||
| var widget *mcpapp.Asset | ||
| if s.opts.MCPAppsEnabled { |
There was a problem hiding this comment.
The unified MCP server serves the widget and links create_chart to it based only on the RILL_ADMIN_MCP_APPS_ENABLED env var. The per-project mcp_apps flag and the mcp_apps_enabled annotation only apply to the direct runtime endpoint.
There was a problem hiding this comment.
Gone: the unified server serves the widget whenever the binary carries the bundle, with no env var, and the per-project flag and annotation are removed. chat_charts is the only gate left.
| continue | ||
| } | ||
| t.RegisterWithMCPServer(srv) | ||
| t.RegisterWithMCPServer(srv, t.AppUI.Meta(widget)) |
There was a problem hiding this comment.
The direct runtime MCP server registers chart_data for every external client whether or not the widget is served, and ChartData.CheckAccess doesn't check mcp_apps. App-only calls are also left out of the tool_call metric and aren't written to the session.
There was a problem hiding this comment.
Fixed: chart_data is now registered only when the widget is served, on both endpoints, and app-only calls count in the tool_call metric. They stay unpersisted on purpose, since they are widget plumbing rather than conversation turns.
dd84335 to
4fb31e2
Compare
… Claude and ChatGPT - Serve a self-contained chart widget (`web-mcp-app`, Svelte + Vega) as the MCP Apps resource `ui://rill/widget/<hash>.html` from the runtime and the admin unified MCP endpoints - `create_chart` runs the chart query server-side for external MCP clients (`runtime/ai/chartquery`) and returns rows, fields, theme and `open_url` in `structuredContent`, with a markdown table fallback in `content` - App-only `chart_data` tool re-queries for grain, top-N and comparison controls; view changes are pushed to the model with `ui/update-model-context` - Gating: `chat_charts` for the tools, `mcp_apps` flag or `mcp_apps_enabled` annotation for the widget on the runtime endpoint, `RILL_ADMIN_MCP_APPS_ENABLED` on the admin endpoint - Transient AI sessions for non-persisting MCP requests, Makefile `runtime.mcpapp.embed`, CI wiring, docs
External MCP clients now describe a chart with a Flint `chart_spec` and a Rill `data` binding (metrics view, time range, filter, grain). The server resolves the query through `chartquery`, with per-channel top-N budgets and comparison folds, and returns the rows, field metadata, semantic types and the project theme. The widget compiles the spec with Flint on the client through the shared `custom-viz/flint/compile.ts`, re-queries grain, top-N and comparison through the app-only `chart_data` tool, and recompiles chart type, layout and curve locally. The deprecated `chart_type` + `spec` shape stays accepted for clients holding a cached tool list, and "Open in Rill" maps Flint-shaped calls into an Explore. Flint compile changes shared with canvas custom charts: Rill's theme owns typography, colors and the color palette (Flint's config and `tableau10` scheme no longer override it); multi-measure series take the measures' display names; row-faceted charts fill the box with upright row labels; derived axes take the measure's label and format; discrete time axes are labeled at their grain.
A built-in skill, distilled from Flint's authoring skill and tied to Rill's `create_chart` arguments, says what to check before charting, which chart type fits which question and the pitfalls that produce misleading charts (stacked vs grouped bars, mixed units, too many categories, totals next to parts). It is an always-apply skill for external clients, so Claude and ChatGPT receive it in the `ai_instructions` of `list_metrics_views` without loading skills themselves, and it is also served by `list_skills` / `load_skill`. Rill's own agents keep their prompt guidance. `rill init --agent` writes the same file as the `rill-charts` skill, and the analysis instructions point external agents at it instead of the in-app chart types.
Collapse the `mcp_apps` feature flag, the `mcp_apps_enabled` annotation and `RILL_ADMIN_MCP_APPS_ENABLED` into the existing `chat_charts` flag. The widget is served wherever the binary carries the bundle, and both the runtime and the unified endpoint advertise `chart_data` exactly when they serve the widget. `chart_data` calls now count in the `tool_call` metric like any other data tool while staying unpersisted. The widget debounces control changes and drops stale `chart_data` responses. Also fix the three golangci-lint findings in `runtime/ai/chartquery`.
The go-sdk v1.8 client defaults to a protocol whose handshake is a `server/discover` request, which the runtime correctly treats as transient. Claude and ChatGPT send `initialize`, which is what the session assertions in `TestMCPApps` exercise.
4fb31e2 to
af0cff6
Compare
|
Thanks, agree on all three.
Rebased on main as well, so CI runs again. |
Renders
create_chartresults as interactive charts inside Claude and ChatGPT through MCP Apps, with the chart specs compiled by Flint.ui://rill/widget/<hash>.htmlresource (a single-file Svelte + Vega bundle from the newweb-mcp-appworkspace, embedded in the binary) and links it fromcreate_chartvia_meta.ui. Hosts without MCP Apps support keep the markdown table and the "Open in Rill" link.create_chartwith a Flintchart_specplus a Rilldatabinding (metrics view, time range, filter, grain). The server resolves the query throughchartquery(per-channel top-N budgets, comparison folds) and returns the rows, field metadata, semantic types and the project theme. The deprecatedchart_type+specshape is still accepted for clients holding a cached tool list.custom-viz/flint/compile.ts), follows the host theme (light/dark, fonts, style variables) and the project theme, and in fullscreen offers controls: time grain, top-N and comparison re-query through the app-onlychart_datatool; chart type, layout and curve recompile locally. It reports its current view to the model viaui/update-model-context./-/ai/.../-/openroute now maps Flint-shaped calls as well.tableau10scheme no longer override it), multi-measure series are labeled with display names, row-faceted charts fill the box with upright row labels, derived axes take the measure's label and format, and discrete time axes are labeled at their grain.rill-chartsskill (distilled from Flint's authoring skill) tells external clients which chart fits which question and the pitfalls to avoid; it is always-apply, so it arrives in theai_instructionsoflist_metrics_views, andrill init --agentwrites it alongside the other skills.chat_chartsalone: it enables the chart tools and the widget, which every endpoint serves whenever the binary carries the bundle, andchart_datais advertised exactly where the widget is.chart_datacalls count in thetool_callmetric but are not persisted. Docs updated indocs/docs/guide/ai/mcp.md.Tested with the Go suites (
runtime/ai,chartquerygoldens, runtime and admin MCP servers), the widget's vitest and Playwright fake-host suites plus its 650 KB size budget, the web-common Flint compile suite, and live in claude.ai (custom connector over a tunnel) and ChatGPT (custom MCP server), both in dark mode; light mode through the harness.Checklist: