Сүрөттөө
WordPress.com has a built-in MCP server. Now self-hosted WordPress does too.
IATO 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
- Install and activate the plugin
- Follow the setup wizard — copy the config into Claude Desktop, or use “Add Custom Connector” with your site URL
- Connect your IATO account for AI-powered analysis (free trial 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 therollbacktool and the original value is restored
With an IATO account (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) — 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.
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.
Скриншоттор
Орнотуу
- Upload the plugin files to
/wp-content/plugins/iato-mcp/or install via the WordPress plugin directory - Activate the plugin via the Plugins menu in WordPress
- Follow the setup wizard that appears — it provides the JSON config for Claude Desktop
- In Claude Desktop, either paste the JSON config or use “Add Custom Connector” and enter your site URL
- 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.
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 (free trial 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.
-
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/messageand are validated byclass-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_filescapability. Thecreate_mediatool 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 achange_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.
МүчөлөрүTranslate “IATO MCP” into your language.
Interested in development?
Browse the code, check out the SVN repository, or subscribe to the development log by RSS.
Өзгөртүүлөр
1.8.2
- Fix:
get_site_settingsno longer corruptspermalink_structure,title, ortagline. The five-field tool was wrapping every value insanitize_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%xxcontent, 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_fieldsate the%xxprefixes 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)
titleandtagline— broader behavior change. These now return the raw stored value, not thesanitize_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_emailandtimezone— unchanged. Explicit decision: these value types don’t legitimately carry%xxin 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_fieldis WP-canonical for these and stays.
- (a)
- Downstream: v1.8.1’s archive-detection path reads
category_base/tag_basedirectly viaget_option()inside the router (not via this MCP tool) and is unaffected. No internal callers ofget_site_settingsoutput exist in the codebase. External MCP callers now receive faithful DB values for the three fixed fields.
1.8.1
- Fix:
resolve_urlcorrectly identifies Elementor Theme Builder archive templates as the renderer for Yoast-stripped category URLs (e.g./build/instead of/category/build/). Previously such URLs returnedroute_type=404even 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 customcategory_base/tag_baseconfigured 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 throughget_term_link()that goes through any activeterm_linkfilter (Yoast, RankMath, etc.) and matches the resulting URL against the input. - Shadowing-dispatch bug.
detect_theme_builder_shadowingtreated Elementor Pro’sfind_via_theme_builder_modulefalsereturn (Pro loaded butget_documents_for_locationmatched nothing) as authoritative, returning thatfalsedirectly and never reaching thefind_via_conditions_metafallback. In REST context — which is 100% of MCP traffic —get_documents_for_locationis 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 fromif ( null !== $found )toif ( is_array( $found ) )so bothfalseandnullfall 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.
- Detect_archive_info gap. The URL classifier only recognised the default
- Bonus: the dispatch fix also restores singular page shadowing detection on Pro-installed sites.
get_post(id, include_shadowing:true)now correctly surfacesis_shadowed_bywhen an Elementor Theme Builder single template overrides the slug-based render. Same dispatch bug had been blocking this since v1.7.x. rendering_post_idsemantics UNCHANGED from v1.7.x. v1.8.0’s additiveresolve_urlcontract 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
.htaccessrewrites without using theterm_linkfilter won’t be detected by Stage 3 ofdetect_archive_info. Rare; rely on aterm_link-based stripper (Yoast, RankMath, etc.) until a workaround is needed.
1.8.0
- Fix:
resolve_urlnow 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 anelementor_librarydocument) previously returnedroute_type=404because the conditions evaluator only matchedinclude/singular/...patterns againsturl_to_postid()‘s post ID — which is 0 on archives. The conditions parser now evaluatesinclude/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_modulealso captures the matching location intotemplate_type. The condition string that fired is surfaced astemplate.condition_matchedfor callers who need to see the match logic. - New:
resolve_urlresponse gains four additive fields.rendering_post_idsemantics are UNCHANGED — still the canonical/slug-based post (now normalized tonullinstead of0on archives so=== nullchecks 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,nullonly on a true 404),effective_render_post_type,shadowed_route_type(route the URL would have had absent the template), and a structuredtemplate{template_id,template_type,condition_matched,builder}object present when shadowing applies.rendering_template_idcontinues to work exactly as documented; the new fields are additive only. - New:
find_elementor_widgetsauto-resolves revision IDs to their parent post. Passing a revision ID viapost_idspreviously returned matches tagged with the revision’s own ID, leaking the parent only through theNNN-revision-vNslug — the brute-force discovery path that motivated this work. Revision IDs are now mapped to their parent viawp_is_post_revision(), deduped, and each match scanned from a revision input carriesresolved_from_revision_idso callers can see the mapping. Default auto-scan also tightenspost_statusfromanyto[publish, draft, pending, private]— trash and auto-draft are no longer scanned by default. - New:
find_elementor_widgetsfilter gains acontainsoperator (case-insensitive substring match against scalar settings), alongside the existingeq|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. Theregexoperator is deferred to a future release where it can get proper backtracking-DoS guards. - Docs:
get_posts,find_elementor_widgets, andresolve_urldescriptions now state the current scope honestly.get_postsnotes thatpost_type=anyexpands to[post, page]and does NOT returnelementor_library.find_elementor_widgetsnotes 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_urlcontinues 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 behindedit_posts.
1.7.2
- Fix:
create_mediano longer hard-rejects payloads that omitsource.type. v1.7.1 began requiring an explicittypefield, breaking callers that worked under v1.6.x where the type was inferred from the presence ofsource.data(base64) orsource.url(URL ingestion). Restored that inference — supply either field and the right mode is picked automatically. Explicittypecontinues to work and remains the documented preferred form. - Fix: URL ingestion no longer rejects the site’s own host. Plugins installed on
example.comcould not ingesthttps://example.com/wp-content/uploads/foo.jpgwithout manually addingexample.comto 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’shome_url()andsite_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_mediatool 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_largeerrors on the base64 path now mention URL ingestion as the alternative, mirroring the helpful tone of the existingurl_source_disablederror. 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 withregister_setting(), but the General-tab form is hijacked through an admin-ajax handler (some hosts 503 onoptions.phpPOSTs 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 extendsajax_save_settings()to call the matching sanitize-and-update path for each of the four media keys, mirroring the existingiato_mcp_api_key/iato_mcp_crawl_id/iato_mcp_toolslines. - 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 MCPnow 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. Theurl_source_disablederror message returned bycreate_mediacontinues 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_mediacalls 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 enablingWP_DEBUG_LOGmid-session and tailing the host’s PHP error log is now one click on the Diagnostics tab. The on-diskerror_log()mirror is preserved for environments that already aggregate logs centrally. - New: backing infrastructure —
IATO_MCP_Media_Phase_Logclass and{prefix}iato_mcp_media_phase_logtable store one ring-buffered row percreate_mediacall. The deferred-subsizes cron path threads itsreq_idthroughwp_schedule_single_eventand appends anasync-subsizes-donephase 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 regresscreate_mediaitself. 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_mediatool 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.phpnow drops theiato_mcp_media_phase_logtable on plugin delete and cleans upiato_mcp_db_versionplus the fouriato_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 thecreate_mediaURL-source feature exhibited. None of them read anyiato_mcp_*options at runtime — the missing-UI gap was isolated tocreate_media. No changes to those four were required.
1.6.4
- Fix:
create_medianow actually accepts uploads under Bearer-token MCP auth. The v1.6.0 implementation gated the handler oncurrent_user_can('upload_files')andcurrent_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 toIATO_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 toupdate_elementor_widgets_bulkandfind_elementor_widgetsfor 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 userequire_cap()correctly —create_mediawas the only regression.
1.6.3
- New:
create_mediaacceptsdefer_subsizes: trueto skip the synchronouswp_generate_attachment_metadatacall and schedule it via WP-Cron instead. The MCP response returns immediately withattachment_idand 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_mediahandler. Every call now writes[iato-mcp create_media:<req_id>] phase=... elapsed=...slines 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 inwp-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_datawithinherit_settings_fromnow copies empty-string values from the source post instead of skipping them. WordPress’sget_post_metareturns''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 ofinherit_settings_fromis “make the target match the source”; that now happens uniformly. - Fix: the default
inherit_keyslist onupdate_elementor_datais 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 theupdate_elementor_dataresponse semantics changed from “source value was empty” to “source value matches target’s existing value (no-op write)”. Each entry now usesreason: '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_comparemigration blocks that backfill new tool names into the savediato_mcp_toolsoption are replaced by a single declarativeTOOL_MIGRATION_BACKFILLmap onIATO_MCP_Settingsplus a one-loop walker iniato_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 theTOOL_NAMESedit — 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 savediato_mcp_toolsoption. 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_datawithinherit_settings_fromnow returns aninherited_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 theTOOL_NAMESconstant but forgot the idempotent migration that appends new tool names to the savediato_mcp_toolsper-tool toggle option — the same migration shape used for the Elementor v2 tools in 1.3.5 and forrollbackin 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 intools/listdespite 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_metaandupdate_post_metaexpose arbitrary post meta over MCP with a centralised security policy. A credential-shaped denylist (*_token*,*_secret*,*_api_key*,*_password*,*_credential*,_oauth_*,_jwt_*,_refresh_token_*, pluswp_capabilitiesand friends) is hard-rejected on writes and redacted on reads —force=truecannot override it. A known-safe allowlist of theme/builder/SEO prefixes (Astrasite-/ast-, Elementor_elementor_, Yoast/RankMath/SEOPress, Kadence, GeneratePress, Genesis, plus_wp_page_templateand_thumbnail_id) lets the assistant write the common cases without ceremony; anything outside both lists requiresforce=true. Every write emits achange_receiptrollback-able under the newtarget_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_settingsis a one-call convenience wrapper for the most common page-level settings cluster on Astra + Elementor sites. Pass abstract names likehide_title: true,sidebar_layout: "no-sidebar",content_layout: "page-builder",disable_header,disable_footer,page_template, orelementor_page_settingsand 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 inskipped[]so the agent can report them back to the user. Returns onechange_receiptper concrete meta key written, so the whole settings cluster is reversible. - New:
set_featured_imagefinally closes the “create a post end-to-end” loop — the assistant can now set or clear_thumbnail_iddirectly 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 samepost_metatarget_type. - New:
create_mediauploads 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) andurl(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.htaccessare 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 achange_receiptunder the newtarget_type=attachment— rollback fully deletes the attachment file viawp_delete_attachment(force=true). - New:
update_elementor_datagainsinherit_settings_from: <post_id>and optionalinherit_keysparameters. 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 onechange_receiptper inherited key inchange_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), andiato_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_postaccepts aslugparameter 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 asanitize_title()round-trip unchanged) and conflicts return aslug_conflicterror with the colliding post’s ID and title rather than silently appending-2like WordPress would. Changing the slug of a non-draft post additionally requiresconfirm_url_break: truesince it breaks every inbound link — drafts are exempt. Slug changes are rollback-able the same way as title/content/status edits: each change emits achange_receiptand can be reversed via therollbacktool. - New:
create_postandupdate_postresponses now include anoticefield 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 viaget_post+get_elementor_dataand apply its structure viaupdate_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
initializeresponse (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 callingcreate_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
mcpServerskey derived from the WordPress site’s hostname (e.g.iato-garennebigby-dev,iato-dynomapper-com) instead of the hardcodediato-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 oldiato-wordpresskey 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
initializeresponse. 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 mandatoryget_page_buildercheck-first rule before any content edit. Closes a class of silent-failure bug whereupdate_poston an Elementor-built post would succeed at the database level but never reach the frontend (because Elementor stores content in_elementor_data, notpost_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 MCPinstructionsfield added in spec rev 2025-03-26; older clients on 2024-11-05 cleanly ignore the unknown field. - New:
get_page_buildernow detects Beaver Builder posts (via_fl_builder_enabledpost meta) and returnsbeaver-builder. Previously these posts fell through to thegutenbergorclassicbranch, 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 onlyrequire_oncewhen the API key is non-empty), so the toggles were placebo — a user could check every box, save, and still getUnknown tool: get_iato_sitemapon every call with no UI signal explaining why. The toggle inputs in those two categories are nowdisabledwhen 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:
rollbacknow appears as a checkbox on the Settings IATO MCP page (under a new “Safety” category). v1.4.5 added rollback to theTOOL_NAMESconstant — 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
mcpServersserver key shown in the Settings page hero card config snippet fromwordpresstoiato-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:
rollbacktool now appears in the Settings IATO MCP per-tool toggle list, and the Settings save no longer silently strips it fromiato_mcp_tools. When v1.4.0 added the rollback MCP tool, the developer forgot to add it to theTOOL_NAMESconstant inclass-settings.php. Consequence: no UI checkbox for it, andsanitize_tools()(whicharray_intersects 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
rollbacktoiato_mcp_toolsfor 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.rollbackin theinitializeresponse now reflects actual tool registration instead of being hardcodedtrue. Previously, an install with rollback disabled (manually or via the strip bug above) would advertiserollback: truein capabilities, causing clients that feature-detect to attempt rollback calls that returnedtool_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:181was usingwp_safe_redirect()for the post-approval callback, butwp_safe_redirectsilently rewrites any URL whose host isn’t on WordPress’sallowed_redirect_hostsallowlist toadmin_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 towp_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:132was passing$_SERVER['REQUEST_URI']throughsanitize_text_field()before building the post-login redirect URL.sanitize_text_fieldstrips%XXpercent-encoded sequences as an HTML-entity defense, which mangled the innerredirect_uriparameter (every:and/removed) and broke the post-login bounce back to /oauth/authorize. Now useswp_unslashonly, which is correct for a server-set value used as a redirect target. - Hardening:
/oauth/authorizenow refuses requests whoseclient_idisn’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:
initializenow echoes the client’s requestedprotocolVersionwhen it’s one we recognize (2024-11-05,2025-03-26,2025-06-18) instead of always returning2024-11-05. Falls back to2025-06-18for 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 …




