Title: IATO MCP
Author: iatoai
Published: <strong>Апрель 24, 2026-ж.</strong>
Last modified: Май 14, 2026-ж.

---

Плагиндерди издөө

![](https://ps.w.org/iato-mcp/assets/banner-772x250.png?rev=3514829)

![](https://ps.w.org/iato-mcp/assets/icon-256x256.png?rev=3514829)

# IATO MCP

 Автору [iatoai](https://profiles.wordpress.org/iatoai/)

[Жүктөө](https://downloads.wordpress.org/plugin/iato-mcp.1.8.2.zip)

 * [Кенен маалымат](https://ky.wordpress.org/plugins/iato-mcp/#description)
 * [Сын-пикирлер](https://ky.wordpress.org/plugins/iato-mcp/#reviews)
 *  [Орнотуу](https://ky.wordpress.org/plugins/iato-mcp/#installation)
 * [Development](https://ky.wordpress.org/plugins/iato-mcp/#developers)

 [Колдоо](https://wordpress.org/support/plugin/iato-mcp/)

## Сүрөттөө

WordPress.com has a built-in MCP server. Now self-hosted WordPress does too.

[IATO MCP](https://iato.ai/wordpress-mcp) connects your WordPress site to Claude
Desktop and other MCP-enabled AI clients. Once connected, you can ask Claude to 
audit your site and fix SEO issues, identify orphan pages, clean up broken links,
and more — all in a single conversation.

#### How it works

 1. Install and activate the plugin
 2. Follow the setup wizard — copy the config into Claude Desktop, or use “Add Custom
    Connector” with your site URL
 3. Connect your [IATO account](https://iato.ai) for AI-powered analysis ([free trial](https://iato.ai)
    up to 500 pages)

#### What Claude can do

**Without an IATO account (45 WordPress tools):**

 * Read and edit posts, pages, and media
 * Create new posts and pages with excerpt support
 * Update SEO titles and meta descriptions (Yoast SEO, RankMath, SEOPress)
 * Update canonical URLs
 * Update image alt text
 * Upload new images to the media library (base64 by default; URL ingestion optional
   with admin allowlist + full SSRF protection)
 * Set and clear the featured image on any post
 * Read and write arbitrary post meta with a credential-key denylist and a known-
   safe theme/builder/SEO allowlist (force=true required outside the allowlist)
 * Set per-post theme + builder page settings (hide title, sidebar layout, content
   layout, etc.) on Astra, Kadence, GeneratePress, and Elementor in one call
 * Read and edit navigation menus
 * Manage categories, tags, and taxonomy terms
 * Manage JSON-LD structured data
 * Manage redirect rules
 * Read and write Elementor page builder data
 * Widget-grained Elementor edits with optimistic concurrency, idempotency, and 
   bulk operations
 * Clone the styling of an existing Elementor post in one call via `update_elementor_data(...,
   inherit_settings_from: <id>)`
 * Resolve URLs to their rendering post (Theme Builder shadowing detection)
 * Search content across the site
 * Read site info and settings
 * Read and filter comments
 * One-call rollback for any tracked write — every change emits a receipt with a
   stable `change_id`; pass it back to the `rollback` tool and the original value
   is restored

**With an [IATO account](https://iato.ai) (12 bridge tools — full analyze-and-fix
pipeline):**

 * Start a new crawl of your site directly from Claude (admin only)
 * Check crawl status and list recent crawl jobs
 * Run a full SEO audit and fix title, meta description, and alt text issues automatically
 * Identify orphan pages not linked from any navigation menu
 * Audit navigation menus for gaps and missing sections
 * Surface thin content with specific improvement recommendations
 * Map broken links to source posts for direct editing
 * Analyze site taxonomy and suggest consolidations
 * Get AI-prioritized suggestions across all areas
 * Flag slow pages with contributing performance factors

#### Supported SEO plugins

 * Yoast SEO
 * RankMath
 * SEOPress
 * Falls back to native WordPress title if none detected

#### Example prompts

> “Crawl my site and fix all missing meta descriptions”
> “Show me pages that aren’t in any navigation menu and add them to the right place”
> “What are the most impactful improvements I can make to my site right now?”
> “Find all broken links and tell me which posts contain them”
> “Audit my categories and tags and suggest consolidations”
> “Set every H2 heading in these Elementor posts to H1”
> “Find all button widgets on the site and change their color to #ff0000”

#### External Services

This plugin connects to the following external service when configured:

**IATO API** ([https://iato.ai](https://iato.ai)) — When you enter an IATO API key
in the plugin settings, the plugin sends requests to `https://iato.ai/api` to retrieve
crawl data, SEO audit results, sitemap information, and AI-generated improvement
suggestions. No data is sent to IATO until you configure an API key. Your public
page URLs (as crawled by IATO) and crawl analysis results are transmitted.

 * [IATO Terms of Service](https://iato.ai/terms)
 * [IATO Privacy Policy](https://iato.ai/privacy)

The plugin also implements an OAuth 2.0 authorization server on your WordPress site
so that MCP clients like Claude Desktop can authenticate via the standard “Add Custom
Connector” flow. This communication stays between the MCP client and your WordPress
site — no data is sent to third parties during authentication.

## Скриншоттор

 * [[
 * Settings page — MCP connection info with endpoint URL and API key
 * [[
 * Settings page — IATO Platform configuration and tool toggles
 * [[
 * Setup wizard — auto-generated Claude Desktop configuration
 * [[
 * OAuth authorization screen — approve AI client connections

## Орнотуу

 1. Upload the plugin files to `/wp-content/plugins/iato-mcp/` or install via the WordPress
    plugin directory
 2. Activate the plugin via the Plugins menu in WordPress
 3. Follow the setup wizard that appears — it provides the JSON config for Claude Desktop
 4. In Claude Desktop, either paste the JSON config or use “Add Custom Connector” and
    enter your site URL
 5. Optionally, go to Settings > IATO MCP to enter your IATO API key for the full analysis
    pipeline

For detailed setup instructions, see the [IATO MCP documentation](https://iato.ai/wordpress-mcp-docs).

## FAQ.KG

### Do I need an IATO account?

No. The plugin works standalone for reading and editing WordPress content with 40
built-in tools. An [IATO account](https://iato.ai) ([free trial](https://iato.ai)
up to 500 pages) unlocks 12 additional bridge tools: start/list/status crawl management,
SEO audit, broken links, content gaps, orphan pages, navigation audit, taxonomy 
analysis, AI suggestions, and performance reports.

### Which WordPress version is required?

WordPress 6.2 or higher with PHP 8.0+. The plugin uses the WordPress REST API and
implements OAuth 2.0 for secure authentication with AI clients.

### Does this work on shared hosting?

Yes. The plugin uses standard HTTP requests (one per MCP call) rather than long-
lived connections, so it works on all hosting environments including shared hosting.

### Which AI clients are supported?

Any MCP-enabled client: Claude Desktop, Cursor, VS Code with GitHub Copilot, and
any client that supports the Streamable HTTP MCP transport.

### How does authentication work?

The plugin generates a secure API key on activation. You can authenticate in two
ways: paste the provided Bearer token config into your AI client, or use Claude 
Desktop’s “Add Custom Connector” flow which handles OAuth 2.0 with PKCE automatically.

### Why does the plugin support two auth methods?

AI clients like Claude Desktop authenticate via a WordPress Application Password(
or the OAuth 2.0 / PKCE flow), which is the WordPress-native pattern most users 
will use. The plugin also accepts the plugin-generated Bearer token at the same 
MCP endpoint — that path is used by the IATO platform’s own integrations (for example,
the dashboard’s “Sync pages, posts, menus, and taxonomy from WordPress” feature,
which composes the plugin’s read tools to pull content into IATO). Both methods 
land at `/wp-json/iato-mcp/v1/message` and are validated by `class-auth.php`. You
don’t have to choose — paste your Bearer token into the IATO platform connection,
generate an Application Password for Claude Desktop, and the same plugin handles
both.

### Is my content sent to IATO or Anthropic?

WordPress content (post titles, meta descriptions, etc.) is never sent to IATO. 
IATO crawls your public URLs the same way a search engine would. Claude processes
content within your AI client session only. The IATO API is only called when you
use bridge tools, and only crawl analysis data (not your content) is transmitted.

### Can I control which tools are available?

Yes. Go to Settings > IATO MCP to enable or disable individual tools. You can turn
off any tool you don’t want AI clients to access.

### Can AI clients upload arbitrary files to my media library?

Only images, and only when the calling user has the `upload_files` capability. The`
create_media` tool enforces an image-only MIME allowlist (JPEG, PNG, GIF, WebP, 
AVIF) verified against actual file bytes — the claimed mime_type is never trusted.
SVG uploads are not supported in this release. Files exceeding the size cap (default
10MB) or the dimension cap (default 8000×8000) are rejected, as are filenames containing`.
php`, `.phtml`, or `.htaccess`. URL-source ingestion is disabled by default; admins
who enable it must also configure a host allowlist, and private/loopback/cloud-metadata
IPs are rejected even for allowlisted hosts. Each upload counts against a per-user
rate limit (default 20/min) and emits a `change_receipt` — rolling back fully deletes
the attachment file. All four limits are configurable from Settings > IATO MCP.

## Сын-пикирлер

There are no reviews for this plugin.

## Contributors & Developers

“IATO MCP” is open source software. The following people have contributed to this
plugin.

Мүчөлөрү

 *   [ iatoai ](https://profiles.wordpress.org/iatoai/)

[Translate “IATO MCP” into your language.](https://translate.wordpress.org/projects/wp-plugins/iato-mcp)

### Interested in development?

[Browse the code](https://plugins.trac.wordpress.org/browser/iato-mcp/), check out
the [SVN repository](https://plugins.svn.wordpress.org/iato-mcp/), or subscribe 
to the [development log](https://plugins.trac.wordpress.org/log/iato-mcp/) by [RSS](https://plugins.trac.wordpress.org/log/iato-mcp/?limit=100&mode=stop_on_copy&format=rss).

## Өзгөртүүлөр

#### 1.8.2

 * Fix: `get_site_settings` no longer corrupts `permalink_structure`, `title`, or`
   tagline`. The five-field tool was wrapping every value in `sanitize_text_field()`,
   which calls `_sanitize_text_fields()` — a function that repeatedly strips `%[
   a-f0-9]{2}` octets as a transport-safety measure for URL-encoded strings. Applied
   to fields that legitimately carry literal `%xx` content, that’s actively destructive.
   Three field-level changes follow, with deliberately distinct framing because 
   they’re different categories of fix:
    - **(a) `permalink_structure` — pure bug fix.** The sanitized output was always
      wrong for this field. WordPress’s permalink structure legitimately carries`%
      category%`, `%postname%`, `%year%`, `%monthnum%`, `%day%`, `%post_id%`, and`%
      author%` as literal placeholder tokens; `_sanitize_text_fields` ate the `%
      xx` prefixes of those tokens (e.g. `/%category%/%postname%/` came back as `/
      tegory%/%postname%/`). Now returns the raw value as WordPress stores it — 
      matching what WP core uses internally when generating URLs.
    - **(b) `title` and `tagline` — broader behavior change.** These now return 
      the raw stored value, not the `sanitize_text_field`-processed form. Practical
      implications beyond `%xx`: HTML entities, leading/trailing whitespace, line
      breaks, and collapsed multiple spaces in the site title or tagline now surface
      to the read tool instead of being stripped or collapsed. Rationale: an admin
      read tool’s contract is to surface what’s stored, not a display-rendered form.
      Most sites won’t notice the change because typical site titles are plain text;
      sites with unusual characters in their title/tagline will see the raw form
      they actually stored.
    - **(c) `admin_email` and `timezone` — unchanged.** Explicit decision: these
      value types don’t legitimately carry `%xx` in practice. PHP timezone identifiers
      are a controlled list with no `%`; `admin_email`‘s RFC percent-encoding form
      is exceedingly rare in single-mailbox use. `sanitize_text_field` is WP-canonical
      for these and stays.
 * Downstream: v1.8.1’s archive-detection path reads `category_base`/`tag_base` 
   directly via `get_option()` inside the router (not via this MCP tool) and is 
   unaffected. No internal callers of `get_site_settings` output exist in the codebase.
   External MCP callers now receive faithful DB values for the three fixed fields.

#### 1.8.1

 * Fix: `resolve_url` correctly identifies Elementor Theme Builder archive templates
   as the renderer for Yoast-stripped category URLs (e.g. `/build/` instead of `/
   category/build/`). Previously such URLs returned `route_type=404` even though
   a Theme Builder template was rendering them. Root cause was two independent bugs:
    - **Detect_archive_info gap.** The URL classifier only recognised the default`/
      category/<slug>/`, `/tag/<slug>/`, and `/author/<slug>/` patterns. Sites with
      Yoast’s “Remove the categories prefix” enabled — or with a custom `category_base`/`
      tag_base` configured in Settings > Permalinks — served archives at URLs that
      didn’t match those patterns, so the classifier returned null and the shadowing
      check had no archive context to evaluate against. A three-stage cascade now
      handles all three cases: default patterns first (zero new cost on standard
      sites), then configured-base patterns, then a bounded reverse lookup through`
      get_term_link()` that goes through any active `term_link` filter (Yoast, RankMath,
      etc.) and matches the resulting URL against the input.
    - **Shadowing-dispatch bug.** `detect_theme_builder_shadowing` treated Elementor
      Pro’s `find_via_theme_builder_module` `false` return (Pro loaded but `get_documents_for_location`
      matched nothing) as authoritative, returning that `false` directly and never
      reaching the `find_via_conditions_meta` fallback. In REST context — which 
      is 100% of MCP traffic — `get_documents_for_location` is bound to the current`
      $wp_query` (the REST endpoint, not the URL being asked about) so it consistently
      returns nothing; the dispatch bug meant the conditions-meta scan that DOES
      evaluate against our explicit URL context was unreachable on every Pro-installed
      site. Fix is a one-line change from `if ( null !== $found )` to `if ( is_array(
      $found ) )` so both `false` and `null` fall through to the meta scan; `array`(
      a positive match) still returns immediately. Bug has existed since v1.7.x;
      v1.8.0’s archive plumbing landed correctly but the dispatch bug blocked it
      from firing.
 * Bonus: the dispatch fix also restores singular page shadowing detection on Pro-
   installed sites. `get_post(id, include_shadowing:true)` now correctly surfaces`
   is_shadowed_by` when an Elementor Theme Builder single template overrides the
   slug-based render. Same dispatch bug had been blocking this since v1.7.x.
 * `rendering_post_id` semantics UNCHANGED from v1.7.x. v1.8.0’s additive `resolve_url`
   contract fully preserved — all new fields (`effective_render_id`, `template{}`,`
   shadowed_route_type`, etc.) populated correctly by v1.8.0 already; v1.8.1 just
   makes the shadowing detection that fills them actually fire in REST context.
 * Documented limitation: plugins that strip the category base via raw `.htaccess`
   rewrites without using the `term_link` filter won’t be detected by Stage 3 of`
   detect_archive_info`. Rare; rely on a `term_link`-based stripper (Yoast, RankMath,
   etc.) until a workaround is needed.

#### 1.8.0

 * Fix: `resolve_url` now resolves Elementor Theme Builder archive routes. Archive
   URLs served by a Theme Builder template (e.g. a category or CPT archive whose
   render is provided by an `elementor_library` document) previously returned `route_type
   =404` because the conditions evaluator only matched `include/singular/...` patterns
   against `url_to_postid()`‘s post ID — which is 0 on archives. The conditions 
   parser now evaluates `include/archive/...` patterns (taxonomies, terms, authors,
   CPT archives, `in_taxonomy`, `post_archive`) against URL-derived context, so 
   archive shadowing is correctly detected. `find_via_theme_builder_module` also
   captures the matching location into `template_type`. The condition string that
   fired is surfaced as `template.condition_matched` for callers who need to see
   the match logic.
 * New: `resolve_url` response gains four additive fields. `rendering_post_id` semantics
   are UNCHANGED — still the canonical/slug-based post (now normalized to `null`
   instead of `0` on archives so `=== null` checks work). The new fields are: `rendering_post_type`,`
   effective_render_id` (single field answering “what actually renders” — template
   ID when shadowed, canonical post ID otherwise, `null` only on a true 404), `effective_render_post_type`,`
   shadowed_route_type` (route the URL would have had absent the template), and 
   a structured `template{template_id,template_type,condition_matched,builder}` 
   object present when shadowing applies. `rendering_template_id` continues to work
   exactly as documented; the new fields are additive only.
 * New: `find_elementor_widgets` auto-resolves revision IDs to their parent post.
   Passing a revision ID via `post_ids` previously returned matches tagged with 
   the revision’s own ID, leaking the parent only through the `NNN-revision-vN` 
   slug — the brute-force discovery path that motivated this work. Revision IDs 
   are now mapped to their parent via `wp_is_post_revision()`, deduped, and each
   match scanned from a revision input carries `resolved_from_revision_id` so callers
   can see the mapping. Default auto-scan also tightens `post_status` from `any`
   to `[publish, draft, pending, private]` — trash and auto-draft are no longer 
   scanned by default.
 * New: `find_elementor_widgets` filter gains a `contains` operator (case-insensitive
   substring match against scalar settings), alongside the existing `eq|ne|in|nin
   |exists`. Useful for finding a widget by its content rather than exact-match 
   on a heading — e.g. `setting.editor.contains="some phrase"`. Scalar-only by design;
   never recurses into nested settings arrays. The `regex` operator is deferred 
   to a future release where it can get proper backtracking-DoS guards.
 * Docs: `get_posts`, `find_elementor_widgets`, and `resolve_url` descriptions now
   state the current scope honestly. `get_posts` notes that `post_type=any` expands
   to `[post, page]` and does NOT return `elementor_library`. `find_elementor_widgets`
   notes that templates are not yet scanned. Both point at the upcoming Layer 2 
   work.
 * Note on disclosure surface: the new fields (`condition_matched`, `template_type`,`
   effective_render_*`) widen what an authenticated caller can learn about site 
   structure on a per-URL basis. `resolve_url` continues to require only authentication(
   no capability check), unchanged from v1.7.x — Theme Builder shadowing was already
   disclosed per-URL since v1.5. The forthcoming template-listing tool (Layer 2)
   will gate full enumeration behind `edit_posts`.

#### 1.7.2

 * Fix: `create_media` no longer hard-rejects payloads that omit `source.type`. 
   v1.7.1 began requiring an explicit `type` field, breaking callers that worked
   under v1.6.x where the type was inferred from the presence of `source.data` (
   base64) or `source.url` (URL ingestion). Restored that inference — supply either
   field and the right mode is picked automatically. Explicit `type` continues to
   work and remains the documented preferred form.
 * Fix: URL ingestion no longer rejects the site’s own host. Plugins installed on`
   example.com` could not ingest `https://example.com/wp-content/uploads/foo.jpg`
   without manually adding `example.com` to the URL allowlist — the allowlist defends
   against fetching arbitrary external hosts, not the site’s own media library, 
   and forcing every admin to allowlist their own domain was the most common papercut
   on this tool. The site’s `home_url()` and `site_url()` host(s) are now implicitly
   trusted. The SSRF IP-resolution guard (`check_host_resolves_publicly`) still 
   runs in both branches, so the bypass only skips the manual allowlist; private/
   loopback/link-local IPs continue to be rejected.
 * Improved: `create_media` tool description rewritten to reflect real-world transport
   behaviour. Base64 is documented as suitable ONLY for tiny assets (favicons, sprite
   icons, ~4 KB of decoded image); larger JSON-RPC payloads are truncated by the
   MCP transport before reaching the plugin, which presents as the call hanging 
   silently. URL ingestion is named as the path for anything bigger, with explicit
   warnings that share/viewer pages (Google Drive share links, Dropbox `?dl=0`) 
   return HTML rather than the file. The previous “~100 KB” guidance was wrong in
   practice and trained agents to attempt uploads that would silently hang.
 * Improved: `file_too_large` errors on the base64 path now mention URL ingestion
   as the alternative, mirroring the helpful tone of the existing `url_source_disabled`
   error. The agent gets a clear next step instead of an opaque size-cap message.

#### 1.7.1

 * Fix: the four Media Uploads settings shipped in 1.7.0 (`iato_mcp_media_url_source_enabled`,`
   iato_mcp_media_url_host_allowlist`, `iato_mcp_media_max_upload_size`, `iato_mcp_media_upload_rate_limit`)
   silently failed to persist on Save. The UI rendered correctly and the fields 
   were properly registered with `register_setting()`, but the General-tab form 
   is hijacked through an admin-ajax handler (some hosts 503 on `options.php` POSTs
   due to upstream WAF/timeout rules) and that handler hardcoded the keys it persisted—
   anything not explicitly listed fell off the floor. 1.7.1 extends `ajax_save_settings()`
   to call the matching sanitize-and-update path for each of the four media keys,
   mirroring the existing `iato_mcp_api_key` / `iato_mcp_crawl_id` / `iato_mcp_tools`
   lines.
 * Docs: CLAUDE.md gains a “Release Checklist (adding a new admin setting)” section
   calling out the three places a new option must land — `register_setting()`, the
   rendered `<input name="...">`, and the AJAX persist handler. Same shape as the
   existing tool-release checklist; closes the structural failure mode that produced
   this 1.7.0  1.7.1 follow-up.

#### 1.7.0

 * New: `Settings > IATO MCP` now includes a Media Uploads card. The four media 
   settings (`iato_mcp_media_url_source_enabled`, `iato_mcp_media_url_host_allowlist`,`
   iato_mcp_media_max_upload_size`, `iato_mcp_media_upload_rate_limit`) were registered
   and enforced at runtime in 1.6.0 but never surfaced in admin UI, so the only 
   way to enable URL-source ingestion or configure the host allowlist was via WP-
   CLI or a direct database edit. The `url_source_disabled` error message returned
   by `create_media` continues to point admins to “Settings > IATO MCP > Media uploads”—
   that path now exists.
 * New: Diagnostics page gains a “Recent media uploads” panel showing the last 100`
   create_media` calls with their full per-phase trace, outcome badge, error code,
   attachment metadata (MIME, dimensions, size), and total duration. Each row expands
   inline via a native `<details>` element to show the phase-by-phase timing. Triage
   that previously required enabling `WP_DEBUG_LOG` mid-session and tailing the 
   host’s PHP error log is now one click on the Diagnostics tab. The on-disk `error_log()`
   mirror is preserved for environments that already aggregate logs centrally.
 * New: backing infrastructure — `IATO_MCP_Media_Phase_Log` class and `{prefix}iato_mcp_media_phase_log`
   table store one ring-buffered row per `create_media` call. The deferred-subsizes
   cron path threads its `req_id` through `wp_schedule_single_event` and appends
   an `async-subsizes-done` phase to the parent row on completion, so a single Diagnostics
   row captures the entire end-to-end timeline of an upload — including the async
   tick that lands minutes later. DB writes are wrapped in try/catch so an observability
   hiccup cannot regress `create_media` itself. Table creation is dbDelta-idempotent
   and runs from both the activation hook and the migration gate, so upgraded installs
   pick it up without a reactivation.
 * Improved: `create_media` tool description now includes practical guidance on 
   when to use base64 vs URL ingestion. Base64 is reliable for small assets (icons,
   badges, screenshots under ~100 KB); URL ingestion is the recommended path for
   production-scale photography and requires admin opt-in via the new Media Uploads
   settings card.
 * Fix: `uninstall.php` now drops the `iato_mcp_media_phase_log` table on plugin
   delete and cleans up `iato_mcp_db_version` plus the four `iato_mcp_media_*` options
   that the 1.6.0 release introduced without matching uninstall coverage.
 * Audit: the other four tools from the 1.6.0 batch (`set_featured_image`, `update_post_meta`,`
   get_post_meta`, `set_page_settings`) were audited for the same “admin-controlled
   toggle without UI” pattern that the `create_media` URL-source feature exhibited.
   None of them read any `iato_mcp_*` options at runtime — the missing-UI gap was
   isolated to `create_media`. No changes to those four were required.

#### 1.6.4

 * Fix: `create_media` now actually accepts uploads under Bearer-token MCP auth.
   The v1.6.0 implementation gated the handler on `current_user_can('upload_files')`
   and `current_user_can('edit_post', $attach_to_post)`, but Bearer-authenticated
   MCP requests don’t establish a logged-in WordPress user — `wp_get_current_user()`
   returns 0 and meta-cap checks against the empty user object always fail, so every
   call returned “You do not have permission to upload files.” regardless of who
   initiated it. Switched to `IATO_MCP_Auth::require_cap()`, which honors the documented“
   plugin key grants full administrative access” auth model — exactly the same fix
   shape v1.3.1 applied to `update_elementor_widgets_bulk` and `find_elementor_widgets`
   for the same bug class. Audited the other v1.6.0 tools (`get_post_meta`, `update_post_meta`,`
   set_page_settings`, `set_featured_image`) and confirmed they all use `require_cap()`
   correctly — `create_media` was the only regression.

#### 1.6.3

 * New: `create_media` accepts `defer_subsizes: true` to skip the synchronous `wp_generate_attachment_metadata`
   call and schedule it via WP-Cron instead. The MCP response returns immediately
   with `attachment_id` and the canonical URL; intermediate sizes are generated 
   on the next cron tick (typically within seconds). Recommended for any caller 
   running through the Anthropic MCP gateway, which times out around 30 seconds —
   sites with image-optimisation pipelines (ShortPixel, Imagify, Smush, etc.) intercepting
   the metadata-generation hook routinely exceed that limit and produce silent hangs
   where the response never arrives but the attachment also never lands. The default
   remains synchronous so existing callers see no behaviour change.
 * New: per-phase diagnostic logging in the `create_media` handler. Every call now
   writes `[iato-mcp create_media:<req_id>] phase=... elapsed=...s` lines to PHP’s
   error log at each stage (entry, source resolve, MIME check, dimension check, 
   sideload, attachment insert, subsize generation, return), including the byte 
   counts and dimensions seen. When a call hangs or fails, the last line in `wp-
   content/debug.log` (or the host’s PHP log) identifies which phase stalled — turning
   the previously-silent failure mode into a one-line diagnosis. The async cron 
   handler logs its own line on completion with attachment_id, subsize count, and
   duration.
 * Fix: `update_elementor_data` with `inherit_settings_from` now copies empty-string
   values from the source post instead of skipping them. WordPress’s `get_post_meta`
   returns `''` for both stored-empty and absent keys, so the prior skip-empty rule
   turned out to silently drop meaningful Astra layout state on real cloning workflows(
   Astra stores per-post overrides as empty strings in some configurations, and 
   skipping them caused targets to retain the wrong layout). The contract of `inherit_settings_from`
   is “make the target match the source”; that now happens uniformly.
 * Fix: the default `inherit_keys` list on `update_elementor_data` is widened from
   8 keys to 14 to cover the full Astra per-post override family (`site-content-
   style`, `site-sidebar-style`, `ast-global-header-display`, `ast-banner-title-
   visibility`, `ast-breadcrumbs-content`, `ast-featured-img`). Cloning a styled
   post now transfers the complete layout state in a single call, not just the original
   4 brief-flagged keys.
 * Refactor: `inherited_skipped[]` in the `update_elementor_data` response semantics
   changed from “source value was empty” to “source value matches target’s existing
   value (no-op write)”. Each entry now uses `reason: 'noop'`. The new shape surfaces
   the case where inherit_settings_from would have written a value but the target
   already had it — useful diagnostic, no behavioural cost.

#### 1.6.2

 * Refactor: the four hand-written `version_compare` migration blocks that backfill
   new tool names into the saved `iato_mcp_tools` option are replaced by a single
   declarative `TOOL_MIGRATION_BACKFILL` map on `IATO_MCP_Settings` plus a one-loop
   walker in `iato_mcp_maybe_run_migrations()`. Same behavior for every install 
   that was already correctly migrated; the new shape eliminates the “remembered
   to add a migration block” failure mode that produced the 1.3.0  1.3.1 fix, the
   1.4.0  1.4.5 fix, and the 1.6.0  1.6.1 fix. Adding a new tool now requires appending
   one line to the map alongside the `TOOL_NAMES` edit — colocated, hard to miss
   at review time.
 * Fix: backfills the three crawl-management tools (`start_iato_crawl`, `get_iato_crawl_status`,`
   list_iato_crawls`) on installs that originally upgraded from 1.1.x to 1.2.x with
   a saved `iato_mcp_tools` option. v1.2.0 introduced those tools but shipped without
   the migration to append them, so any user who had saved their per-tool toggles
   in 1.1.x and configured an IATO API key has had the crawl-management tools invisibly
   disabled for the entire 1.2  1.6 interval. The 1.6.2 backfill catches them automatically
   on first request after upgrade. No-op for installs that already have the names
   in the saved option (idempotent), and no-op for fresh installs (the option starts
   empty and every tool is enabled by default).
 * Fix: `update_elementor_data` with `inherit_settings_from` now returns an `inherited_skipped[]`
   array in the response listing keys that were in the configured inheritance list
   but absent on the source post (so the assistant can see which clone targets had
   no source value rather than silently getting fewer receipts than the default 
   list implies). Each entry is `{ key, reason: 'source_empty' }`. The skip-empty
   behavior itself is unchanged — copying an explicit empty string from a source
   post that never set a key would stomp the target’s existing value.

#### 1.6.1

 * Fix: the five new MCP tools added in 1.6.0 (`get_post_meta`, `update_post_meta`,`
   set_page_settings`, `set_featured_image`, `create_media`) now register correctly
   on sites upgrading from a previous version. v1.6.0 added them to the `TOOL_NAMES`
   constant but forgot the idempotent migration that appends new tool names to the
   saved `iato_mcp_tools` per-tool toggle option — the same migration shape used
   for the Elementor v2 tools in 1.3.5 and for `rollback` in 1.4.0/1.4.5. Without
   it, `is_tool_enabled()` filtered the new names out of the registry on every upgraded
   install, so the tools never appeared in `tools/list` despite shipping in the 
   plugin. New installs were unaffected (the option is empty on first activation
   and all tools are enabled by default). Single one-shot migration; no-op for installs
   that already have the tool names in the saved option.

#### 1.6.0

 * New: `get_post_meta` and `update_post_meta` expose arbitrary post meta over MCP
   with a centralised security policy. A credential-shaped denylist (`*_token*`,`*
   _secret*`, `*_api_key*`, `*_password*`, `*_credential*`, `_oauth_*`, `_jwt_*`,`
   _refresh_token_*`, plus `wp_capabilities` and friends) is hard-rejected on writes
   and redacted on reads — `force=true` cannot override it. A known-safe allowlist
   of theme/builder/SEO prefixes (Astra `site-`/`ast-`, Elementor `_elementor_`,
   Yoast/RankMath/SEOPress, Kadence, GeneratePress, Genesis, plus `_wp_page_template`
   and `_thumbnail_id`) lets the assistant write the common cases without ceremony;
   anything outside both lists requires `force=true`. Every write emits a `change_receipt`
   rollback-able under the new `target_type=post_meta`. Closes the long-standing
   gap that left the assistant unable to touch per-post theme settings on Astra 
   and similar themes.
 * New: `set_page_settings` is a one-call convenience wrapper for the most common
   page-level settings cluster on Astra + Elementor sites. Pass abstract names like`
   hide_title: true`, `sidebar_layout: "no-sidebar"`, `content_layout: "page-builder"`,`
   disable_header`, `disable_footer`, `page_template`, or `elementor_page_settings`
   and the tool maps each to the right concrete meta key for the active theme. Astra-
   specific keys are silently skipped on non-Astra themes and surfaced in `skipped[]`
   so the agent can report them back to the user. Returns one `change_receipt` per
   concrete meta key written, so the whole settings cluster is reversible.
 * New: `set_featured_image` finally closes the “create a post end-to-end” loop —
   the assistant can now set or clear `_thumbnail_id` directly instead of bouncing
   the user to wp-admin. Validates that the supplied attachment is an image, captures
   the previous thumbnail ID in the receipt, and rolls back via the same `post_meta`
   target_type.
 * New: `create_media` uploads new images to the media library. Two source modes:`
   base64` (default and recommended — the WordPress server never makes an outbound
   HTTP request on agent input) and `url` (default-disabled; admins must explicitly
   enable it and add hosts to an allowlist before any fetch is attempted). The URL
   path runs full SSRF guards: DNS resolution + private/loopback/link-local/cloud-
   metadata IP rejection, hard timeout, redirect cap, and re-validation of every
   redirect destination’s resolved IP. MIME is verified against actual file bytes(
   never the claimed mime_type), filenames containing `.php`, `.phtml`, `.phar`,
   or `.htaccess` are rejected, and SVG is hard-rejected this release regardless
   of how the upload is presented. Size (default 10MB) and dimension (default 8000
   ×8000) caps are configurable; per-user rate limit (default 20/min) is enforced
   via a transient. Successful uploads return the attachment ID, public URL, generated
   intermediate sizes, and a `change_receipt` under the new `target_type=attachment`—
   rollback fully deletes the attachment file via `wp_delete_attachment(force=true)`.
 * New: `update_elementor_data` gains `inherit_settings_from: <post_id>` and optional`
   inherit_keys` parameters. When set, the tool copies a curated default set of 
   theme + Elementor page-level meta keys (`site-post-title`, `site-sidebar-layout`,`
   site-content-layout`, `ast-main-header-display`, `footer-sml-layout`, `_wp_page_template`,`
   _elementor_page_settings`, `_elementor_template_type`) from the source post to
   the target in the same MCP call, returning one `change_receipt` per inherited
   key in `change_receipts[]`. Collapses the “clone the styling of an existing post”
   workflow from four or more tool calls to one — and means the new post no longer
   renders with the wrong theme title bar above the Elementor content.
 * New: four new admin settings under Settings > IATO MCP control upload behaviour—`
   iato_mcp_media_url_source_enabled` (default off), `iato_mcp_media_url_host_allowlist`(
   one host per line), `iato_mcp_media_max_upload_size` (bytes; default 10MB), and`
   iato_mcp_media_upload_rate_limit` (per-user per-minute; default 20). Existing
   installs upgrade with secure defaults — URL ingestion stays off until explicitly
   enabled.

#### 1.5.0

 * New: `update_post` accepts a `slug` parameter to rename a post’s URL slug via
   MCP — previously the agent had no way to update a slug and the user had to do
   it manually in the WP editor. Input is strictly validated (lowercase a-z 0-9 
   and hyphens only, no leading/trailing/double hyphens, max 200 chars, must survive
   a `sanitize_title()` round-trip unchanged) and conflicts return a `slug_conflict`
   error with the colliding post’s ID and title rather than silently appending `-
   2` like WordPress would. Changing the slug of a non-draft post additionally requires`
   confirm_url_break: true` since it breaks every inbound link — drafts are exempt.
   Slug changes are rollback-able the same way as title/content/status edits: each
   change emits a `change_receipt` and can be reversed via the `rollback` tool.
 * New: `create_post` and `update_post` responses now include a `notice` field on
   page-builder-driven sites (Elementor, Divi, WPBakery, Beaver Builder) when the
   call would produce content that doesn’t match the site’s existing post format.
   On Elementor the notice tells the agent to fetch a reference post via `get_post`
   + `get_elementor_data` and apply its structure via `update_elementor_data`; on
   Divi/WPBakery/Beaver it tells the agent the layout must be finished in WP admin.
   On Gutenberg-only sites the field is absent — vanilla installs see no spurious
   warnings. Closes the gap that left agents creating posts with plain HTML on Elementor
   sites, producing structurally orphaned drafts that looked nothing like the rest
   of the site.
 * New: the dynamic instructions injected into the MCP `initialize` response (added
   in 1.4.8) now include a NEW-POST WORKFLOW block whenever a non-Gutenberg builder
   is active. The block primes the agent to (1) ask the user for a reference post
   URL before calling `create_post`, (2) fetch the reference’s structure, and (3)
   port that structure onto the new post — so the right path happens on the first
   call, not after the user notices the formatting problem.

#### 1.4.10

 * Fix: the JSON config snippets emitted by the plugin (setup wizard Method 3, dismissible“
   Ready to Connect” notice, Settings hero card) now use a unique-per-site inner`
   mcpServers` key derived from the WordPress site’s hostname (e.g. `iato-garennebigby-
   dev`, `iato-dynomapper-com`) instead of the hardcoded `iato-wordpress`. Agencies
   managing multiple WordPress installs from a single AI client (Claude Desktop,
   Claude Code, etc.) can now paste config snippets from many IATO MCP installs 
   into the same client config file without one silently overwriting another (JSON
   object keys are unique, so two snippets sharing a key was a silent collision).
   Existing connections that were set up with the old `iato-wordpress` key continue
   to work — the inner key is a display name only, not part of any HTTP request —
   so no migration is needed.

#### 1.4.9

 * Docs: added the plugin demo video to the top of the Description section on the
   WordPress.org plugin page (auto-embedded by WordPress.org’s readme renderer when
   a YouTube URL is on its own line). No code changes; safe to skip if you’ve already
   updated to 1.4.8.

#### 1.4.8

 * New: dynamic page-builder-aware server instructions injected into the MCP `initialize`
   response. The plugin now detects which page-builder plugins are active on the
   WordPress site (Elementor, Divi, WPBakery, Beaver Builder, Gutenberg) and emits
   a context-specific instruction string telling the AI agent which write tools 
   are correct for which builder, with a mandatory `get_page_builder` check-first
   rule before any content edit. Closes a class of silent-failure bug where `update_post`
   on an Elementor-built post would succeed at the database level but never reach
   the frontend (because Elementor stores content in `_elementor_data`, not `post_content`).
   Detected-but-unsupported builders (Divi, WPBakery, Beaver Builder for writes)
   are explicitly flagged so the agent tells the user to edit in the WP admin instead
   of attempting a write that won’t take effect. Uses the standard MCP `instructions`
   field added in spec rev 2025-03-26; older clients on 2024-11-05 cleanly ignore
   the unknown field.
 * New: `get_page_builder` now detects Beaver Builder posts (via `_fl_builder_enabled`
   post meta) and returns `beaver-builder`. Previously these posts fell through 
   to the `gutenberg` or `classic` branch, misleading the agent about how to handle
   them.

#### 1.4.7

 * Fix: Settings  IATO MCP no longer presents the IATO Platform and Crawl Management
   tool toggles as functional when no IATO API key is configured. Previously the
   checkboxes appeared enabled and saveable, but bridge tool registration is gated
   by a separate condition at `iato-mcp.php:85` (the bridge tool files only `require_once`
   when the API key is non-empty), so the toggles were placebo — a user could check
   every box, save, and still get `Unknown tool: get_iato_sitemap` on every call
   with no UI signal explaining why. The toggle inputs in those two categories are
   now `disabled` when the API key is empty, the category card grays out (55% opacity),
   and an inline banner under the heading explains: “These tools require an IATO
   API key. Add it under ‘IATO Platform’ above to enable them — until then, these
   toggles have no effect.” When the user pastes an API key and saves, the categories
   become interactive again.

#### 1.4.6

 * Fix: `rollback` now appears as a checkbox on the Settings  IATO MCP page (under
   a new “Safety” category). v1.4.5 added rollback to the `TOOL_NAMES` constant —
   which fixed the sanitize-strip behavior — but the Settings UI rendering loop 
   iterates a separate constant, `TOOL_CATEGORIES`, which also needed rollback added.
   Without the category entry, the checkbox was never rendered. Adding `'Safety'
   => ['rollback']` closes the gap.
 * Polish: unified the inner `mcpServers` server key shown in the Settings page 
   hero card config snippet from `wordpress` to `iato-wordpress`, matching the dismissible
   setup notice. Cosmetic only — the inner key is a user-facing display name they
   can rename — but eliminates an unnecessary inconsistency between the two snippets.

#### 1.4.5

 * Fix: `rollback` tool now appears in the Settings  IATO MCP per-tool toggle list,
   and the Settings save no longer silently strips it from `iato_mcp_tools`. When
   v1.4.0 added the rollback MCP tool, the developer forgot to add it to the `TOOL_NAMES`
   constant in `class-settings.php`. Consequence: no UI checkbox for it, and `sanitize_tools()`(
   which `array_intersect`s saved values against TOOL_NAMES) was stripping it from
   existing installs every time a user clicked Save Settings. Once stripped, `is_tool_enabled('
   rollback')` returned false and the tool stopped registering. Adding rollback 
   to TOOL_NAMES fixes both the UI and the strip behavior.
 * Fix: idempotent migration restores `rollback` to `iato_mcp_tools` for any install
   where it had been stripped by the previous bug. Runs once on plugin upgrade, 
   no-op for installs that didn’t lose it.
 * Fix: `capabilities.rollback` in the `initialize` response now reflects actual
   tool registration instead of being hardcoded `true`. Previously, an install with
   rollback disabled (manually or via the strip bug above) would advertise `rollback:
   true` in capabilities, causing clients that feature-detect to attempt rollback
   calls that returned `tool_not_found`.

#### 1.4.4

 * Fix: clicking Approve on the OAuth consent screen no longer redirects users to/
   wp-admin instead of back to the OAuth client. The handler at `class-oauth.php:
   181` was using `wp_safe_redirect()` for the post-approval callback, but `wp_safe_redirect`
   silently rewrites any URL whose host isn’t on WordPress’s `allowed_redirect_hosts`
   allowlist to `admin_url()` — which means every external OAuth callback (claude.
   ai, cursor.sh, etc.) was being silently rewritten to /wp-admin/, leaving the 
   connector stuck on “Connect” because the client never received an authorization
   code. Switched to `wp_redirect()`, which is the correct primitive for OAuth callbacks(
   the protocol requires an external redirect by design).
 * Fix: the not-logged-in branch of the authorize handler at `class-oauth.php:132`
   was passing `$_SERVER['REQUEST_URI']` through `sanitize_text_field()` before 
   building the post-login redirect URL. `sanitize_text_field` strips `%XX` percent-
   encoded sequences as an HTML-entity defense, which mangled the inner `redirect_uri`
   parameter (every `:` and `/` removed) and broke the post-login bounce back to/
   oauth/authorize. Now uses `wp_unslash` only, which is correct for a server-set
   value used as a redirect target.
 * Hardening: `/oauth/authorize` now refuses requests whose `client_id` isn’t registered
   via the dynamic client registration endpoint at `/oauth/register`. Previously
   the redirect_uri allowlist was opt-in (validated only when the client_id existed
   in the registered set) — after the wp_redirect change above lets external redirects
   through, that opt-in shape was an open-redirect surface. Spec-compliant clients(
   Claude, Cursor, etc.) already register before authorize, so this is a no-op for
   them.
 * Fix: `initialize` now echoes the client’s requested `protocolVersion` when it’s
   one we recognize (`2024-11-05`, `2025-03-26`, `2025-06-18`) instead of always
   returning `2024-11-05`. Falls back to `2025-06-18` for unknown requests. Forward-
   compat for clients on newer MCP revs.

#### 1.4.3

 * Fix: dismissible “MCP — Ready to Connect” admin notice restructured. The previous“
   1. Copy / 2. Open Claude Desktop / 3. (Optional) IATO key” framing implied a 
   sequential three-step flow, but Step 1’s snippet and Step 2’s “Or use Add Custom
   Connector” sub-line were actually two mutually-exclusive connection methods, 
   and Step 3 was unrelated optional setup. Notice now leads with the endpoint URL(
   with its own Copy button), then presents Option A (Connectors UI / OAuth, recommended)
   and Option B (Claude Desktop config file with the mcp-remote stdio snippet) as
   clearly-labeled alternatives separated by an “— or —” divider, with the IATO 
   API key and “see the setup wizard for other clients” line moved to a non-numbered
   footer. Same content, structure no longer suggests dependence between the two
   paths.

= 1.4.2 …

## Мета

 *  Нуска **1.8.2**
 *  Акыркы жаңыртуу **21 саат мурун**
 *  Активдүү орнотуулар **Fewer than 10**
 *  WordPress нускасы ** 6.2 же андан жогору **
 *  Tested up to **6.9.4**
 *  PHP нускасы ** 8.0 же андан жогору **
 *  Тил
 * [English (US)](https://wordpress.org/plugins/iato-mcp/)
 * Тег:
 * [AI](https://ky.wordpress.org/plugins/tags/ai/)[Claude](https://ky.wordpress.org/plugins/tags/claude/)
   [mcp](https://ky.wordpress.org/plugins/tags/mcp/)[seo](https://ky.wordpress.org/plugins/tags/seo/)
   [sitemap](https://ky.wordpress.org/plugins/tags/sitemap/)
 *  [Advanced View](https://ky.wordpress.org/plugins/iato-mcp/advanced/)

## Рейтинг

Азырынча эч кандай сын-пикир жок.

[Your review](https://wordpress.org/support/plugin/iato-mcp/reviews/#new-post)

[See all reviews](https://wordpress.org/support/plugin/iato-mcp/reviews/)

## Мүчөлөрү

 *   [ iatoai ](https://profiles.wordpress.org/iatoai/)

## Колдоо

Комментарийлер барбы? Жардам керекпи?

 [Колдоо форумун көрүү](https://wordpress.org/support/plugin/iato-mcp/)