Support / Royal MCP Pro / Undo Tokens & Reversibility

Undo Tokens & 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

CodeMeaning
no_recent_operationNo-argument call and the current user has no unconsumed, unexpired operations.
token_not_foundThe undo_token value doesn’t exist in the undo store. Likely a typo or the token was never issued.
token_consumedThe token was already used. Tokens are one-shot.
token_expiredMore than 72 hours have passed since the token was issued.
undo_handler_missingRare. The originating tool did not register a reversal handler. Should not happen for shipped Pro tools.
undo_handler_failedThe 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_capsThe 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 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.