# Undo Tokens & Reversibility

> Every destructive Royal MCP Pro tool returns a 72-hour, one-shot undo token. Covers issuance, consumption with or without the token, and dry-run mode.

- Canonical: <https://royalplugins.com/support/royal-mcp-pro/undo-tokens-and-reversibility/>
- Last updated: 2026-08-11
- HTML version: <https://royalplugins.com/support/royal-mcp-pro/undo-tokens-and-reversibility/>

---

Every destructive Royal MCP Pro tool returns a 72-hour undo token. This guide covers how tokens are issued in the response envelope, how to consume them (with or without the token string), dry-run preview mode on bulk tools, error codes, and how to see undo state in the Activity Log.

Lost the token?

Call `royal_mcp_undo_last_operation` with no arguments to reverse your most recent unconsumed, unexpired operation. The tool finds it for you.

## Overview

When an AI assistant calls a Royal MCP Pro tool that changes something — publishes a post, updates an Elementor widget, imports a Divi template, runs a bulk find-and-replace — the response envelope carries an `undo` object with a 64-character hex token. The token can be passed back to `royal_mcp_undo_last_operation` within 72 hours to reverse the operation.

Four properties are baked into every undo token:

- **72-hour TTL.** Tokens expire 72 hours after issuance.
- **One-shot consumption.** A token can only be consumed once. After it’s used, the same token cannot be re-used.
- **Per-tool reversal logic.** Each tool registers its own reversal handler so the undo does the correct inverse operation (a post-content write restores the prior content; a media upload deletes the created attachment; a template clone deletes the created template).
- **Audit-log linked.** Every issued token appears in the Activity Log with its expiration + consumption state + affected object.

## Token in the response envelope

When a destructive Pro tool completes successfully, its response envelope carries an `undo` object alongside the tool’s structured content. Example from a Divi image-URL replace operation:

```
{
  "isError": false,
  "content": [
    {
      "type": "text",
      "text": "Replaced 3 image URL(s) on post 42 (format=divi_5_blocks).\n\n--- royal-mcp-pro payload ---\n\u0060\u0060\u0060json\n{...}\n\u0060\u0060\u0060"
    }
  ],
  "structuredContent": {
    "post_id": 42,
    "format_detected": "divi_5_blocks",
    "replacements": 3,
    "telemetry": {
      "content_length_before": 4192,
      "content_length_after": 4237,
      "divi_format": "divi_5_blocks"
    },
    "undo": {
      "token": "3f7b8e2a...",
      "expires_at": "2026-08-14T10:23:15Z"
    }
  },
  "undo": {
    "token": "3f7b8e2a...",
    "expires_at": "2026-08-14T10:23:15Z"
  }
}
```

Royal MCP Pro auto-appends a fenced JSON block to `content[0].text` containing the structured payload plus the `undo` object. MCP clients that only render `content[0].text` (some Claude Desktop configurations, some ChatGPT Plugins UI setups) can still parse the token from the fenced block. Structure-aware clients read it directly from `structuredContent.undo` or the envelope-level `undo`.

## Consuming a token

Pass the token back to `royal_mcp_undo_last_operation`:

```
Undo the last operation using royal_mcp_undo_last_operation with undo_token 3f7b8e2a...
```

The tool looks up the token’s stored reversal payload, dispatches to the original tool’s undo handler, and returns a summary of what was restored:

```
{
  "isError": false,
  "content": [
    {
      "type": "text",
      "text": "Undone: divi_replace_image (audit #138).\nRestored 1 posts (0 missing)."
    }
  ],
  "structuredContent": {
    "undone": true,
    "token": "3f7b8e2a...",
    "tool_name": "divi_replace_image",
    "audit_id": 138,
    "undone_at": "2026-08-11T18:42:07Z",
    "restored_state_summary": "Restored 1 posts (0 missing).",
    "posts_restored": 1
  }
}
```

The exact `restored_state_summary` text varies by originating tool — snapshot-restore handlers (like `divi_replace_image`, `divi_import_template`, `elementor_update_widget`) report “Restored N posts (M missing)”; create-then-delete handlers (like `divi_clone_page`, `divi_library_create`) report “Deleted post N”. The token is now consumed — trying to use it again returns `token_consumed`.

## No-argument mode

When you don’t have the specific token — the response was trimmed, the client dropped `structuredContent`, the caller lost track of it — call the tool with no arguments:

```
Undo my last operation using royal_mcp_undo_last_operation.
```

The tool finds the calling user’s most recent unconsumed, unexpired operation and reverses it. This is the “undo my last write” escape hatch.

Only the current user’s operations

No-argument mode scopes to the calling user — you can only auto-undo an operation you made yourself. Undoing another user’s operation always requires the explicit token (or an elevated capability — see below).

Returns `no_recent_operation` if there’s nothing to undo — either the user has never made a destructive call, or their last operation has already been consumed or expired.

## Error codes

| Code | Meaning |
| --- | --- |
| `no_recent_operation` | No-argument call and the current user has no unconsumed, unexpired operations. |
| `token_not_found` | The `undo_token` value doesn’t exist in the undo store. Likely a typo or the token was never issued. |
| `token_consumed` | The token was already used. Tokens are one-shot. |
| `token_expired` | More than 72 hours have passed since the token was issued. |
| `undo_handler_missing` | Rare. The originating tool did not register a reversal handler. Should not happen for shipped Pro tools. |
| `undo_handler_failed` | The reversal handler ran but the underlying operation failed (e.g., trying to restore a post that has since been permanently deleted). The response’s `message` field explains what went wrong. |
| `insufficient_caps` | The current user isn’t the original actor and doesn’t hold `royal_mcp_pro_undo_any`, `manage_options`, or super-admin. See [Who can undo](#permissions) below. |

## Dry-run preview mode

Undo tokens give you rollback *after* a change lands. Dry-run mode on bulk tools gives you the complementary pattern: preview what a change would do *before* it commits.

The clearest example is `wp_bulk_find_and_replace`. It has a mandatory two-step commit envelope: step one is dry-run only (returns a preview + a `commit_token`); step two takes the `commit_token` and applies the writes. This means every bulk find-and-replace forces a preview + confirmation round-trip before anything is written.

The dry-run response includes `total_estimated_replacements`, a `commit_token` (5-minute TTL, single-use), and a `sample_changes` array with per-post match counts + a representative preview snippet from each matched post — enough to review the intended edit against expectations before you spend the `commit_token`.

### Other tools with dry-run mode

- `wp_migrate_theme_assets` (Pro composer) — dry-run reports which posts would be touched by a theme URL rewrite; nothing gets written.
- `divi_prepare_shortcode_migration` (Pro Divi tool) — dry-run scans posts for Divi 4 / Divi 5 / mixed / not-Divi state and reports counts; nothing gets backed up.
- `elementor_bulk_replace_widget_setting` (Pro Elementor tool) — dry-run returns the match count without writing.

Every one of these returns a preview and lets you decide whether to commit. Combined with the 72h undo token issued on commit, it’s hard to get into a state you can’t back out of.

## Activity Log correlation

Every issued undo token is linked to an audit row in **Royal MCP Pro → Audit Log**. The audit row records:

- The tool that issued the token
- Its expiration timestamp
- Whether it’s been consumed (and when)
- The affected object (post ID, term ID, media ID, etc.)

The Activity Log supports an **Undo state** filter with four options:

- **Any** — default; show all rows
- **Only active tokens** — still-consumable tokens (not expired, not consumed)
- **Only already undone** — operations that have already been reversed
- **Never issued a token** — read-only tool calls

Each row with an active token also gets an inline **Undo** action link. Click it to reverse the operation from the admin UI without going through an MCP client — useful when the AI conversation has moved on or when a colleague needs to undo something you did.

Retention

Audit rows are kept for 90 days by default and pruned by a daily cron. You can adjust the retention period via the `royal_mcp_pro_audit_retention_days` filter; set it to `0` to disable pruning entirely.

## Who can undo

The undo tool checks three things before dispatching a reversal:

1. The **current user is the original actor** — the same user account that made the change can always undo it.
2. **OR** the current user holds one of: `royal_mcp_pro_undo_any` (Pro-specific undo-any capability), `manage_options`, or super-admin.
3. The tool’s specific per-operation capabilities are re-checked at reversal time (e.g., `edit_post` for a post-content restore) — if the caller has since lost their edit rights, the undo refuses even if the actor match passes.

By default, `royal_mcp_pro_undo_any` is not granted to any role. Grant it explicitly via a snippet or a capability manager if you want editors or authors to be able to undo other users’ MCP operations:

```
// Grant undo-any capability to the administrator role
$role = get_role( 'administrator' );
if ( $role ) {
    $role->add_cap( 'royal_mcp_pro_undo_any' );
}
```

Administrators have `manage_options` by default so they already pass the check — the explicit capability is only useful when you want to grant undo-any to a lower role.

## Related documentation

- [**Universal Audit Log**](https://royalplugins.com/support/royal-mcp-pro/understanding-the-audit-log/) — every column in the Activity Log explained, filtering, CSV export
- [Undo Token Errors](https://royalplugins.com/support/royal-mcp-pro/undo-token-errors/) — troubleshooting each error code with fix instructions
- [Bulk Price Updates + Undo](https://royalplugins.com/support/royal-mcp-pro/bulk-price-updates-with-undo/) — walk-through of the dry-run → commit → undo pattern on a WooCommerce bulk workflow
- [Divi 5 Post Emptied by Builder](https://royalplugins.com/support/royal-mcp-pro/divi-5-post-emptied-by-builder/) — when the undo token isn’t the right recovery path (revision restore is)
- [Cross-plugin Composers](https://royalplugins.com/support/royal-mcp-pro/cross-plugin-composers/) — how the SiteVault pre-backup composer step complements per-tool undo

[← Documentation Hub](https://royalplugins.com/support/royal-mcp-pro/)
[Divi Pro Tools reference →](https://royalplugins.com/support/royal-mcp-pro/divi-tools/)
