Semaphor
MCP

How Cards Read and Show

Change a card's column headers, number and date formats, hidden columns, widths, total row, data labels, legend, axes, chart variant and highlights, and a dashboard's title, through Semaphor MCP.

An agent can finish a card the way a person does in the editor: name its columns, format its numbers and dates, hide a column, add a total row, put the values on the bars, move the legend, title an axis, stack the bars, or highlight the rows that need attention. It reads each setting in the outline and changes it with the same words, through semaphor_propose_change and semaphor_apply_change like every other change (see Making Changes).

Name a column, format it as dollars and hide another
{
  "dashboardId": "d_8f2k",
  "changes": [
    {
      "op": "card.update",
      "cardId": "c_suppliers",
      "columns": {
        "Net Purchase Value": { "label": "Purchase value", "format": { "type": "currency", "currency": "USD", "maximumFractionDigits": 0 } },
        "Net Tons": { "hidden": true }
      }
    }
  ]
}

The proposal reads it back in plain words, with a value in the new format:

Renames the column "Net Purchase Value" to "Purchase value" in "Purchases by supplier", formats Purchase value like $12,480, and hides Net Tons for everyone (it still sorts and filters).

Read the Settings

semaphor_get_dashboard_details with fields: ["outline"] gives each editable card its settings, in the shape a change takes:

A card's settings in the outline
{
  "cardId": "c_suppliers",
  "title": "Purchases by supplier",
  "type": "table",
  "editable": true,
  "columns": {
    "Supplier": { "field": "supplier_id", "role": "dimension", "label": "Supplier", "inherits": ["label", "format"] },
    "Net Purchase Value": { "field": "net_purchase_value", "role": "measure", "label": "Net Purchase Value", "width": 180,
                            "format": { "type": "currency", "currency": "USD" }, "inherits": ["label"] }
  },
  "totals": { "row": true, "measures": { "Net Purchase Value": "overall" } },
  "highlights": [
    { "ruleId": "h_1", "column": "Net Purchase Value", "apply": "row", "enabled": true,
      "when": { "operator": "<", "value": 1000 }, "color": "red" }
  ],
  "editorOnly": ["Column display (Supplier)"]
}
FieldWhat it holds
columnsEach column by the name its header shows: label, format (the card's own), hidden, width, and inherits, what it takes from the model. A column that inherits its format shows the model's format, which comes with the query's results. field and role say which column it is.
orientation, stack, donutA chart's variant (see Chart Variants)
columnOrder, totals, dataLabels, legend, axesThe card's own settings, when it has them. columnOrder shows only when the author has set an order.
highlightsEach highlight rule, with the ruleId that removes it
editorOnlySettings the card has that a change doesn't state, in the editor's words. A change keeps them exactly as they are.

Send a card's settings back as you read them and nothing changes, so an agent can copy them, change only what the user asked, and send them back.

Columns

columns on card.add or card.update names each column by its name in the outline. Send only the settings that change.

SettingValues
labelThe header. null returns it to the model's label.
formatA number, currency, percent, duration or date format, in the shape the outline shows. Send a whole format with its type, or only the fields to change, which are merged onto the format the column shows. null returns the column to the model's format.
hiddentrue hides the column for everyone who views the card; it still sorts and filters. false shows it again. Tables and detail tables.
widthPixels, as the table draws it, from 50 to 500. null returns it to automatic. Tables and detail tables.
Formats
{ "Revenue": { "format": { "type": "number", "compact": true } } }
{ "Margin": { "format": { "type": "percent", "percentValueMode": "fraction", "maximumFractionDigits": 1 } } }
{ "Order month": { "format": { "type": "temporal_bucket", "presentation": { "mode": "preset", "preset": "long" } } } }
{ "Ordered at": { "format": { "type": "date", "preset": "short" } } }
{ "Revenue": { "format": { "maximumFractionDigits": 0 } } }
  • A date grouped by month, quarter or year takes a temporal_bucket format: a preset (short, long, numeric, iso, and the week, quarter and fiscal presets at their grains) or, for a month or year, a pattern no finer than the grain. A date grouped by hour, minute or second takes a pattern such as HH:mm, or h:mm a for a 12-hour time, on every database; a pattern the grain doesn't take is refused with the patterns it does.
  • A date shown as it is, in a detail table, a SQL table or a Matrix level, takes a date format with a preset or a pattern. A detail table shows only the time with a pattern such as HH:mm, or a 12-hour time with { "pattern": "h:mm a", "dialect": "date_fns" } on PostgreSQL, Snowflake and ClickHouse (see Dates and times in a table). The same field may appear twice in a detail table; the outline names the copies apart.
  • Each format is checked against the card that draws it, so the sentence's sample is what the viewer sees. A column whose card shows its values as they come (a chart's categories, text on a table) takes no format, and the refusal says where one applies.
  • A SQL table's columns are named by the query's result columns. The proposal runs the card once to read them.

Card Settings

SettingValuesCards
columnOrderColumn names, as the outline shows them, in the order wanted; the columns not named keep their places (see Column order)Tables and detail tables, SQL ones included
totals{ "row": true } adds a total row; false removes it. measures sets each measure's total (see Totals)Tables built from a dataset
dataLabels{ "show": true } shows the values on the chart; add format for a number format of their own, or null to follow the measureBar, line, area and pie charts
legend{ "show": true, "position": "bottom" }; position is top, bottom, left or rightBar, line, area and pie charts
axes{ "x": {...}, "y": {...} }, each { "title", "min", "max" }; null removes a title or returns a bound to automaticBar, line and area charts
columnFiltersfalse stops viewers filtering the table's columns from their headers; true lets them again. On unless turned off (see Table column filters)Tables, detail tables and Matrix cards built from a dataset
tableSearchfalse removes the table's search box; true brings it back. On unless turned off (see Table column filters)Tables and detail tables built from a dataset
trendtrue draws a line of the KPI's value over the period it counts, on the date it counts on (no Trend By needed); false leaves the space under the value to the summary's drivers when the summary shows on the card, else a target's progressKPIs built from a dataset
trendGrainThe trend's grain: "automatic" (fits the period the KPI counts: Last 7 days by day, Last 90 days by week, Year to date by month), or "hour", "day", "week", "month", "quarter", "year". A KPI with a Trend By draws by the Trend By's grainKPIs built from a dataset, without a Trend By
Values on the bars, the legend at the bottom, and the vertical axis from zero
{
  "op": "card.update",
  "cardId": "c_rev_region",
  "dataLabels": { "show": true },
  "legend": { "show": true, "position": "bottom" },
  "axes": { "y": { "title": "Revenue", "min": 0 } }
}

x and y are the axes as they appear on screen: on a horizontal bar chart, y lists the categories and x shows the values. A range goes on the axis that shows values.

Column order

columnOrder sets the order every viewer starts with, as an author's drag of a column header in the editor does, and saves the same thing. Any column can go anywhere, a measure between two group columns included. Name only the columns to move: the rest keep their places after the column they follow.

Put Net between Material and Notes
{
  "op": "card.update",
  "cardId": "c_sales",
  "columnOrder": ["Yard", "Material", "Net", "Notes"]
}
  • The readback names the new order: "Puts the columns of "New sales" in order: Yard, Material, Net, Notes." Hidden columns are left out of it.
  • The query doesn't change: only the columns' order on screen and in downloads.
  • A table pivoted by columns keeps its row columns before the pivoted ones: order the row columns among themselves and the measures among themselves. A measure before a row column is refused with value_invalid and the rule in words.
  • An unknown name is refused with column_not_found and the card's columns in allowed; a name given twice is refused with value_invalid.
  • SQL tables name their result columns; the proposal runs the card once to read them.
  • Viewers who arranged the table themselves keep their own order; Reset columns returns them to this one.

Totals

While a table's total row shows, the outline lists what each measure totals to, by the column's name: "overall" (the measure worked out once over the whole table, the default), "sumOfRows" (the rows added together), or "none" (a table calculation with no single total, such as a moving average). A change sets a measure to its default, as the outline shows it, or to "sumOfRows"; row can be left out to change only the measures.

Add a total row and add up Net Tons row by row
{
  "op": "card.update",
  "cardId": "c_purchases",
  "totals": { "row": true, "measures": { "Net Tons": "sumOfRows" } }
}
  • The readback says what each total is: "Adds a total row to "Purchases by supplier" and totals Net Tons as the sum of all rows." Sum of rows on a distinct count or an average adds "(adds each row's value; a customer in two rows counts twice)".
  • A total the measure can't have is refused with value_invalid and what it takes: "overall" on a moving average reads "can't have an overall total: A moving average has no single total.", with allowed ["none", "sumOfRows"].
  • A column that isn't a measure is refused; a total is set for each measure. A measure can also be named by its result alias.
  • A total row change needs the user's yes, since the total runs its own query.
  • SQL tables take no totals; their total row adds up the rows as before.

A KPI's trend runs on the date the KPI already counts on: its Trend By, else the dataset's primary date, else the field of the dashboard's date filter. "trend": true needs no Trend By. A trendGrain on a KPI with a Trend By is refused with value_invalid and the fix: change the Trend By's timeGrain, or remove the Trend By to set a grain here.

Chart Variants

A card's type is one of the eight card types, and its variant is a property of it:

Varianttype and properties
Horizontal barbar with "orientation": "horizontal"
Stacked barbar with "stack": "stacked" (horizontal too, with both)
Stacked line, stacked arealine or area with "stack": "stacked"
Doughnutpie with "donut": true
Stack the bars by a second field
{
  "op": "card.update",
  "cardId": "c_rev_region",
  "stack": "stacked",
  "query": { "dimensions": ["region", "category"] }
}

A variant change keeps the card's legend, data labels, axes and colors. A bar chart turned horizontal keeps each axis's title and range with its role, so the value axis keeps "Revenue" and its range. Changing to another chart family, such as a bar chart to a table, works like the editor's type picker.

Highlights

highlight.add colors the cells or rows of a table, detail table or KPI where a condition holds, shades a column on a color scale, or adds data bars:

Rows with margin under 10% in red
{ "op": "highlight.add", "cardId": "c_orders", "column": "Margin", "when": { "operator": "<", "value": 0.1 }, "color": "red", "apply": "row" }
A color scale and data bars
{ "op": "highlight.add", "cardId": "c_orders", "column": "Margin", "scale": { "low": "red", "high": "green" } }
{ "op": "highlight.add", "cardId": "c_orders", "column": "Revenue", "bars": { "color": "blue" } }
FieldValues
columnA column's name in the outline
when and color{ "operator": "=", "!=", ">", ">=", "<" or "<=", "value" }, { "operator": "between", "values": [low, high] }, { "operator": "contains", "value" }, { "operator": "is_null" }, { "top": n } or { "bottom": n }, with the color for the cell or row where it holds
scale{ "low", "high", "mid" }: colors from the column's lowest value to its highest
bars{ "color" }: a bar in each cell, in proportion to its value
applycell (the default) or row, for a condition

Colors are red, amber, green, blue and gray, the editor's own, or a hex color such as #1d4ed8. A KPI's highlight colors its value when a condition holds.

The proposal's change entry gives the new rule's ruleId, and so does the outline afterwards. highlight.remove takes it and keeps the card's other rules:

Remove one highlight
{ "op": "highlight.remove", "cardId": "c_orders", "ruleId": "h_1" }

The Dashboard's Title and Description

Rename the dashboard
{ "op": "dashboard.update", "title": "Weekly sales review" }

description sets the dashboard's description; null removes it. The outline shows both.

Confirm

A proposal's confirm says whether the user needs to see it before it's applied:

  • not_needed: the proposal only changes how cards read and show (labels, formats, hidden columns, widths, data labels, legend, axes, variants and highlights) or the dashboard's title and description. An agent can apply it as soon as it's proposed.
  • needed: anything else, such as a query, a type, a card added or removed, a filter, a total row or a KPI's trend (each runs its own query). It's also needed when a change would remove something viewers saved, such as a column their bookmarks use; the viewer_settings_affected warning says what. Show the summary to the user first.

The apply checks again, and refuses with confirm_changed if the proposal now needs the user's confirmation.

When a Setting Is Refused

A refused setting changes nothing in the proposal and says what to do. Each refusal names the card and column as the user sees them, lists what the card or setting takes in allowed, and often carries a suggestedChange you can send as written.

CodeWhen
setting_not_for_cardThe card doesn't take that setting, such as axes on a pie chart
column_not_found, column_ambiguousNo column, or two, have that name; allowed lists the card's column names
columns_unavailableA SQL table's query couldn't be run to read its columns
format_invalidA format that isn't valid, or that the column's card doesn't draw
date_finer_than_grainA date format finer than the column's grouping; allowed lists that grain's presets
color_unknownA color that isn't one of the five names or a hex color
highlight_not_foundNo highlight on the card has that ruleId
value_invalidA width outside the column's range, an axis that ends before it starts, an empty title, or two labels for one column

On this page