=== WP AI Builder ===
Contributors: toolgive
Tags: ai, mcp, elementor, page builder, artificial intelligence
Requires at least: 6.4
Tested up to: 7.1
Requires PHP: 8.0
Stable tag: 1.22.6
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Connect supported MCP-compatible AI clients (Claude, ChatGPT, Cursor, Windsurf, VS Code, Cline, Continue, Zed, and others) to your WordPress site. Build Elementor pages, audit SEO, edit content, and manage your site through conversation.

== Description ==

WP AI Builder is a self-hosted WordPress plugin that implements the Model Context Protocol (MCP) directly on your server. It gives compatible MCP clients governed access to your WordPress site. Client discovery limits, authentication support, and installed integrations affect available features; test your chosen clients before launch.

**1.22.0 — security and write-safety release**

- SVG import: no XML entity expansion, strict allowlist sanitizer, HTTPS-only pinned download.
- Outbound requests: DNS pin bound to the real connection; every redirect hop re-validated.
- Dry runs report exactly what the real write would do; no draft left behind for predictable refusals.
- Per-page write lock: concurrent edits no longer erase each other; retried appends never duplicate.
- Undo restores the page template and the "no Elementor data" starting state.
- Four high-risk tools now work through the normal dry run → approval → execute flow.

**1.21.1 — validation you can act on, template engine removed**

- Value checks before saving (widget options, units, ranges, repeaters, global tokens) with refusals that name the element, the setting and the allowed values — and no empty draft left behind.
- Optional layout `expectations` lock intent (e.g. three cards in one row).
- `wp_elementor_append_section` is back in the tool list; fixes to image alt text, rating scale and HTML conversion output.
- The old template engine (~40 files) is gone; the SSRF guard now protects every tool call.

**1.21.0 — the AI designs, the plugin builds**

- No page templates: every page is designed by the connected AI and written exactly as native, editable Elementor widgets.
- New required `wp_design_guide`: design rules plus a palette decision based on what published pages really use. Unused or default Kit colours are not treated as the brand; the AI chooses its own palette.
- New native `wp_elementor_append_section` for section-by-section building with safe retries.
- Pre-save checks refuse invented setting names, missing widgets, demo text, spacer containers, broken row/grid maths and icons Elementor cannot display — with the exact fix.
- Refusal messages now reach the AI instead of a generic "internal error".

**1.16.0-rc1 staging candidate**

- Complete live Elementor style/responsive control discovery and widget-specific guidance.
- Four read-only tools for design preflight, focused composition, media discovery and reference analysis.
- Native document saves for new pages, editor-lock protection and verified persistence before requested publication.
- Failed creations remain drafts; native save refusal never triggers a metadata bypass.
- Private inline screenshot output, stronger image validation and accurate cache scope reporting.
- See README.md and docs/AUDIT_1.16.0-rc1.md for current tests, limits and competitor comparison.

**Earlier RC8 candidate additions**

- Read-only `wp_audit_site_chrome` to inspect published header/footer template conflicts, with no automatic global changes.
- More accurate default H2 diagnostics and a warning for boxed containers with percentage widths that may not be active.
- `wp_elementor_design_review` includes optional site-chrome evidence.
- Deployment ZIP excludes CLI tests and tooling scripts (available in a separate developer source archive).
- Not certified for production: test against WordPress/Elementor, browser viewports, permissions, OAuth, recovery and external integrations.

**Earlier RC7 candidate additions**

- Site-aware design direction and responsive composition contracts.
- Optional saved design preferences with explicit administrator confirmation.
- One-call technical/layout/CSS review with optional viewport screenshots.
- Standard MCP image-content output and safer handling of draft screenshot previews.

**What it does**

- Build complete Elementor pages from a single instruction
- Run real Lighthouse performance and accessibility audits (Google PageSpeed Insights)
- Audit SEO across all pages and auto-fix missing meta fields
- Find and fix broken links with HTTP verification — one page or the whole site, including redirected links, links to noindexed pages and orphan pages
- Find every mention of a product or offer across the site, with the percentages and prices used next to it — spot outdated affiliate offers in one call
- Suggest internal links a page is missing, with the exact anchor text
- Look up any URL or slug: post ID, redirects (Rank Math, Redirection, old slugs), final destination, noindex
- Read any plugin setting (secrets redacted) and change LiteSpeed Cache settings safely; request clearing of supported cache layers in one call
- Edit content, manage menus, categories, tags, media, and custom fields
- Manage users, roles, capabilities, and plugins
- Run CLI-style WordPress operations with full approval gating
- Write and manage WPCode snippets
- Inject site-wide or per-page custom CSS
- Persist session state between conversations

**Architecture**

All operations run on your own server. No WordPress credentials pass through any third-party relay. The AI client connects directly to your site via the MCP endpoint.

**Security model**

Most operations are governed by a layered security architecture. The following protections apply to every tool by the registration gate:

- WordPress capability checks (edit_posts, manage_options, etc.)
- Risk classification (safe / moderate / high / destructive)
- Approval gates for destructive operations (only a logged-in human can approve)
- Read-only mode to prevent all writes
- Activity logging for every operation

The following protections are per-tool — they depend on how each tool is registered. Check each tool's `inputSchema` (via `tools/list` from your MCP client) for the authoritative list of accepted arguments:

- Dry-run preview (`dry_run: true`) — supported by many write tools but **not every tool**. Tools opt in to dry-run support at registration time; the default is off.
- Automatic backups before writes — applies only to Elementor write paths (those that route through `ElementorDataHelper::transact()`). Other write operations (post/page content edits, menu operations, theme file edits, SEO meta writes, option updates, cache operations) have their own safety mechanisms (draft workflows, approval queues, SafetyPolicy gating) but do not receive an Elementor backup.
- Full undo support (72-hour window) — applies to operations that record an undo token in the `UndoStore`.

**Supported AI clients**

- Claude (claude.ai) — primary tested client
- ChatGPT with MCP connector
- Cursor (add MCP server in settings)
- Windsurf (add MCP server in settings)
- VS Code with MCP extension
- Any other client that speaks MCP/JSON-RPC over HTTP
- Any MCP-compatible client supporting OAuth 2.0 PKCE

== Installation ==

1. Upload the `wp-ai-builder` folder to `/wp-content/plugins/`
2. Activate the plugin through the WordPress Plugins menu
3. Go to **WP AI Builder** in your WordPress admin
4. Follow the setup wizard to connect your AI client

For Claude: Settings → Integrations → Add custom integration → paste the MCP endpoint URL.
For Cursor / Windsurf / VS Code: Add MCP server → paste the endpoint URL.
For ChatGPT: Settings → Connectors → Custom connector → paste the endpoint URL.

The endpoint URL is shown in the WP AI Builder admin panel after installation.

For Claude specifically (recommended):
1. In claude.ai, go to Settings → Integrations → Add custom integration
2. Enter your MCP endpoint URL (shown in WP AI Builder settings)
3. Approve the connection in your WordPress admin

== Frequently Asked Questions ==

= Is it free? =
Yes. WP AI Builder has a Free plan with core WordPress management, native Elementor JSON page/post creation, basic SEO and diagnostics, and 50 MCP tool calls per day. Starter and Pro unlock additional tool families and higher daily limits.

= Does my data pass through a third-party server? =
No. The MCP server runs directly on your WordPress installation. The AI client connects straight to your site.

= Can the AI publish content without my approval? =
Destructive operations (permanent deletion, user management, live search-replace) require explicit approval through a browser confirmation page. The AI cannot self-approve.

= Does it work without Elementor? =
Yes. Content management, SEO, media, user management, and CLI operations work without Elementor. Page building requires Elementor.

= Which version of Elementor is required? =
Native layouts using available Free widgets work with Elementor Free. Pro widgets, dynamic templates and other integrations require their corresponding installed features; availability must be inspected at runtime.

= Does the Lighthouse audit require an API key? =
The tool works without a key, but shared hosting often exhausts Google's anonymous quota quickly. A free Google PageSpeed Insights API key (25,000 audits/day) is recommended. Add it in WP AI Builder → Settings.

== Changelog ==

= 1.22.6 =
* Fix: dashboard version now matches the installed version.
* Fix: removed the highlight behind the "Active" status value.
* Fix: Connect AI Client now detects a working connection.
* Fix: Approval Queue and Audit Log styling; accurate OAuth scope description on the consent screen.
* Fix: missing logo and payment badges on the Pricing page; restored the Freemius checkout script.
* Improved: clearer OAuth consent screen and much more detailed Dashboard, Setup Wizard, Connect, Security, Backup, Help, Activity Log and Approval Queue pages.

= 1.22.1 =
* Fix: render_faq no longer reports a false failure for add-on accordion, tabs and toggle widgets (ElementsKit, Essential Addons, Royal, UAEL...). A widget that renders nothing still fails the check.

= 1.22.0 =
* Security: SVG XML parsing without entity expansion; strict SVG allowlist sanitizer; redirect-safe, HTTPS-only, size-capped SVG download.
* Security: DNS pinning now applies to the actual cURL connection (with peer-IP verification); per-redirect validation for all outbound requests during tool calls; own-origin exception requires scheme+host+port.
* Dry runs share the real write's validation (blocking_errors); top-level structure checked before a draft is created.
* Per-post write lock for all Elementor mutations; append replay checked under the lock.
* Backups record empty baseline, page template and meta presence; restore puts them all back.
* dry_run + approval flow for wp_blocks_delete_reusable, wp_site_editor_patch_global_styles, wp_site_editor_upsert_template, wp_build_session_rollback (rollback now authorises and validates each target).
* Gateway propagates target failures; validator enforces minItems/maxItems/uniqueItems/min/maxProperties.
* Fix: global styles tools used a removed WordPress API and wrote records WordPress ignored.

= 1.21.3 =
* Fix: deprecated-toggle advice no longer recommends the non-existent toggle_type setting; points to nested-accordion's real default_state and max_items_expended controls.
* Fix: render verification now recognises Elementor nested accordions (e-n-accordion markup), so valid FAQ sections no longer report render_faq failure.
* Fix: table verification again covers the UAEL data-table widget (uael-data-table); price-table remains excluded.
* Note: wp_design_guide acknowledgement is per user for 12 hours (any connected client of that user), not strictly per client.

= 1.21.2 =
* Fix: price-table no longer triggers a false render_tables failure.
* Fix: design_guide_required no longer fires after the guide was read when the token's client_id is missing or changes.
* Deprecated accordion/star-rating warnings now name the replacement widget.

= 1.21.1 =
* Fix: wp_elementor_append_section (native) was wrongly blocked as retired; registered again. Core tool list corrected (wp_design_guide).
* New: DesignPreflight value checks in the native write path and the save gateway; readable refusals; no orphan drafts; layout expectations on create/build/append/preflight.
* New: wp_elementor_preflight_design runs exactly the write checks; optional post_id baseline.
* Fix: add_image alt text, add_rating scale, html_to_elementor keys for Elementor 4 (flex_gap, start/end alignment, selected_icon, widget style keys).
* Security: SSRF guard for outgoing requests now active during every MCP tool call (same-origin allowed).
* Removed: the template render pipeline and its dead classes and handlers; admin pipeline tab and test render. Historical test reports removed from the package.

= 1.21.0 =
* Removed all fixed page templates and preset styles. The connected AI designs every page; the plugin builds exactly the native Elementor tree it sends. Retired tools: wp_builder_mcp_build, wp_elementor_apply_layout_skill, the wp_layout_* skill/recipe tools, wp_preview_section_type, wp_elementor_update_section, wp_builder_mcp_upgrade_page_to_premium, wp_builder_mcp_optimize_page_visual_quality, wp_builder_mcp_apply_brand_kit, wp_builder_mcp_verify_pipeline_integrity, wp_design_check_layout_history, wp_design_record_layout.
* New: wp_elementor_append_section now takes native Elementor JSON designed by the AI (one section per call, operation_id retries, backups, verified save).
* New: wp_design_guide, required once per session before page writes. Gives design principles, composition rules, page planning, verified Elementor setting names and a palette/typography decision from real site usage: unused or Elementor-default Kit colours are not treated as the brand, so the AI chooses its own palette; colours really used on published pages, a saved brand profile or a saved design direction are kept.
* New: pre-save checks on create, build, append, update and batch update refuse unknown setting keys, uninstalled widgets, Elementor demo text, empty spacer containers, wrapping-row width maths, grids with empty rows and Font Awesome icon names Elementor cannot display, each with the exact fix. Unchanged elements on a rebuilt page are not re-judged.
* Fix: handler refusal messages (e.g. "requires confirm=true", invalid element ids, the raw-HTML editability gate) now reach the AI, scrubbed, instead of an anonymous internal-error reference.
* Fix (review): disabling the design guide no longer blocks page creation; real sentences are no longer mistaken for placeholder text; dynamic-tag content and column-flow grids are accepted; a retried section append can no longer be added twice; error messages hide server paths and internal addresses.
* Updated: AI connection instructions, wp_builder_mcp_get_started and wp_builder_mcp_plan_website describe the AI-designed workflow and no longer prescribe layouts.

= 1.20.2 =
* Fix: pros_cons sections no longer render with empty card bodies when items use PRO:/CON: prefixes instead of separate pros/cons keys.
* Fix: review_summary sections containing "Label: Score/Total" string items now render as a visual scorecard grid instead of a plain bullet list.
* Fix: pricing sections now render as styled plan cards with the price prominently displayed; previously fell through to the default plain-text renderer.
* Fix: features sections now render as a 2-column icon-style feature grid; previously fell through to the default plain-text renderer.

= 1.20.1 =
* Fix: hero sections appended via wp_elementor_append_section now output h1 instead of h2, giving every page exactly one H1 for SEO.

= 1.20.0 =
* New wp_elementor_append_section tool: build an Elementor page one designed section at a time, with safe retries.
* Brand-coloured heroes keep white text and stay WCAG AA readable; hero and dark-section text follow one contrast-checked colour.
* Layout builds switch default-template pages to Elementor Full Width without overriding a template the user chose.

= 1.16.0-rc1 =
* Improve Elementor controls, responsive discovery, composition/media/reference evidence and native save reliability.
* Protect editor locks, failed drafts and screenshot privacy; correct cache scope reporting.
* Add isolated WordPress, HTTP and desktop/tablet/mobile acceptance tests. See the release audit for measured results and untested integrations.


= 1.15.36-rc5-dev =
* Experimental read-only native layout-contract warnings for CSS-dependent peer card grids.
* Reports desktop layout risks without changing content or claiming browser visual verification.
* Adds six independent regression assertions.


= 1.15.36-rc4 =
* Bound oversized scalar control descriptions and option labels in runtime widget schemas, and cap aggregate metadata values per response while reporting truncation.
* Preserve same-origin CSS @import rules while continuing to remove cross-origin, non-HTTP, and policy-disallowed imports. This restores safe local CSS imports without weakening external-resource filtering.
* Add regression tests for bounded strings, safe same-origin imports, explicit-port origin checks, and data-URL stylesheet imports. This remains a staging candidate; live client and security gates are still required.

= 1.15.36-rc3 =
* Fix runtime widget-schema discovery so widgets with more than 300 controls can be inspected through deterministic pagination.
* Add control search and exact lookup; expose additional public control metadata while excluding non-serializable implementation values.
* Paginate large option lists and report total counts, offsets, and any metadata truncation explicitly.
* Add deterministic regression tests for widget controls, option pagination and CSS resource filtering. Harden CSS imports, escaped resource names, non-HTTP schemes, cross-origin ports and quoted `image-set()` candidates. This remains a staging candidate, not a launch certification.

= 1.15.36-rc2 =
* Replace authority claims in active tool metadata with factual capabilities, side effects and limits. Server approval remains separate from client consent.
* Bound oversized responses without including the original nested payload in the preview; return an affected target ID when available.
* Clarify SEO checklist scope, incomplete checks and optional image advisories. Score H1 findings by structured status instead of fragile message prefixes.
* Return an actionable missing-page error. Add isolated client-reporting acceptance tests; no production approval or permission checks were relaxed.

= 1.15.36-rc1 =
* Release candidate: approval retries preserve the exact token and bind resolved content references; replay returns an execution receipt.
* Fix protected-page double approvals, missing native layout schemas and DesignTool helpers, misleading tool annotations, and hidden schema constraints.
* Verify backup persistence; preserve escaped metadata; decode site snapshots correctly and report partial restoration. Oversized snapshots fail instead of truncating.
* Correct onboarding/design guidance, plugin-inventory warnings, request-timing labels, malformed JSON-RPC input handling, and stale release documentation.
* Add isolated WordPress, dispatcher, protocol, recovery, read-tool and browser acceptance tests. See docs/TESTING_REPORT_v1.15.36-rc1.md for evidence and untested release gates.


= 1.15.32 — measurable layout sizing diagnostics =
* Layout diagnostics now report each direct Elementor column/container's raw desktop, tablet and mobile sizing settings, including legacy column widths and common container sizing controls.
* Reports complete legacy column-width totals as facts, without scoring or changing the design. This helps the connected AI compare the intended widths with the saved JSON.
* Content-volume metrics now count common user-facing text fields instead of treating styling values (font names, colors, units and other configuration strings) as page copy.
* Advanced diagnostics now report WooCommerce integration status from the actual plugin activation and registered-tool count, removing a stale hard-coded “deferred” message.
* No automatic visual redesign, aesthetic score, or write-time rejection has been added.

= 1.15.30 — AI brain / MCP body architecture =
* The connected AI/user is now the creative authority. WP AI Builder performs technical validation, security checks, approvals, backups/undo, sanitization and exact Elementor execution; it does not judge or block aesthetics.
* Native JSON creation no longer auto-applies Brand Memory, hidden compatibility CSS, or design-score gates.
* Added objective Elementor layout diagnostics (structure, widget/media/text counts, labels, responsive/global references) with no aesthetic score.
* Deprecated aesthetic-check tools now return objective signals only; v1.15.29 professional scoring is superseded.
* Removed hidden frontend visual overrides and header/footer suppression from the plugin bootstrap.
* Added session-state JSON references and ordered chunk references for large Elementor payloads, so AI clients can stage and execute exact JSON without hitting direct tool-call size limits.
* Legacy template builder remains for compatibility but defaults to strict/no-filler behavior; JSON-first creation is the recommended path.

= 1.15.29 — Historical professional-design experiment (superseded by 1.15.30) =
* Added a core professional-composition guard to native Elementor JSON creation. Structurally valid but template-like layouts are blocked during dry-run before approval can be issued.
* The guard checks text dominance, visual anchors, single-column overuse, split-layout variety, dense prose runs, signature elements, repeated section patterns, and Elementor section labels.
* Added `design_quality_mode` with `professional` as the default, `editorial_minimal` for intentionally restrained editorial work, and `basic` only for user-requested plain utility/legal pages.
* `wp_verify_page_design` now reports both technical and professional-design scores; the weaker score governs the overall grade, preventing technically-correct template layouts from receiving misleading 100/A+ results.
* The primary JSON tool now explicitly instructs AI clients to read the site design and content layout before building.
* Regression suite expanded to 485 assertions, including fixtures that reject a generic heading/paragraph stack and accept a varied media-led composition.

= 1.15.28 — ChatGPT OAuth compatibility =
* Fixed ChatGPT connection failures caused by the MCP endpoint asking clients for `scope=mcp` while the OAuth server rejected that same scope as `invalid_scope`.
* MCP authentication challenges now request the advertised `mcp:admin` scope.
* New requests using the historical generic `mcp` scope are safely mapped to non-destructive `mcp:admin`; they do not inherit the historical wildcard's destructive authority.
* Scope-less connections default to `mcp:admin` so normal build/edit tools are available immediately after approval; `mcp:destructive` still requires an explicit grant and the existing human approval flow.
* Free / Starter / Pro entitlements and WordPress capability checks continue to restrict tool availability independently.

= 1.15.27 — Plan entitlements, JSON-first posts, dependency accuracy =
* Added central server-side Free / Starter / Pro tool entitlements based on the approved feature matrix: Free 138 tool entries, Starter 226, Pro up to 272 when conditional WooCommerce tools are available.
* Paid tools are filtered from tools/list and blocked again at tools/call execution. Plan-denied calls are rejected before daily quota is consumed.
* Tool-list cache keys now include the active subscription plan so upgrades/downgrades cannot reuse stale tool catalogs.
* wp_elementor_create_page_from_json now supports post_type=page|post and writes the correct Elementor document type (wp-page/wp-post), with native WordPress edit/publish capability checks.
* wp_html_to_elementor remains available for genuine HTML imports but its schema now explicitly redirects normal new page/post creation to the JSON tool.
* wp_session_state_write/delete now require a write-capable OAuth scope and are no longer advertised as read-only operations.
* Dependency reporting now includes minimum-version compatibility, optional Elementor Pro awareness, consolidated choose-one SEO alternatives, and the documented authenticated /wp-json/wp-ai-builder/v1/dependencies endpoint.
* Safety-matrix recovery reporting is conservative: mutating tools without an explicit recovery guarantee are reported as having no guaranteed automatic backup/rollback instead of ambiguous unknown coverage.
* WordPress dashboard plan comparison now matches the enforced feature tiers and daily tool-call limits.
* Validation: bundled regression suite expanded to 465 assertions; all pass before packaging. Live WordPress/Freemius/Elementor staging validation is still required before production launch.

= 1.15.26 — Security hardening and audit remediation =
* OAuth defaults to read-only `mcp:read`; invalid-only scope requests return `invalid_scope`. The legacy `mcp` wildcard is no longer advertised for new grants, while existing legacy tokens remain governed by the migration policy.
* Media URL uploads now enforce the media-domain allowlist on every redirect hop, in addition to the existing SSRF checks and DNS pinning.
* IPv6 cURL DNS-pinning entries use bracketed address notation; literal-IP URLs skip unnecessary DNS overrides.
* Uninstall cleanup escapes underscores in the option prefix to avoid matching unrelated option names.
* PHP version diagnostics now match the PHP 8.0 minimum, and the displayed MCP tool count is dynamic.
* Validation: 442/442 regression assertions pass; 141 PHP files pass syntax lint; runtime security matrix loads 259 tools with zero load errors. Live WordPress and native cURL integration checks remain required before production certification.

= 1.15.5 — Self-audit: Application Password access restored + approval-flow deadlock fix =
* Security regression fixed (CRITICAL — found in self-audit): the v1.15.3 fail-closed OAuth scope change silently broke EVERY Application Password client. Application Passwords authenticate via Basic auth, so no OAuth Bearer token is ever presented — the scope payload stayed empty and ALL 248 scoped tools were denied (and tools/list returned an empty catalog), despite Application Passwords being a documented connection method on the plugin's own Connection Config page. Requests are now classified by authentication mode: Bearer-token requests live under the OAuth scope contract (enforced exactly as before); WordPress-native credentials (Application Password / logged-in browser session) are governed by WordPress capabilities, role-based group access, governance and SafetyPolicy — the same trust model as every other wp-admin action the user can take. The tools/list cache key now includes the auth mode so the two request classes can never share a cached catalog. Narrow Bearer tokens remain strictly enforced (verified by test).
* Approval flow fixed (HIGH — found in self-audit): approving a TOOL operation (delete / Elementor / SEO / section write) from the Approval Queue admin page burned the approval without executing it. adminApprove() called the browser-side executor, found none registered (only wp_run_cli has one), recorded a false "No executor registered" failure — and then flipped the row to EXECUTED anyway, so the AI's retry saw already_executed and the human-approved operation could NEVER run. This reintroduced (one layer down) the exact "human approves, AI still cannot execute" dead-end the eighth review flagged for v1.15.3. Tool approvals are now AUTHORIZE-ONLY in the browser: the row stays APPROVED for the AI's retry, which re-validates the tool name + canonical payload hash before executing. The magic-link path no longer writes false failure audit entries; the admin UI message now says what actually happened ("Approved. The requesting AI client can now execute this operation…").
* Race + double-validation fixed (MEDIUM — found in self-audit): the four assert*Allowed() confirmation gates validated the approval TWICE per attempt (consumeCriticalNonce + a redundant consumeCriticalConfirmationWithDetail call for the error message). If a human clicked Approve between the two calls, the second call consumed the approval while the code still threw — burning the approval without executing anything. Now a single validation pass serves both the decision and the detailed error (halves the DB round-trips too — proven by a per-attempt DB-read counter in the test suite).
* Audit-trail completeness: magic-link approvals now record WHICH administrator clicked Approve (approved_by was only set on the admin-page path); AI-retry consumption writes an honest result_summary on the approval row ("Consumed by an MCP tools/call retry…") instead of leaving administrators with a silently flipped row.
* Admin UX: the pending-approval count cache is now ACTUALLY invalidated (create/approve/decline/adminApprove/adminDecline/pruneExpired all drop it — the comment claimed invalidation that no code performed, so the admin badge could lag 30 seconds behind reality).
* Info hygiene: the dispatch error path now writes the SCRUBBED exception message to the Activity Log (previously the raw exception — which can contain filesystem paths or DB details — was logged while the AI-facing message was properly redacted).
* Tests: suite expanded 239 → 265 assertions (new Tests 26–30: Application Password dispatch through the real pipeline, auth-mode cache-key separation, admin-approve authorize-only + AI-retry end-to-end, single-validation DB-read counter, approved_by attribution, countPending invalidation). The runtime probe grew 15 → 28 checks and now exercises the Application Password flow and the admin-approve → AI-retry flow through the REAL dispatch pipeline. 265/265 assertions + 28/28 probe checks pass, executed from the shipped ZIP.

= 1.15.4 — Approval payload binding + OAuth scope context + MCP 2026 envelope conformance =
* Security (HIGH — 8th review F-01): Approval issuance is now integrated with tool binding. The v1.15.3 dry-run helper read `__tool_name`/`__tool_args` keys that no tool ever provided, so every approval row was created with tool='unknown' and the v1.15.3 validator rejected every retry (tool_mismatch) — the protected-page approval flow dead-ended after the human approved. The helper now reads the real tool name + coerced arguments from the dispatch context (the same place the consumer reads them) and FAILS CLOSED (missing_dispatch_context) when the context is absent — no more unbound 'unknown' approvals.
* Security (HIGH — 8th review F-02, shipped together with F-01 as the audit required): Approvals now bind the COMPLETE mutation payload. A canonical SHA-256 hash of every semantic argument (content, Elementor layout tree, CSS, replacement text, SEO values, force — recursively key-sorted; only dry_run/confirm/critical_confirm excluded) is stored with the approval (new payload_hash column, schema 1.2) and compared at execution (hash_equals). Any changed payload for the same target → payload_mismatch, approval NOT consumed. Consumption without a dispatch context fails closed (missing_context). Argument comparison is now strict and type-aware (string "1" no longer matches integer 1).
* Security (MEDIUM-HIGH — 8th review F-03): OAuth scope context no longer depends on WordPress hook order. checkAuth() now explicitly verifies the presented Bearer token inside the permission callback (OAuthServer::bearerTokenFromRequest + verifyToken), repopulating the verified scope claims even when another plugin resolved the current user earlier (previously: valid token + early user resolution = every scoped tool denied). Invalid/expired/revoked presented tokens are rejected 401 (never silently falling back); a cookie session for user A + bearer for user B is rejected as an identity conflict.
* Security (MEDIUM — 8th review F-04): The tools/list cache key now includes a digest of the verified token scopes — a read-only token and a broad token for the SAME WordPress user can no longer receive each other's cached catalog. Dispatch-level scope enforcement was already correct; this fixes catalog isolation.
* Protocol (HIGH for strict 2026 clients — 8th review F-05): MCP 2026-07-28 result envelopes are now schema-conformant. resultType is 'complete' (the spec-defined value) instead of 'initialize'/'ping'/'list'/'discover'/'tool'; cacheScope is 'private' (spec-valid) instead of 'server'; server/discover now carries ttlMs + cacheScope hints; and NONE of these fields are sent to 2025-11-25 (or older) clients, whose strict schemas reject unknown fields. The transient cache stores the version-neutral payload; the envelope is added per request, and stale envelope fields cached by ≤ v1.15.3 are stripped before serving.
* Protocol (MEDIUM — 8th review F-06): New header-conformance modes. Default 'compat' validates Mcp-Method/Mcp-Name whenever the client sends them (documented exception); 'strict' (wp_builder_mcp_header_conformance option or wp_builder_mcp_header_conformance_mode filter) enforces the 2026-07-28 required headers and rejects absence with -32020. Mismatches are always rejected. Additionally, INCONSISTENT protocol-version declarations (MCP-Protocol-Version header vs request _meta disagreeing) are now rejected with -32020 instead of silently executing one version while echoing another.
* Security (MEDIUM — 8th review F-07): Legacy 'mcp' wildcard migration path. Token records now carry their granted scope; a new admin warning lists active legacy wildcard grants; and a new wp_builder_mcp_legacy_scope_mode option lets the site operator 'allow' (default, warning only), 'migrate' (refreshed legacy tokens are re-issued with the explicit non-destructive scope set — destructive capability requires deliberate re-authorization), or 'deny' (legacy refreshes refused until reconnect).
* Release assurance (MEDIUM — 8th review F-08): New release-time tool security matrix. scripts/generate-tool-matrix.js (AST-based) parses every ToolRegistry::register() call site and generates docs/TOOL_SECURITY_MATRIX.md: per-tool capability, OAuth scope, risk, dry-run, approval, backup, rollback, irreversibility — with moderate+ tools carrying 'unknown' backup/rollback listed as explicit OPEN ITEMS with source files (248 tools classified; 121 open items tracked).
* Security (self-identified during F-01 remediation): wp_delete_page / wp_delete_post dry-run previews and the ElementorDataHelper::transact() dry-run branch no longer issue self-satisfiable 16-char hex nonces to the AI (the v1.13-era path let the AI permanently delete ANY page/post with no human in the loop). Critical-page trash/delete and every force=true deletion now route through the human ApprovalQueue with full tool + payload-hash binding; plain trash of non-critical pages still needs only confirm=true.
* Tests: Suite expanded to 25 tests / 239 assertions covering all of the above, PLUS a first-of-its-kind runtime probe that executes the REAL dispatch pipeline end-to-end (dry-run → human approval → same-payload execution → payload-substitution rejection → strict header mode). The probe exposed and the release fixes two additional defects: the response sanitizer was quarantining the new discovery cache hints into _meta, and the v1.7.0-era exception-masking layer was hiding deliberate safety-policy rejections (payload_mismatch / approval_pending / approval_expired / …) behind a generic "internal error occurred" message — a new SafetyPolicyException type makes them surfaceable with machine-readable error codes (end-to-end payload binding incl. the substitution attack, fail-closed issuance/consumption, scope-separated cache keys with signed tokens, MCP envelope conformance on both protocol versions, header modes, version-disagreement rejection, explicit bearer re-verification, legacy wildcard inventory, release matrix).

= 1.15.1 — Least-privilege scopes + honest safety matrix + Bearer-token check fix =
* Security (HIGH — ChatGPT review Finding A): `Scopes::defaultScopes()` was too broad — granted `mcp:read + mcp:content + mcp:design + mcp:seo + mcp:media` (5 scopes, several of which permit writes) when an OAuth client didn't specify a scope. Narrowed to **`mcp:read` only** (least privilege). Site admins can grant additional scopes explicitly from the OAuth Clients admin page OR by including the scope in the authorize URL. `mcp:destructive` is NEVER granted by default — it requires explicit request via the authorize URL AND the human administrator sees a destructive-scope warning on the consent screen.
* Security (HIGH — ChatGPT review Finding A): The OAuth consent screen previously said only "Same WordPress permissions as your signed-in account" — obscuring the actual granular MCP scope grant. Now displays every requested and granted scope with its plain-language description (`Scopes::describe()`), with a red destructive-scope warning when `mcp:destructive` is in the grant set.
* Architecture (HIGH — ChatGPT review Finding B): The per-tool safety matrix previously derived `backup_coverage` and `rollback_coverage` from group+risk heuristics — e.g., assigning `theme_draft` backup to all `system`-group tools even though the system group includes operations other than theme-file editing. This could misrepresent real recovery guarantees. Now uses **explicit per-tool declarations** via two new `ToolRegistry::register()` parameters: `string $backupCoverage = 'unknown'` and `string $rollbackCoverage = 'unknown'`. Defaults to `'unknown'` (not heuristic-derived) so audits can find tools that haven't been explicitly classified. Safe-risk tools auto-set `backupCoverage='none'` and `rollbackCoverage='n/a'` (read-only ops have nothing to back up). The `irreversibility` field now also supports `'unknown'` as an honest value (was heuristic in v1.15.0).
* Compatibility (MEDIUM — ChatGPT review Finding C): `docs/SECURITY.md` previously stated MCP 2026-07-28 support was "CONFORMANT" — overstated because the Mcp-Method/Mcp-Name header-presence trade-off remains. Updated to "CONFORMANT WITH ONE DOCUMENTED EXCEPTION" with an explicit honest qualifier that the plugin is NOT 100% spec-conformant on the 2026-07-28 wire format (the header-presence rule is preserved as a documented compatibility trade-off because requiring presence breaks Claude.ai and other production MCP clients).
* Compatibility (HIGH — first review Finding #6): The `/mcp` GET/DELETE handler used `is_user_logged_in()` to decide between 405 (method not allowed) and 401 (auth required). But OAuth Bearer-token clients don't have a WP browser session — they authenticate via the `Authorization: Bearer ...` header. So a valid OAuth client doing GET /mcp got a 401 instead of 405, breaking MCP clients that probe the endpoint. Fixed: now detects EITHER authentication method (WP browser session OR Bearer token in Authorization header, checked via `$_SERVER['HTTP_AUTHORIZATION']` / `REDIRECT_HTTP_AUTHORIZATION` / `apache_request_headers()`). If either is present → 405 "Method not allowed". If neither → 401 with WWW-Authenticate (so OAuth clients get the resource_metadata discovery URL).
* Tests: Updated Test 12 to verify the new least-privilege default (mcp:read only, was 5 scopes). Updated Test 15 to register tools WITH explicit `backupCoverage`/`rollbackCoverage` declarations and verify the matrix respects them; added a 4th test tool (`matrix_undeclared`) WITHOUT explicit declarations to verify the matrix honestly reports `'unknown'` instead of heuristic-deriving from group.

= 1.15.0 — Architectural hardening (MCP conformance + granular OAuth + safety matrix) =
* Architecture (F-04 — MCP 2026-07-28 wire-format conformance, CRITICAL per all 4 prior reviews): The previous releases advertised 2026-07-28 support but the documentation explicitly noted partial implementation. v1.15.0 closes the gaps: (1) `server/discover` now emits both `protocolVersions` (legacy field for backward compat) AND `supportedVersions` (modern 2026-07-28 field); (2) Server identity is now emitted both at the top-level `serverInfo` (legacy) AND under `_meta.io.modelcontextprotocol/serverInfo` (modern spec location); (3) `tools/list` now emits cache hints `ttlMs` (300000 ms = 5 min) and `cacheScope` ('server') per the 2026-07-28 spec; (4) `resultType` is now emitted on every response result variant (`initialize`, `ping`, `tools/list`→'list', `tools/call`→'tool', `server/discover`→'discover'); (5) the `sanitizeResultFields()` allowlist was updated to permit these new top-level fields instead of quarantining them into `_meta`. The Mcp-Method/Mcp-Name header-required behavior is preserved as documented (validated-if-present, not required-presence) — requiring presence breaks Claude.ai and other production MCP clients per the existing v1.5.3 BUG-MCPHEADER comment.
* Architecture (F-06 — Granular OAuth scopes, CRITICAL per all 4 prior reviews): The OAuth scope was previously hardcoded to the single string 'mcp'. v1.15.0 introduces a 9-scope granular system via a new `includes/OAuth/Scopes.php` class: `mcp:read`, `mcp:content`, `mcp:design`, `mcp:seo`, `mcp:media`, `mcp:file`, `mcp:code`, `mcp:admin`, `mcp:destructive`. The scopes form an implication hierarchy (e.g., `mcp:admin` implies everything except `mcp:destructive`, which must be granted explicitly). Backward compatibility: tokens issued by v1.14.x with `scope=mcp` are treated as the legacy wildcard — they grant all scopes until the admin explicitly downgrades the client. Tools declare their `requiredScopes` at registration time (default auto-derived from risk + group: safe→read, moderate+wordpress→content, moderate+elementor→design, moderate+seo→seo, high→admin, destructive→destructive). Dispatch enforces scope at execution time via `OAuthServer::currentTokenScopes()` + `Scopes::tokenHasAnyScope()` between the capability check and the role-group check in `dispatchToolCall()`. OAuth metadata endpoints (`/.well-known/oauth-protected-resource`, `/.well-known/oauth-authorization-server`) now advertise the 9 explicit scopes; the legacy `mcp` wildcard is no longer advertised for new grants.
* Architecture (F-07 — Per-tool safety matrix, HIGH per all 4 prior reviews): New `ToolRegistry::getSafetyMatrix()` method returns a machine-verifiable per-tool matrix with: capability, risk, supports_dry_run, required_scopes, approval_required (always/conditional/never), backup_coverage (elementor/theme_draft/none), rollback_coverage (elementor_backup/theme_draft/undo/none/n/a), irreversibility (reversible/snapshot_only/irreversible). New admin-only REST endpoint `/wp-ai-builder/v1/tools/safety-matrix` exposes the matrix for admin UI consumption and external audit. The matrix is auto-derived from the registry — no per-tool maintenance required.
* Architecture (Reviewer recommendation #2 — central security gateway): Verified single-point dispatch through `JsonRpcServer::dispatchToolCall()` — every tools/call flows through 27 mandatory gates (REST permission_callback → body guards → JSON parse → shape validation → protocol negotiation → header/body integrity → notification handling → method dispatch → Freemius daily limit → usage increment → tool name validation → reserved-arg strip → enabled lookup → capability check → **NEW scope check** → role-group check → input validation → per-risk rate limit → governance gate → handler invocation → per-tool SafetyPolicy gate → WP_Error normalization → error-flag promotion → activity logging → response size cap → JSON encode → envelope construction → outer post-processing → HTTP response). No alternate path exists. Documented in `docs/SECURITY.md`.
* Tests (F-05 — Expanded test coverage): Bundled regression test suite grew from 70 assertions across 10 tests (v1.14.2) to 95+ assertions across 15 tests (v1.15.0). New tests cover: scope implication hierarchy (Test 11, 14 assertions), scope validation + default fallback (Test 12, 7 assertions), default-scope-for-risk-and-group derivation (Test 13, 7 assertions), ToolRegistry requiredScopes auto-derivation (Test 14, 6 assertions), and the safety matrix derivation (Test 15, 12 assertions). All tests pass.
* Backward compatibility: Existing tools that don't pass `requiredScopes` to `ToolRegistry::register()` auto-derive a default from risk + group. Existing OAuth tokens with `scope=mcp` are treated as wildcards. Existing MCP clients that omit Mcp-Method/Mcp-Name headers continue to work. No breaking changes for v1.14.x installations upgrading to v1.15.0.

= 1.14.2 — SSRF pinning + IP-range denylist + no-cURL fail-closed =
* Security (CRITICAL): UrlResolverTool link-audit fetches of external URLs now route through `UrlSafety::fetchPinned()` which (a) runs `preflightWithResolvedIps` internally, (b) pins the TCP connection to the pre-flight-verified IP via cURL's CURLOPT_RESOLVE (preserving the original hostname for TLS SNI + cert validation), (c) enforces `limit_response_size=2048` at the transport layer, (d) disables redirects (`redirection=0`). The v1.14.1 fix added a preflight check but then called `wp_safe_remote_get` on the ORIGINAL URL — leaving a time-of-check/time-of-use gap because wp_safe_remote_get re-resolves the hostname at TCP-connection time and is therefore still vulnerable to DNS rebinding between preflight and connection. The reviewer's recommendation was to pin the actual connection, not just preflight. v1.14.2 implements this fully.
* Security (CRITICAL): `UrlSafety::isIpSafe()` was rewritten with an explicit IANA special-use IPv4 + IPv6 CIDR denylist using `inet_pton` packed-binary prefix matching. The v1.14.1 implementation relied on PHP's `FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE`, which does NOT cover several special-use ranges — the third review confirmed that `100.64.0.1` (RFC 6598 CGNAT), `198.18.0.1` (RFC 2544 benchmarking), `192.0.2.1`/`198.51.100.1`/`203.0.113.1` (RFC 5737 documentation), `224.0.0.1` (multicast), `240.0.0.1` (reserved for future use), `255.255.255.255` (limited broadcast), `0.0.0.0` (this network), `192.0.0.1` (IETF protocol), and IPv4-mapped IPv6 (`::ffff:127.0.0.1`) were all incorrectly classified as SAFE in v1.14.1. The new denylist blocks all IANA-registered special-use ranges; IPv4-mapped IPv6 and IPv4-compatible IPv6 addresses are unwrapped to their embedded IPv4 and re-checked against IPv4 rules.
* Security: `UrlSafety::fetchPinned()` now fails closed with a `WP_Error('curl_required', ...)` on PHP installations without the cURL extension (ext-curl). Previously, calling `fetchPinned` on PHP without cURL raised `Error: Undefined constant "...CURLOPT_RESOLVE"` (a fatal Error, not a catchable Exception) — breaking OAuth client-metadata fetches and media downloads on cURL-less PHP installs. The fix: check `defined('CURLOPT_RESOLVE')` at the top of `fetchPinned`. If cURL is missing, return a WP_Error explaining the requirement with installation hints. NEVER silently fall back to an unpinned fetch — that would reintroduce the DNS-rebinding SSRF the helper exists to prevent.
* Reliability: Uninstall cleanup now drops the `wpaib_usage` table (created by `PlanManager` to track daily AI request counts per plan). The v1.14.1 uninstall dropped the approval-queue, audit-log, governance-log, and activity-log tables but missed `wpaib_usage` — leaving plugin-owned usage data behind after uninstall. v1.14.2 explicitly drops it. Tested for both single-site and multisite installs.
* Documentation: Plan limit discrepancy fixed. The main plugin file's top comment said `Free → 100 requests/day` but `PlanManager::DEFAULT_LIMITS` actually sets `free => 50`. Aligned the comment to `50` to match the code (single source of truth).
* Tests: Bundled regression test suite expanded from 47 to 70+ assertions. New Test 9 covers all the IANA special-use IPv4 + IPv6 ranges that v1.14.1 incorrectly classified as safe (with positive controls confirming just-outside-range addresses are allowed). New Test 10 covers the cURL-missing fail-closed path using the `$GLOBALS['__wp_builder_mcp_test_no_curl']` test hook.

= 1.14.1 — Test-suite fix + SSRF audit gap closed =
* Fixed: the bundled regression test suite (`tests/run-tests.php`) failed as shipped in v1.14.0 because `SafetyPolicy::isCriticalPost()` calls `absint()` (a WordPress helper) but the test harness did not stub it. PHP fatalled with "Call to undefined function absint()" before any test could run. The v1.14.1 test file now stubs `absint()` plus `esc_url`, `esc_html`, `esc_attr`, `wp_verify_nonce`, `get_permalink` (defensive — these are used by code paths adjacent to those under test and would otherwise fatal if a future test exercised them). After the fix, all 47 test assertions pass.
* Fixed: the v1.14.0 test suite had a 1/47 failure in Test 8 (`UrlSafety::preflightWithResolvedIps + fetchPinned — pre-flight block`). The test set the DNS override under `$GLOBALS['__wp_test_dns']`, but `UrlSafety::resolveHostname()` reads `$GLOBALS['__wp_builder_mcp_test_dns']` — different variable name. Without the override firing, the code fell through to real DNS resolution of the synthetic `private-rebinding.test` hostname, which returned no IPs, producing `reason='unresolvable'` instead of the expected `reason='private_or_reserved_ip'`. Variable name aligned; test now passes.
* Security (SSRF audit): the v1.14.0 `UrlSafety::fetchPinned()` helper hardened the OAuth client-metadata fetch paths and the media downloader, but the ChatGPT review (Finding 3) correctly noted that other outbound HTTP calls existed in the codebase and the existence of a safe helper does not prove every fetch path uses it. v1.14.1 closes this gap for the one user-influenced path that was missed: `UrlResolverTool::wp_resolve_url` (and the link-audit fetches) now call `UrlSafety::preflightWithResolvedIps($href)` before `wp_safe_remote_head`/`wp_safe_remote_get` on external URLs from page content. If the resolved IP is private/reserved/loopback, the fetch is skipped with `status='blocked'` and `detail='SSRF pre-flight blocked: <reason>'` — no network request is made. The remaining outbound calls in the codebase (CliTool → api.wordpress.org, SearchConsoleTool → oauth2.googleapis.com, DiagnosticsTool → home_url('/'), SiteInsightsTool → home_url('/'), VisionTool → get_preview_post_link, ElementorCatalog → get_permalink, VisionTool screenshot service URL from admin-set option) are either fixed-endpoint calls to well-known APIs or self-loopback fetches to the site's own hostname — none are user-influenced, so SSRF is not a concern on those paths.

= 1.14.0 — Critical-approval redesign + OAuth hardening + CORS strict mode =
* Security (CRITICAL): The critical-operation confirmation flow (homepage, posts page, privacy policy, admin-protected pages, force-delete on any page) was redesigned. Previously the dry-run preview returned a one-time nonce directly in the MCP result, which an AI caller could read and immediately submit on the live destructive call — defeating the intended human-approval gate. Critical operations now route through the existing human-admin Approval Queue: the AI receives only an approval_id and an approve_url that works exclusively in a browser (admin login + WordPress CSRF nonce required). The AI's retry call with critical_confirm=<approval_id> fails with `approval_pending` until a human administrator opens the approve_url and confirms. Approval rows are single-use end-to-end (PENDING → APPROVED → EXECUTED) with an atomic compare-and-set so two concurrent AI retries cannot both execute. The full audit trail (approver, decided_at, args) is preserved. Backward-compatible: the `critical_confirm_nonce` response field is kept so v1.13.0-era MCP clients that read it still find a value to pass back; the value is now an approval_id (small integer) instead of a 16-char hex nonce.
* Security: OAuth client-metadata (CIMD) fetches in both `handleAuthorize()` and `handleRegister()` now use a new `UrlSafety::fetchPinned()` helper that pins the TCP connection to a pre-flight-verified IP via cURL's CURLOPT_RESOLVE. This closes the DNS-rebinding time-of-check/time-of-use gap that `wp_safe_remote_get` alone did not cover. Unlike the deprecated `buildPinnedUrl()` approach (which rewrote the URL to point at the IP literal and broke TLS cert verification for hosts whose cert is issued to the hostname — i.e. virtually every site), `fetchPinned()` keeps the URL hostname intact so TLS SNI + cert validation still validate against the original hostname.
* Security: OAuth metadata fetches now enforce a hard response-size cap of 65536 bytes at the HTTP transport layer (`limit_response_size` request arg). Previously the size check ran AFTER the full body was downloaded — a malicious metadata server could stream a 1GB response and exhaust server memory before the post-fetch check rejected it. The existing post-fetch size check is retained as defence-in-depth.
* Security: CORS handling now returns HTTP 403 for preflight requests whose Origin is not in the allowlist, instead of HTTP 204 for all preflights. Aligns with the MCP 2026-07-28 spec requirement that servers reject invalid origins. Added the newer `Mcp-Method` and `Mcp-Name` headers to the allowed-headers list so browser clients sending them don't fail preflight. Desktop clients (no Origin header) are unaffected.
* Reliability: OAuth client registrations that were created via the public `/register` endpoint but never completed an authorization flow are now auto-pruned after 7 days by a daily WP-Cron. Previously a botnet or rotating-IP attacker could register 100 junk clients and permanently exhaust the 100-client hard cap, blocking new legitimate connections. Active clients (last_authorized > 0) are never pruned regardless of age. The `handleAuthorize()` flow now stamps `last_authorized` on each successful consent. Deactivation clears the scheduled cron.
* Reliability: media-download SSRF protection now uses the same `fetchPinned()` helper, fixing a TLS-verification regression where the previous IP-rewrite approach broke HTTPS downloads from any host whose certificate was issued to the hostname.
* Documentation: README.md, CHANGELOG.md, docs/TESTING_REPORT, and docs/RELEASE_CHECKLIST synchronized to v1.14.0. The v1.10.0-era testing report has been replaced with a v1.14.0 report covering the redesigned critical-approval flow, the new fetchPinned() SSRF helper, and the abandoned-client cleanup.
* Tests: a minimal PHP regression test suite is now bundled inside the plugin under `tests/`. Run via `php tests/run-tests.php` from the plugin root. Covers SafetyPolicy, ApprovalQueue, and UrlSafety helpers — the three classes touched by the v1.14.0 redesign.

= 1.13.0 — Complete nonce coverage + dead-code cleanup =
* Fixed: 5 Elementor write tools (initializePage, buildPageFromJson, bulkUpdateAcrossPosts, findAndUpdate, update_section) that previously bypassed `ElementorDataHelper::transact()` and therefore did not issue a critical_confirm nonce in dry_run on critical pages. All 5 now route through the new `SafetyPolicy::maybeIssueCriticalNonceForDryRun()` helper.
* Fixed: `WidgetRegistry` fallback list had a wrong widget name (`carousel` instead of `image-carousel`) — corrected to match Elementor's actual widget type.
* Removed: dead `SecretVault` class (encrypt/decrypt/decryptOption methods were never called anywhere in the codebase). The intended consumer (`wp_builder_mcp_pagespeed_api_key` storage) was never wired up; the API key is stored as-is in wp_options like other WordPress settings.

= 1.9.3 — Fewer false alarms in audits =
* Fixed: wp_gsc_list_sitemaps passed through Google's deprecated "indexed" count, which the API always returns as 0 — audits reported "51 pages submitted, 0 indexed" as a critical problem. Only the real submitted counts are returned now.
* Fixed: wp_audit_all_links reported pages linked from every page's footer or menu (e.g. About Us, Terms & Conditions) as orphans, because only links inside page content were counted. Navigation menus and the site-wide header/footer/sidebar (read from the live homepage) now count.
* Fixed: memory diagnostics said "WP_MEMORY_LIMIT is stuck at the default 40MB" whenever the constant was 40M — but WordPress never lowers PHP's memory_limit, so a site with PHP at 2048M really has 2048M. The check now uses the limit that applies and warns only below 128 MB.
* Improved: wp_seo_audit_all_pages verifies H1s on the live pages for every page in the batch (parallel requests, results cached), instead of stopping after 15 and reporting the rest as unverified.

= 1.9.2 — H1 checks now use the live page =
* Fixed (important): the H1 checks in wp_seo_audit_page, wp_seo_audit_all_pages, wp_seo_fix_page and wp_verify_page_design (1.8.2–1.9.1) assumed the theme prints the page title as an H1. Astra's page-builder layout does not, so pages whose only H1 was an Elementor heading were reported as "double H1" — and following that advice removed their only H1. The checks now load the live page (cache-busted) and count the H1s visitors actually get; the result is cached until the page changes. When the live page cannot be loaded (drafts, blocked loopback requests), the result is reported as "unverified" with no advice to change any heading.
* Audit output now includes h1_verified_live and the text of each live H1, so it is clear what was counted.

= 1.9.1 — Fixes from live testing on two production sites =
* Fixed: links like /web-hosting/my-post/ that only reach a post because WordPress guesses it from the slug were reported as working. Live checks showed visitors get a 301 redirect, so wp_resolve_url, wp_audit_post_links and wp_audit_all_links now report them as redirects with the correct URL to update to (or as 404 where WordPress URL guessing is turned off).
* New: link audits report placeholder links that go nowhere (href="#", e.g. "Learn more" buttons whose link was never set). Accordion/tab toggles and in-page anchors are not flagged.
* Fixed: wp_audit_post_links counted every link twice on classic (non-Elementor) posts.
* Fixed: wp_find_mentions read SEO titles/descriptions left behind by deactivated SEO plugins (never shown to visitors) and reported them as live claims; it now reads only the active SEO plugin. Snippets no longer contain Elementor styling values (element IDs, fonts, colours, layout words), and false "claims" such as "1 %" or "rs2026" are gone.
* Fixed: wp_internal_link_opportunities could suggest a link the page already had when the existing link used a redirecting or slug-guessed URL.
* Improved: wp_resolve_url with "urls" and check_live=true now includes each URL's live HTTP status.

= 1.9.0 — Final review release =
* New: wp_audit_all_links — checks the links on every published post and page (paged, 20 at a time): broken internal links, redirected links with the URL to update to, links to noindexed pages, pages with no internal links, and — at the end of the scan — orphan pages nothing links to.
* New: wp_find_mentions — every page that mentions a product, brand or phrase (titles, SEO titles/descriptions, page text, Elementor widgets), with snippets, the links pointing to it, and a summary of the percentages and prices used next to it, so outdated or inconsistent offers stand out.
* New: wp_internal_link_opportunities — for one page, the other pages it mentions (by focus keyword or title) but does not link to yet, with the exact anchor text and a snippet.
* New: wp_cache_purge — clears page cache (LiteSpeed, WP Rocket, W3 Total Cache, WP Super Cache, WP Fastest Cache, SiteGround, Autoptimize, Hummingbird — whichever are active), the object cache and Elementor CSS, for the whole site or one page.
* New: wp_resolve_url accepts "urls" to check up to 50 URLs in one call.
* Improved: the AI now receives each tool's full description and every parameter's explanation (previously cut to ~150 characters and parameter help removed), so it picks the right tool and arguments first time. Internal change-log tags are removed from the text it sees.
* Improved: common alternative parameter names are accepted — id / page_id → post_id; search / search_string / search_text / query → whichever the tool uses; replace / replacement / new_text; per_page / limit; url / link. Comment, media and menu IDs are never confused with post IDs.
* Improved: wp_elementor_find_and_update explains that new_text replaces only the matched text, and that search_text must match the stored text exactly; wp_elementor_get_element explains it returns everything inside a section.
* Fixed: tools/list could fail on PHP 8.0 with WordPress 6.4 (array_is_list() is PHP 8.1+ and WordPress only polyfills it from 6.5).
* Fixed: link checks no longer report WordPress system URLs (/wp-admin/, login, REST API, feeds, search) as broken.
* Fixed: a database preview in wp_run_cli called $wpdb->prepare() without placeholders, which logs a WordPress notice.
* Includes everything from 1.8.1 and 1.8.2.

= 1.8.2 — Fixes from the Digital Tool Zone session =
* Fixed: "OAuthServer::recordIssuedToken() failed after 5 CAS attempts" on sites with a persistent object cache (Memcached, Redis, LiteSpeed). Token records and revocations now compare against the database value, not a possibly stale cached copy, and clear every cached copy after writing.
* Fixed: section/column backgrounds set without Elementor's background-type flag saved but never showed. wp_elementor_build_page_from_json, wp_elementor_create_page_from_json, wp_elementor_update_element and wp_elementor_batch_update now add the flag (classic or gradient) automatically, move a misplaced "_title" into settings, and list what they fixed in "auto_fixes".
* Fixed: wp_elementor_build_page_from_json (rebuilding an existing page) now generates missing element IDs like create_page_from_json does, and sanitizes html / text-editor content the same way (it previously stored script tags verbatim). Element updates are sanitized too.
* Fixed: SEO audits reported "No H1" on pages whose H1 is the page title printed by the theme (Astra), and missed real double H1s (theme title + H1 heading widget). wp_seo_audit_page, wp_seo_audit_all_pages, wp_seo_fix_page and wp_verify_page_design now account for the theme title, Elementor Canvas / Full Width templates, Elementor "Hide Title" and Astra "Disable Title".
* Improved: wp_list_media returns up to 100 items per call with total and next_offset for paging, includes each item's alt text and parent post, and has search, parent_id and missing_alt filters (missing_alt treats an empty alt as missing).
* Improved: code snippets that touch protected settings, define wp-config constants (which have no effect from a snippet), run destructive SQL, change users/roles, or run shell/file operations get plain-language review warnings in the response and in the snippet's WPCode note, for the person who activates it.
* Improved: server instructions say which tool creates a new page and which rebuilds an existing one.

= 1.8.1 — Plugin/theme installs over MCP =
* Fixed: `wp theme install`, `wp theme update`, `wp plugin install` and `wp plugin update` through wp_run_cli crashed with "Call to undefined function request_filesystem_credentials()" because WordPress's installer files are not loaded outside wp-admin. They now load, and a failed install reports WordPress's real reason (download failed, no write access, already latest) instead of a PHP error.

= 1.8.0 — Find things fast, see plugin settings, fewer dead ends =
* New: wp_resolve_url — give it a URL, path or slug and get the post/page/category/author behind it in one call (ID, status, edit link), whether it is noindexed, and any redirect that applies (Rank Math, Redirection plugin or WordPress old-slug) with the final destination. Optional live check compares with what visitors really get. Rules left behind by deactivated redirect plugins are flagged instead of reported as working.
* New: wp_audit_post_links — checks every link on one page as visitors see it and lists broken links, redirected links (with the URL to change them to) and links to noindexed pages, problems first.
* New: wp_get_option — read any plugin setting stored in WordPress options (LiteSpeed, Rank Math, Elementor…) or search option names. Read-only; passwords, keys and tokens are redacted automatically.
* New: wp_litespeed_get_settings / wp_litespeed_update_settings — read and change LiteSpeed Cache settings through LiteSpeed's own save routine (dry run by default; add/remove items in exclusion lists; CDN and object-cache settings stay manual).
* Improved: wp_list_posts and wp_list_pages can filter by slug, category, search text and paging, and return slug, status, parent, dates and link plus total and next_offset, so finding a post by its URL no longer needs page-by-page browsing.
* Improved: wp_run_cli — `wp post list` supports --name, --s, --posts_per_page, --offset, --category_name, --fields, --format=ids/count/json/csv and more; flags a command doesn't understand are reported as "ignored_flags" instead of being silently dropped; `wp help <command>` and `<command> --help` work and point to the right dedicated tool (e.g. LiteSpeed, WooCommerce, Elementor); errors now return exit_code 1.
* Improved: wp_content_search / wp_content_edit explain that they need a post_id and how to get one.
* Improved: server instructions include a short "finding things fast" guide.
* Fixed: comment tools (approve, unapprove, spam, unspam, trash, untrash, delete) and wp_update_category now say "No comment/category found with ID …" instead of "__clone method called on non-object" or "Empty Term."; restoring an invalid revision no longer reports success.
* Fixed: several tool descriptions and error messages pointed to tools that no longer exist (e.g. wp_builder_mcp_create_freeform_page, wp_elementor_rebuild_page_css); they now name the real tools. The editor sidebar suggests the new link checker.

= 1.7.1 — Honest image audits & zero-setup Search Console =
* Fixed: alt-text checks reported "all images have alt text" on pages where every image had empty alt. The checkers rebuilt the page from Elementor data and only understood four core widgets, so images inside add-on widgets (Royal Elementor, ElementsKit, image boxes, cards) were invisible; one checker also excused every alt="" as decorative. wp_seo_fix_page, wp_seo_audit_page, wp_seo_audit_all_pages, wp_verify_page_design and wp_elementor_get_all_images now audit the page as Elementor actually renders it, report missing and empty alt separately, and never claim success over zero images. H1 counts use the rendered page too.
* Fixed: wp_seo_fix_page now re-renders the page after writing alt text and only reports images whose alt actually appears; images whose widget ignores the media-library alt are listed for manual fixing.
* Fixed: alt-text changes (from any tool or from the media library) were hidden behind Elementor's element cache and LiteSpeed's page cache for up to 24 hours. Those caches are now cleared/purged when alt text changes, and audits bypass Elementor's cache.
* New: Search Console tools use Google Site Kit's existing connection automatically (no token setup). Falls back to the Site Kit owner's connection for administrators, then to the existing service-account / access-token options. site_url defaults to the Site Kit property; query dates default to the last 28 days. Errors now say exactly what to connect.
* Improved: initialize reports a display title for the server.

= 1.7.0 — Connection reliability & security audit =
Every fix below was reproduced on a live WordPress + Elementor install and re-verified with the official MCP TypeScript SDK client (246/246 tools called end-to-end, 35/35 regression checks).
* Fixed (critical): Claude.ai received zero tools. 7 parameter-less tools serialised `"properties": []` (a JSON array), which fails the MCP SDK schema and makes the client reject the ENTIRE tools/list. Schemas are now always emitted as JSON objects.
* Fixed: parameters named `description`, `pattern` or `default` were deleted from 18 tool schemas (e.g. wp_seo_update_description had no description argument).
* Fixed (security): token revocation silently did nothing on sites where the revocation list had never been written — a "revoked" token kept working, and issued tokens never appeared in the Security Center.
* New: OAuth refresh tokens with rotation. Connections renew automatically instead of forcing a manual reconnect every 7 days. Re-use of a rotated refresh token revokes the whole connection; disconnecting revokes both tokens.
* Fixed: GET /mcp answered 401 to clients holding a valid token, which MCP SDKs treat as "re-authenticate" — it now answers 405 as the spec requires. DELETE /mcp answers 405 instead of 404.
* Fixed: every MCP request was counted twice by all rate limiters, halving the configured limits.
* Fixed: notifications are acknowledged with 202 Accepted (spec) instead of 204; malformed JSON returns a JSON-RPC -32700 parse error.
* Fixed: the daily plan limit now reaches the AI as a readable tool error instead of an HTTP 429 that looked like a dropped connection.
* Fixed: 7 theme tools could never run (governance required confirm=true but the schema rejected it); 4 more tools' descriptions mentioned confirm/dry_run that their schemas rejected.
* Fixed: 15 tools reported failures with isError=false; failures are now flagged as errors.
* Fixed crashes: wp_reply_comment (always failed), wp_builder_mcp_optimize_page_visual_quality (pages without explicit section padding), wp_bulk_moderate_comments (unknown comment id), tools/list error path (undefined method → HTTP 500), Search Console service-account signing (never succeeded on PHP 8).
* Improved: tool results are compact JSON (~19% fewer tokens per call); replies to comments are attributed to the connected user; OAuth responses send Cache-Control: no-store; OAuth endpoints work on subdirectory installs; governance no longer writes to the database or debug log on every page load.

= 1.6.5 — Launch-ready release =
* Pre-launch final audit — every known issue and improvement addressed before consumer release.
* Fixed (Array bug — third location): The Array-to-string cast bug (items[] objects rendering as "Array") was fixed in LayoutBuilder in v1.6.1 and in the main FreeformBuilder items handler in v1.6.2. A third path — the icon-list renderer inside FreeformBuilder's prose layout handler — also contained the unsafe cast and was reached when items[] contains card objects but looksLikeStats=false. Fixed with the same is_array() guard used in the other two locations. Also fixed: the stat-detection filter itself now skips array items gracefully instead of casting them, preventing "Array" from being tested against the stat regex.
* Fixed (BRIEF.md — anti-AI-tell rules): Added explicit list of five specific AI design tells to the design brief, drawn from the frontend-design and taste-skill files reviewed earlier: (1) warm-cream-and-terracotta default palette, (2) three identical SaaS cards in a row, (3) all-caps eyebrow labels on every section with maximum one per three sections rule, (4) fake-precise numbers not from the brief, (5) placeholder copy disguised as real copy. Added a mandatory copy self-audit checklist to run before building any section.
* Improved (tool descriptions — 20 improvements): Applied the aitmpl component quality standard ("specific, not generic") to short tool descriptions across CommentTool, TaxonomyTool, MenuTool, PagesTool, and Elementor tools. Each improved description names what the tool returns, what it requires, and when to use it — replacing single-sentence descriptions that gave no actionable guidance. Examples: "Create a new tag." → "Create a new tag in the WordPress taxonomy. Accepts name and optional slug." | "Approve a comment." → "Approve a pending comment and make it publicly visible on the post."
* Confirmed clean: 130 PHP files — zero syntax errors. 11/11 integration suites. 18/18 determinism tests. Zero duplicate tools. Zero TODO/FIXME markers. Zero bare die/exit calls.

= 1.6.4 =
* Updated: WordPress compatibility declarations updated to reflect test environment. "Tested up to" raised from 6.8 → 7.1 (tested on WordPress 7.1.2). "Requires at least" aligned to 6.4 in both the plugin header and readme (was inconsistently 6.0 in the header and 6.4 in readme). No code changes — the plugin was already compatible; the declarations now accurately reflect the tested version.

= 1.6.3 =
* Improvements based on independent third-party code review comparing WP AI Builder against WPVibe.
* Fixed (documentation — PHP version mismatch): readme.txt declared "Requires PHP: 8.1" while the plugin header and runtime gate both enforce PHP 8.0. The readme.txt now correctly states 8.0, matching the codebase and giving the plugin broader hosting compatibility on paper. No code change — the plugin already ran on PHP 8.0.
* Fixed (documentation — multi-client impression): readme.txt listed only "Claude (claude.ai) — primary tested client" under Supported AI clients, giving independent reviewers the impression the plugin is Claude-only. Expanded the list to explicitly name ChatGPT, Cursor, Windsurf, VS Code, and any MCP/JSON-RPC client. Added per-client setup instructions for all four major clients.
* Fixed (documentation — README.md PHP version): README.md requirements listed "PHP 8.1+" — corrected to "PHP 8.0+" to match the actual gate.
* Fixed (documentation — README.md multi-client): Opening line and requirements section now clearly state compatibility with Claude, ChatGPT, Cursor, Windsurf, and any MCP-compatible AI client.
* Improved (admin UI — AI Design Brief): Renamed "Claude Design Brief" to "AI Design Brief" and updated all in-page text from Claude-specific to client-agnostic language. The brief itself is a design instruction set that works with any capable AI; the Claude-specific branding was an inaccurate presentation.
* Improved (wp_run_cli description): Added user create/update and role management commands (set-role, add-role, remove-role, user meta) to the quick-reference in the tool description. These commands were already implemented; they simply weren't listed, causing reviewers to score user management coverage lower than it deserved.

= 1.6.2 =
* Pre-launch audit pass — comprehensive review of all fixes made across v1.5.2–v1.6.1 before first public release.
* Fixed (CRITICAL — Array bug in FreeformBuilder): The Array-to-string cast bug fixed in LayoutBuilder (v1.6.1) also existed in FreeformBuilder.php, which handles the build path for who_should_use and similar sections when routed through wp_builder_mcp_build. The items[] handler was casting each item as (string) $item, producing the literal word "Array" for any object-format item. Fixed by detecting array items and extracting their title/description fields before rendering, consistent with the LayoutBuilder fix.
* Fixed (CRITICAL — pros/cons CSS unreliable across style modes): The CSS protecting pros and cons card text from white-on-white overflow used attribute selectors matching specific hex background-color values (e.g. [style*="background-color: #f0fdf4"]). There are 8 style modes with different colour variants. Only 3 of the 8 pros backgrounds and 3 of the 8 cons backgrounds were covered, meaning 5 style modes could still produce invisible text in pros/cons cards inside dark sections. Fixed by adding wpbmcp-pros-card and wpbmcp-cons-card CSS class markers directly to those card containers in the Elementor data, and targeting those classes in the injected page CSS — reliable across all style modes regardless of palette.
* Fixed (dead code): RC170 computed a $headingStyle variable (accent underline for H2 headings) but never applied it to the heading widget. The variable was built and silently discarded. Removed to prevent maintenance confusion.
* Verified: All fixes from v1.5.2–v1.6.1 confirmed present and correct: subscribe text filter, EHF header meta, pros/cons card shadows, comparison table column ordering, schema accuracy, pill buttons (50px radius), hero minimum 80px padding, premium page CSS, Shape C threshold 600, who_should_use routing to plan_cards renderer, duplicate tool registration removed, hero subheadline opacity 0.96, FreeformBuilder layout/type key fix.
* Verified: 130 PHP files — zero syntax errors. 11/11 integration suites passing. 18/18 determinism tests passing. Zero duplicate tool registrations. Zero TODO/FIXME markers. Zero bare die/exit calls. Zero placeholder strings outside validation code.

= 1.6.1 =
* Fixed (CRITICAL — "Array Array Array" bug): The "Who Should Use Which?" section, and any section passing card items as objects ({title, description} format), was rendering the word "Array" three times instead of the actual card content. Root cause: the Shape B card grid renderer in the generic content handler cast each item to string, and PHP casts an array to the literal string "Array". Fixed by detecting array items and extracting their title/description fields before rendering, matching the same logic already used in the plan_cards renderer.
* Fixed (content routing): Sections typed as who_should_use, who_should_choose_which, who_should_choose, or use_cases now route directly to the plan_cards renderer (which already handles array items correctly) rather than falling through to the generic handler.
* Fixed (hero subheadline visibility): The hero subheadline text colour was rgba(255,255,255,0.88) — at high contrast this reads as noticeably grey on a dark background rather than clean white. Increased to rgba(255,255,255,0.96) across all four hero patterns (review, comparison, roundup, standard).
* Improved (design — analysis sections): Sections with analytical content ("Writing Quality: Claude Wins", "Coding: Both Excellent", "Accuracy: Claude Wins Clearly") were falling into Shape D and rendering as indistinguishable plain paragraphs. Extended the Shape C threshold from 260 to 600 characters so these sections receive an accent-left-border highlight box, giving each analysis section its own visual identity on the page.
* Improved (design — premium buttons): All CTA buttons now use a pill shape (border-radius: 50px) instead of slight-rounding (6px). Pill-shaped buttons are a strong signal of intentional, premium design and match the visual language of modern SaaS and content sites.
* Improved (design — hero padding): Hero sections now receive a minimum of 80px vertical padding (previously uncapped at base+40px which could produce cramped results). A generous hero is a primary signal of page quality.
* Improved (design — page-level CSS): Expanded the injected page CSS to include premium typography rules (line-height 1.75 for body text, letter-spacing on headings, styled list items), smooth hover transitions on buttons (opacity + slight Y-lift + box-shadow), and table vertical-align fixes.
* Improved (design — Shape B cards): Cards in the generic item grid now receive a full border + box-shadow treatment matching the plan_cards renderer, instead of bare rounded boxes.

= 1.6.0 =
* Full codebase audit and quality pass before new-site testing.
* Fixed: Duplicate tool registration — wp_elementor_get_outline was registered twice (once in ElementorTool.php with a brief description, once in ElementorCatalog.php with a full description). The duplicate in ElementorTool.php has been removed. The richer ElementorCatalog registration is kept.
* Fixed: wp_builder_mcp_build schema contradiction — sections[] was described as "Optional: provide your own section spec... If omitted, sections are generated from page_type" but the actual validation requires sections[]. The description now correctly states sections[] is REQUIRED and explains the AI must design and write all content before calling the tool.
* Fixed: headline field schema still said "If omitted, a placeholder is used" — contradicting the pure execution model. Updated to "Write this yourself — the MCP does not generate headlines."
* Fixed: Comparison table column ordering — when an AI passes rows with the feature/label column as a non-first key (e.g. {chatgpt:'Y', claude:'Z', feature:'X'}), the table rendered with Feature in the wrong position. The tableHtml function now detects label-like keys (feature, category, criteria, attribute, aspect, metric, label, item, name) and reorders them to be first, regardless of how the AI passes them.
* Confirmed clean: No PHP parse errors across all 130 files, no TODO/FIXME markers, no bare die/exit calls, no unprotected file operations, no remaining placeholder strings in non-validation code.
* Confirmed working: Subscribe text filter, pros/cons CSS fix, EHF header meta, Elementor CSS regeneration, backup system, SEO integration.

= 1.5.9 =
* Fixed (pros/cons invisible text): HTML source analysis confirmed the root cause. The wpbmcp-dark CSS rule was forcing white text across ALL descendants of dark sections, including the nested pros (green) and cons (red) cards inside them — making their text invisible against their light backgrounds. Fixed by scoping the white-text rule to direct content only (using > child combinator) and adding explicit colour overrides for nested light-background cards (pros: #166534 green, cons: #991b1b red).
* Fixed: Subscribe text confirmed GONE in this HTML file — the v1.5.8 regex fix is working correctly. No changes needed.
* Confirmed working: wpbmcp-hero and wpbmcp-dark CSS classes ARE being applied to dark section containers as intended.
* Confirmed working: Elementor CSS regeneration (Post::create()->update()) IS running after every build.
* Note (duplicate header/footer): This is a site configuration issue on DTZ, not an MCP bug. The Elementor Header Footer (EHF) plugin is set to display on all pages, running alongside Astra's native header. Fix: Elementor → Templates → Theme Builder → check Header/Footer display conditions. Our MCP sets per-page ehf-header/ehf-footer meta to 'default' on new builds (v1.5.8) — this will take effect on newly built pages after installing v1.5.9.

= 1.5.8 =
* Fixed (duplicate header/footer — root cause confirmed): The EHF (Elementor Header Footer) plugin by Brainstorm Force renders its own header/footer site-wide by default, running alongside Astra's native header. Earlier filter hooks (ehf_header_footer_display) were the wrong hook names and did not fire. Fixed by setting per-page post meta (ehf-header, ehf-footer = 'default') on all MCP-built pages at build time. Migration on activation fixes all existing MCP-built pages. This matches how EHF's own display conditions system works.
* Fixed (Array Array in items sections): When an AI passes items[] as an array of objects (e.g. [{title:'Blog writing', tool:'Claude'}]) instead of strings, the renderer was casting the array directly to string, outputting "Array Array". Fixed: items handler now detects array items and extracts title + text/description fields, rendering them as "Title — Description" inline.
* Fixed (hero heading invisible on dark backgrounds): Astra's global h1 CSS (color: var(--ast-global-color-8) = #000) was overriding Elementor's inline heading color in hero sections. Fixed by: (1) injecting page-level custom CSS that forces white text in dark containers via !important, (2) adding wpbmcp-hero and wpbmcp-dark CSS class markers to dark section containers so the CSS can target them precisely.
* Improved: Content item cards now have box shadows and 12px border radius matching the premium card style applied to pros/cons cards in v1.5.6.

= 1.5.7 =
* Fixed (subscribe text — root cause identified and fixed): HTML source analysis confirmed the text "Thank you for reading this post, don't forget to subscribe!" is injected raw (no HTML wrapper) directly after Elementor content by the SureForms/Brainstorm Force plugin ecosystem via the_content filter. Fixed with a targeted regex that strips any text matching "Thank you for reading this post...subscribe" after the full content is assembled, plus a broader fallback that catches subscribe text injected after any closing HTML tag.
* Fixed (duplicate header — root cause identified and fixed): HTML source analysis confirmed both 'ehf-header' (Elementor Header Footer plugin) and 'ast-hfb-header' (Astra's own Header Footer Builder) were rendering simultaneously on MCP-built pages, producing two site navigation bars. Fixed by disabling EHF's display filter on MCP-built pages via ehf_header_footer_display and hfe_header_footer_display hooks, leaving only Astra's native header active.

= 1.5.6 =
* Fixed (subscribe text — definitive): Added a template_redirect hook that runs at PHP_INT_MAX priority on all MCP-built Elementor pages. Any content injected by third-party plugins (newsletter forms, "thank you for reading" text, Astra content hooks) is stripped before it reaches the page. This works regardless of which plugin is injecting the text.
* Fixed (duplicate footer): Reverted the elementor_header_footer template change from v1.5.5 which was causing the footer to render twice. Reverted to default page template. The subscribe text is now suppressed via the the_content filter hook instead, which does not cause layout duplication.
* Fixed (empty sections bypass): Added validation for sections that have a heading but no content body, items, rows, pros, cons, or FAQ items. These empty-heading sections were bypassing the placeholder check by passing empty strings instead of [PLACEHOLDER] text. The build now rejects them with a clear list of which sections need content written.
* Improved (visual design — card shadows): All card containers (pros/cons, feature cards, review cards) now carry an Elementor Pro box shadow (0 4px 16px rgba(0,0,0,0.07)) and 12px border radius. Pages using Elementor Pro will render these shadows. Elementor Free ignores them harmlessly.
* Improved (visual design — hero gradient): Hero sections now use a linear gradient background (135 degrees, primary colour to 20% darkened) instead of a flat colour, giving the hero visual depth and a more premium feel.
* Improved (visual design — card borders): Cards now include a 1px solid border using the style's border colour token, adding definition and separation.

= 1.5.5 =
* Fixed: "Thank you for reading this post, don't forget to subscribe!" text now definitively suppressed. Previous fix (disabling Astra page header) was insufficient — the text comes from a WordPress content hook, not the theme header. Fix: MCP-built pages now use the 'elementor_header_footer' page template, which removes all theme content wrappers while keeping site navigation and footer. Additional Astra meta keys (ast-breadcrumbs-content) also disabled for completeness.
* Improved: Section background alternation is now more visually distinct. section_alt colors updated from near-white (#f9fafb) to clearly-tinted values (#eef2f7, #e8f4f8) that create real visual rhythm between sections.
* Improved: Section type backgrounds now semantically assigned. Comparison tables, pros/cons cards, pricing, and trust badges always get the tinted background. CTA, verdict, and review summary sections always get the dark hero background. Regular content alternates for rhythm.
* Improved: Accent section padding increased (+24px vs previous +14px) for more dramatic visual presence on hero, verdict, and CTA sections.
* Improved: Data section padding increased (+10px) so comparison tables and pros/cons cards have breathing room.
* Improved: premium_saas style section_padding_y increased from 48 to 60px for more spacious feel.

= 1.5.4 =
* Architecture: MCP is now a pure execution engine. Claude (or any AI) is the designer and writer. The MCP executes exactly what the AI provides and nothing else.
* Removed: All automatic section template generation. The MCP no longer generates page structure when sections[] is not supplied. Calling wp_builder_mcp_build without sections[] now returns a clear error explaining that the AI must design and supply all content first.
* Removed: All placeholder text generation. Every [PLACEHOLDER — ...] fallback removed from all build methods (65 instances). The MCP will never insert dummy content into a page.
* Added: Placeholder validation. If any section passed to wp_builder_mcp_build contains [PLACEHOLDER text, the build is rejected immediately with a list of the offending fields and instructions to complete them.
* Added: Content requirement validation. createAffiliateReviewPageDraft and createComparisonPageDraft now reject calls where essential content (verdict, pros/cons, comparison rows) is missing rather than silently building an empty page.
* Updated: wp_builder_mcp_build description now clearly states the AI must write all content before calling the tool.
* Updated: wp_builder_mcp_get_started now guides the AI to prepare all content before building.

= 1.5.3 =
* Fixed: All 11 deprecated create_* builder tools (wp_builder_mcp_create_premium_page, wp_builder_mcp_create_freeform_page, wp_builder_mcp_create_draft_landing_page, and 8 others) are now removed from the MCP tool listing. AI clients no longer see or attempt to use them. Handler functions are kept for internal use by BuildOrchestrator.
* Fixed: Astra theme "Thank you for reading this post, don't forget to subscribe!" text now suppressed automatically on all MCP-built pages. The plugin sets Elementor page settings (hide_title, ast-main-header-display: disabled) immediately after every build.
* Fixed: render_verified and content_verified are now true after successful builds. The verification engine now runs at LEVEL_RENDER by default, using Elementor's own server-side rendering to confirm heading counts, button presence, and table structure — no HTTP request needed.
* Fixed: wp_builder_mcp_create_freeform_page silent content drop — FreeformBuilder now accepts 'type' as an alias for 'layout' in all four rendering code paths. Sections like {type:'comparison_table'} now render their full content instead of empty white containers.
* Fixed: wp_builder_mcp_build crash on comparison and software_review page types — both missing methods (createComparisonPageDraft, createAffiliateReviewPageDraft) now implemented in SiteBuilderTool.

= 1.5.2 =
* Fixed (Bug 1): wp_builder_mcp_build crashed with "Call to undefined method createComparisonPageDraft()" and "Call to undefined method createAffiliateReviewPageDraft()" when building comparison or software review pages. Both methods are now implemented in SiteBuilderTool and route through the correct layout skill pipeline.
* Fixed (Bug 2): wp_builder_mcp_create_freeform_page silently dropped all rich content — comparison tables, pros/cons cards, FAQ accordions, scores tables — producing empty containers with headings only. Root cause: FreeformBuilder read the 'layout' key from sections but wp_builder_mcp_build and BuildOrchestrator pass sections using the 'type' key. Fixed in all four code paths (buildSections loop, buildSection, sidebar_toc hero detection, sidebar_toc body sections).
* No new tools or features added. Both fixes are pure bug corrections.

= 1.5.1 =
* Fixed: Fatal error when two copies of WP AI Builder are active simultaneously. The plugin now detects the conflict and shows a clear admin notice ("deactivate and delete the older version") instead of crashing WordPress. This was the cause of the "Plugin could not be activated because it triggered a fatal error" message seen when upgrading.
* ACTION REQUIRED to upgrade: (1) Deactivate and Delete the old WP AI Builder 1.4.0 entry in your Plugins list. (2) Then Activate WP AI Builder 1.5.1. Having both active at once causes the fatal error.

= 1.5.0 =
* Fixed: Plugin now activates correctly on PHP 8.0+ (was PHP 8.1+). Removed all readonly class property declarations which are PHP 8.1-only syntax. All properties remain functionally immutable via constructor assignment — no behaviour change.
* Fixed: PHP version gate lowered to 8.0. Activation on PHP 7.x now shows a clear, actionable error message with exact steps to upgrade PHP in your hosting control panel, instead of a cryptic fatal error.
* Fixed: ZIP package structure corrected — files now install under wp-ai-builder/ as WordPress requires. Previous versions had a packaging bug where files were nested under work/wp-builder-mcp/ causing "No valid plugins were found" on upload.
* No new tools or features added. All changes are bug fixes and compatibility improvements only.

= 1.4.5 =
* Fixed (RC169): backup is now fail-closed — live write/delete is BLOCKED if backup cannot be created
* Fixed (RC169): backup stored in protected uploads/wp-builder-mcp-backups/ (not web-accessible themes folder), with .htaccess blocking direct access
* Fixed (RC169): backup filenames randomised (8 hex chars) to prevent enumeration
* Fixed (RC169): backup taken AFTER validation — dry runs and invalid inputs no longer trigger a full theme backup
* Fixed (RC169): wp_file_delete now backs up before live delete (edit and write were already backed up)
* Improved: wp_file_delete dry_run shows file size, risk level, and backup plan
* Test suite: RC167 expanded to 21 checks (up from 16)

= 1.4.4 =
* Fixed (RC168): draft-first invariant now applies to ALL write/edit/delete operations — supplying an explicit theme slug no longer bypasses the draft gate. If a live theme slug is given without live=true, the tool returns a clear error with the correct draft slug if one exists
* Added: backupThemeDir() — zips the live theme directory before any live=true write, so live emergency edits are automatically backed up
* Improved: when a live theme is blocked, the error message tells the caller whether a corresponding draft exists and gives the exact slug to use
* RC168 tests: 3 additional checks added to RC167 suite (now 16 total): explicit live slug rejection, explicit draft slug pass-through, backup helper presence

= 1.4.4 =
* Fixed (RC168): draft-first invariant now applies to ALL write/edit/delete operations — supplying an explicit theme slug no longer bypasses the draft gate. A live theme slug given without live=true returns a clear error with the correct draft slug if one exists
* Added: backupThemeDir() — zips the live theme directory before any live=true write, providing an automatic backup for emergency live edits
* Improved: error messages when a live theme is blocked now include whether a corresponding draft exists and the exact slug to use
* RC168 tests: 3 additional checks added to RC167 suite (now 16 total)

= 1.4.3 =
* Fixed: Draft-first theme editing now enforced in code, not just documentation. wp_file_edit, wp_file_write, and wp_file_delete are BLOCKED when no draft exists — they return a clear error directing users to wp_theme_create_draft. Emergency live override available with live=true + confirm=true (high-risk, activity logged)
* Fixed: Visual QA auto-fix now routes through ElementorDataHelper::save() (backup → write → verify → CSS regeneration) instead of calling update_post_meta() directly, matching the same verified pipeline as all other Elementor writes
* Fixed: BuildOrchestrator no longer overwrites verification failures with success=true — if the lower-level pipeline reports a failed write or failed verification, the build result reflects that honestly
* Fixed: README.md rewritten — no longer contains old RC73/RC75 release history; now a clean, current user-facing document describing version 1.4.3
* Added: wp_file_edit and wp_file_write schemas include live parameter for emergency live edits
* Added: RC167 security test suite (12 checks: draft gate, BuildOrchestrator verification, documentation coherence)

= 1.4.2 =
* Fixed: wp_builder_mcp_build (primary build tool) returned "BuildOrchestrator not available" on every call — missing file created
* Added: Visual QA loop — after every build, VisualQualityFeedback audits the result and auto-applies spacing fixes when score < 75
* Added: SEO meta (title, focus keyword) written automatically after every build
* Added: Quality audit score included in every build result
* Improved: Theme file edit tools now prominently warn about draft-first workflow
* Improved: wp_builder_mcp_build infers page_type from title/intent when not provided

= 1.4.1 =
* Fixed: Approval system — all 18 approval-gated operations now actually execute after human approval (none did before)
* Fixed: Magic-link CSRF — approval links now require a POST with a WordPress nonce; GET only shows a confirmation page
* Fixed: Clients could not supply internal flags to bypass approval gates
* Security: Approval endpoints now refuse machine credentials (Bearer/Basic) — only human browser sessions can approve
* Security: Plugin's own security settings, token-signing secret, and other plugins' credentials are now protected on all read/write paths
* Security: Plugin transients (OAuth codes, rate limiters) cannot be read or deleted through tools
* Security: OAuth tokens bound to this site's endpoint; tokens for other resources are rejected
* Security: SVG upload event-handler detection fixed; attribute boundary correctly identified
* Security: Theme file syntax check upgraded to PHP's real parser (catches missing semicolons)
* Security: Search-replace is serialization-safe; skips protected options; scoped to this site's table prefix
* Fixed: countElementorWidgets() fatal error in bulk snapshot (missing default parameter)
* Fixed: ActivityLogger class not found in bulk update (missing use statement)
* Fixed: Semicolons inside quoted strings (CSS values) are no longer blocked by the CLI
* Fixed: Astra option patch now follows real WP-CLI syntax and verifies the write landed
* Added: wp_inject_custom_css — site-wide CSS injection
* Added: wp_elementor_set_page_css — per-page CSS
* Added: wp_session_state_read/write/delete — persistent session state between conversations
* Added: wp_set_image_alt_bulk — set alt text across the media library and all Elementor pages in one call

= 1.4.0 =
* Added: wp_audit_page_lighthouse — real Google Lighthouse audit via PageSpeed Insights API
* Added: Google PageSpeed API key field in admin settings
* Added: wp_set_image_alt_bulk — bulk alt text setter
* Improved: Lighthouse tool with 3-hour caching, retry logic, grouped issues, Core Web Vitals

= 1.3.9 =
* Fixed: Bulk find-and-replace (wp_elementor_bulk_update_across_posts) now saves with wp_slash() to prevent data corruption
* Fixed: URL matching in search_widget now works correctly (JSON slash-escaping was blocking matches)
* Fixed: wp_elementor_set_page_css stores CSS in dedicated meta, not in Elementor page settings
* Fixed: SEO H1 double-counting root cause — audit now reads Elementor data OR post_content, never both
* Fixed: Alt text counts no longer inflated — cross-references media library before flagging missing alt
* Security: Meta search blocks credential-like meta keys; checks per-post permissions
* Security: Broken link scanner uses exact host matching (SSRF prevention); capped at 150 HTTP checks/scan
* Added: Per-post permission checks on SEO meta, SEO snapshots, custom field values, media details

= 1.3.8 =
* Fixed: BUG-01 countElementorWidgets PHP fatal (missing default parameter)
* Fixed: BUG-02 ActivityLogger class not found in ElementorTool
* Fixed: BUG-05 Semicolons inside quoted strings now allowed in CLI
* Fixed: OAuth approval page colour matched to dashboard purple (#7C3AED)
* Improved: CLI risk level based on actual command type, not always 'high'
* Added: wp_inject_custom_css, wp_elementor_set_page_css, wp_session_state_*
* Fixed: Better Elementor JSON parse errors with remediation hints
* Added: source field on broken links indicating which tool to use to fix each one

== Upgrade Notice ==

= 1.15.5 =
Self-audit release, no configuration needed. Restores tool access for Application Password clients broken since v1.15.3 (Basic-auth requests are no longer denied by the OAuth scope gate), fixes the approval-flow deadlock where approving a delete/Elementor/SEO operation from the admin page burned the approval before the AI could execute it, eliminates a double-validation race in the confirmation gates, records which admin clicked Approve on magic links, and fixes the stale pending-approval count badge. OAuth Bearer clients are completely unaffected — scope enforcement is unchanged and verified by tests. Update recommended for ALL sites (especially Application Password users).

= 1.15.4 =
Fixes all 10 findings from the eighth independent security review of v1.15.3: (1) protected-page approvals now carry the real tool name and a canonical hash of the COMPLETE payload the human reviewed — any changed content/layout/CSS between approval and execution is rejected (payload_mismatch); (2) OAuth scope context no longer depends on WordPress hook ordering — valid tokens keep their scopes on every request lifecycle; (3) tools/list cache is isolated per OAuth scope; (4) MCP 2026-07-28 result envelopes are schema-conformant (resultType 'complete', cacheScope 'private', cache hints on discovery) while 2025 clients receive clean responses; (5) optional strict header-conformance mode + rejection of inconsistent protocol-version declarations; (6) legacy 'mcp' wildcard tokens now surface an admin warning with an allow/migrate/deny migration policy; (7) force-deletes of ANY page/post now require human approval (the old dry-run hex-nonce shortcut is gone). The ApprovalQueue table is upgraded to schema 1.2 automatically (adds a payload_hash column). Backward-compatible with existing MCP clients and tokens; update recommended.

= 1.15.1 =
Targeted security fixes for 3 findings from the fifth independent review of v1.15.0: (1) OAuth default scopes narrowed from 5 (read+content+design+seo+media) to mcp:read only — least privilege by default; (2) Consent screen now displays the actual granted MCP scopes with descriptions + destructive-scope warning; (3) Safety matrix uses explicit per-tool declarations instead of group heuristics — defaults to 'unknown' for honest reporting. Also fixes a /mcp GET/DELETE handler bug where OAuth Bearer-token clients got a 401 instead of 405 because the code only checked WP browser session. Backward-compatible — existing clients continue to work, but new connections now default to read-only scope until the admin explicitly grants more. Update recommended.

= 1.15.0 =
Architectural hardening release: implements full MCP 2026-07-28 wire-format conformance (supportedVersions, _meta server identity, cache hints, resultType on all responses); adds granular OAuth scopes (mcp:read/content/design/seo/media/file/code/admin/destructive) with implication hierarchy and execution-time enforcement; introduces a machine-verifiable per-tool safety matrix with admin REST endpoint; expands the bundled test suite to 95+ assertions across 15 tests. Backward-compatible — existing tools auto-derive scopes, legacy 'mcp' scope tokens work as wildcards. Update recommended.

= 1.14.2 =
Security hardening: closes the SSRF pinning gap from v1.14.1 (UrlResolverTool link-audit fetches now use fetchPinned for actual connection pinning + redirects disabled), rewrites isIpSafe with an explicit IANA special-use CIDR denylist (v1.14.1 incorrectly classified CGNAT/benchmarking/documentation/multicast as safe — now blocked), makes fetchPinned fail closed on PHP without ext-curl instead of throwing a fatal Error, and adds the wpaib_usage table to uninstall cleanup. Update immediately if running v1.14.1.

= 1.14.1 =
Fixes two defects in v1.14.0 (broken bundled test suite + one SSRF coverage gap in the link-audit tool). No new features. All 47 test assertions now pass. Update recommended for anyone running v1.14.0 on a staging site.

= 1.14.0 =
Critical security update. The critical-operation confirmation flow (homepage / posts page / privacy policy / admin-protected page edits, and force-delete on any page) now requires genuine human approval via the existing Approval Queue — the AI caller can no longer self-satisfy the confirmation by reading the nonce from the dry-run preview. Also hardens OAuth metadata fetches against DNS-rebinding SSRF, enforces response-size caps at the HTTP transport layer, rejects disallowed CORS origins with HTTP 403 per the MCP 2026-07-28 spec, and auto-prunes abandoned OAuth client registrations so the 100-client limit can't be permanently exhausted by attackers. Update immediately.

= 1.13.0 =
Complete critical_confirm nonce coverage: 5 Elementor write tools that previously bypassed the transact() safety gate now route through it correctly. Removed dead SecretVault class. Update recommended.

= 1.4.2 =
Fixes a critical bug where the primary build tool (wp_builder_mcp_build) returned an error on every call. Update immediately.

= 1.4.1 =
Critical security update. The approval system was non-functional in all previous versions — destructive operations appeared to be queued but never executed. Update immediately.
