Skip to main content

Reverting changes

Every MCP mutation is recorded in mcp_changes, a per-workspace change feed. Your agent can list recent changes and revert any that fall within the 24-hour window.

The two tools

mcp.list_changes

mcp.list_changes({ limit?: number, cursor?: string })
→ {
changes: Array<{
id: string,
kind: string, // tool name, e.g. "workspace.update_brand"
primaryEntityKind: string,
primaryEntityId: string,
summary: string,
revertible: boolean,
revertibleUntil: ISO timestamp,
revertedAt?: ISO timestamp,
createdAt: ISO timestamp,
}>,
nextCursor?: string,
}

Returns the workspace's change feed, newest first. Includes both revertible changes and tombstone rows (revertible: false) for irreversible actions like sends.

mcp.revert_change

mcp.revert_change({ changeId: string })
→ { reverted: true, summary: string }
| { error: "not_revertible" | "expired" | "already_reverted" | ... }

Reverts the named change. Revert behavior depends on the kind:

  • Create-style (funnel.split.create, email.template.create, api_key.create): the created entity is archived/deleted.
  • Update-style (workspace.update_brand, email.template.update, team.change_role): the prior state is restored from previousState snapshot.
  • Tombstone (email.draft.send_now, billing.cancel_subscription, workspace.delete): cannot be reverted. The entry exists in the feed for audit/visibility only.

Tombstone rows

Some actions cannot be undone. Email sends leave the inbox; cancelled subscriptions trigger Stripe webhooks; deleted workspaces cascade-delete millions of rows. For these, mcp_changes still records the change (with revertible: false) so the agent's change feed stays complete — but mcp.revert_change will error with not_revertible.

ActionWhy tombstone
email.draft.send_nowEmails left the building
email.campaign.send_testTest email sent
billing.cancel_subscription / billing.resume_subscription / billing.change_planReal money flowed
team.remove_member (post-accept)The user already had access
api_key.revokeSecret destroyed
tracking.site.delete / email.domain.delete / email.sender.deleteCascade ripples through linked entities
workspace.delete / workspace.transfer_ownershipFundamentally one-way

The 24-hour window

revertibleUntil is 24 hours after createdAt. After that, the change is read-only history. Tombstones never become revertible regardless of time.

Snapshot-and-restore for updates

Update-style changes write a JSON snapshot of the entity's prior state into mcp_changes.previousState. On revert, the snapshot is written back via tx.update(...).set(snapshot). This works for any single-entity update — workspace settings, email template body, sender display name, etc.

For multi-entity creates (a single funnel.split.create call creates a parent split row, multiple FunnelTrack rows, multiple FunnelStage rows, and may modify the funnel's canvasState), the revert handler knows to undo all of them via the autoCreatedEntities array.

UI awareness

The dashboard polls mcp.list_changes every 30 seconds. When new agent-driven changes appear:

  • A toast notification shows "Agent made N change(s). Refresh to see latest."
  • Clicking the toast refreshes the active page's data.
  • Users can also browse the full feed in Settings → Developer → Activity alongside the per-call audit log.

This means if you and an agent edit the same template at the same time, you'll see the agent's change within 30 seconds and can choose to revert it before your edits land.

Agent etiquette

Good agents:

  • Surface changes that touched the user's recent context (e.g. "I just updated workspace brand from #7c3aed to #ff5733 — say 'revert that' if you'd rather keep the old color")
  • Offer revert proactively after destructive-feeling mutations
  • Don't list every change in a chatty way; the feed is for audit, not narration
  • Safety tiers — what's revertible and what isn't.
  • MCP — main MCP overview.