Skip to content

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 was evolvellc. This caused 401/403 errors that looked like credential or permission issues. Rule: When adding a site to sites.json, verify the username exists via wp user list before saving. The 3 valid admin usernames across Evolve sites are: evolvellc, evolveseo, and jim@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_post with status: "future". The API accepted the status but published immediately because there's no date parameter to set a future date. Rule: The WordPress MCP update_post tool cannot schedule posts — it has no date field. 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_metadata and get_seo_metadata returned 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_meta with _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_plugin for Schema Pro on evolvebusiness.com after select_site. The API returned success but the _links URL 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 using update_post_meta() inside wp eval '...'. The result was double-escaped JSON that broke Elementor parsing (get_page_structure returned "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 an eval-file script that deletes then inserts the meta row directly. Reference: build/clone-elementor-data.php. Don't use wp eval with update_post_meta for 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)) inside wp eval-file script silently strips escaped quotes (\"") from embedded strings. FAQ answers containing quoted phrases like "Albany personal injury attorney" produced stored JSON that wp post meta get displays "correctly" but fails json.loads() and breaks the schema mu-plugin's rendering. Rule: update_post_meta() applies wp_unslash() internally (WP magic-quotes legacy). Always wrap encoded JSON with wp_slash() before storing:

$json = wp_json_encode( $schema, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES );
update_post_meta( $post_id, '_evolve_schema', wp_slash( $json ) );
This pairs with the double-escape rule above: wp eval (inline, shell-escaped input) → different behavior than wp eval-file (file input, no shell escaping). For wp eval-file with update_post_meta, use wp_slash(). For wp eval inline 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_items and create_menu_item both require the parameter named menus (plural) — NOT menu_id. Using menu_id returns a validation error. Also: menus must be a number, not string. Rule: For WordPress menu MCP tools, pass menus: 2 (number). For child menu items, parent: 1311 targets 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 "..." and wp 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's evolve_schema_output() does foreach ($schemas as $schema) expecting $schemas to 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 the isset($schema['@graph']) check inside the loop never matches. Result: 0 schema blocks output despite valid _evolve_schema meta. 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 $schemas as an iterable list of complete schema documents. Update build/schema-deploy-locations.php and any future schema deploys to use:

$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 ) );
Recovery: read existing meta, decode, check if top-level has @graph or @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) has post_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 \$500 and \$2,500 thinking the backslash would escape against variable interpolation. PHP did NOT consume the backslash because $5 can'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,500 instead 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 $5 because it's invalid syntax, and you don't need to defend against it. Sweep before deploy: grep -nE '\\[$][0-9]' file.php should 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_page call against post 2425 on evolvebusiness.com returned "Invalid post ID." Confirmed page 2425 existed by re-running get_page minutes earlier. Ran list_sites — the active site had silently switched to upstateconcerts. Page 2425 doesn't exist on upstateconcerts, hence the error. Calling select_site evolvebusiness restored 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 with mcp__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 with list_sites to confirm targeting before the write. Date: 2026-04-30