Releases: Digitizers/elementor-mcp
Release list
v1.34.0 — collateral diff on governed page writes
Ships three versions together (the last published release was v1.32.0).
1.34.0 — collateral diff on governed page writes
Every other check in the write path asks "did my change land?". This one asks "did anything else change?" — the question Respira's builder-type guard exists for, where every known incident came from the writer's own serialiser retyping or emptying nodes the caller never addressed, with ids, order and stored text intact so nothing complained.
After a governed save, three forms of the page are compared: as stored before, what the tool asked for, and what persisted.
- Targets are derived, never declared. A node whose payload differs between before and asked is one the tool changed; no ability had to say so. A node's payload excludes its
elementschildren, so a targeted child does not make its ancestors look targeted. - Collateral = an untargeted node that
changed, wasretyped, orvanished. Comparing before against persisted keeps Elementor's save-time normalisation out of the verdict — the before tree already went through it. - Not landed = a setting the tool asked for that is absent afterwards (absence only; a value Elementor rewrote into its canonical shape is Elementor doing its job). Judged after this plugin's own prop coercion, so an advertised alias renamed onto its canonical property is never mistaken for a dropped setting.
- Default is
warn: the finding rides the run's existing warnings channel plus anelementor_mcp_governance_collateralaction.refuse— restore the pre-write snapshot and returngovernance_collateral_refused, telling the agent not to retry — is behind theemcp_collateral_guard_modefilter. No option, no admin toggle: anything other than the default is a deliberate, named decision by a site operator.
1.33.1 — Angie bridge SDK
@elementor/angie-sdk 1.5.0 → 1.7.3, bundle rebuilt. Upstream publishes no changelog past 1.0.x, so the delta was read from the tarballs; the one behaviour that changed for us is that refresh pings are now filtered per SDK instance.
Field-checked on a live site (WP 7.1, Elementor 4.2.4, Angie 1.1.16): the bridge registers within ~2–3 s (isAngieReady() true, empty queue, aura-design-engine: registered), serves its six read-only tools to the editor, and stays registered across opening, closing and reopening the Angie sidebar. The new per-instance filter behaves correctly in both directions — a ping belonging to another instance is ignored, an unstamped one is accepted. One observation about forced queue resets, which affects Angie's own first-party servers identically and is not reachable in normal use, is recorded in #69.
1.33.0 — bundled MCP Adapter
Adapter 0.4.1 → 0.6.1 together with its wordpress/php-mcp-schema runtime dependency, stricter input validation and fail-closed permission handling, and an explicit meta.mcp.public = false on every ability.
v1.32.0 — operator rules enforced on Elementor writes
1.32.0 — operator rules enforced on Elementor writes
- New (governance): operator rules are enforced on Elementor writes (Aura P4.1, plan 3 of 4). An operator's rule in Aura —
blockorwarnon a page/post by id or a freeze of the whole site — is pushed to SiteAgent, and this plugin now asks SiteAgent's matcher before every governed write. Elementor saves through its own paths rather than/wp/v2/posts, which is why SiteAgent's own core-REST enforcement could not cover them and this plugin needs its own point.- Rules decide before anything is written or snapshotted. Inside a governed run the order is grant → rules → snapshot → write: a
blockreturnsaura_rule_blocked(403) naming the rule and its reason, with nothing executed and no snapshot taken. A create-style tool is refused before its callback runs, so it cannot insert a draft and then be told no. - What a write declares — declared per ability, default create. Each ability declares
meta.governance.writes: 'edit'or'create'(or omits the key, which is 'create', fail closed); an'edit'ability declares the input post id as bothpost:<id>andpage:<id>(an operator does not know which one "checkout" is); a'create'ability declares the whole site (site:*) until the new id exists, PLUS the input post id's own touches when one is present — an ability like save-as-template reads a source post id and inserts a new template, and inferring "edit" from that source id's mere presence (fixed in a later patch of this release) let a site freeze catch the write only after the insert had already happened. Design-token and global-class writes declare the whole site the same way, viameta.governance.scope. - A rule outranks an approval. A valid grant does not bypass a
block; the refusal says "approval does not override a rule; release the rule first". warnproceeds and the outcome carrieswarnings: [{rule, reason}], once per rule — on an array result as a key, on an error (the write failed after the warn) in its data, on a scalar by wrapping it as{ value, warnings }.- Previews are exempt — a dry run writes nothing; rules block execution, not sight.
- Nothing is re-implemented here. Matching, the signed ruleset, expiry and the forensic hooks (
aura_worker_rule_blocked/aura_worker_rule_warned) stay in SiteAgent; this plugin only declares and translates. Without SiteAgent there are no rules and no policy — and a SiteAgent that is installed but cannot evaluate rules refuses the write (aura_rules_unavailable, 503) rather than lifting a block it cannot see.
- Rules decide before anything is written or snapshotted. Inside a governed run the order is grant → rules → snapshot → write: a
- New:
server-inforeports rules —rules: { enforced, source, state: absent|outdated|incomplete|ready, ruleset: { seq, rule_count, received_at } | null, points }— and says in plain words when a fork-only site enforces none, when an outdated SiteAgent (has the snapshot engine, predates 2.10.0's rules engine) needs updating rather than installing, or when an incomplete SiteAgent is refusing every write.
Pull requests: #63 (rules bridge), #62 (WP 7.1), #61 (plugin ZIP). Pairs with SiteAgent ≥ 2.10.2 and Aura P4.1.
Full Changelog: v1.31.0...v1.32.0
v1.31.0
Security release. Recommended for every site, and important if you run this plugin without SiteAgent.
Sites on 1.30.0 are offered this automatically through the self-updater.
What 1.30.0 did not cover
1.30.0 closed the "second door" — this plugin's write tools being callable from any other MCP server installed on the same site — with two guards:
- At registration, write tools declare
meta.mcp.type = 'private', which a co-installed server reads and refuses to serve. - At execute, a governed write arriving from anywhere but this plugin's own MCP server must carry a valid approval grant.
The second of those lives in the SiteAgent governance wrapper, which does nothing when SiteAgent is absent. So on a standalone install, the metadata was the whole protection — and it depends on the other plugin continuing to honour that convention. That is a bet, not a posture.
What this release adds
A write-capable tool is now refused at the permission stage, on every site, when the caller is not on this plugin's own MCP server, WP-CLI, or a non-REST context. No other plugin has to cooperate for it to hold.
- Presence, not verification. The gate checks that a grant is present and lets the governance layer verify it once. Verifying in both places would burn the grant's single-use nonce and reject the legitimate call on its own approval. Where no verifier is installed, a foreign write is refused outright rather than accepted on a header nothing can check.
- It wraps, never replaces. The tool's own capability check still runs, and runs last — so this can only ever deny more than before.
What is unaffected
- Read tools. An assistant on another server answering "what is on this page" from this plugin's data is the entire point of the read-only bridge.
- Dry runs. A preview-capable tool called with
applyfalsy writes nothing, so there is nothing to approve — exempt here, as it already was at execute time. elementor_mcp_expose_writes_to_foreign_mcp. The documented escape hatch works exactly as before: a write you open through it stays reachable, andserver-infonames the ones you opened.
Diagnostics
server-info reports the two guards separately, so a standalone site is described as closed rather than exposed — it says that no grant can be verified there and that foreign servers are refused outright, instead of implying a gap that no longer exists.
Verification
1145 tests. Each guard was confirmed by reverting it and watching its own tests fail.
Full detail in CHANGELOG.md.
v1.30.0
Security release. Recommended for every site, and urgent on any site running Elementor's Angie.
Sites on 1.29.0 are offered this automatically through the self-updater, on the normal Plugins / Dashboard → Updates screens.
What was reachable
This plugin's write tools were callable from any other MCP server installed on the same site — a transport with no approval queue, no audit trail and no fleet visibility behind it.
Abilities are registered with WordPress, not with a server: wp_register_ability() publishes to a site-wide registry, so a second MCP server enumerates it and serves what it finds without asking anyone. Elementor's Angie 1.1.12 ships exactly such a server at /mcp/angie. Its discovery admits every third-party ability that does not declare a non-tool meta.mcp.type — which was every ability by default, this plugin's included — and its execute-ability proxy then runs them by name.
Read from Angie's source (Mcp_Adapter_Ability_Discovery, Mcp_Adapter_Ability_Permissions), not from its documentation.
Two guards, covering different sites
At registration, a write tool now declares meta.mcp.type = 'private'. Angie's listing and its execution gate consult the same value, so a type it does not serve removes the tool from the menu and from the door. This is the only protection on a site without SiteAgent, where the governance wrapper leaves every ability untouched.
At execution, a governed write arriving on any route other than this plugin's own MCP server must present a valid approval grant — whether or not grant enforcement is switched on. The refusal lands before the tool runs, so an unauthorized create cannot insert a draft and then be rolled back. A valid grant still passes: a grant is gateway context, bound to the exact tool and params, whatever route carries it.
Read tools stay available to other servers on purpose. An assistant answering "what is on this page" from this plugin's data is the entire point of the read-only bridge. Only writes are withheld.
Escape hatches
Both off by default:
elementor_mcp_expose_writes_to_foreign_mcp— re-expose write tools to other MCP servers.elementor_mcp_trusted_write_context— trust a named REST route, for an operator with a genuine second integration.
Diagnostics
server-info now reports write exposure: whether writes are withheld, which write tools (if any) are left open and why, whether the execution-side check is actually running (it needs SiteAgent — a fork-only site is protected by the registration metadata alone, and the report says so), and which other MCP servers are active on the site, including which of them publish these tools directly.
Verification
Every governed family — page data, kit design tokens, global classes — is exercised end-to-end from a foreign transport carrying a valid grant, confirming the snapshot and render check still fire. 1117 tests.
Full detail in CHANGELOG.md.
v1.29.0
Nine correctness and discoverability fixes on the atomic (Elementor 4.x) write path, all found while building a real client site with these tools. Most were silent — the tool reported success and Elementor dropped the value.
Sites on 1.28.0 are offered this automatically through the self-updater, on the normal Plugins / Dashboard → Updates screens.
Silent write failures
font-familywas emitted with the wrong$$type, so no font set through any tool ever applied. Elementor types it asFont_Family_Prop_Type, which does not shareString_Prop_Type's key, and the style schema silently drops props that do not match.update-atomic-widgetcould not change a style at all — it wrote onlysettings, but on an atomic element size, spacing and appearance live in thestylesmap. It now accepts the same flat style params as theadd-*tools and merges them into the element's base variant, member-wise for composite props so touching one padding side no longer drops the other three.add-custom-csswas a no-op on atomic elements. It wrote the Elementor 3.xsettings.custom_csscontrol, which atomic elements never read. It now writes a style variant, base64-encoded — Elementor decodes that field throughbase64_decode()and a plain CSS string is dropped to an empty rule.- The fallback write path never invalidated Elementor's rendered-element cache. That path is the one taken in non-browser contexts — i.e. how the MCP writes — so a correct write served the pre-change markup until the cache expired. Indistinguishable from a failed write, and intermittent.
Capabilities that existed but were never advertised
add-flexboxandadd-div-blocknow publish everything the builders accept. Borders, radii, widths, gradients and per-side spacing always worked; the schema advertised 14 parameters and none of them, so agents concluded atomic containers could not express them. Both tools now derive their schema from a single shared source, and tests pin schema and builder together in both directions.- New:
positionandbox-shadow— the two capabilities that were genuinely missing. The input key iscss_position, notposition, which the layout tools already use for the insert index. Offsets use logical names (offset_inline_startand friends) because the inline axis follows text direction;offset_leftwould name the wrong edge on every RTL page.
Discoverability
- New:
server-info, a diagnostic that cannot be switched off. Reports plugin / Elementor / Elementor Pro versions, atomic state, the resolved MCP adapter source and version, whether the server endpoint is on, and registered vs exposed vs suppressed ability counts with the suppressed slugs and the cause of each gap. A fresh install presenting as "zero tools exposed" previously had nothing anywhere to explain it. add-nav-menupublished the wrong parameter for choosing the menu.menu_nameis the widget's accessible label; the SELECT that picks the navigation ismenu. On a site with more than one menu, the wrong navigation was inserted.replace-system-colorsandupdate-global-colorsnow describe what they each write and name each other.update-global-colorsappends tocustom_colorsand never touches the four system slots every Global Color picker references, so a brand palette could land while Elementor's stock colour stayed bound to the roles that matter.
Full detail in CHANGELOG.md.
1.28.0 — GitHub self-updater
This is the release that turns on self-updating. From here on, sites running 1.28.0+ are offered new versions on the normal Plugins and Dashboard → Updates screens — no more manual zip uploads.
Because detection is release-only, this version must be installed once by hand (or via the SiteAgent Elementor Studio installer); every version after it arrives through WordPress.
New — GitHub self-updater
Bundles Plugin Update Checker 5.6 and points it at this repository's tagged Releases (includes/class-updater.php).
- Release-only detection. The library's default GitHub strategy falls back latest-release → latest-tag → branch, so a pushed version tag would offer an update before its Release was published. The strategy list is reduced to
latest_release: nothing is offered until a Release exists. - Anonymous update checks. WordPress's default user agent is
WordPress/6.x; https://your-site.example— it would hand every site's URL to GitHub. Both the update check and the package download (across the release-asset redirect hosts) send a neutralelementor-mcp/<version>instead. No site data leaves the site. - Package source. A Release that attaches a built
elementor-mcp*.zipis preferred; otherwise the tag's source archive is served — the repository root is the plugin root and the vendored libraries are committed, so the archive installs as-is.
Upgrading
Install this version once, then check Dashboard → Updates. Nothing else changes: no new tools, no behaviour changes to existing ones.
Full changelog: https://github.com/Digitizers/elementor-mcp/blob/main/CHANGELOG.md
v1.27.1 — Angie bridge fixes from the first live run
Patch release. Both fixes came out of the first live run of the Angie bridge (WP 6.9 / Elementor 4.2.2 / Angie 1.1.11) — the K3 smoke test — and landed after the v1.27.0 tag, so the 1.27.0 build does not contain them.
The bridge never loaded in the Elementor editor
The one surface it exists for. Its bundle was registered on admin_enqueue_scripts, which the editor never fires: that screen is rendered from admin_action_elementor, before admin-header.php, and Elementor then calls remove_all_actions( 'wp_enqueue_scripts' ) and rebuilds a front-end style document from its own hooks. The bridge now also rides elementor/editor/after_enqueue_scripts — the hook Angie's own editor integration uses — and guards against a double localize, since wp_localize_script() prints a script tag on every call.
The registration promise was meaningless
The bridge called registerServer() with a config carrying no type. The SDK answers that with a warning and hands off to registerLocalServer() without awaiting it, so the promise resolved before the registration had begun and a failure inside it could never reach the catch. It now calls registerLocalServer() directly.
Registration state is now observable
window.emcpAngieBridgeDebug exposes isAngieReady(), registrations() and pending(). A registration that stays queued is otherwise indistinguishable from a successful one: nothing throws, nothing logs, the tools simply never appear — which is exactly the state the smoke test was stuck in. It exposes state, not authority: every tool call still goes through the REST routes, the server-side allowlist, the read-only invariant, and each ability's own permission callback.
Verified live
Angie discovers the six read-only tools and executes them — list-pages, list-global-classes and list-variables all returning 200 through emcp/angie/v1/execute/.
Worth knowing: the bridge's tool list is independent of the admin Tools toggles. Every ability is registered with the Abilities API regardless; the elementor_mcp_ability_names filter only trims the list handed to the MCP Adapter server, and the bridge resolves its own allowlist through wp_get_ability().
Full details in CHANGELOG.md.
v1.27.0 — P3.3 atomic correctness + attachment-alt authorization
P3.3 — atomic (Elementor 4.0+) correctness
Writes to atomic elements now land as the typed prop shapes Elementor actually stores, instead of saving successfully and rendering as nothing.
Whole-tree prop coercion. Atomic_Props::coerce_tree() runs at the top of save_page_data() — before the governance snapshot — and coerces every element's settings against the widget's own prop schema, so a plain title: "Hi" becomes the envelope the prop declares. Candidate envelopes come only from members the prop described itself (get_key, get_prop_types, get_shape, get_item_type, aliases); the generic primitive fallback runs only for props that did not describe themselves. Eight review rounds converged on that rule: Elementor's primitive validation accepts an "empty" enveloped value for a non-required prop before the type/enum check, so a guessed foreign envelope stores a malformed prop that validates and then renders nothing.
Atomic style wiring. update-element-settings hoists root-level styles / editor_settings to their proper top-level keys, deep-merges instead of replacing, and syncs local class references so a written style is actually referenced from settings.classes. Hoisting requires a positive atomic signal — the e- slug prefix is a convention, not an authority, and when there is no answer the element is treated as classic (an un-hoisted key is inert; a wrongly hoisted one destroys data).
Shared convenience mapper. Elementor_MCP_Atomic_Widget_Map is the single source of the friendly-param → typed-prop mapping, so the add-atomic-* tools and build-page produce byte-identical settings for the same input — build-page previously passed settings through raw and an atomic widget given friendly params came out empty. Props the caller already typed pass through untouched rather than being sanitized to an empty string.
Security
The attachment alt write (_wp_attachment_image_alt) implied by add-atomic-image / build-page is now authorized against the attachment (edit_post on the attachment — editing the page a media item sits on never granted edit rights over the item) and deferred until the page save succeeds. A media prop the caller typed itself cancels the write outright: the alt belongs to the image the widget renders, and the friendly image_id then names an attachment that is not on the page.
Also fixed
- Upstream-correct media shapes —
e-imageemits an id-XOR-urlimage-srcwith animage-attachment-idenvelope and the alt insidesrc;e-self-hosted-video'ssourceis thevideo-srcshape on 4.x, where a bare url envelope makes Elementor refuse the element outright. - SEO extractor descends through an atomic image's
src, and for a media-library image the library's alt is authoritative — Elementor renders_wp_attachment_image_altand ignoressrc.alt, so an empty library alt is the real state. - Four upstream ports — silent-save verification (a truthy
Document::save()that persisted nothing now falls back to direct meta), local style classes re-minted on duplicate, fatal-proof ability registration, andstructuredContentnormalization for strict MCP clients.
readme.txt also catches up: its Stable tag was two releases behind, and its changelog and upgrade notices were missing 1.26.0.
Tests: 852 green on PHP 8.0 / 8.1 / 8.2.
Full details in CHANGELOG.md.
v1.26.0 — Angie bridge (read-only): Aura Design Engine inside Angie
Angie bridge (read-only v1) — K3
Registers "Aura Design Engine" as an MCP server inside Elementor's Angie assistant via the public @elementor/angie-sdk. Opt-in, off by default (Connection tab).
Six read-only inspection tools: list-widgets, get-widget-schema, get-page-structure, list-pages, list-global-classes, list-variables — Angie can answer "what's on this page / which widgets, classes, variables exist / what settings does X take".
No write tools. Angie is an agent (the LLM picks the exact tool + params); this cookie/capability transport cannot carry the per-mutation human approval the governed write path requires. A mutating ability reached through the bridge returns bridge_writes_phase_b. Governed writes stay exclusive to the MCP connection (snapshot-before-write + rollback + optional approval grants).
Enforcement: server-side allowlist with a read-only invariant (annotations.readonly && !destructive) checked against the live registry on both list and execute; each ability's own permission callback runs before execution; anything outside the allowlist is a plain 404. Responses tagged _meta.source: angie-bridge. Bundle enqueued only when the Angie plugin is present.
Deps: @elementor/angie-sdk 1.5.0 + @modelcontextprotocol/sdk 1.30.0 (pinned exactly; includes the #31 security bump with a rebuilt bundle), built with vite 6.
Tests: 17 new bridge tests (permission matrix, allowlist regression guard, read-only invariant, delegation, error mapping); full suite 739 green on PHP 8.0/8.1/8.2.
Full details in CHANGELOG.md.
v1.25.1 — de-brand upstream links + readme sync
Maintenance release.
- De-branded the leftover upstream links. The admin header's Read the Docs and Get Support buttons pointed at the original author's commercial site (
emcp.msrbuilds.com/docs,support.msrbuilds.com) — content this fork does not serve — as did the proxybin/README.mdplugin link. All three now point at the fork's own GitHub (README + Issues). Attribution to@msrbuilds(GPL-3.0) is unchanged. - readme.txt Stable tag synced to 1.25.1, with the previously-missing 1.25.0 (global-classes governance) and 1.25.1 changelog entries added.
No functional/tool changes. Full suite 722 green.