Mcp Gotchas¶
MCP Site Resets to Default After Restart¶
From legacy section: WordPress / MCP
Pattern: MCP site resets to default after a session restart, causing page creation to land on the wrong site. Rule: Always call
select_site [site-name]explicitly before any create/update/delete operation — even if you selected it earlier in the session. After a restart, assume the active site is wrong until confirmed. Date: 2026-03-01
sites.json Username Must Match Actual WP User¶
From legacy section: WordPress MCP / sites.json
Pattern: crlmag and flexpresents had
"username": "evolve"in sites.json but that user didn't exist on either site. The actual admin wasevolvellc. This caused 401/403 errors that looked like credential or permission issues. Rule: When adding a site to sites.json, verify the username exists viawp user listbefore saving. The 3 valid admin usernames across Evolve sites are:evolvellc,evolveseo, andjim@evolvebusiness.com. Date: 2026-04-22
WP MCP update_post Has No Date Parameter¶
From legacy section: WordPress MCP — Blog Scheduling
Pattern: Tried to schedule posts for future dates via
update_postwithstatus: "future". The API accepted the status but published immediately because there's nodateparameter to set a future date. Rule: The WordPress MCPupdate_posttool cannot schedule posts — it has nodatefield. Leave posts as drafts and schedule publish dates manually in WP admin, or write a WP-CLI batch script to set dates. Date: 2026-04-07
WP MCP SEO Tools Are SiteSEO-Only¶
From legacy section: WordPress MCP — Blog Scheduling
Pattern:
update_seo_metadataandget_seo_metadatareturned 404 on evolvebusiness.com. The CommunityTech plugin's SEO endpoints are hardcoded to/siteseo/— incompatible with The SEO Framework. Rule: The WordPress MCP's SEO tools (update_seo_metadata,get_seo_metadata,audit_seo) only work with SiteSEO. For TSF sites, set meta via WP admin or WP-CLI (update_post_metawith_genesis_title,_genesis_description). Date: 2026-04-07
WordPress MCP deactivate_plugin May Hit Wrong Site¶
From legacy section: Schema Deployment
Pattern: Called
mcp__wordpress__deactivate_pluginfor Schema Pro on evolvebusiness.com afterselect_site. The API returned success but the_linksURL showed iclawny.com — Schema Pro was still active on evolvebusiness.com. Wasted time debugging "ghost" LocalBusiness + Corporation output until we checked via WP-CLI. Rule: For any destructive operation (deactivate plugin, update options, delete meta), use WP-CLI via SSH with explicit--path=:wp --path=/home/u488157871/domains/DOMAIN/public_html plugin deactivate PLUGIN. The WordPress MCP is fine for reads but unreliable for site-targeted writes. Date: 2026-04-16
update_post_meta via WP-CLI eval Double-Escapes Large JSON¶
From legacy section: Elementor / WordPress MCP
Pattern: Cloned
_elementor_data(231KB JSON) from post 1264 to new pages usingupdate_post_meta()insidewp eval '...'. The result was double-escaped JSON that broke Elementor parsing (get_page_structurereturned "Failed to parse _elementor_data"). Same issue would have corrupted the clone silently. Rule: For cloning large JSON meta values (_elementor_data,_evolve_schema, etc.), use raw DB inserts via$wpdb->insert()to bypass WordPress serialization/escaping. Write aneval-filescript that deletes then inserts the meta row directly. Reference:build/clone-elementor-data.php. Don't usewp evalwithupdate_post_metafor anything over a few hundred bytes of JSON. Date: 2026-04-16
update_post_meta + wp_json_encode Needs wp_slash() Wrapper¶
From legacy section: Elementor / WordPress MCP
Pattern: Deploying JSON-LD schema with
update_post_meta($id, '_evolve_schema', wp_json_encode($schema))insidewp eval-filescript silently strips escaped quotes (\"→") from embedded strings. FAQ answers containing quoted phrases like"Albany personal injury attorney"produced stored JSON thatwp post meta getdisplays "correctly" but failsjson.loads()and breaks the schema mu-plugin's rendering. Rule:update_post_meta()applieswp_unslash()internally (WP magic-quotes legacy). Always wrap encoded JSON withwp_slash()before storing:This pairs with the double-escape rule above:$json = wp_json_encode( $schema, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ); update_post_meta( $post_id, '_evolve_schema', wp_slash( $json ) );wp eval(inline, shell-escaped input) → different behavior thanwp eval-file(file input, no shell escaping). Forwp eval-filewithupdate_post_meta, usewp_slash(). Forwp evalinline with large JSON, use$wpdb->insert()to bypass WP entirely. Date: 2026-04-24
WordPress MCP create_menu_item Uses menus Not menu_id¶
From legacy section: Elementor / WordPress MCP
Pattern:
list_menu_itemsandcreate_menu_itemboth require the parameter namedmenus(plural) — NOTmenu_id. Usingmenu_idreturns a validation error. Also:menusmust be a number, not string. Rule: For WordPress menu MCP tools, passmenus: 2(number). For child menu items,parent: 1311targets the parent menu item ID (not the page ID). Required shape for a page menu item:{title, menus, type: "post_type", object: "page", object_id, parent, menu_order, status: "publish"}. Date: 2026-04-16
TSF Meta Keys Are _genesis_title and _genesis_description¶
From legacy section: Elementor / WordPress MCP
Pattern: Needed to set SEO meta titles/descriptions for TSF (The SEO Framework) via WP-CLI. Grep'd for
_tsf*keys but none existed. TSF actually stores meta using Genesis-compatible keys. Rule: To set TSF meta titles/descriptions via WP-CLI:wp post meta update [id] _genesis_title "..."andwp post meta update [id] _genesis_description "...". Also relevant:_genesis_noindex,_genesis_nofollow,_genesis_noarchive,_tsf_title_no_blogname. Confirmed on evolvebusiness.com. Date: 2026-04-16
_evolve_schema Mu-Plugin Expects Array of Schema Blocks, Not Single Block¶
From legacy section: Elementor / WordPress MCP
Pattern:
build/schema-deploy-locations.php(run 2026-04-24) stored each location page's schema as a single document{"@context": ..., "@graph": [...]}(not wrapped). The mu-plugin'sevolve_schema_output()doesforeach ($schemas as $schema)expecting$schemasto be an array of blocks like[{"@context": ..., "@graph": [...]}]. With an unwrapped single doc, foreach iterates over the top-level keys (@context, @graph) as scalars/sub-arrays, and theisset($schema['@graph'])check inside the loop never matches. Result: 0 schema blocks output despite valid_evolve_schemameta. Confirmed shape mismatch via comparison: post 1264 (working) stores[{...}], post 2495 (broken) stored{...}. Rule: When deploying schema to_evolve_schema, ALWAYS wrap in an outer array — even for a single block. The mu-plugin treats$schemasas an iterable list of complete schema documents. Updatebuild/schema-deploy-locations.phpand any future schema deploys to use:Recovery: read existing meta, decode, check if top-level has$schema = [ '@context' => 'https://schema.org', '@graph' => [...] ]; $wrapped = [ $schema ]; // <-- ALWAYS wrap $json = wp_json_encode( $wrapped, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ); update_post_meta( $pid, '_evolve_schema', wp_slash( $json ) );@graphor@type(means unwrapped), wrap in[$d], re-store via$wpdb->insert. Done for /locations/ on 2026-04-25. Date:* 2026-04-25
Service Pages Live Under /services/ Slug Parent — Don't Assume Flat URLs¶
From legacy section: Elementor / WordPress MCP
Pattern: Built location pages with inline links to service pages using flat URLs like
https://evolvebusiness.com/seo/,/local-seo-google-maps/, etc. All 10 returned 404. Real URLs are/services/seo/,/services/local-seo-google-maps/because every service page (1252, 1264, 1273, 1284, 1292, 1298, 2408, 2409, 2410, 2411) haspost_parent = 837(the /services/ hub). Rule: Before building internal links to ANY page on a multi-tenant site, query post_parent:wp post list --post__in=<ids> --fields=ID,post_name,post_parent. Walk the parent chain to construct the full URL path. Never assume slug == URL path. On evolvebusiness.com specifically: services are at/services/{slug}/, locations are at/locations/{slug}/, neither at the root. Date: 2026-04-25
PHP Heredoc \$<digit> Leaks the Backslash to Output¶
From legacy section: Elementor / WordPress MCP
Pattern: Built location-page HTML inside PHP heredoc strings. Wrote
\$500and\$2,500thinking the backslash would escape against variable interpolation. PHP did NOT consume the backslash because$5can't be a variable name (variables can't start with digits) — so PHP left the literal\$in the output. The rendered live page showed\$500 to \$2,500instead of$500 to $2,500. Jim caught it on the Albany page. Rule: In PHP heredoc, only escape\$when the following character could legitimately start a variable name (letter or underscore). For dollar AMOUNTS, write the bare$500— PHP won't try to interpolate$5because it's invalid syntax, and you don't need to defend against it. Sweep before deploy:grep -nE '\\[$][0-9]' file.phpshould return zero hits in heredoc-built HTML. Same logic applies to currency in any string-interpolation context (template literals, double-quoted strings). Date: 2026-04-25
WordPress MCP active-site context can drift mid-session¶
From legacy section: SEO NEO / Workbook
Pattern: Mid-session, an
update_pagecall against post 2425 on evolvebusiness.com returned "Invalid post ID." Confirmed page 2425 existed by re-runningget_pageminutes earlier. Ranlist_sites— the active site had silently switched toupstateconcerts. Page 2425 doesn't exist on upstateconcerts, hence the error. Callingselect_site evolvebusinessrestored context and the update succeeded. Rule: When a WordPress MCP write fails with a confusing error (Invalid post ID, page not found, slug mismatch) — first verify active site context withmcp__wordpress__list_sites. The MCP server's active-site state is global and can drift mid-session, not just at session start. For destructive operations on a multi-site MCP setup, always lead withlist_sitesto confirm targeting before the write. Date: 2026-04-30