Skip to main content
Glama
elementor-mcp-agent
by Mogacode-ma

elementor-mcp-agent

npm version License: MIT Mogacode-ma/elementor-mcp-agent MCP server GitHub stars

Agency-grade MCP server for WordPress Elementor. Multi-site management, safe Elementor edits with backup + auto-rollback + CSS flush, template export/import, global widget detection, screenshots, WP-CLI escape hatch.

Built for agencies running many client sites on Elementor / Elementor Pro who want Claude (or any MCP client) to drive the toil — without breaking pages.


How this was built

elementor-mcp-agent was built end-to-end with Claude Code over ~48 hours. The process is intentionally open:

  • Architecture, code, tests, docs — all generated through Claude Code pair-programming sessions

  • The 7 bugs documented in this post-mortem were caught in real E2E testing against a live WordPress + Elementor install, not after the fact

  • v1.2's post-write verification pattern was shipped 2 hours after a reader's comment (Mads Hansen on Dev.to) — the changelog credits the source

This isn't vibe-coded software thrown over the wall. Every release ran through lint + typecheck + 27 unit tests + (for v1.0) full E2E against a real WordPress install before publishing. The MCP itself hardcodes guardrails that prevent the model from making destructive WP-CLI calls.

I run a small WordPress agency and use this tool every day on client sites. If you're skeptical about agentic codegen for production infrastructure, the entire commit history is in the open — judge for yourself.


Related MCP server: Respira for WordPress

Why this exists

There are 25+ WordPress MCP servers on GitHub today. None targets the agency multi-site workflow with:

  • Real backup before every edit (postmeta via WP-CLI when SSH available, JSON file fallback — never silently lost)

  • Two-call confirmation for any destructive op (TTL 60s)

  • JSON validation + auto-rollback if an edit produces invalid Elementor data

  • 3-level CSS flush fallback (REST → wp-cli native → option/meta delete → re-save)

  • Global widget awareness — preflight check warns if a page references shared widgets

  • WP-CLI escape hatch for everything the REST API can't do safely

  • Screenshots via headless Chrome (no puppeteer dep)


Install

npx -y elementor-mcp-agent

Configure

export ELEMENTOR_MCP_SITES='[{
  "id": "client-acme",
  "url": "https://acme.example.com",
  "username": "admin",
  "application_password": "xxxx xxxx xxxx xxxx xxxx xxxx",
  "ssh": {
    "host": "host.example.com",
    "user": "username",
    "port": 22,
    "path": "/path/to/wordpress",
    "wp_cli_path": "wp"
  }
}]'

Generate the WordPress Application Password at https://{your-site}/wp-admin/profile.php#application-passwords-section.

The ssh block is optional but unlocks 8 additional tools (WP-CLI escape hatch + reliable custom-postmeta backups). The MCP works without SSH — backups go to local JSON files instead.

wp_cli_path auto-detects if omitted (tries wp, then ~/bin/wp.phar, then ~/wp-cli.phar).

Claude Desktop config

{
  "mcpServers": {
    "elementor": {
      "command": "npx",
      "args": ["-y", "elementor-mcp-agent"],
      "env": {
        "ELEMENTOR_MCP_SITES": "[{\"id\":\"acme\",\"url\":\"https://acme.com\",\"username\":\"admin\",\"application_password\":\"...\"}]"
      }
    }
  }
}

Tools (34)

Sites & health

  • list_sites — enumerate the pool

  • ping_site — auth + version probe

  • site_health — multi-call health snapshot

Pages

  • list_elementor_pages — pages in builder mode

  • read_page_elementor — parsed summary + optional full tree

  • list_widgets_in_page — flat widget inventory with excerpts

  • list_global_widgets — shared widgets (edit one → affects every page using it)

  • preflight_check — validate a page is safe to edit

  • elementor_find_replace — text replace with dry-run → token → apply → backup → validate → rollback if invalid

  • list_elementor_backups / restore_elementor_backup — full restore chain with pre-restore safety backup

  • duplicate_elementor_page — clone within a site (data + page_settings + edit_mode)

Templates

  • list_elementor_templates — Theme Builder distinguished from regular library

  • export_elementor_template — portable JSON

  • import_elementor_template — drop into target site

  • apply_template_to_page — push template data onto an existing page

WP-CLI escape hatch (require SSH)

  • wp_cli_run — arbitrary wp-cli command with destructive-pattern detection + confirmation

  • wp_search_replace — wp search-replace with mandatory dry-run

  • wp_elementor_flush_css — 3-level fallback

  • wp_plugin_list / wp_plugin_update (with confirmation)

Visual

  • screenshot_page — headless Chrome PNG of any URL

  • compare_screenshots — SHA-256 + byte-delta

Widgets (v1.1 — widget-level CRUD)

  • read_widget — fetch one widget by id (read-only)

  • update_widget_settings — shallow-merge settings, with backup + validate + flush

  • delete_widget — remove a widget from its parent container

  • duplicate_widget — clone as sibling with fresh id

  • swap_widget_type — replace widgetType + settings, preserve id + position

  • add_widget — append a widget into a parent container

  • move_widget — move a widget between containers (with position)

Bulk & fleet (v1.1)

  • bulk_find_replace_site — find/replace across every Elementor page of one site, per-page backup + validate + flush

  • fleet_find_replace — same across every site in the pool (sequential, dry-run mandatory)

  • restore_from_file — restore _elementor_data from a JSON file backup, with pre-restore safety backup

Fleet

  • check_elementor_versions — flag outdated installs against wordpress.org latest


Post-write verification (v1.2)

Every mutating widget tool re-reads the page from canonical WP after the write and surfaces persisted state to the model. The HTTP write API can lie — return 200 OK while plugin filters or REST quirks silently drop the payload. This contract makes that observable.

Every applied response carries:

{
  "mutated": true,                  // false = no-op OR silent drop
  "warnings": [],                   // non-fatal issues
  "verification": {
    "method": "Re-read /wp/v2/pages/42 and check widget abc settings…",
    "reread_ok": true,
    "matches_requested": true,      // false = write API lied
    "persisted": { /* canonical state */ },
    "notes": "…explanation when something diverged"
  }
}

If verification.matches_requested === false, treat as a failure even if the HTTP layer said OK. The original payload survives in backup_meta_key — restore via restore_elementor_backup.


Safety guarantees

Hardcoded in src/elementor/policies.ts:

BACKUP_BEFORE_WRITE                 = true
BACKUP_PAGE_SETTINGS                = true
VALIDATE_JSON_AFTER_EDIT            = true
BLOCK_GLOBAL_WIDGET_WRITES_BY_DEFAULT = true
CONFIRMATION_TTL_SECONDS            = 60
GLOBAL_WIDGET_CONFIRMATION_TTL_SECONDS = 30
FLUSH_CSS_AFTER_WRITE               = true
MAX_ELEMENTOR_DATA_BYTES            = 5_000_000

And these wp-cli patterns are hard-blocked regardless of confirmation:

  • rm -rf

  • sudo *

  • db reset --yes / db drop --yes


End-to-end verified

v1.0.0 was tested in real conditions against a live WordPress install with Elementor 4.0.9:

  • ✅ 21/24 tools validated end-to-end at the v1.0.0 baseline (the suite now exposes 34 — see Tools)

  • ✅ find_replace → backup → restore round-trip preserves data

  • ✅ duplicate_page copies data + page_settings + edit_mode

  • ✅ apply_template_to_page with auto-backup

  • ✅ wp_cli_run destructive flow (post delete) requires confirmation

  • ✅ screenshots identical detection via SHA-256

  • ✅ CSS flush uses wp elementor flush-css when SSH available, falls back to option-delete otherwise

7 bugs found during testing, all fixed:

  • REST API silently drops unregistered postmeta writes → switched to WP-CLI primary for backups

  • wp not in SSH PATH on managed hosts → auto-detection + wp_cli_path config

  • SSH post-quantum banner pollution → stderr filter

  • Default Kit returned as "widget" → client-side filter

  • _elementor_page_settings type object/string mismatch → normalisation

  • Chrome cold-start screenshot timeout → bumped to 60s

  • Templates listing same filter bug → fixed


Roadmap

v1.1 ✅ shipped

  • Widget-level CRUD: read_widget, update_widget_settings, delete_widget, duplicate_widget, swap_widget_type, add_widget, move_widget

  • bulk_find_replace_site (across all Elementor pages of one site)

  • fleet_find_replace (across all sites in pool)

  • restore_from_file

v1.2

  • Global styles read/write

  • Theme Builder template push across sites

  • Section/column-level operations

v2.0

  • WooCommerce-aware tools

  • Visual diff (pixel comparison)

  • Schedule + cron scheduling


If this saved you time

The fastest way to support the project is a ⭐ star on GitHub — it helps other agencies running Elementor sites find this and tells me what to keep building.

You can also:

  • Open an issue for bugs, edge cases, or missing tools

  • Start a discussion for design or workflow questions

  • Share what you built with it — I'd love to hear

License

MIT — © 2026 MogaCode.


Développé par Mogacode, agence de référencement en abonnement : sites qui rankent.

Available Tools

34 tools
add_widgetA

Append a new widget to a parent container (section, column, or container) on a page. Re-reads to confirm the new widget exists under the parent. Two-call confirmation.

Inputs: page_id (required), parent_id (required), widget_type (required), site_id, +2 more. Returns: mode, page_id, confirmation_token, backup_meta_key, +7 more

apply_template_to_pageA
Destructive

Copy the _elementor_data + _elementor_page_settings of a SOURCE template (or page) onto a TARGET page on the same site. Backs up the target first. Use to apply a section/page template to an existing draft.

Inputs: source_id (required), target_page_id (required), site_id, backup_to_file, +1 more. Returns: mode, target_page_id, source_id, confirmation_token, +4 more

bulk_find_replace_siteA
Destructive

Find/replace plain text in every Elementor page on a single site. TWO-CALL FLOW: dry-run returns per-page match_count + total + confirmation_token. Apply iterates each page (auto-backup + validate + flush per page). Slower than wp_search_replace but works without SSH and gives per-page granularity.

Inputs: find (required), replace (required), site_id, widget_type, +2 more. Returns: mode, site_id, pages_scanned, total_match_count, +4 more

check_elementor_versionsA
Read-onlyIdempotent

Fleet-wide Elementor version audit. For every site, fetches installed Elementor/Pro versions and compares against wordpress.org latest. Flags outdated installs.

Inputs: site_ids. Returns: checked, latest_elementor_free, sites

compare_screenshotsA
Read-onlyIdempotent

Compare two screenshot files via SHA-256 hash equality and size delta. Quick way to spot whether a page changed visually after an edit. For pixel diffs, use a dedicated tool externally.

Inputs: before_path (required), after_path (required). Returns: identical, before_bytes, after_bytes, delta_bytes, +2 more

delete_widgetA
Destructive

Remove a widget from a page by id. Two-call confirmation. Backs up before deleting; re-reads to confirm the widget is gone.

Inputs: page_id (required), widget_id (required), site_id, confirmation. Returns: mode, page_id, confirmation_token, backup_meta_key, +5 more

duplicate_elementor_pageA

Duplicate an Elementor page within the same site. Creates a new draft page, copies _elementor_data + _elementor_page_settings + _elementor_edit_mode, flushes CSS.

Inputs: source_page_id (required), site_id, new_title, status. Returns: new_page_id, new_page_url, source_page_id, title, +2 more

duplicate_widgetA

Duplicate a widget in place (right after the original). The clone gets a new id. Re-reads to confirm the clone persisted. Two-call confirmation.

Inputs: page_id (required), widget_id (required), site_id, confirmation. Returns: mode, page_id, confirmation_token, backup_meta_key, +6 more

elementor_find_replaceA
Destructive

Find/replace plain text in every widget on one page. TWO-CALL FLOW: dry-run returns match_count + detailed widget hits + confirmation_token. Second call with token applies the change with auto-backup + JSON validation + auto-rollback if validation fails + CSS flush.

Inputs: page_id (required), find (required), replace (required), site_id, +4 more. Returns: mode, page_id, match_count, affected_widgets, +6 more

export_elementor_templateA
Read-onlyIdempotent

Export an Elementor template (section, page, header, footer, etc.) as a portable JSON object. Output goes into import_elementor_template on another site.

Inputs: template_id (required), site_id. Returns: template_id, title, type, summary, +1 more

fleet_find_replaceA
Destructive

Find/replace plain text across every Elementor page of every site in the pool. Same flow as bulk_find_replace_site but iterates across sites. Returns per-site + grand-total summary. Dry-run first; second call applies. Use sparingly — this is the nuclear option.

Inputs: find (required), replace (required), site_ids, widget_type, +2 more. Returns: mode, sites_scanned, total_match_count, by_site, +2 more

import_elementor_templateA

Import a portable template JSON (output of export_elementor_template) into a target site as a new library entry. Useful for cross-site template sync.

Inputs: portable_json (required), site_id, override_title. Returns: new_template_id, title, type, url

list_elementor_backupsA
Read-onlyIdempotent

List timestamped backups of a page's Elementor data (created by previous edit ops). Use restore_elementor_backup with one of these meta keys to roll back.

Inputs: page_id (required), site_id. Returns: page_id, total, backups

list_elementor_pagesA
Read-onlyIdempotent

List pages built with Elementor (have _elementor_edit_mode = 'builder'). Returns id, title, slug, status, modified date.

Inputs: site_id, per_page, search. Returns: total, pages

list_elementor_templatesA
Read-onlyIdempotent

List Elementor library entries on a site: saved sections, pages, popups, headers/footers (Theme Builder Pro), single/archive templates (Theme Builder Pro), and global widgets. Type filter narrows results.

Inputs: site_id, type, per_page. Returns: total, templates

list_global_widgetsA
Read-onlyIdempotent

List all global widgets on a site (Elementor library entries of type 'widget'). These are shared across pages — editing one affects every page using it.

Inputs: site_id. Returns: total, globals

list_sitesA
Read-onlyIdempotent

List every WordPress site configured. Best called first in a session.

Inputs: no named parameters. Returns: total, default_site_id, sites

list_widgets_in_pageA
Read-onlyIdempotent

Flat list of every widget in a page with id, type, parent path, and an excerpt of the first text setting (for spot-checking before find/replace).

Inputs: page_id (required), site_id, widget_type. Returns: page_id, total, widgets

move_widgetA
Destructive

Move a widget to a different parent (or different position in the same parent). Re-reads to confirm new parent. Two-call confirmation.

Inputs: page_id (required), widget_id (required), new_parent_id (required), site_id, +2 more. Returns: mode, page_id, confirmation_token, backup_meta_key, +6 more

ping_siteA
Read-onlyIdempotent

Verify connectivity + authentication to a WordPress site. Returns user identity + WP/Elementor/Elementor Pro versions if accessible.

Inputs: site_id. Returns: ok, site_id, url, wp_version, +4 more

preflight_checkA
Read-onlyIdempotent

Validate a page is safe to edit. Checks: page exists, is Elementor-built, data parses cleanly, references valid global widgets, isn't currently locked by another editor.

Inputs: page_id (required), site_id. Returns: safe_to_edit, page_id, title, issues, +3 more

read_page_elementorA
Read-onlyIdempotent

Fetch a page's Elementor data structure summary. With verbose=true returns the full parsed tree (potentially MBs).

Inputs: page_id (required), site_id, verbose. Returns: page_id, title, summary, global_references, +1 more

read_widgetA
Read-onlyIdempotent

Fetch a single widget's full settings by id. Use list_widgets_in_page to find the id first.

Inputs: page_id (required), widget_id (required), site_id. Returns: widget_id, widget_type, settings

restore_elementor_backupA
Destructive

Restore a page's _elementor_data and _elementor_page_settings from a backup created by a previous edit. TWO-CALL FLOW with confirmation token.

Inputs: page_id (required), backup_meta_key (required), site_id, settings_meta_key, +1 more. Returns: mode, page_id, backup_meta_key, settings_meta_key, +4 more

restore_from_fileA
Destructive

Restore a page from a JSON backup file (created by ANY earlier op with backup_to_file=true or by direct fullBackup with to_file). Requires the file_path returned by that backup. Two-call confirmation.

Inputs: page_id (required), file_path (required), site_id, confirmation. Returns: mode, page_id, file_path, method, +3 more

screenshot_pageA
Read-only

Capture a PNG screenshot of a page's frontend (visitor-facing URL). Requires a Chrome/Chromium binary on the host. Returns the local file path so the LLM can analyse it or compare against another shot.

Inputs: site_id, page_id, url, width, +2 more. Returns: url, file_path, bytes, sha256, +2 more

site_healthA
Read-onlyIdempotent

Comprehensive site health snapshot: WP/PHP/Elementor versions, disk space (if SSH), plugin count, theme info. Aggregates multiple REST calls into a single overview.

Inputs: site_id. Returns: site_id, url, wp_version, php_version, +8 more

swap_widget_typeA
Destructive

Replace a widget's type (e.g., heading → button) while preserving its id and position. Provide full new_settings — the old settings are NOT carried over (different widget types have incompatible schemas). Re-reads to confirm. Two-call confirmation.

Inputs: page_id (required), widget_id (required), new_widget_type (required), site_id, +2 more. Returns: mode, page_id, confirmation_token, backup_meta_key, +6 more

update_widget_settingsA
Destructive

Shallow-merge a partial settings object into one widget. Backs up the page first, validates the result, auto-flushes CSS, then re-reads the page and verifies the patch persisted (matches_requested in the response). Two-call confirmation.

Inputs: page_id (required), widget_id (required), settings_patch (required), site_id, +1 more. Returns: mode, page_id, confirmation_token, backup_meta_key, +6 more

wp_cli_runA
Destructive

Execute an arbitrary wp-cli command on a site via SSH. The wp prefix and --path are added automatically — pass only the args (e.g. 'post list --post_type=page'). Destructive commands (delete, drop, search-replace without --dry-run, plugin deactivate/uninstall) require a two-call confirmation flow.

Inputs: args (required), site_id, timeout_ms, confirmation. Returns: mode, command, stdout, stderr, +5 more

wp_elementor_flush_cssA
Idempotent

Flush Elementor's CSS cache on a site using the 3-level fallback strategy (REST endpoint → wp-cli native → option/meta delete). Always call after writing _elementor_data programmatically.

Inputs: site_id, page_id. Returns: method, details

wp_plugin_listA
Read-onlyIdempotent

List installed plugins on a site with name, version, status (active/inactive), and update_version (if outdated). Uses WP-CLI for accurate version data including update_version.

Inputs: site_id, only_outdated. Returns: total, plugins

wp_plugin_updateA
Destructive

Update one or more plugins on a site to their latest version. Requires confirmation token (uses wp-cli).

Inputs: plugins (required), site_id, confirmation. Returns: mode, plugins, command, stdout, +4 more

wp_search_replaceA
Destructive

Run wp search-replace against wp_postmeta (default) — the standard agency way to update Elementor text content. ALWAYS dry-run first; the apply call requires a confirmation token. Includes --precise --all-tables-with-prefix by default if you specify table='all'.

Inputs: find (required), replace (required), site_id, table, +3 more. Returns: mode, command, stdout, stderr, +4 more

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A4.2/5.0

    Scored across 34 tools

    Disambiguation5/5

    Each tool has a clearly distinct purpose, with find/replace tools scoped at different levels (page, site, fleet) and separate tools for different widget operations. No two tools appear to do the same thing.

    Naming Consistency5/5

    All tool names follow a consistent snake_case verb_noun pattern (e.g., add_widget, list_sites, update_widget_settings). The naming is predictable and easy to understand, even for compound operations like bulk_find_replace_site.

    Tool Count5/5

    With 34 tools covering widget CRUD, page management, find/replace, backups, templates, screenshots, site health, and plugin management, the count is well-scoped for the Elementor domain. Each tool serves a specific need without redundancy.

    Completeness5/5

    The tool surface covers the full workflow: reading, writing, duplicating, moving, swapping, deleting widgets; managing pages (list, duplicate, backup, restore); find/replace at multiple levels; template import/export; site health; and plugin management. No obvious gaps are apparent.

    Maintenance

    ActivitySlowing
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • -
      license
      Not graded
      quality
      Not graded
      maintenance
      Enables comprehensive WordPress and Elementor management through 60 powerful tools with modular configuration modes. Supports content creation, page building, element manipulation, file operations, and template management with enterprise-grade security and debugging capabilities.
      -
    • A
      license
      Not graded
      quality
      B
      maintenance
      MCP server for AI-assisted WordPress editing across 12 page builders. 172 tools for content management, page builder editing, WooCommerce, SEO analysis, accessibility scanning, and site intelligence. Edits native builder formats (Elementor, Bricks, Divi, Gutenberg, Beaver Builder, and 7 more) with duplicate-before-edit safety, optimistic locking, and surgical element-level operations
      11
      MIT
    • A
      license
      B
      quality
      C
      maintenance
      infomaniak-mcp-agent lets Claude (or any MCP client) drive a real Infomaniak account end-to-end through 54 tools — web hosting, mail, kDrive, domains, DNS, DNSSEC, FTP/SSH users, AI catalogue and more. Every destructive operation goes through a strict two-phase commit (plan + single-use confirmation token) so the agent can never silently mutate your account. Open Source
      74
      113 npm
      8
      MIT