= 1.2.5 =
This release completes the security review requested by the WordPress.org Plugins Team after the Subscriber privilege-escalation reports against the `wp_update_user_meta` tool: CVE-2026-103068 (Patchstack, affecting <= 1.2.2) and CVE-2026-19807 (Wordfence, reported by Décio Brandão, affecting <= 1.2.3 — the incomplete-fix follow-up where the 1.2.3 key blocklist still omitted `wp_capabilities` and `wp_user_level`). Both are the same root cause and were first closed in 1.2.4 by replacing the blocklist with a strict allow-list; this release hardens every step of the reported attack chain, audits all 336 existing tools for the same class of permission gap, adds undo for AI changes, adds 24 tools for block-level editing, bulk content, read-only Site Editor access, and WooCommerce store management (360 tools in total), fixes permission and data-handling bugs in existing tools found by new automated tests, fixes connections that expired early and Activity times shown in the wrong time zone, and refreshes the admin design. Existing Contributor-and-above connections, settings, and Activity history are kept on upgrade.

**Security: who can connect (OAuth)**
* The CVE chain began with any logged-in account, including a self-registered Subscriber, approving the OAuth consent screen and minting an MCP bearer token for itself. Authorization now requires a minimum capability, `edit_posts` (Contributor and above) by default. Subscriber, Customer, and other read-only accounts see a "not allowed to connect" message instead of the consent screen.
* Developers can require a higher capability with the new `bcs_mcp_connect_capability` filter (for example `manage_options` for administrators only). Values that grant nothing extra (`read`, `exist`, an empty string) are ignored and fall back to `edit_posts`.
* The same check also runs in three later places: when the consent form is submitted, when an authorization code is exchanged for tokens, and when a refresh token is used. An account demoted between those steps cannot finish the flow.
* Every MCP request (Streamable HTTP and the legacy SSE/messages transports) re-checks the token owner. A token stops working at once if its owner is deleted, demoted below the required capability, or (on multisite) no longer a member of the site. These rejections are written to the Activity log with a specific reason.
* The `refresh_token` grant now authenticates confidential clients (`client_secret_post`) with their client secret before the refresh token is consumed, matching the `authorization_code` grant. Previously a leaked refresh token alone was enough.
* The token endpoint now rejects unknown client IDs explicitly instead of continuing without client authentication.
* The consent form submission (`POST /authorize`) re-validates that a PKCE S256 code challenge is present, instead of trusting only the earlier GET step.

**Security: MCP REST endpoint**
* The MCP transport routes (`/mcp` GET, POST and DELETE, `/sse`, `/messages`) now use a real `permission_callback` instead of `__return_true`. It checks, in order, that the server is enabled, that the caller's IP is allowed, the rate limit (POST requests), and a valid OAuth bearer token whose owner may still connect. The handlers keep their own checks as a second layer.
* A denied request returns the same MCP error body and `WWW-Authenticate` header as before, so AI clients discover the OAuth login exactly as they did. Before release, twelve request types (no token, invalid token, valid token, every method and transport) were compared before and after the change and returned identical responses.
* Each request still counts once toward the 60-requests-per-minute limit, even though both the permission check and the handler look at it.
* Only two routes stay public, each documented in the code: the discovery route and CORS preflight (`OPTIONS`) requests, which never carry credentials.
* The public discovery response no longer includes the plugin version.

**Security: users, roles, and accounts**
* (Shipped in 1.2.4, re-verified in this release.) `wp_update_user_meta`, `wp_delete_user_meta`, and `wp_get_user_meta` only accept a fixed allow-list of profile fields (first_name, last_name, nickname, description, locale, admin_color, show_admin_bar_front). Writes and deletes require `promote_users` plus `edit_user` on the target, and reject array or object values. `wp_capabilities`, `{prefix}capabilities`, `{prefix}user_level`, session tokens, and password fields cannot be read or changed through these tools by any role, administrators included.
* `wp_assign_user_role` refuses to change the caller's own role and only accepts roles returned by `get_editable_roles()`, the same restriction as the role dropdown in wp-admin's user editor. Previously any registered role could be assigned once `promote_users` passed.
* `wp_update_user` now requires `edit_users` to change an email address, including your own. wp-admin asks for email confirmation when you change your own address, and a direct change through MCP would have skipped it. Changing display name and first or last name works as before.
* `wp_reset_user_password`, `wp_get_user_sessions`, and `wp_destroy_user_sessions` also check `edit_user` for the specific target. On multisite this stops a site administrator from acting on a Super Admin.

**Security: post and term meta**
* `wp_update_post_meta`, `wp_delete_post_meta`, `wp_update_term_meta`, and `wp_delete_term_meta` follow WordPress's protected-meta rules. Underscore-prefixed (protected) keys need core's `edit_post_meta` / `delete_post_meta` (or `edit_term_meta` / `delete_term_meta`) check, the same rule the REST API uses. Administrators (`manage_options`) can still manage plugin-owned protected meta.
* Core file-path and bookkeeping keys can never be written or deleted through these tools, by any role: `_wp_attached_file`, `_wp_attachment_metadata`, `_wp_attachment_backup_sizes`, `_wp_trash_meta_status`, `_wp_trash_meta_time`, `_wp_trash_meta_comments_status`, `_edit_lock`, and `_edit_last`. A rewritten `_wp_attached_file` could otherwise point a later media deletion at an arbitrary file such as wp-config.php.
* Meta values must be a string, number, boolean, or flat list of those. Nested arrays and objects are rejected instead of being stored, and term meta array values are now sanitized (they were previously stored as received).
* Meta keys must be non-empty strings.
* `wp_get_post_meta` hides protected (underscore) keys from users who can read but not edit the post. It also redacts credential-like values (keys containing password, secret, token, api_key, license_key, and similar), as the Activity log and options tools already did. `wp_get_term_meta` applies the same redaction.

**Security: publishing and content permissions**
* `wp_update_post`, `wp_update_page`, and `wp_schedule_post` require the post type's `publish_posts` capability to move content into `publish`, `future`, or `private`. A Contributor could previously publish or schedule their own draft by passing a status. Contributors can still save drafts and submit them for review (`pending`).
* Post and page status values are restricted to `draft`, `pending`, `publish`, `future`, and `private`. Other values are rejected with a clear error.
* `wp_create_post` validates the post type, requires that type's `create_posts` capability (so an Author can no longer create pages or other restricted types by passing `post_type`), applies the same status checks, and requires `edit_others_posts` to set another user as the author.
* `wp_get_posts` limits non-published results to the caller's own posts unless they can edit others' posts, and drops any result the caller cannot read. This stops Contributors from listing other authors' private and draft titles.
* `wp_parse_blocks`, `wp_get_acf_fields`, and `elementor_get_page_outline` check `read_post` for the requested post. Previously the content of private or draft posts could be read with only `edit_posts`.
* `elementor_import_template` requires permission to publish Elementor templates, because imported templates are created as published.
* `wp_create_comment` attributes comments to the connected account (user ID, display name, email, URL) and checks `read_post` on the target post. Only users who can moderate comments may set a different author name or email. Previously any caller could post under any name and email, including a site administrator's.
* `bbp_moderate_reply` requires `delete_post` to trash or restore a bbPress reply, not only `edit_post`, matching wp-admin.
* `wp_run_shortcode` now requires `manage_options`. It runs any registered shortcode and returns the output to the AI client, and some plugins' shortcodes run code or reveal data, so it should not be available to Contributor-level connections. The tool description says so.

**Security: sensitive data and requests**
* `wp_get_plugin_settings` now redacts whole options whose name marks a secret (salt, key, token, password, secret, nonce, and similar), not only matching keys inside an option's value. Asking for a prefix such as `nonce` or `auth` could otherwise have returned WordPress's own `nonce_salt` or `auth_salt` options in plain text. The plugin name must be at least 3 characters, so short prefixes such as `wp` cannot sweep up unrelated core options.
* `wp_find_broken_links` uses `wp_safe_remote_head()`. Post content can no longer make the server probe private, loopback, or internal network addresses. Only http/https links are checked, and site-relative links are resolved against the site URL.
* The plugin's own-tables lookup (used when optimizing tables) now uses `wpdb::prepare()` with `esc_like()` instead of an interpolated table prefix.

**Security: full tool permission audit**
* All 336 tools were checked: each one verifies the connected user's WordPress capability for its specific action before doing anything. The audit is a static scan of every tool in `execute_tool()` plus targeted review of the tools a reviewer would treat as high risk: plugin, theme and core installs, plugin activation, user and role changes, two-factor reset, sessions, email sending, options, transients, logs, PHP and server information, user CSV export, Code Snippets, shortcodes, and Elementor imports.
* Confirmed: plugin and theme installs accept only a WordPress.org directory slug and use core's installer (and so respect `DISALLOW_FILE_MODS`), `wp_get_option` uses a fixed allow-list, `wp_get_transient` and `wp_set_transient` are limited to the plugin's own prefix, session tools return only hashed token prefixes, the user CSV export neutralizes spreadsheet formulas, and no tool creates users, accepts PHP code, edits files, or runs SQL supplied by the AI client. (Code Snippets tools can only switch existing snippets on or off, and need `manage_options`.)

**New: undo for AI changes**
* 114 tools now return a single-use undo token when they actually change something. The AI client can pass it to the new `bcs_undo_operation` tool, or an administrator can use the Undo button in Activity. Tokens work for 7 days. After that the undo record and its saved copies are deleted permanently by the daily cleanup.
* What can be undone:
  * Post and page edits, scheduling, and passwords; SEO fields, custom fields, featured image, page template, post format, categories and tags, revision restore, and language.
  * Block-level edits (insert, update, move, and delete a block) and bulk post creates and updates.
  * Bulk content edits: search and replace, and Elementor text and image replacement.
  * Created items: posts, pages, duplicates, Elementor, Bricks, and Divi clones, templates, reusable blocks, translations, events, venues, products, coupons, and variations. Undo moves them to Trash, or removes items that have no Trash (menu items, variations, uploaded media).
  * Trash actions, including bulk Trash of posts and comments, and WooCommerce product, variation, and coupon and Events Calendar event deletes.
  * WooCommerce: product, stock, variation, batch variation, attribute, coupon, and order status updates, attribute create and delete, order notes, and product review moderation and replies.
  * Events Calendar updates; comments (edit, approve, spam, create); terms (create, edit, delete) and term meta; media details and uploads.
  * Menus, menu items, reordering, duplicates, and menu locations; widgets; ACF fields, repeaters, options pages, and field groups.
  * User profiles, user meta, and roles; scheduled events; the plugin's transients; plugin activation and deactivation.
  * Redirection, Gravity Forms entries, bbPress, BuddyPress groups and activity, Custom Post Type UI, Code Snippets, and AffiliateWP.
* What cannot be undone, so no token is returned: permanent deletes (`force=true`), deleting media, users, menus, or form entries, sending email or password-reset links, payments, refunds, subscriptions, memberships and enrolments, mailing-list sign-ups, plugin, theme, and core installs or updates, cache or maintenance actions, and new WooCommerce orders, shipping zones and methods, and tax rates. A call that changed nothing (for example a dry run) gets no token.
* How it works: the original post-edit and Trash undo snapshots the post before the change. The new change recorder (`includes/MCP/Change_Recorder.php`) listens to WordPress's own hooks while a supported tool runs. It saves each post, meta value, term assignment, term, comment, user, allow-listed option, cron event, plugin state, or plugin table row just before it changes, and the after-state when the tool finishes. Deleted posts and terms are captured with all their rows, so they are put back with their original IDs.
* Undo is all or nothing. It first validates every item and refuses if any of them was changed again after the AI call, so newer work is never overwritten, or if the current user lacks permission for any item. Nothing is changed in either case. It then restores in reverse order, and restored WooCommerce products are re-saved so lookup tables and caches stay in sync.
* A token can only be redeemed by the same WordPress user through the same OAuth client that received it, and it is claimed atomically before any change, so it cannot be used twice in parallel. Restoring a trashed published, scheduled, or private post returns it to that status only if the caller can still publish it.
* Tokens are stored as SHA-256 hashes in a dedicated `{prefix}bcs_mcp_undo` table and are never written to the Activity log or passed to the `bcs_mcp_tool_called` action hook. The saved copies stay in your database and are never sent anywhere. Uninstall removes the table.
* Trash undo tokens are only issued when the site keeps Trash for at least 7 days (`EMPTY_TRASH_DAYS`; the default is 30), so trashed items cannot be purged before the undo window ends.
* Destructive tool schemas now advertise the `confirm` argument used by the optional "Ask before destructive actions" setting.

**New: undo from the WordPress admin**
* Every Activity entry that can still be undone shows an Undo button next to View, for 7 days. This covers all 114 undoable tools. Undone entries show an Undone label, and the entry detail view shows the undo deadline in the site's time zone with its own Undo button.
* Admin undo requires `manage_options` and a nonce, and runs the same checks as AI undo: the current administrator's per-item permissions, a refusal if anything changed again after the AI call, the all-or-nothing validation pass, and single use. Undoing from the admin also uses up the AI client's token.
* Every admin undo is written to the Activity log as client "WordPress admin", with the administrator's login and the original entry number, as an audit trail.

**New: block editing, bulk content, read-only Site Editor access, and store management (24 tools)**
* Block-level editing: `wp_get_post_blocks` lists a post's blocks as a tree with a path for each block ("2", "2.0"), and returns one block's full markup on request. `wp_insert_block`, `wp_update_block`, `wp_move_block`, and `wp_delete_block` change a single block without rewriting the rest of the post. Inner blocks of Groups, Columns, and other containers are supported, block parent rules (for example a Column only inside Columns) are enforced, and unregistered block names return a warning. Every change is saved as a normal revision and can be undone.
* Bulk content: `wp_bulk_create_posts` (up to 25) and `wp_bulk_update_posts` (up to 50) apply the same checks as the single-post tools to each item and report per-item results, so one failure does not stop the rest. One undo token covers the whole batch. `wp_get_content_calendar` shows published, scheduled, draft, and pending content by day for up to 366 days; other people's unpublished items are only listed for users who can edit them.
* Site Editor, read-only: `wp_get_templates` and `wp_get_template` list and read block theme templates and template parts, and `wp_get_global_styles` reads the saved Global Styles. They need `edit_theme_options`. Writing templates, template parts, Global Styles, or site settings is deliberately not offered, because those change site-wide HTML, CSS, and behaviour; the post tools keep refusing those post types as before.
* WooCommerce orders and reviews: `wc_create_order` creates manual orders with products and variations, billing and shipping addresses (copied from the customer when omitted), shipping lines, coupons, payment method, and status. No payment is taken; everything is validated before the order exists, and a failure part-way removes the incomplete order. `wc_get_product_reviews`, `wc_moderate_product_review`, and `wc_reply_to_product_review` list reviews with rating and verified-owner status, approve, hold, spam, or trash them, and reply as the connected account.
* WooCommerce shipping and tax: `wc_create_shipping_zone`, `wc_update_shipping_zone`, `wc_delete_shipping_zone`, `wc_add_shipping_method`, `wc_update_shipping_method`, `wc_delete_shipping_method`, `wc_create_tax_rate`, `wc_update_tax_rate`, and `wc_delete_tax_rate`. Regions are checked against WooCommerce's country, state, and continent lists. Shipping method settings go through each method's own WooCommerce field validation (for example flat rate cost formulas), and unknown setting keys or select values are rejected. `wc_get_shipping_zones` now also returns each method's `instance_id`.
* Integration tools only appear when they can work: WooCommerce tools need WooCommerce, template tools need a block theme, and the Global Styles tool needs a block theme or a theme.json.

**Security for the new tools**
* Block markup from the AI client is always passed through `wp_kses_post()`, including for administrators who have `unfiltered_html`. This removes scripts, event handlers, and `javascript:` URLs from the HTML, and WordPress's block-attribute filter cleans every block attribute.
* Block and bulk tools check `edit_post` (or `read_post` for reading) on every post, and the same publishing rules as the single-post tools. Order tools require `edit_shop_orders`, review tools `moderate_comments` and `edit_comment`, and shipping and tax tools `manage_woocommerce`.
* No new tool makes outbound HTTP requests.

**Security and reliability fixes in existing tools (found by new automated tests)**
* Every tool now rejects calls whose arguments are not a JSON object or lack an argument the tool's schema marks as required, with a clear "Missing required argument" error. Previously many tools raised PHP warnings and some failed with a fatal error.
* `wp_get_comments` returned pending (unapproved) comments, and comments on private and draft posts, to any connected account. Users who cannot moderate comments now only see approved comments on posts they can read, as in wp-admin.
* `seo_bulk_get_meta` listed titles and SEO data of other people's drafts, pending, and private posts. Unpublished items are now only included for users who can edit them.
* `cf7_get_form` returned a Contact Form 7 form's mail settings (recipients and headers) to any account that can edit posts. It now requires Contact Form 7's own edit permission.
* `wp_create_reusable_block` let Contributors publish synced patterns. It now requires the pattern type's create permission (`publish_posts` in core), as the block editor does.
* `bricks_clone_page` and `divi_clone_page` did not check access to the source post, so an Author could copy another user's private or draft content. They now require `edit_post` on the source and permission to create that post type, refuse the protected post types, and assign the copy to the connected user.
* `wp_approve_comment`, `wp_spam_comment`, and `wp_trash_comment` caused a fatal error for a comment ID that does not exist, and did not check `edit_comment` on the specific comment. Both are fixed.
* `wc_get_shipping_zones` failed with a fatal error on current WooCommerce versions, which no longer return zone objects from `WC_Shipping_Zones::get_zones()`, and it read zone locations with the wrong type. It now loads each zone by ID.
* `elementor_swap_image` never matched anything, because Elementor stores its data as JSON with escaped slashes, and `elementor_bulk_replace_text` missed text containing slashes, quotes, or non-ASCII characters. Both now replace inside the decoded values, so the JSON stays valid. They also cover draft and private pages the user can edit, not only published ones.
* Backslashes were removed when content was saved, because WordPress unslashes post and meta data. This corrupted escapes in block attributes and broke Elementor's JSON data. Post and meta writes in `wp_create_post`, `wp_update_post`, `wp_duplicate_post`, `wp_duplicate_page`, `wp_search_replace_content`, `wp_create_reusable_block`, `wpml_translate_post`, and the Elementor, Bricks, and Divi tools now slash their data first.
* `wp_export_users_csv` only checked `list_users`, which WooCommerce also gives shop managers, so a Shop Manager's AI client could export every account's email and roles, administrators included. Bulk user export now requires `manage_options`.
* `bbp_get_topics` and `bbp_get_replies` returned topics and replies from hidden bbPress forums (bbPress stores them as published), including to Subscribers. Results now go through bbPress's own `bbp_user_can_view_forum()`, so each user sees exactly what bbPress shows them.
* `wpml_get_translations` listed the titles and statuses of draft and private translations, and `wpml_get_post_language` answered for any post. Both now follow the same rule as wp-admin: published content needs read access, anything else needs edit access.
* `learndash_get_course_progress` and `learndash_enroll_user` now check `edit_user` for the specific person and that the ID is a LearnDash course.
* `wpforms_get_forms` now uses WPForms' own view permission (administrators by default) instead of `edit_posts`.
* `backup_run_backup` ran a full UpdraftPlus backup inside the AI client's request (which times out and holds a server worker), called the files-only hook so the database was never backed up, and passed unchecked values to UpdraftPlus. It now queues the backup on WP-Cron with UpdraftPlus's correct hook (files, database, or both) and only accepts db, plugins, themes, uploads, and others.
* `mc4wp_add_subscriber` called an API method that does not exist and always failed. It now uses Mailchimp for WP's `add_list_member()` and defaults to double opt-in (Mailchimp emails the person to confirm), so nobody is subscribed without consent; `skip_confirmation` subscribes immediately when consent was already given.
* `bp_get_activity` and `bp_post_activity` caused a fatal error when BuddyPress's optional Activity component is off. They now only appear when it is on.
* `polylang_translate_string` caused a fatal error when no language was given. It now uses the current language in that case and rejects unknown language codes.
* Slim SEO was never detected (the plugin checked for a constant Slim SEO does not define), so SEO tools reported no SEO plugin on Slim SEO sites.
* The Redirection tools now report "Redirection has not finished its setup" instead of a database error when Redirection's table does not exist yet.

**Security: second per-tool pass (read access, non-public content, and credentials)**
* Non-public post types are no longer readable by the wrong accounts. WordPress maps `read_post` to `read` for a published post, so any connected account could read published items of a private, non-public post type — the kind of type plugins use for contact-form messages (Flamingo), donations, form entries, and similar records. A single rule now governs every read tool: published items of a public type need read access; published items of a non-public, non-publicly-queryable type, and anything unpublished, need edit access on that specific item, exactly as wp-admin does. This covers `wp_get_post`, `wp_get_posts`, `wp_get_page`, `wp_get_pages`, `wp_search`, `wp_get_post_meta`, `wp_get_seo_meta`, `wp_get_acf_fields`, `wp_get_post_format`, `seo_bulk_get_meta`, `wp_get_content_calendar`, `wp_get_content_gaps`, the comments filter, the `wordpress://posts/{id}` resource, and the translation tools.
* Password-protected posts no longer return their content to accounts that can only read (not edit) the post. `wp_get_post`, `wp_get_page`, `wp_get_post_blocks`, `wp_parse_blocks`, `elementor_get_page_outline`, `tec_get_event`, the posts resource, and the resource listing withhold the body unless the caller can edit the post.
* `wp_get_posts`, `wp_search`, `seo_bulk_get_meta`, and `wp_get_content_gaps` validate the requested `post_type` and refuse the blocked Site Editor types.
* `wp_get_post_revisions` now requires `edit_post`. Revisions hold earlier, possibly unpublished, content.
* `wp_update_comment` now checks `edit_comment` for the specific comment (it previously only checked the general `moderate_comments` capability). `wp_create_comment` no longer ends the request with `wp_die()` when WordPress's duplicate or flood check fires; it returns the error instead.
* `wp_update_acf_field` and `wp_update_acf_options_page` reject a field name that is not a real ACF field. ACF's `update_field()` with an unknown name writes raw meta or a raw option under that name, which would have bypassed the protected-meta rules (for example writing `_wp_attached_file`). The field name is now resolved to a real ACF field, run through the same protected-meta guard as `wp_update_post_meta`, and refused when it starts with an underscore. `wp_create_acf_field_group` and `wp_update_acf_field_group` require each field to carry a name and type.
* Credential-like values are masked in more places: `wp_list_cron_jobs` arguments, `wp_get_theme_mods`, `wp_get_acf_fields`, `wp_get_acf_options_page`, Code Snippets code previews, and the CookieYes and Complianz settings. The debug and error log tools (`wp_get_debug_log`, `wp_get_error_log`) now mask passwords, keys, tokens, and `Authorization`/`Bearer` values line by line, and read only the last 2 MB of the file so a very large log cannot exhaust memory.
* `wp_get_attachment_metadata` now checks `read_post` on the attachment.
* `wp_add_post_terms` requires the taxonomy's `assign_terms` capability, checks the taxonomy applies to the post type, and verifies each term exists, matching the REST API.
* `wp_add_widget` only accepts a registered widget type, so an arbitrary `widget_*` option cannot be created.
* `bricks_get_page_data` and `divi_get_page_data` now require `edit_post` on the post (they previously let any Contributor read any post's builder content).

**Security: WooCommerce tool hardening**
* `wc_get_order` and `wc_get_order_notes` now require `edit_shop_orders` (they previously only checked `view_woocommerce_reports`), because they return addresses, phone numbers, email, and order notes. Both confirm the order exists first.
* `wc_get_store_stats`, `wc_get_sales_report`, `wc_get_top_sellers`, `wc_get_customer_lifetime_value`, and `wc_get_product_performance` now read from WooCommerce's own order tables when High-Performance Order Storage (HPOS) is enabled, instead of always querying the posts table (which is empty under HPOS). Totals are correct on both storage layouts.
* `wc_update_order_status` validates the status against `wc_get_order_statuses()`. An unknown status previously became "pending" silently.
* `wc_create_product` and `wc_update_product` validate the product `type` against `wc_get_product_types()`, instantiate the matching WooCommerce class, validate the status, and require `edit_post` (update) or `delete_post` (delete) on the specific product. `wc_update_stock` checks `edit_post` on the product (or its parent).
* Variation tools (`wc_create_variation`, `wc_update_variation`, `wc_delete_variation`, `wc_batch_update_variations`) confirm the variation belongs to the named product, check `edit_post` on the parent, limit a batch to 100, and restrict a variation's status to enabled/disabled.
* `wc_update_coupon` rejects a code already used by another coupon and validates the discount type.
* `wc_refund_order_items` checks each line item belongs to the order and that the refund amount and quantity do not exceed what was ordered.
* Custom Post Type UI: `cptui_create_post_type` and `cptui_create_taxonomy` reject reserved and already-registered slugs, enforce WordPress's 20- and 32-character slug limits, and require the taxonomy's post types to exist.
* The Events Calendar create/update tools validate and normalise event dates (and reject an end before the start), require the post type's publish capability, and slash their saved values. `givewp_refund_donation` now states that it only marks the record refunded and does not return money through the gateway.
* The media upload helper sanitises the title, caption, and alt text and slashes them before saving.

**Security hardening: OAuth, REST, and admin screens (full code review)**
* The OAuth consent screen ("Connect this AI client?") now sends `X-Frame-Options: DENY` and `Content-Security-Policy: frame-ancestors 'none'`. Without them another site could load the screen invisibly and trick a logged-in user into clicking "Allow connection" (clickjacking). It is also sent with no-cache and `Referrer-Policy: no-referrer` headers.
* The consent screen shows the domain the access will be sent to. Because AI clients register themselves and pick their own name, it also shows a clear warning when a client uses the name of a well-known AI product (Claude, ChatGPT, Cursor, Gemini, Copilot) but sends access to a domain that does not belong to it.
* Cancelling the consent screen redirected to the submitted redirect URI before checking that it belongs to the client. The client and its registered redirect URI are now validated before any redirect, and the `state`, `code`, and error values added to the redirect are URL-encoded so they cannot inject extra parameters.
* Client registration is bounded (name up to 100 characters, at most 10 redirect URIs, only the supported authentication methods), and registered clients that never completed a connection are removed by the daily cleanup after 7 days.
* The legacy static OAuth client (set only through stored settings) accepts any HTTPS redirect URI, so it is now honoured only as a confidential client with a secret.
* The plugin lifts other plugins' "REST API for logged-in users only" lockdowns for its own MCP routes. It decided this by looking for `bcs-mcp/v1` anywhere in the request URI, so adding `?x=bcs-mcp/v1` to any other REST URL (for example the users endpoint) lifted the lockdown for that request too. It now checks the REST route actually being served.
* JSON-RPC batch requests are limited to 20 calls per HTTP request (the rate limit counts a request once), and invalid batch entries get an error instead of reaching the dispatcher.
* Activity exports to CSV prefix cells that a spreadsheet would run as a formula (`=`, `+`, `-`, `@`); client names and tool names in the Activity log are controlled by the AI client or the registering client.
* The tool browser's search highlighting re-inserted tool names and descriptions as HTML without escaping; they are now escaped (tools can be added by other plugins through the `bcs_mcp_tools` filter).
* Reviewed and confirmed: every admin AJAX action checks a nonce and `manage_options`; all Activity queries are parameterized with allow-listed sort columns; tokens, codes, and undo tokens are random 256-bit values stored only as SHA-256 hashes; PKCE S256 is required; authorization codes are single-use and expire in 10 minutes; every one of the 360 tools checks a capability before it changes anything (static audit plus role-by-role tests).
* The user-meta allow-list is the definitive fix for both CVE-2026-103068 and CVE-2026-19807: because only the seven profile fields are accepted, no capability or role meta key can be written whatever its spelling, so a blocklist gap like the one in 1.2.3 cannot recur. `wp_update_user_meta` additionally requires `promote_users` (a primitive capability only administrators hold, so the "`edit_user` resolves to read for your own account" weakness no longer matters) plus `edit_user` on the target, and rejects non-string values.
* Added automated regression tests run before release: a replay of the CVE-2026-103068 / CVE-2026-19807 chain (a Subscriber cannot obtain a token, and a Contributor's token cannot write `wp_capabilities`, `{prefix}capabilities`, `{prefix}user_level`, `wp_user_level`, or any capability/role meta under any spelling, against itself or another account; no test account's roles, capabilities, or level change), and a data-breach "canary" that plants unique markers in password hashes, session tokens, application passwords, secret options, theme mods, and protected meta, then calls every available tool as every role and confirms no marker is ever returned — to anyone, administrators included — and that private, draft, password-protected, and non-public-type content never reaches a role that should not see it.

**Fixed: AI clients asking to reconnect after a few days**
* Refresh tokens are single-use: each refresh swaps them for a new pair. Hosted AI clients often refresh from several workers at once, or retry after a network error. The losing request then presented an already-swapped token, was rejected, and the client reported "Connection has expired", even though the connection was otherwise valid for 30 days. A swapped refresh token now stays usable for a 120-second reuse window, so parallel and retried refreshes succeed. Disconnecting a client or resetting OAuth still revokes it at once, and reuse after the window is refused.
* Failed token refreshes are now recorded in Activity (action `oauth_refresh_failed`) with the client name and the exact reason: revoked, expired after 30 days without a refresh, reused after the window, not found (for example after Reset OAuth State or a backup restore), or the account no longer being allowed to connect.

**Fixed: Activity times now use the site's time zone everywhere**
* Activity entries were stamped by the database server's clock (MySQL `CURRENT_TIMESTAMP`), but shown and filtered as if they were site time. Depending on the host, entries could say "5 hours ago" right after they happened, calls could land on the wrong day in Calls today, the 7-day chart, and the date filters, and times could show in UTC.
* New entries are now written in the site's time zone (Settings → General) by the plugin itself (new `includes/Log_Time.php`). Existing entries are converted once on update, using the difference between the database clock and the site time zone. Rows are left unchanged when the two already match. Entries from before a daylight-saving change may be off by an hour, because the conversion uses today's offsets.
* Every place that shows or counts log times now uses site time: the Activity list and entry details, the "ago" labels (computed on the server, so the viewer's browser time zone no longer matters), Overview's Calls today, 7-day chart and recent activity, the WordPress Dashboard widget, the date filters, the 90-day cleanup, the CSV export file name, and the undo deadline.

**Fixed**
* Creating a comment through MCP raised several "Undefined array key" PHP warnings, because `wp_allow_comment()` was called without the full set of fields WordPress expects. The comment parent, type, IP, agent, and dates are now supplied.
* When a valid token was rejected because its account had been demoted or deleted, the rejection was logged before the token's OAuth client was looked up, so it always showed the client as Unknown. The client name is now resolved first.
* An IP allowlist entry with an out-of-range CIDR mask (for example `/40`) caused a PHP error on every MCP request. Masks are now validated, and `/0` is handled explicitly.
* Copy buttons with an icon lost the icon after showing "Copied". The original button content is now restored.
* Small buttons (Clear all activity, Export CSV, and others) grew on hover, focus, and click, because a general button style reset their padding and height in those states. They now keep the same size in every state.
**WordPress.org guideline compliance**
* Re-checked the plugin against all 18 Detailed Plugin Guidelines, the Plugin Review Team's common issues list, and Plugin Check (no errors or warnings, including low-severity ones).
* Guideline 7 (no undisclosed external requests): the readme's External Services section lists every way data leaves the site, with terms and privacy policy links:
  * Tool results are returned to the AI client you connect (Anthropic, OpenAI, Cursor).
  * WordPress.org installs and updates.
  * Image downloads from a URL you give.
  * The broken-link checker's HEAD requests.
  * Integration tools that call Mailchimp, Google Site Kit, or Akismet through those plugins' own APIs, and store refunds through the configured payment gateway.
  * Local self-checks against your own site.
* Guideline 7: the OAuth authorization screen tells the approving user that site content returned by tools is sent to the AI provider and handled under that provider's terms.
* Guideline 8: plugin and theme installs come only from WordPress.org by directory slug, no tool accepts a download URL, and no scripts, styles, or fonts are loaded from other servers.
* Guideline 9 (no misleading claims): the readme already promised "an optional IP allowlist", but no setting existed to configure it. Added a Restrict by IP address field under Connect & settings. It accepts IPv4/IPv6 addresses and IPv4 CIDR ranges, separated by commas, spaces, or new lines. Invalid entries are dropped on save, and the cleaned list is shown back.
* Guideline 1 (licensing): included the SIL Open Font License text for the bundled DM Sans and DM Mono fonts (assets/fonts/DM-Sans-OFL.txt, assets/fonts/DM-Mono-OFL.txt). Added a Credits section to the readme and a `License URI` header to the main plugin file.
* Guideline 11 (no dashboard hijacking): the setup and review notices are dismissible, nonce-protected, and only shown to administrators. The dashboard widget can be hidden from Screen Options.
* Guideline 12 (readme written for people, not keyword stuffing): replaced the repetitive "WordPress AI" wording in the plugin header description and the `wordpress ai` tag. The tags are mcp, ai, ai automation, claude, chatgpt. Added a trademark notice stating the plugin is not affiliated with the AI providers it supports.
* The admin theme bootstrap script is printed with `wp_print_inline_script_tag()` instead of a raw `<script>` tag.
* Uninstall also deletes the per-user notice-dismissal records (`bcs_mcp_*` user meta), so no plugin data is left behind, as the readme promises.
* Readme rewritten and expanded: example requests, a full breakdown of tools and integrations, the security model, undo, dashboard and diagnostics, privacy, developer hooks, step-by-step installation and connection, recommended settings including firewall allowlisting, and an FAQ covering who can connect, the permission model, undo, expired connections, localhost use, Activity log contents, multisite, troubleshooting, and deactivation versus uninstall.

**Design: refreshed admin interface**
* The admin stylesheet had three overlapping restyles: the original theme, a flat pass that removed all depth, and a blue brand pass. They are unified into one consistent design layer built on the plugin's deep navy brand colors. Behaviour is unchanged.
* Navigation: a deep navy page header, with the page tabs in a separate white segmented bar below it and a navy pill for the current page. Inactive tabs stay neutral.
* Color: a light blue-grey background with pure white cards and hairline borders. Navy is used for primary buttons, the current step, and toggles, so the important action on each screen stands out. Success, warning, and error colors were retuned for contrast.
* Depth: soft, layered shadows give cards, banners, and panels a clear visual hierarchy. Stat cards and client choices lift slightly on hover.
* Page header details: a subtle blue glow and fine dot texture. Status, version, and action controls use frosted-glass pills.
* Typography and spacing: larger page and card titles with tighter letter-spacing, and tabular numbers on stat cards and counts. Small uppercase labels use wider tracking, and card spacing is more generous.
* Components:
  * Buttons have a consistent shape, with gradient primary, white secondary, and outlined danger styles.
  * Inputs, selects, and text areas get rounded corners and a soft focus ring.
  * Toggles use a gradient when on.
  * Code chips, badges, and counts get a soft tint instead of borders.
  * Setup Health rows get icon tiles, and the Most Used Tools bars are thicker.
  * The setup wizard has refined step markers and client cards.
  * Diagnostics gets a status-colored summary card and cleaner results.
  * The Activity table has a compact header and row height.
  * The modal is softer, and the save toast floats.
* OAuth authorization screen: a centered card on a soft gradient background, with a clearer title, account and permission panels, and full-width actions.
* Dark mode: a matching deep-navy palette with translucent borders, raised surfaces, and a brighter blue accent.
* Accessibility: visible two-ring keyboard focus indicators on buttons, tabs, and inputs. Hover motion and transitions are disabled when the system "reduce motion" setting is on.

**Design: redesigned Overview page**
* Status hero at the top. It states whether the server is offline, online and waiting for a first client, or live with N clients connected, with a matching color and a live indicator. It shows the MCP endpoint with a one-click copy button and three quick actions: start setup or connect another app, check the connection, and view activity.
* Six small stat cards replaced by four headline metrics:
  * Calls today, with a success-rate bar (green for succeeded, solid red for failed, grey when there are no calls yet) and succeeded/failed counts.
  * Calls in the last 90 days, with the 7-day total.
  * Connected clients, with a link to manage connections.
  * Available tools, with a link that opens the tool catalog.
* New 7-day activity chart: success and failed calls per day as stacked bars, a highlighted today column, per-day tooltips, totals in the legend, and an empty-state message when there has been no activity.
* Setup Health has a progress bar, and the call to action on unfinished checks is now Fix.
* The old "Connect your first AI client" banner is folded into the hero.

**Design: redesigned Connection health page**
* Status hero with a progress ring showing how many checks passed (for example 4 of 6). It is colored by result: green for ready, amber for needs attention, red for error. It spins while a test runs. Next to it are the status pill, a summary message, the last-tested time, and two actions: run the test again, or verify a real call from an AI app in Connect & settings.
* Check results are grouped by priority: Needs attention first (failures before warnings, each as a highlighted card with a Warning or Failed chip), then a compact two-column list of passed checks. Group headings show counts. Fresh results from the Run test button use the same grouping.
* What the test covers is a compact, always-visible card: a two-column grid of check names, with each check's full explanation in a tooltip and in screen-reader text.
* New Firewall allowlist card listing this site's exact OAuth discovery, /authorize, /token, /register, and MCP REST paths (including any subdirectory install path), with a Copy paths button and a reminder not to redirect POST requests.
* The rest of the page and the test behaviour are unchanged. Element IDs used by the test script are kept.

**Design: redesigned Activity page**
* New summary strip at the top: saved entries, 7-day success rate, errors in the last 24 hours (highlighted when there are any), and the most active client over 7 days.
* Quick filter chips (All activity, Succeeded, Failed, Today) above the detailed filters. They set the existing filter controls and reload results instantly, and they stay in sync when the detailed filters, search, or Clear are used.
* Easier-to-scan rows:
  * The time column leads with a relative time ("5 minutes ago"), with the exact date and time underneath, both in the site's time zone.
  * Failed entries show their saved failure reason under the tool name, on one line with the full text on hover, so you no longer need to open each entry to see why it failed.
  * Status is a colored pill, and failed rows get a red edge marker.
  * The actions column shows View first, then Undo (amber) or Undone (grey) where they apply.
  * Rows loaded by search, sorting, filters, and pagination use the same layout.
* Entry detail view redesigned:
  * The tool name and status at the top, with a red banner showing the failure reason.
  * A summary grid: client, exact time, relative time, and entry number.
  * A readable parameters table and the response pretty-printed as formatted JSON.
  * The undo deadline and an Undo button when the change can still be undone.
  * Styled from the stylesheet instead of inline styles, so it follows light and dark mode.
* Row checkboxes are styled to match light and dark mode.
* Sort icons in the Activity table are larger and always visible. The sorted column shows a solid navy arrow, and hovering a column header highlights its icon.
* The View button is a clearly visible tinted button. It turns solid navy when you hover it or its row.
* The Clear all activity button keeps its size on hover and shows a stronger red glow instead.

**Design: redesigned Connect & settings page**
* The MCP Server switch is a status card with an icon tile and an Online/Offline pill that follows the toggle.
* The three setup steps span the full width of the setup panel. Connecting lines turn green as steps are completed.
* AI app choices are cards with a colored monogram tile (Claude.ai, ChatGPT, Cursor, Claude Desktop) and a radio-style check mark on the selected app. The separate Selected app box is removed because the selected card now shows it.
* The sidebar is three clear cards instead of one collapsible panel:
  * Your MCP endpoint, with a full-width Copy URL button and tags for OAuth 2.0 + PKCE, Streamable HTTP, and No API key needed.
  * Connected AI apps, always visible, with a count.
  * Settings, with the destructive-action confirmation toggle and expandable rows for Restrict by IP address (showing whether it is on or off) and Disconnect all AI apps.
* Step headings no longer show a heavy focus box when the wizard moves between steps. They are still focused for screen readers.

**Design: simplified connection wizard**
* The progress bar is three clickable step tiles, each with a one-line hint (Pick your AI app, Paste the URL & approve, Send a test message). You can jump back to any step, or forward to the next step you have reached. The current step is underlined in blue and completed steps in green.
* Step 1 advances automatically: clicking an AI app opens its setup steps right away. The Continue button still works for keyboard users.
* Step 2 leads with the action: the site URL with Copy, then a prominent Copy URL and open app button, then a clean numbered checklist connected by a line instead of stacked boxes. The troubleshooting line is a small warning tip, and plan requirements show as an info note.
* Step 3 is a short four-part timeline: copy the test message, start listening, send it in your AI app, then the result. The part to do next is highlighted. The result shows a spinner while waiting, a green Connection verified state (with the client name) when the call arrives, and an amber state on timeout or error.
* A disabled Finish button is clearly greyed out instead of nearly invisible, and the Next button shows an arrow.

**Database and developer notes**
* Database version 2.1.2 (from 2.0.0). Updating creates the `{prefix}bcs_mcp_undo` table (with a `log_id` column linking each undo record to its Activity entry) and converts existing Activity times to the site's time zone once. Settings, connections, and Activity history are kept.
* New files: `includes/MCP/Undo_Store.php` (undo tokens and records), `includes/MCP/Change_Recorder.php` (change snapshots for undo), and `includes/Log_Time.php` (site-local Activity times).
* New filter: `bcs_mcp_connect_capability` (default `edit_posts`). New tool: `bcs_undo_operation`.
* New Activity actions: `oauth_refresh_failed` for failed connection renewals, and `bcs_undo_operation` entries from client "WordPress admin" for undos made in wp-admin.

**Housekeeping**
* Version bumped to 1.2.5 (the in-development 1.3.0 number was never released; all of its changes ship here). Readme "Tested up to" confirmed for WordPress 7.1, and the translation template (languages/bcs-mcp-manager.pot) regenerated.

= 1.2.4 =
* Security: Restrict user meta reads, writes, and deletes to safe profile fields. Writes and deletes require permission to promote users and reject array or object values, preventing role and capability changes through user meta. Role assignment also checks permission to edit the target user.

= 1.2.3 =
* Added a guided connection setup for Claude.ai, ChatGPT, Cursor, and Claude Desktop, with app-specific steps, endpoint copying, and a safe test prompt.
* Refreshed the admin navigation, dashboard, connection settings, diagnostics, and activity screens to make setup and routine checks easier to follow.
* Added dashboard metrics for available tools, calls today, failed calls today, and connected clients.
* Improved light and dark appearance, including clearer focus and active states and more consistent spacing across plugin screens.
* Updated and expanded the plugin documentation and troubleshooting guidance. Existing connections and settings are retained.
* Addressed WordPress coding-standard findings in translated strings and admin template variables.
= 1.2.2 =
* Added 19 new WordPress AI tools, bringing the total to 335+: plugin/theme install-from-WordPress.org and update-to-latest (`wp_install_plugin`, `wp_update_plugin`, `wp_install_theme`, `wp_update_theme`), a WordPress core update tool (`wp_update_core`), WP-Cron event scheduling and deletion (`wp_schedule_cron_event`, `wp_delete_cron_event`), classic widget add/remove (`wp_add_widget`, `wp_remove_widget`), a Redirection update tool (`redirection_update_redirect`), a Gravity Forms entry delete tool (`gf_delete_entry`), bbPress reply posting and moderation (`bbp_create_reply`, `bbp_moderate_reply`), an Easy Digital Downloads refund tool (`edd_refund_order`), an AffiliateWP affiliate approve/reject tool (`affwp_set_affiliate_status`), GiveWP donation recording and refund tools (`givewp_create_donation`, `givewp_refund_donation`), a Code Snippets activate/deactivate tool (`code_snippets_set_active`), and a two-factor reset tool (`wp_reset_2fa`)
* Plugin and theme installs are restricted to the WordPress.org directory by slug — no tool accepts an arbitrary download URL
* Security: `wp_add_widget` runs string values in `settings` through `wp_kses_post` before saving. Writing directly to the widget option bypasses the widget's own `WP_Widget::update()`, which is what normally sanitizes saved content, so this closes a path for unescaped markup to reach the public-facing site via a widget's front-end render.
* Documentation: added an External Services disclosure for `wp_install_plugin`, `wp_update_plugin`, `wp_install_theme`, `wp_update_theme`, and `wp_update_core`, which contact the official WordPress.org API using WordPress core's own installer/updater classes — the same servers and code path as wp-admin's "Install Now"/"Update Now" — and documented that these tools accept a plugin/theme slug only, never an arbitrary download URL, per the WordPress.org Plugin Directory guideline on not installing code from third-party servers
* Redesigned the plugin's admin UI: added a one-click light/dark theme switcher (light by default, remembered per browser) next to Settings on every screen, refreshed the color palette and typography, gave buttons and icon controls a consistent rounded style, and reworked the Tools browser's category list into an animated, easier-to-scan accordion — all scoped to this plugin's own admin pages, with no effect on the rest of wp-admin
* New optional safeguard: a "Require confirmation for destructive actions" toggle in Settings (off by default). When enabled, the ~28 delete-type tools (posts, pages, media, terms, comments, users, menus, cron events, widgets, transients, and the WooCommerce/ACF/Gravity Forms/Events Calendar/CPT UI equivalents) return a preview of what would happen instead of executing on their first call, and only run once the connecting AI client resubmits the identical call with an added `"confirm": true` argument

= 1.2.1 =
* Fixed: uninstalling the plugin left the `bcs_mcp_review_notice` option behind instead of removing it along with the plugin's other options, contradicting this readme's "clean uninstall, no orphaned data" claim
* Hardened: the sensitive-value redaction list (used by the Activity Log and by generic postmeta/usermeta read tools) now also matches meta keys containing the bare substring `pass` — e.g. a third-party plugin storing a credential under a key like `smtp_pass` or `ftp_pass` is now redacted; previously only keys containing the full word `password` or `passwd` were caught
* Fixed: security plugins that ship a "restrict the REST API to logged-in users" toggle (Wordfence, All In One WP Security, and similar) could block AI clients entirely, since this plugin's MCP/OAuth routes are intentionally anonymous at the WordPress-login level and authenticate the caller themselves via OAuth Bearer token. This plugin's own routes are now automatically exempted from that kind of blanket lockdown set by another plugin.
* Diagnostics: the "OAuth discovery endpoint reachable" check now gives a specific, actionable message based on the actual HTTP status returned (401/403/406 now correctly points at a firewall/security-plugin block instead of the generic "rewrite rules" message) instead of one generic failure message for every cause
* Diagnostics: added a new check that detects active security plugins (Wordfence, All In One WP Security, iThemes/Solid Security, Sucuri Security, WP Cerber) and lists exactly which URLs and HTTP methods to allowlist in their firewall if a client still can't connect
* Diagnostics: the discovery-endpoint check now also detects when a 404 response never actually reached WordPress at all (identified by the response missing any PHP/WordPress fingerprint). Previously this was misreported as "rewrite rules not flushed," which sent people to the wrong settings screen; it now correctly says this needs a firewall/allowlist fix, and explicitly names any active security plugin that could be the cause (its own protection layer, e.g. Wordfence's "Extended Protection" mode, can run before WordPress loads exactly like a host or CDN block would) rather than only pointing at the host/CDN
* Added a dedicated FAQ entry with step-by-step guidance for Wordfence/AIOS/firewall-blocked connections and for the server/CDN-level `.well-known` blocking case
* New: a proactive admin notice (shown on the Plugins screen and this plugin's own screens, not just when you manually run Diagnostics) that detects OAuth discovery/registration problems automatically — a host or CDN blocking `/.well-known/*` before PHP runs, Sucuri/CloudProxy specifically, a response that's HTML instead of JSON (another plugin or theme intercepting the request), Plain permalinks, or a host redirecting `POST /register` to `/register/` with a 301 (which OAuth clients don't follow, silently breaking registration even when discovery works). Each case gets its own specific fix, and the notice is dismissible and re-checkable per admin
* Fixed: clicking "Test Connection" on the Diagnostics page didn't clear the separate cache the admin notice above uses, so the notice could keep reporting an already-fixed problem for up to its own refresh interval. Both now refresh together
* Fixed: a rejected connection attempt (an invalid/expired token, an IP-allowlist block, or a rate limit) never appeared in the Activity Log at all — only successful tool calls were recorded, so a failing connection looked like total silence with nothing to debug from. These rejections are now logged with a specific reason (e.g. "token was explicitly revoked" vs. "token expired at ... UTC" vs. "no matching access token found — never issued, or issued by a different site/environment") instead of the generic "invalid or expired" message alone. The routine unauthenticated request every OAuth client sends first (before it has a token) is deliberately not logged, since logging that would flood the log with noise rather than signal
* Documentation: identified SiteGround specifically (via its `X-CDN-C: static` free-CDN response header) as the most common cause of the "host/CDN blocking `.well-known/`" case — SiteGround's nginx reserves that entire path prefix for its own SSL certificate validation, fleet-wide, and per multiple reports won't adjust it per-site even on request. Added FAQ guidance covering how to identify it, the Cloudflare-edge-proxy workaround other SiteGround-hosted sites have used, and the tradeoffs of working around it by manually supplying an OAuth Client ID/Secret in your AI client instead of relying on automatic discovery
* Documentation: added an FAQ entry explaining "This connector has no tools available" on a first-time connection that resolves after disconnect/reconnect — confirmed via Anthropic's own issue tracker to be a Claude Desktop client-side bug (zero traffic reaches the server during the failure), not something a server-side fix can address

= 1.2.0 =
* Added 96+ new WordPress AI tools, bringing the total to 316+ across all supported integrations
* New integrations: MonsterInsights, Sucuri Security, and TranslatePress, plus WP Mail SMTP configuration status
* Fixed: admin screens were loading DM Sans / DM Mono from Google's font CDN on every page view even though local copies were already bundled; now fully self-hosted with no external requests

= 1.1.0 =
This release adds 56 new WordPress MCP tools (215+ total, up from 150+), six more SEO plugin integrations, two new community/events integrations, a dedicated connection diagnostics page, and a redesigned admin dashboard — plus security hardening and bug fixes. **Fully backward compatible:** existing OAuth connections, access tokens, Activity Log history, and settings are preserved automatically on update — no re-authorization, reconfiguration, or action of any kind is required from existing users. Full details below.

**New: SEO tools now support 6 plugins (was 2)**
* SEO meta tools (per-post title/description/focus keyword/noindex, bulk SEO audit, site-wide title separator and homepage settings) now auto-detect and support Yoast SEO, Rank Math, All in One SEO (AIOSEO), SEOPress, Slim SEO, and The SEO Framework
* AIOSEO is read and written directly through its own database table (not postmeta), matching how AIOSEO v4+ actually stores data

**New: Community & Events integrations**
* BuddyPress — list members, read extended profile (xProfile) fields, read and post to the activity stream, list and create groups, list group members, list friend connections
* The Events Calendar — full event CRUD (create, read, update, delete), plus venues, organizers, and event categories

**New: Forms, LMS & e-commerce integrations**
* Contact Form 7 — list forms, read form fields and mail settings
* WPForms — list forms, read entries (requires WPForms Pro or entry storage enabled)
* Ninja Forms — list forms, read submissions
* MemberPress — list memberships and members, get a member's subscriptions and transaction history
* LearnDash — list courses, get a user's course progress, enroll/unenroll a user
* Easy Digital Downloads — products, orders, single order detail, customers, store stats
* WooCommerce Subscriptions & Bookings — list subscriptions, cancel a subscription, list bookings (added to the existing WooCommerce category)

**New: Page builder & site management integrations**
* Bricks Builder — read a page's element tree, clone a page with its Bricks content
* Divi Builder — read a page's shortcode content, clone a page with its Divi content
* Redirection — list, create, and delete URL redirects
* UpdraftPlus (Backup & Migration) — list backup sets, trigger a new backup, check job status
* FluentCRM (Email / CRM) — list contacts, create or update a contact by email, list email campaigns

**New: Core WordPress tools**
* Cache status detection — reports which page-caching and object-caching plugins are active
* Users CSV export
* Full Site Health diagnostics — runs the same tests shown under Tools → Site Health and summarizes critical/recommended/passed counts
* `wp_bulk_delete_comments` — delete or trash multiple comments by ID in one call
* `wp_duplicate_menu` — duplicate a nav menu including all of its items
* `wp_get_multisite_info` — check multisite status and list network sites

**New: Admin dashboard & setup experience**
* Dedicated "Connection Test" page (between Settings and Activity Log) — checks HTTPS, permalinks, and live MCP/OAuth-discovery endpoint reachability, and reports the specific reason a connection would fail instead of a generic error
* "Setup Health" checklist on the Dashboard — at-a-glance status for server enabled, HTTPS, pretty permalinks, and whether an AI client is connected, with a "Fix now →" shortcut
* In-admin review prompt (shown only on this plugin's own screens, only after real successful tool calls) with "Remind me later" and "No thanks" options — never shown on first activation

**Improved: Dashboard & Settings UI**
* The "Enable MCP Server" toggle is now a full-width, color-coded banner at the top of Settings instead of a small switch buried inside a card
* Removed the "Quick Connect" card from the Dashboard (it duplicated the per-client setup steps already on Settings); the "WordPress Tools" browser now spans the full page width
* Dashboard pairs "Setup Health" on the left with the stat cards on the right (3 per row, 2 rows), matched to equal height
* Setup Health checklist rows show a check/warning icon and a "Ready" / "Needs attention" status tag, and are noticeably more compact
* "Recent Activity" and "Most Used Tools" are now always shown side by side (2/3 + 1/3 columns) instead of "Most Used Tools" being hidden entirely until there's data — it now shows an empty state like Recent Activity does
* Activity Log pagination now truncates to "1 2 3 … 8 9 10" style instead of listing every page number when there are many pages
* Fixed excess vertical spacing between the tool name and its usage bar in "Most Used Tools" (Dashboard and Settings)

**Improved: Connection Test & Activity Log**
* Redesigned the "Connected AI Clients" list on Connection Test: branded per-client color and avatar, total calls, calls today, and a session-expiry countdown, plus a "View activity" button that jumps straight to the Activity Log pre-filtered to that client
* Activity Log table no longer scrolls inside its own box — it now scrolls with the page like the rest of the admin screen
* Activity Log gained a "Show ⌄ per page" control (10 / 20 / 50 / 100, default 20); pagination now sits to the right of it instead of centered alone
* "Last tested" timestamp on Connection Test now displays in the site's configured local timezone (Settings → General → Timezone) instead of the server's UTC time
* Removed the redundant per-client "connected X ago" breakdown from the Dashboard's Setup Health card — the full detail now lives on the Connection Test page
* Removed the "No test run yet" placeholder that could stay visible after a test had actually completed

**Fixed**
* `tools/list` now actually filters out addon-gated tools whose required plugin isn't active, instead of advertising all 215+ tools regardless of what's installed — a connected AI client only ever sees tools that will work on the site
* Removed the "REST endpoint reachable" check from Connection Test — a self-request through some caching/security-plugin setups could return HTTP 200 with a body that didn't parse as expected, producing a false failure on connections that were actually working fine. The remaining 4 checks (server enabled, HTTPS, permalinks, OAuth discovery) cover the same ground without the false positive
* The "Enable MCP Server" toggle's label text was never actually rendered bold, and the switch itself blended into the banner background — both now have proper contrast
* Activity Log's built-in connection-test entries were being mislabeled after the plugin rename
* A missing `translators:` comment on the OAuth authorization screen was breaking automatic `.pot` generation
* `wc_create_variation` / `wc_update_variation` — attribute values passed as `{name, option}` could silently save as an empty string instead of the intended value; the variation's postmeta key was being double-prefixed, and a brand-new attribute or term was never registered on the parent product as usable for variations. Both are now handled automatically
* `wp_get_site_health_tests` could hang the whole request ("connector's server isn't responding") if a network-dependent Site Health test wasn't already excluded, or if a single test threw a fatal error; hardened with a wider skip list for network-calling tests, a 3-second HTTP timeout clamp for the duration of the run, and per-test error isolation so one broken test can't take down the whole diagnostic
* A tool call that hit an uncaught PHP fatal error (as opposed to a caught `Exception`) was left stuck at `status = pending` in the Activity Log forever instead of being recorded as `error`

**Security**
* `wp_export_users_csv` now neutralizes spreadsheet formula-injection characters (`=`, `+`, `-`, `@`) and escapes embedded quotes in exported fields
* Dynamic database table names in the WPForms, MemberPress, and Redirection queries now use `$wpdb->prepare()`'s `%i` identifier placeholder instead of raw string interpolation

**Changed**
* Plugin renamed to "ByteCoreStack - MCP Connector for AI Tools" (previously "AI Connector – MCP for Claude, ChatGPT, Gemini & More")
* Readme tags updated for search discoverability (`mcp, ai, claude, chatgpt, mcp-server`)
* All new integrations activate automatically when their corresponding plugin is detected, matching the existing WooCommerce/ACF/Elementor/Gravity Forms behavior — no configuration required
* 215+ WordPress MCP tools across 30 categories (215 verified at release)

= 1.0.0 =
* Initial release
* 150+ WordPress MCP tools across 20 categories (159 verified at release)
* OAuth 2.0 with PKCE (authorization code flow, Dynamic Client Registration, refresh tokens)
* Discovery endpoints: `/.well-known/oauth-protected-resource` and `/.well-known/oauth-authorization-server`
* Streamable HTTP transport (MCP 2025-11-25) as primary transport
* Legacy SSE transport (`/sse` + `/messages`) for older client versions — compatible with all clients, not restricted by User-Agent
* `Mcp-Session-Id` response header on `initialize` for session tracking (required by Cursor)
* `X-Accel-Buffering: no` on SSE responses for nginx compatibility
* Auth validation on session termination (DELETE /mcp)
* Activity logging with client detection (Claude, ChatGPT, Gemini, Cursor, Windsurf, and others), storing each call's parameters/result locally for audit purposes with sensitive-looking values redacted automatically
* IP allowlist support
* Admin dashboard with today's success/fail stat cards, settings page, and activity log with CSV export
* WP Dashboard widget (7-day activity bar chart)
* WooCommerce (33 tools), ACF (3 tools), Elementor (6 tools), and Gravity Forms (2 tools) integrations — activate automatically when those plugins are present
* Developer API: `bcs_mcp_register_tool()` helper and `bcs_mcp_tools` filter for custom tools
* No tool creates new WordPress users; `wp_update_user` no longer accepts a `role` parameter — use `wp_assign_user_role` (requires `promote_users`) for role changes
* Clean uninstall: removes all plugin tables, options, and transients with no orphaned data
* Translation-ready: full `.pot` file included in `/languages` for translators
