Live Changes in Embedded Dashboards
Let your own agent change the dashboard your user is looking at, unsaved, with Undo, using one token for the iframe and the MCP.
Your product already shows a Semaphor dashboard in an iframe, and it has its own agent. With live changes, your user asks your agent for a change ("add revenue by region as a bar chart", "make that weekly", "rename the Region filter to Area") and it appears in the embedded dashboard a moment later, without a refresh.
The change lands unsaved. Your user sees it, can undo it, can keep editing by hand, and keeps everything with Save or discards it with Cancel, as with any edit they make themselves. Your agent reads what is on screen, unsaved cards and filters included, and is told what happened to each change. You add no front-end code: your agent keeps calling the Semaphor MCP from your server, and your page keeps showing the iframe.
Set It Up
Mint one live token per user session
Call POST https://semaphor.cloud/api/v1/token from your server with the mcp option and live: true. The same token opens the embedded dashboard and calls the MCP.
curl -X POST https://semaphor.cloud/api/v1/token \
-H "Content-Type: application/json" \
-d '{
"projectId": "p_...",
"projectSecret": "<project-secret>",
"tenantId": "tenant_123",
"endUserId": "maria",
"endUserEmail": "maria@acme.com",
"targetDashboardId": "d_8f2k",
"parentOrigin": "https://app.acme.com",
"mcp": { "live": true },
"tokenExpiry": 28800
}'| Field | What it does |
|---|---|
mcp: { "live": true } | Makes the token live: the iframe it opens becomes a live screen, and the MCP changes that screen instead of saving. |
mcp.scopes | Read scope (mcp:read), the default and the only scope a live token takes. Your agent changes the dashboard on screen, and your user's Save keeps the change; the token itself never saves. If your agent should save directly, use a separate write token kept on your server; live with mcp:write is refused with mcp_live_write_not_allowed. |
mcp.sql | On by default. false turns off the MCP's SQL tools and the SQL form of a card in changes. The dashboard's own cards, SQL cards included, still run as in any embed. |
| Identity fields | orgUserId, or endUserId with tenantId, as for any MCP token. Changes reach the screen only when that user can edit the dashboard. |
targetDashboardId | The dashboard the iframe opens. |
parentOrigin | Your page's exact origin, if you want the optional host events below. |
tokenExpiry | Seconds, 60 to 86,400 (24 hours). Mint it for a working session: an open embed's token isn't refreshed. |
Keep the project secret on your server
Mint tokens on your backend. Pass the accessToken to your page for the iframe and to your agent for the MCP; never send the project secret to a browser.
Show the iframe with the toolbar
Put the token in the iframe and show the toolbar, so your user has Save and Cancel:
<iframe src="https://semaphor.cloud/embed/<accessToken>?showControls=true" width="100%" height="100%"></iframe>The iframe connects for live changes on its own while it shows a dashboard your user may edit, with the toolbar shown. Nothing else is needed on your page.
The connection is a long-lived streaming response, so a proxy or firewall that holds responses back until they finish will stop it; if your users are on a corporate network, check that it passes streaming responses through. On a self-hosted deployment with more than one API container, point them all at the same Redis, so your agent's call reaches the tab whichever container it lands on.
Give the same token to your agent
Your agent calls https://semaphor.cloud/api/mcp with Authorization: Bearer <accessToken>, as in Connect with a Token. With a live token, the dashboard tools read the screen and semaphor_propose_change sends its change there.
What Your Agent Does
A turn is two calls:
- Read the screen.
semaphor_get_dashboard_detailswithfields: ["outline"]describes the dashboard as it is on screen right now, unsaved edits included. The cards, bindings, SQL and template fields, andsemaphor_get_dashboard_analysis_context, read the screen the same way. A call that reads contents names one dashboard withdashboardId. - Propose the change.
semaphor_propose_changebuilds the change from what is on screen, sends it there, and waits a few seconds for the result. With a live token it changes the open dashboard in place: it takes nosaveAs,previewordomainId, and it never saves.
{
"dashboardId": "d_8f2k",
"changes": [{
"op": "card.add",
"type": "bar",
"title": "Revenue by region",
"query": {
"domainId": "dom_sales",
"datasetName": "orders",
"measures": ["revenue"],
"dimensions": ["region"]
}
}]
}The result carries delivery, which says what became of the change:
{
"proposalId": "chg_...",
"summary": ["Adds the bar chart \"Revenue by region\"."],
"delivery": { "deliveryId": "8a1f...", "outcome": { "status": "applied" } },
"next": "The change is on the user's screen, unsaved; they keep it with Save or remove it with Undo. ..."
}delivery.outcome.status | What it means | What the agent does |
|---|---|---|
applied | The change is on screen, unsaved. | Tells the user what changed. They keep it with Save or remove it with Undo. |
not_applied | The screen didn't apply it. reason and message say why: dashboard_changed (the user changed the dashboard, or saved or cancelled, while the agent worked), not_editable (the editor is open, or Save isn't shown), refused (the change no longer fits) or expired (it arrived too late to land). | For dashboard_changed and expired, reads the outline again and proposes again. Otherwise tells the user. |
not_confirmed | The screen didn't confirm it within a few seconds: it closed or reconnected after receiving the change, or its answer was lost. The change may or may not be on screen. | Reads the outline, which shows the screen as it is, and proposes only what is missing. |
The deliveryId matches the one in the host events, so your page and your agent can refer to the same change.
When the screen can't be read
| Code | Situation |
|---|---|
screen_unavailable | The dashboard isn't open on a connected live screen with this token: the tab is closed or reconnecting, the user can't edit it, or the toolbar isn't shown. Propose returns it too when the screen closed between the read and the change; nothing was sent, so proposing again is safe. |
multiple_screens | The dashboard is open in more than one tab or window with this token. Ask the user to close the others. |
screen_copy_too_large | The dashboard on screen is larger than 2 MiB. |
one_dashboard_per_read | A read of contents named several dashboards. Call again with dashboardId for each. |
live_channel_unavailable | Live changes couldn't be reached for a moment. Try again shortly. |
semaphor_analyze with fromCard runs the card as saved. A card that exists only on screen is described in the outline; build its query from the outline's fields.
What Your User Sees
The change appears in place, and its data loads like any card's. The dashboard is in editing, with Save and Cancel in the toolbar, and a one-line note under the toolbar reads Changed by your assistant · Undo. The note stays until Save, Cancel or Undo. Your user can undo the change, keep editing by hand, ask for more, or save.
Mirror Changes in Your Page (optional)
With parentOrigin in the token, the iframe posts two more messages to your page beside dashboard:dirty-state (see Warn before leaving with unsaved changes), so your chat can show what landed:
{
source: 'semaphor-embed',
version: 1,
type: 'dashboard:change-applied', // or 'dashboard:change-undone' after the note's Undo
dashboardId: 'd_8f2k',
payload: { deliveryId: '8a1f...', summary: ['Adds the bar chart "Revenue by region".'] },
}Save and Cancel are reported by dashboard:dirty-state, as today.
Token Lifetime
A live token works for the iframe and the MCP until its tokenExpiry. When it expires while the dashboard is open, the live connection closes and the dashboard shows its session-expired state, as embeds do. Mint one token per working session, with a tokenExpiry that covers the session and no more, and mint a new one for the next session.