# Widgets

<!-- Generated by scripts/gen-docs-mcp.mjs from the shared tool catalog. Do not edit by hand —
     edit the catalog in packages/shared/src/mcp.ts and regenerate. -->

## Tools

A widget is two halves: a **pipeline** (which data-source tools feed it, plus the transform that shapes the rows) and a **payload** (the chart or table config). `add_widget` creates it, and `update_widget` wires it (`pipeline`), dresses it (`style`) and places it (`layout`).

| Tool | What it does | |
|---|---|---|
| [`list_widgets`](#list_widgets) | List widgets | Read-only |
| [`get_widget`](#get_widget) | Get widget | Read-only |
| [`add_widget`](#add_widget) | Add widget | Additive write |
| [`update_widget`](#update_widget) | Update widget | Destructive write |
| [`get_chart_recipe`](#get_chart_recipe) | Get chart recipe | Read-only |
| [`render`](#render) | Render chart | Read-only |
| [`remove_widget`](#remove_widget) | Remove widget | Destructive write |

## list_widgets

`Read-only`

List all widgets on a dashboard: id, title, type (payload dialect), kind (the semantic renderer — kpi | table | pivot | progress for Vistazo-native widgets, the lead series type for ECharts widgets, markdown, visualization), layout, and the current pipelineId. ARCHIVED (removed) widgets are hidden by default — pass includeArchived:true to list them too, each marked archived:true; that is how a widget to restore is found without already knowing its id. Pass `query` instead to SEARCH widget titles and descriptions across every dashboard you can access in the workspace (`dashboardId` then optional). For one widget's full config and pipeline wiring, use get_widget; to retitle or edit a description, update_widget's `style`.

### `query`

Search text: a full-text search across widget titles and descriptions in every dashboard you can access within the workspace, in place of one dashboard's list.

## get_widget

`Read-only` · `Reaches outside Vistazo`

Read ONE widget; `include` picks what comes back (default `config`), and each value's rules are on `include`. `config`: the widget in full — title, description, type + semantic kind, archived (true = soft-deleted by remove_widget, restorable with its `restore`), layout, the stored payload config (echartsConfig / vistazoConfig / markdownContent / visualizationRef), the skill fields, and the complete pipeline wiring: every fetch stage's mcpId / toolName / argumentTemplate / role plus the persisted transform function body; it fetches nothing. `data`: the last published rows without refreshing, the cheap way to read a chart's numbers. `inputs`: `config` plus a live sample per fetch stage, to author a transform against. READ `config` BEFORE EDITING a widget: update_widget's `pipeline` and `style` replace what they touch, so fetch the current wiring/config here and re-send it with your modification instead of reconstructing it from guesses. One value returns that read alone; several return an object keyed by value.

### `include: inputs`

the HYDRATE read for editing a widget's data — everything `config` returns PLUS, per fetch stage, the arguments as rendered against the dashboard's current parameter values, the alias the transform receives that stage under (`inputs.<alias>`; the first stage is also `rawResponse`), the full row count, and a fresh 3-row sample of the unwrapped payload, fetched live from the source now, nothing cached or written. Each stage also carries the source's `note`, and a partial result (hasMore) adds a warning naming the stage. Read it BEFORE writing or changing a transform, so it is authored against the real shape instead of a guess; then update_widget's `pipeline` with transformFunctionBody + fields and NO transformedData validates that transform against these same sources before anything is written. A stage whose source is unreachable comes back with `error` instead of a sample (a `style` edit never needs the data; a transform edit needs every stage reachable). Costs one call per stage against the live sources, so it is a deliberate authoring read, not a browse. Requires a connection to each stage's server (CONNECTION_NOT_AUTHENTICATED names set_connection as the remedy).

### `include: data`

the widget's last refresh result without refreshing — the cheap way to read a chart's numbers; echoes `title`. Two freshness facts, never conflated: `lastRefreshedAt` is when the pipeline last RAN, `latestPeriod` ({field, value, granularity}) the newest period IN the rows. Refreshed minutes ago with latestPeriod 2019 means a pinned fetch window (check_freshness names the argument). latestPeriod is best-effort; null is never evidence of freshness. `fields` gives the units and precision each field RENDERS with — check it before reading meaning into a small difference; `formatUnknown` columns have unknown units. `status` is the last run (idle, running, or error with errorMessage); `dataState` the cache (populated, empty = a real zero-row result, never_materialized, missing, no_pipeline). With no rows, `nextAction` names the step; `sourceOnly: true` marks a widget that feeds a filter and draws no chart.

## add_widget

`Additive write`

Add a widget to a dashboard. `type` names the PAYLOAD you will supply — vistazoConfig (a native kpi | table | pivot | progress, picked with `kind`), echartsConfig (an ECharts option), visualizationRef (a library entry, via entryId), markdownContent (prose, no pipeline) or file (one uploaded image or PDF via `fileId`, optional `caption`; no pipeline). Placing a file SHARES it with everyone who can view the dashboard, the public too when it is public. Where dialects overlap take the purpose-built kind: one value is a vistazoConfig kpi, never an ECharts gauge. The chart kind lives INSIDE the payload (series[].type, or `kind`). GRID: 12 columns; a row is 80px tall plus a 12px gap, so h rows draw h×80 + (h−1)×12 px (h=4 ≈ 356px). Widths divide the CONTAINER — the canvas beside the open sidebar (~315px), not the window. By container width: ≥996px the authored 12-column layout; 768–995px two per row in mobile order (widgets wider than 6 go full width); 480–767px widgets of width ≤3 pair up two per row and everything else is one per row; below 480px everything is one per row. Placement defaults to full width (w=12) below existing widgets; a narrower widget with no x/y goes beside the last row's widgets when they leave room for it, else below; pass x/y (as update_widget's `layout`) to place it exactly — a side-by-side row is chart {x:0,width:8} + KPI {x:8,width:4} at one y. A series with `seriesField: "*"` is a TEMPLATE that expands into one series per numeric row field no other series claims (one line per country when the countries are a multi_select). A file widget shows its file at once. The payload may ride on the create (`markdownContent`, `echartsConfig` or `vistazoConfig`, matching `type`, validated as update_widget's `style` validates it), so a heading or note is ONE call; without one the widget starts empty for update_widget's `style`. A data widget still needs update_widget's `pipeline`, then refresh, then VERIFY with get_widget include data (rowCount 0 means the fetch matched nothing). Requires edit access.

## update_widget

`Destructive write` · `Reaches outside Vistazo`

Change an existing widget. Pass the sections you change, any mix in one call, applied in this order: `pipeline` REPLACES the whole data wiring (a fresh pipelineId), `pipelineArguments` edits only stage arguments in place, `style` sets the title, description and payload (it is how a widget is renamed), and `layout` moves or resizes it. Each section's own description holds its rules. READ get_widget FIRST: `pipeline` and a `style` payload replace what they touch, so re-send the current wiring or config with your change. A section that fails stops the call, and the reply names the sections already applied. Requires edit access.

### `style`

The widget's presentation: its title, description, and the payload its type declares — echartsConfig, vistazoConfig (whose `kind` selects kpi | table | pivot | progress), markdownContent, or a file widget's `caption`. Markdown shows an uploaded image inline as `![alt](attachment:<fileId>)`, which shares that file with everyone who can view the dashboard (the public too when it is public). This section is also what RENAMES a widget — title and description live here — and what repoints a visualizationRef widget at another library entry (`entryId`). PARTIAL: fields you omit are left unchanged, so a title-only edit never touches the stored payload; a payload you do pass replaces the stored one and must match the widget's type. ECharts series binding: series[].name IS the row-field key the series reads — ADDING a series means adding an entry whose name is the new field's key; use seriesNames only when the DISPLAY label must differ from the field key (it renames while preserving the binding — never repurpose an existing series' name for display text). A series bound by seriesField may carry {{ params.\<key> }} in its name, resolved like a title, so a highlighted series reads as the selected member. The `echartsConfig` field's own description is the one source for the drawable series types, the row shape each takes, the MUST flags whose absence draws a wrong chart, and the orientation / semantic-formatting / bar-zero rules — read it there rather than from here, and call get_chart_recipe for the composed forms (waterfall, Gantt, beeswarm, violin, maps, highlight-member to pick out one member, small-multiples for mixed magnitudes) and a complete kpi/table/pivot/progress config. For position/size/mobile-order use `layout`; for data wiring, `pipeline`. After an echartsConfig edit, render (kind widget) shows the card as drawn — clipped or overlapping labels, a legend over the plot.

### `layout`

The widget's grid layout; only the fields you pass change. GRID: 12 columns; a row is 80px plus a 12px gap (h=4 ≈ 356px). Widths divide the CONTAINER (the canvas beside an open sidebar), not the window: ≥996px the authored layout; 768–995px two per row in mobile order (wider than 6 goes full width); 480–767px widths ≤3 pair up, the rest one per row; below 480px one per row. `y` is stacking order (empty rows compact away); `x` (0–11) and `w` (2–12) place within a row, x+w ≤ 12, so widgets sharing a `y` share a row ({x:0,w:4} beside {x:4,w:8}). `h` is 2–20, or 1 for a chromeless markdown widget. `order` (≥0) is the narrow-screen stack position; widgets with one sort before all without, so set it on every widget or none. Execute the user's desktop coordinates faithfully.

### `pipeline`

Wires one or more connected MCP data-source tools to the widget — the REWIRE section. It REPLACES the whole pipeline (a fresh pipelineId; the old one is deleted), so read the wiring with get_widget first and re-send every stage with your change. To change only a stage's ARGUMENTS (a country code, a pinned end_year), use `pipelineArguments`: it edits them in place. Single tool: mcpId + toolName (+ argumentTemplate, run in the dashboard's workspace — no `workspaceId` in it); a \<mcpId>__\<tool> name is refused. Several: `invocations` (2+ stages fetched in parallel) feeding ONE transform, which receives `inputs` keyed by each stage's `alias` (else its tool name, _2/_3 on repeats) plus `rawResponse` for the first stage. Send transformFunctionBody + fields and OMIT transformedData: the server fetches every stage, runs your transform, seeds its rows, and REFUSES the write (error `transform_degraded`) if it throws, returns no rows from sources that had some, misses a declared field, or a source is unreachable. When every source returns nothing, zero rows publish with a `note` — never invent a placeholder row. get_widget include inputs samples each stage first. An EXTERNAL MCP client must send transformFunctionBody: a refresh it triggers never writes a transform. Transforms RESHAPE stage results and never carry data — no fetched values as literals. Refused with nothing written: an argument outside the tool's input schema, a {{ dates.\<expr> }} outside the date grammar, a duplicate alias. Published rows must fit 900 KB (bytes, not rows). Rolling windows: see argumentTemplate; pair them with update_dashboard's `cadence` or the window stops at its last refresh. A pipeline serves ONE role: `widget` (chart data) or `parameter_source` (dropdown options). Its stages[] carry each source's `note`; it warns when a source returned a partial result (hasMore), every measure is 0 or null, rows leave cells empty, or the title/description still names an argument value you changed (fix with `style` / update_dashboard).

### `pipelineArguments`

Stage edits that change the widget's fetch ARGUMENTS in place without re-sending its pipeline — the small-edit counterpart to `pipeline`, which replaces every stage, the transform and the fields and mints a new pipelineId. Here the pipelineId, the stage list, the persisted transform and the fields are untouched, and stages you don't name keep their arguments. Reach for it for "append ;ITA to country_code", "move end_year to 2025", "drop the pinned end bound so the window rolls". One entry per stage you change, each targeting by `invocationId` (from get_widget) or `stageIndex` (0-based); omit both when the pipeline has one stage. Per entry, EITHER `setArguments` / `removeArguments` (removing an argument that isn't there is refused) OR `argumentTemplate` to replace that stage's whole template. Values may be {{ params.\<key> }} or {{ dates.\<expr> }} holes; an untouched hole survives verbatim. Before anything is written the merged result is checked against the tool's input schema and the date grammar (a bad value is refused with nothing changed), and the stale-prose check runs; the response also warns when the republished rows leave cells empty, as `pipeline` does. With a persisted transform the merged arguments are also FETCHED and the transform re-run first: a patch that breaks it is refused (error `transform_degraded`), and one that works republishes the rows at once (`rows.source` 'validated'; zero rows when every source returned nothing); without one the previous rows stay ('previous') until refresh. The response echoes every stage's effective wiring (with the source's `note` when re-fetched; a partial result (hasMore) warns), before/after for what changed, so verify the merge rather than assume it.

## get_chart_recipe

`Read-only`

Return the build recipe for a chart form — one of: waterfall, gantt, beeswarm, population-pyramid, violin, boxplot-outliers, xy-scatter, choropleth, flow-map, highlight-member, small-multiples, kpi, table, pivot, progress. highlight-member is the one for "pick out", "highlight" or "emphasise" ONE member (a country, a company, the selected filter value) against the rest — in a bar ranking, a line over its peers, or a reference line. small-multiples compares indicators of different magnitude, one panel each, where a shared axis lets the largest flatten the rest. kpi, table, pivot and progress each return a COMPLETE vistazoConfig to copy (a KPI's format, comparison/companion secondary and sparkline; a table's columns and rules) with the add_widget kind and size it needs. The composed forms are compositions of ordinary series and transform columns rather than ECharts series types, so the echartsConfig argument docs name them without spelling them out; each recipe returns a minimal config, the row shape the transform must emit, and the rules that make it draw correctly (a waterfall's stackStrategy, a Gantt's value-axis type, the atlas a choropleth resolves against). Call it before authoring one of those instead of guessing, and pass the config to update_widget's `style`. A form no series type or recipe draws (a ridgeline/joyplot, a marimekko) is a library visualization through save_visualization; never fake one from offset series. A static lookup: it reads no workspace data, fetches nothing, and writes nothing.

## render

`Read-only`

Render a chart ON THE SERVER and read what it drew — call it BEFORE telling the user a chart renders or looks right, fix what it reports, render again. kind 'widget' draws an echartsConfig widget (id = widgetId) as its card does; kind 'visualization' runs a library entry (id = entryId) — after every save_visualization, and after its data changes. A chart that cannot draw returns ok:false with the error. Read the summary first. A widget draws its PUBLISHED rows and is refused without them (widget_rows_never_run, _error or _expired) — refresh first, so a blank chart always means the chart. A visualization takes `rows`, else `widgetId`'s published rows, else its sampleRows. KPI, table and markdown widgets are refused. Headless: label widths are estimates, so a borderline hit is a hint, not a verdict. Both kinds share one hourly render ceiling.

## remove_widget

`Destructive write`

Remove a widget — a SOFT DELETE: hidden everywhere, its pipeline PARKED, nothing destroyed — or bring a removed one back with `restore: true`, whose description holds the restore's rules. Confirm with the user only when the request does not identify the widget unambiguously ("drop the chart" on a board with three); when it names the widget, or the id you resolved matches what was asked, the request IS the confirmation. A removal's dashboardId must be the widget's own dashboard, or the call refuses. The response echoes its title and kind plus restorable:true. No cleanup call follows: it leaves list_widgets (unless includeArchived:true, and its `query`), get_dashboard, the canvas and the public link, and its pipeline stops running and spending. get_widget still reads it by id; every other write refuses while it is archived. Requires edit access.

### `restore`

true brings back a widget remove_widget archived, with its pipeline, wiring, transform and last published rows intact — the widget counterpart of archive_dashboard's restore:true. Not manage_version, which rolls a whole dashboard back to a version. Find it with list_widgets includeArchived:true. It returns to its STORED grid slot; if a widget added meanwhile sits there, compaction pushes one down, so the response echoes its position for update_widget's `layout`. Its data is as old as the removal: refresh to bring it current. Refuses a widget that is not archived.
