MCP Changes
Dated changes to Semaphor MCP's tools and their shapes.
Changes to the tools, their arguments and the outline, newest first. A change that alters a shape an agent may already read says what to check.
November 2026: dashboard calculated fields
- Agents add a dashboard's own calculated fields. A dashboard proposal takes
calculation.add(with the dataset'sdomainIdanddataset),calculation.updateandcalculation.remove. A card query names the field by itsname, as it names any field of its dataset. See Calculated fields. - Card queries name domain calculations. A domain's calculated facts, dimensions and metrics read back by name in the outline's queries and can be named in a new card's query.
- The outline lists them under
calculatedFields: each field's dataset, name, label, formula in field names,use,dataTypeandusedBy. - A domain proposal takes
calculation.updatefor a domain calculation's label, description or formula. Removing a domain calculation stays on the domain page. - New refusals:
calculated_field_name_taken,calculated_field_not_found,calculated_field_in_useandcalculated_field_not_for_matrix.
Check in your code: the outline may carry calculatedFields, and a card query may name one of them.
November 2026: dataset names checked at save
- A save checks the datasets a dashboard names. A proposal or save that adds a card or filter naming a dataset its semantic domain doesn't have, or a physical table more than one dataset reads, is refused with
document_invalid. Itsdetails.errorCodeisunknown_dataset;detailsnames the card or filter, the field, thedatasetNameand thecandidates, and the fix lists the datasets to name instead. - Older names still work. A name that reaches one dataset by another spelling, such as the table of a renamed dataset, saves as it is, and a name the saved dashboard already has on the same card or filter is let through.
Check in your code: a document_invalid refusal may now carry details.errorCode: "unknown_dataset"; re-read the domain's datasets with semaphor_list_datasets and propose again with one of them.
November 2026: table column filters
- Viewers filter a table's columns from their headers, on Tables, Detail tables and Matrix cards built from a dataset; it's on for every one.
card.addandcard.updatetakecolumnFilters: falseto turn it off for a table, andtrueto turn it back on. See Card settings. - Viewers search a table, on Tables and Detail tables built from a dataset.
card.addandcard.updatetaketableSearch: falseto remove the search box, andtrueto bring it back. - The outline reads it only when it's off: a table's
columnFiltersortableSearchisfalse. A SQL table, a chart or a KPI refuses it withsetting_not_for_card. - Agents pin filters on a card.
filter.addtakes acardId(instead ofsheetIds) and aposition;filter.updateandfilter.removetake a pinned filter's id and its card'scardId(required when copies of one visual share the id), andfilter.updatetakespositionto reorder. The outline lists each card'spinnedFiltersin order. See Pinned filters.
Check in your code: a card in the outline may carry pinnedFilters.
November 2026: number filters as ranges
- Number filters show as a range or a list.
filter.addandfilter.updatetakeshowAs: "range" | "list". A new number filter is a range unlessshowAssays list or the change givesdefaultValues. See Number filters. - A range opens with a condition.
defaultConditionis{ "operator", "values" }in the card filters' vocabulary (between,not_between,>=,<=,>,<,=,!=,is_null,is_not_null);nullonfilter.updateclears it. - The outline reads both. Number filters carry
showAs; a range carriesdefaultCondition, ordefaultValueswhen it was saved earlier with values to tick (defaultValues: nullclears them, adefaultConditionreplaces them). - SQL cards apply every condition. A dashboard filter's not equal, is empty and is not empty now apply to SQL cards, and numbers reach them as numbers, on every warehouse.
Check in your code: a number filter added without showAs or defaultValues is now a range, and its sentence says "a range". Code that read a number filter's default from defaultValues should also read defaultCondition when showAs is "range".
November 2026: totals per measure
- Agents set each measure's total. A table's
totalstakesmeasures, by column name:"overall"(the measure over the whole table, the default) or"sumOfRows"(its rows added together).rowis optional, so a change can set a measure's total alone. See Totals. - The outline reads each total. While a table's total row shows,
totals.measureslists what each measure totals to, including"none"for a table calculation with no single total. - Totals are Overall by default. A distinct count, an average or a median totals to its value over the whole table, where the total row was empty before; a comparison table totals its comparison columns.
Check in your code: code that read totals as exactly { "row": boolean } should allow measures. A total that changed from a sum to an Overall value can be put back with "sumOfRows" for that measure.
November 2026: semantic model editing
- Agents improve a semantic model.
semaphor_propose_changewith adomainIdtakesdomain.update,dataset.update,field.updateandmetric.update(labels, descriptions, Context, formats, the direction that's good, the field that names a row, hiding a field),calculation.add(a new calculation, placed by its formula) andrelationship.update(turn a relationship off, change its join). See Improve a Semantic Model and Change a Semantic Model. - Context changes item by item. Synonyms and example questions take
{ "add": [...], "remove": [...] }; instructions are replaced, andnullclears them. - One line per change, saying what changes, from what to what, and what stays; a definition's line says what in the domain reads it, and the proposal is
risk: medium. - Nothing typed by hand is lost. If a value a change replaces was changed after the preview, apply returns
proposal_stalewithchanged(each value as{ what, now }) and saves nothing. - More of a field in the schema.
semaphor_get_dataset_schemafields carryformatandisDisplayFieldwhen set.
Check in your code: semaphor_propose_change lists more changes for organization Authors and higher, so its definition is larger. A domain proposal holds either relationship.add changes or the new changes, not both, and up to 20. A change that sends a field's or metric's name is refused with "Change the label instead."
October 2026: date windows
- Agents state the editor's date windows.
timeWindowonsemaphor_analyze,semaphor_plan_analytics_recoveryobligations and card queries, anddefaultPeriodon a filter, take the windows a dashboard's date filter saves:periods(whole periods, such as"from": -3, "to": -1for the last 3 complete months, with"toDate": truefor so far),rolling(elapsed time; minutes and hours are rolling only),monthDayandabsolute. See Time windows. - The anchor is required:
"now","latest_data"or{ "lastComplete": unit }. Year to last completed month, once set only in the editor, is{ "kind": "periods", "unit": "year", "from": 0, "to": 0, "toDate": true, "anchor": { "lastComplete": "month" } }. - Saved windows follow the editor's rules. A card's
timeWindowand a filter'sdefaultPeriodcount days and longer in whole periods, and take minutes and hours only on a date field with a time of day. A default counts from the run, never from the latest data, and has no fixed dates. A refused window'sfixlists the windows to send instead. - Results report the window that ran.
metadata.resolvedTimeWindowhas the window as asked, itslabel, and thestart,end,timezoneandlatestDatait ran with. A period change reports the same fields inmetadata.timeWindow; a comparison reports its window without bounds, and the dates it compared ascomparison_windowentries inexecutionResult.constraints. - An empty period change says why.
metadata.noChangesgives areason(row_limit,no_changes_in_direction,no_values_in_windoworno_comparable_values), amessage, and afixwhen a different request helps. A period change no longer answers from a different period when its own window has no changes. - Readback is the stored window. The outline's
defaultPeriodand a card query'stimeWindoware what you'd send. A stored window that can't be saved again, such as a rolling 7-day default, carriesdefaultPeriodReadOnlyortimeWindowReadOnlywith the refusal and its replacements, and stays while the field is left out.
Check in your code: windows in the earlier form, { "unit", "value", "anchor"?, "completeness"? } and { "kind": "calendar", ... }, are refused with the form to send. anchor: "latest_available" is now "latest_data". metadata.timeWindowResolution is replaced by metadata.resolvedTimeWindow, and a period change's metadata.timeWindow takes the same fields: windowStart, windowEnd and latestAvailableAt are now start, end and latestData, and requestedAnchor, appliedAnchor, fallbackApplied, completeness and excludedPartialPeriod are gone (it keeps referenceTime, filteredChangeCount and unfilteredChangeCount). Code that treated an empty changes list as "no change" should read metadata.noChanges. The warning window_saved_as_fixed_dates is gone.
October 2026: first-run fixes
- The watch link after a new dashboard. In logged-in sessions,
semaphor_apply_changereturnswatchUrlin itstargetfor a dashboard it created, as it already did for one it saved. Thesemaphor_get_dashboard_detailsdescription says whatwatchUrlandwatchingare. See Watch Changes Land in Your Browser. - A change on the watch page is no preview. A proposal that goes to your watch page comes back with
deliveryand noconfirm, since there is nothing to apply, and itsnextnames the cards it added or changed, so the agent corrects one withcard.updateinstead of adding a second. - A new dashboard is laid out. After its changes, each sheet with cards is arranged on every screen size, unless a
card.addstates awidthorpositionor the changes includelayout.arrange. See Build a New Dashboard. - A width alone places a card beside the last one when the row has room, so two halves sit side by side; it used to start a new row.
- Refusals name fields as a query does. A refusal that lists a card's groupings writes them as
query.dimensionstakes them: a plain name on the card's dataset,{ "name", "datasetName" }for a joined one. Display labels were listed before and never accepted. - A row count keeps its label.
{ "name": "__count", "label": "…" }works incard.addandcard.update. - A format change reads "formats Revenue like $12,480", so the sample doesn't read as a value.
Check in your code: percent_of_total with scope: "partition_total" and no resetBy (or an empty one) is now refused with transform_field_invalid, listing the card's groupings; it used to give a share of the grand total. A partition_total read from the outline and sent back unchanged is still accepted. In logged-in sessions, code that reads confirm on a proposal should allow it to be absent when delivery is present.
October 2026: report a problem
semaphor_report_problem. An agent sends Semaphor support its own account of what went wrong, with your request in your words and, when one is involved, the proposal. It asks you first, and returns a report number. Every session gets it. See Report a Problem.
Check in your code: tools/list now has one more tool for every session. Code that checks for an exact tool count should expect one more.
October 2026: building a report
- A new dashboard in one proposal.
semaphor_propose_changetakescreate: { title, description }in place ofdashboardId, with up to 50 of the changes an edit takes. The proposal previews the cards with real numbers, and apply saves the dashboard private to you. Organization users and end users with the Power User role can create one. See Build a New Dashboard. - Sheets.
sheet.addadds a sheet andsheet.updaterenames one. A$handlefromsheet.addnames the new sheet in later changes. - Cards as tabs.
layout.tabsputs cards in one spot as tabs, the first named shown;layout.untabgives a card its own spot again. Cards keep their IDs, filter bindings and highlights. - Text cards.
card.addwithtype: "text"andcard.updatetaketextas blocks: headings, paragraphs and one-level lists of runs, each run text or one of the card's variables, withbold,italicandlink. Text is taken as written. The outline reads text cards in the same blocks. See Text cards. confirm: a new dashboard, a new sheet, tabs and a text card's words need the user's yes; renaming a sheet doesn't.
Check in your code: dashboard.generate is gone; build the dashboard with create and the changes you want, and a proposal made with it before now returns proposal_stale. semaphor_apply_change may return created entries of kind sheet. An outline text card is now editable with text blocks in place of query; a text card whose words come from a query stays read only.
October 2026: what a number means
- Comparisons and targets on a measure. A measure in
query.measurestakescomparison:inherit,off,previous_period,previous_year(with offsets, weekday alignment and to-date) ortarget. The outline shows each measure's own comparison. See What a Number Means. - Table calculations. A measure takes
transform:running_total,moving_average,difference_from_previousorpercent_of_total, withorderByandresetBy. Cards with a table calculation can now be changed through the MCP. - Which way is good, and how the change shows.
columns[name].polarity,comparisonDisplayandinheritComparisononcard.addandcard.update. A measure's direction from the semantic model reaches cards built through the MCP. semaphor_analyzetakescomparisonandtransformon its measures too.
Check in your code: an outline measure may now be an object with comparison or transform where it was a name. semaphor_analyze refuses a comparison with a key it doesn't take, naming it (invalid_comparison), where it used to ignore the key; offsets, alignment and to-date are refused there, since they are card settings.
October 2026: how cards read and show
- Card settings in a change.
card.addandcard.updatetakecolumns(each column'slabel,format,hiddenandwidth),totals,dataLabels,legendandaxes, and a chart's variant asorientation,stackanddonut. See How Cards Read and Show. - Highlights.
highlight.addandhighlight.removecolor cells and rows where a condition holds, shade a column on a color scale, or add data bars. - The dashboard's title and description.
dashboard.update. The outline shows the description. - The outline shows each editable card's settings, with
inheritsandeditorOnly. - The server's instructions give one recipe: read the outline, change only what the user asked, propose, and apply when
confirmisnot_neededor the user agrees. The tool definition lists a format's stored fields, and a refused format's fix states them. confirmon every proposal:not_neededwhen a proposal only changes how cards read and show, or the dashboard's title and description;neededotherwise. An apply checks again and refuses withconfirm_changedwhen that has changed.- Horizontal bars, stacked bars, stacked lines and areas and doughnuts can be changed.
Check in your code: the outline's type for those five variants is now the base type with the variant as properties. A stacked bar that read "type": "stacked_bar" now reads "type": "bar", "orientation": "vertical", "stack": "stacked", and a doughnut reads "type": "pie", "donut": true. Bar, line, area and pie cards carry their properties too.