SECTOR-DEMO · FULL TOOL COVERAGELIVE

FULL TOOL DEMO

Every MCP tool tested live · plugin v3.9.5 · MCP v3.9.5
55 tools demoed · 51 PASS · 2 INFO · 2 SKIPPED · companion to /testing/
▶ TEST 01

check_mcp_reachability

Probe the WP site from the MCP. Returns plugin + root reachability, latency, and a human-readable failure hint.

inputjson
{}
responsejson
{
  "plugin": {"reachable": true, "plugin_version": "3.9.5", "latency_ms": 215.9},
  "root":   {"reachable": true, "http_status": 200, "latency_ms": 392.7},
  "hint": null
}
Pass. — plugin reachable 215ms · root 200/392ms · 2026-04-27
▶ TEST 02

get_wp_status

WP version, PHP, theme, active plugins, edition (free/pro), license state, MCP version, version_parity, readonly_mode. Single source of truth for environment.

inputjson
{}
responsejson
{
  "wordpress_version": "6.9.4",
  "php_version": "8.4.19",
  "theme": "Astra 4.13.0",
  "agenticwp_version": "3.9.5",
  "mcp_version": "3.9.5",
  "version_parity": true,
  "edition": "pro",
  "licensed": true,
  "readonly_mode": false,
  "active_plugins": ["agenticwp", "astra-widgets", "elementor", "plugin-check", "seo-by-rank-math", "updraftplus", "woocommerce"]
}
Pass. — 7 active plugins · plugin+MCP both 3.9.5 · parity ✓
ℹ TEST 03 · INFO

check_mcp_update

Fetch the static manifest at agenticwp.racunalnicar.eu/mcp-latest.json and compare to running MCP_VERSION. Returns latest, update_available, sha256, download_url.

inputjson
{}
responsejson
{
  "current": "3.9.5",
  "latest": "3.9.3",
  "update_available": false,
  "released": "2026-04-23",
  "sha256": "bb6961f0876e6df60fb4454ffca1d6a78bcd6afe3d61275c9fc7cdc3a1addaac",
  "min_python": "3.10"
}
Info. — TRANSPARENCY: manifest currently shows 3.9.3, running 3.9.5 — drift visible. update_available=false because we’re ahead. Manifest will catch up when 3.9.5 ships publicly.
▶ TEST 04

get_option

Read any wp_options row by name. Auto-unserializes. Returns {option, value, exists}.

inputjson
{ "name": "blogname" }
responsejson
{ "option": "blogname", "value": "AgenticWP", "exists": true }
Pass. — wp_options is the read primitive · returns exists:false (not 404) when missing
▶ TEST 05

update_option

Write any wp_options row. Protected names (siteurl, home, admin_email, active_plugins, template, stylesheet) blocked. Auto-serializes objects.

inputjson
{
  "name": "agenticwp_demo_marker",
  "value": { "created_at": "2026-04-27", "run_id": "demo-2026-04-27-21-15" }
}
responsejson
{ "success": true, "option": "agenticwp_demo_marker", "updated": true }
Pass. — scoped to demo marker · cleaned up via delete_option round-trip below
▶ TEST 06

delete_option

Delete a wp_options row. Protected names blocked. Round-trip: we created the marker above, then deleted it here.

inputjson
{ "name": "agenticwp_demo_marker" }
responsejson
{ "success": true, "option": "agenticwp_demo_marker" }
Pass. — create→delete · no orphan left in wp_options
▶ TEST 07

get_postmeta

Read post meta. Pass key for single value, omit for all. Use for RankMath, ACF, Elementor data.

inputjson
{ "post_id": 113 }
responsejson
{
  "post_id": 113,
  "meta": {
    "_elementor_edit_mode": "builder",
    "_elementor_template_type": "wp-page",
    "_elementor_data": "… (full Elementor JSON, ~40KB)",
    "_elementor_version": "4.0.3",
    "rank_math_internal_links_processed": "1",
    "agenticwp_demo_a": "A", "agenticwp_demo_b": "B", "agenticwp_demo_c": "42"
  }
}
Pass. — this very page’s meta · captured at the point demo_single + demo_a/b/c keys existed (cleaned up after)
▶ TEST 08

update_postmeta

Write a single post meta value. Auto-serializes. Use for RankMath SEO meta, page templates, etc.

inputjson
{ "post_id": 113, "key": "agenticwp_demo_single", "value": "demo single value" }
responsejson
{ "success": true, "post_id": 113, "key": "agenticwp_demo_single", "value": "demo single value", "updated": true }
Pass. — scoped to demo key · cleaned up by delete_postmeta below
▶ TEST 09

update_postmeta_bulk

Set N meta keys atomically in one call. Faster than N update_postmeta. Ideal for setting all RankMath fields at once.

inputjson
{
  "post_id": 113,
  "meta": { "agenticwp_demo_a": "A", "agenticwp_demo_b": "B", "agenticwp_demo_c": 42 }
}
responsejson
{ "success": true, "post_id": 113, "updated_count": 3, "meta": { "agenticwp_demo_a": "A", "agenticwp_demo_b": "B", "agenticwp_demo_c": "42" } }
Pass. — 3 keys in one round-trip · cleaned up after run
▶ TEST 10

delete_postmeta

Delete a single post meta entry by key.

inputjson
{ "post_id": 113, "key": "agenticwp_demo_single" }
responsejson
{ "success": true, "post_id": 113, "key": "agenticwp_demo_single" }
Pass. — round-trip with update_postmeta · clean state at end
▶ TEST 11

get_termmeta

Read taxonomy term meta. Use for RankMath SEO on categories/tags, ACF term fields.

inputjson
{ "term_id": 1 }
output (after our write)json
{ "term_id": 1, "meta": { "agenticwp_demo_termmeta": "2026-04-27" } }
Pass. — term 1 = Uncategorized · empty {} before our write · cleaned up after
▶ TEST 12

update_termmeta

Write a single term meta value.

inputjson
{ "term_id": 1, "key": "agenticwp_demo_termmeta", "value": "2026-04-27" }
responsejson
{ "success": true, "term_id": 1, "key": "agenticwp_demo_termmeta", "value": "2026-04-27", "updated": true }
Pass. — scoped to demo termmeta · cleaned up by delete_termmeta below
▶ TEST 13

delete_termmeta

Delete a single term meta entry.

inputjson
{ "term_id": 1, "key": "agenticwp_demo_termmeta" }
responsejson
{ "success": true, "term_id": 1, "key": "agenticwp_demo_termmeta" }
Pass. — full CRUD round-trip on term meta complete
▶ TEST 14

get_astra_settings

Returns the full astra-settings option (~50KB, 100s of theme keys: header builder, colors, typography, footer, layout, container width, sidebars). Equivalent to get_option(‘astra-settings’) but typed.

inputjson
{}
output (truncated — full payload >50KB)json
{
  "site-content-layout": "normal-width-container",
  "header-html-1": "…",
  "button-h-color": "…",
  "theme-color": "…",
  "custom-css": "… (88KB at this point — includes the demo_page_113_styles block we added)",
  "… (~200 more keys)"
}
Pass. — response too large to embed verbatim · use update_astra_settings (Tier 3) for merge writes
▶ TEST 15

flush_cache

Flush WordPress caches. Default flushes all (object_cache + elementor_css + astra_css + rewrite_rules). Pass per-flag to flush selectively.

inputjson
{ "object_cache": true, "elementor_css": false, "astra_css": false, "rewrite_rules": false }
responsejson
{ "success": true, "flushed": ["object_cache"] }
Pass. — granular flags tested · zero-disruption · object_cache only this run
ℹ TEST 16 · INFO

get_debug_log

Tail of wp-content/debug.log (or WP_DEBUG_LOG path). Filters by level + since timestamp. Absolute server paths redacted. When debug logging is off, returns exists:false (the case here).

inputjson
{ "lines": 20 }
responsejson
{
  "path": "[REDACTED]",
  "exists": false,
  "lines": [],
  "count": 0,
  "message": "debug.log does not exist. Enable WP_DEBUG_LOG to start capturing errors."
}
Info. — TRANSPARENCY: this demo doesn’t have WP_DEBUG_LOG enabled · the read path is exercised; the response is the legitimate "no log" output · enable WP_DEBUG_LOG in wp-config to capture warnings/fatals · would surface PHP fatals / deprecations / notices when on
▶ TEST 17

list_revisions

List standard WP revisions for any post/page. Returns id, date, author, content_bytes per revision. For Elementor pages prefer list_elementor_revisions — plain WP revisions don’t snapshot _elementor_data.

inputjson
{ "post_id": 113, "post_type": "pages" }
responsejson
{
  "post_id": 113, "count": 2,
  "revisions": [
    { "id": 116, "date": "2026-04-27T21:11:18", "content_bytes": 1923 },
    { "id": 114, "date": "2026-04-27T21:09:35", "content_bytes": 1398 }
  ]
}
Pass. — page revisions captured at start of run · count grows with each save
▶ TEST 18

list_elementor_revisions

List Elementor revisions of a page. Each captures the full _elementor_data snapshot. Returns revision_id, date, author, element_count. Use before restore_elementor_revision.

inputjson
{ "page_id": 113 }
responsejson
{
  "page_id": 113, "revision_count": 2,
  "revisions": [
    { "revision_id": 116, "date": "2026-04-27 21:11:18", "author_name": "glava", "element_count": 2 },
    { "revision_id": 114, "date": "2026-04-27 21:09:35", "author_name": "glava", "element_count": 1 }
  ]
}
Pass. — 2 Elementor revisions at capture · element_count tracks structure changes
▶ TEST 19

get_elementor_revision

Read full _elementor_data for one revision. Inspect a snapshot before deciding to restore.

inputjson
{ "page_id": 113, "revision_id": 116 }
responsejson
{
  "page_id": 113, "revision_id": 116,
  "date": "2026-04-27 21:11:18",
  "author_name": "glava",
  "element_count": 2,
  "size_bytes": 1572,
  "elements": [ /* full snapshot at that point */ ]
}
Pass. — 1572 bytes at rev 116 · 2 top-level containers · this page has grown ~25× since
▶ TEST 20

get_elementor_components

List built-in component templates with their parameters. Used by build_elementor_section.

inputjson
{}
responsejson
{
  "components": {
    "hero":           { "params": ["title", "subtitle", "button_text", "button_url", "background_image_url", …] },
    "heading":        { "params": ["title", "size", "color", "align"] },
    "text":           { "params": ["html", "text", "color"] },
    "image":          { "params": ["url", "image_id", "size"] },
    "button":         { "params": ["text", "url", "bg_color", "text_color", "size"] },
    "video_bg":       { "params": ["video_url", "title", "subtitle", …] },
    "products_grid":  { "params": ["rows", "columns", "orderby", "order", "category_ids"] },
    "icon_list":      { "params": ["items", "icon_color"] },
    "category_cards": { "params": ["cards"] },
    "cta_banner":     { "params": ["title", "subtitle", "button_text", …] },
    "spacer":         { "params": ["height"] }
  }
}
Pass. — 11 components · introspection for build_elementor_section
▶ TEST 21

list_elementor_pages

List Elementor-enabled pages. type=’page’ (default), ‘template’, ‘all’, ‘any’ (every CPT with builder enabled).

inputjson
{ "type": "page" }
responsejson
{
  "total": 3,
  "pages": [
    { "id": 113, "title": "Demo",       "url": "…/demo/",        "element_count": 3 },
    { "id": 41,  "title": "Build Log",  "url": "…/build-log/",    "element_count": 0 },
    { "id": 8,   "title": "Home",       "url": "…/",             "element_count": 9 }
  ]
}
Pass. — 3 Elementor pages on this site · Demo page snapshot taken at element_count=3 (now ~25)
▶ TEST 22

list_elementor_widgets

List registered Elementor widget slugs. mode=’names’ (default, ~167 strings, never fatals) / ‘meta’ (title+icon+categories) / ‘full’ (heavy — controls schema; 100KB+ on Pro sites).

inputjson
{ "mode": "names" }
output (truncated)json
{
  "total": 167,
  "mode": "names",
  "widgets": [
    "accordion", "alert", "animated-headline", "archive-posts",
    "button", "contact-buttons", "countdown", "form", "gallery",
    "heading", "html", "icon-box", "image", "loop-grid", "nav-menu",
    "nested-tabs", "portfolio", "reviews", "tabs", "testimonial",
    "text-editor", "video", "wc-add-to-cart", "woocommerce-products",
    /* … 143 more */
  ]
}
Pass. — 167 widgets · Elementor core + WooCommerce + Astra widgets + WP block widgets
▶ TEST 23

get_elementor_outline

Lightweight tree of an Elementor page (id, elType, widgetType, label, children) — no settings. Saves ~100x context vs get_elementor_page. Optional max_depth.

inputjson
{ "page_id": 113 }
output (snapshot at the time)json
{
  "page_id": 113, "element_count": 3,
  "outline": [
    { "id": "d541a2a", "elType": "container", "children": [
      { "id": "97313c9", "elType": "container", "children": [
        { "id": "b22442b", "elType": "widget", "widgetType": "heading", "label": "AgenticWP — Full Tool Demo" },
        { "id": "bb70cdb", "elType": "widget", "widgetType": "heading", "label": "Every MCP tool tested live…" }
      ]}
    ]},
    { "id": "sect001",  "elType": "container", "children": [{ "id": "sect001w", "widgetType": "html" }] },
    { "id": "t000002", "elType": "container", "children": [{ "id": "t002w",    "widgetType": "html" }] }
  ]
}
Pass. — cheap to call repeatedly · ideal first read on any page
▶ TEST 24

get_elementor_page

Full Elementor JSON of a page (settings + nested children of every element). Heavy — for inspection only. Pages >50 top-level elements emit a size warning. Response includes X-AgenticWP-Size + X-AgenticWP-Element-Count headers for budget-aware clients.

inputjson
{ "page_id": 113 }
output (described — too large to embed)json
{
  "page_id": 113,
  "title": "Demo",
  "status": "publish",
  "post_type": "page",
  "size_bytes": 17000,    /* grows as more sections insert */
  "element_count": 17,    /* at this snapshot */
  "elements": [ /* full nested tree of every container + widget on the page */ ]
}
Pass. — prefer get_elementor_outline / get_elementor_element when you don’t need the whole tree · this is the source of truth read
▶ TEST 25

get_elementor_element

Read ONE element by id. Avoids downloading the full page. Find element ids via get_elementor_outline.

inputjson
{ "page_id": 113, "element_id": "b22442b" }
responsejson
{
  "page_id": 113, "element_id": "b22442b",
  "element": {
    "id": "b22442b",
    "elType": "widget",
    "widgetType": "heading",
    "settings": { "title": "AgenticWP — Full Tool Demo", "header_size": "h1", "title_color": "#FFFFFF", "align": "center" },
    "elements": []
  }
}
Pass. — ~200 bytes for one widget vs ~50KB for full page · this is the page’s hero h1
▶ TEST 26

diff_elementor_element

Preview what update_elementor_element would change. No save. Returns added/changed/removed keys + change_count. Use before risky edits.

inputjson
{
  "page_id": 113,
  "element_id": "b22442b",
  "settings": { "title": "AgenticWP — Full Tool Demo (preview)", "title_color": "#7ee787" }
}
responsejson
{
  "dry_run": true, "mode": "merge", "change_count": 2,
  "changed": {
    "title":       { "before": "AgenticWP — Full Tool Demo", "after": "AgenticWP — Full Tool Demo (preview)" },
    "title_color": { "before": "#FFFFFF",                  "after": "#7ee787" }
  },
  "removed": []
}
Pass. — 2 settings would change · NO save happened · use before destructive merges
▶ TEST 27

find_pages

Find pages by slug/title/parent/status. Lightweight (no content). Use BEFORE get_elementor_page when you don’t already know the ID. is_elementor flag = quick triage.

inputjson
{ "per_page": 5 }
responsejson
{
  "count": 5,
  "pages": [
    { "id": 41,  "title": "Build Log",    "slug": "build-log",    "is_elementor": true },
    { "id": 109, "title": "Cart",         "slug": "cart",         "is_elementor": false },
    { "id": 110, "title": "Checkout",     "slug": "checkout",     "is_elementor": false },
    { "id": 45,  "title": "Control Room", "slug": "control-room", "is_elementor": false },
    { "id": 26,  "title": "Cookie Policy","slug": "cookies",      "is_elementor": false }
  ]
}
Pass. — 5 of N pages alphabetically · slug/title/parent/status filters supported
▶ TEST 28

find_products

Find WooCommerce products by search/sku/category/brand/attribute. Returns id, name, sku, prices, stock, categories. Pagination supported.

inputjson
{ "search": "demo", "per_page": 5 }
responsejson
{
  "count": 1, "total": 1, "total_pages": 1,
  "products": [
    { "id": 118, "name": "Demo Product (test surface)", "sku": "demo-test-1",
      "price": "9.99", "stock_status": "instock",
      "categories": ["Uncategorized"] }
  ]
}
Pass. — matched the test product we created earlier in this run · before the create call this returned count=0 · now run-able as catalog grows
▶ TEST 29

list_menu_locations

List theme nav locations + assigned menu + item count + all available menus. Catches the "is the mobile_menu location wired up?" question without digging through Customizer.

inputjson
{}
responsejson
{
  "total": 5,
  "locations": [
    { "location": "primary",              "menu_id": 2,    "menu_name": "Primary", "item_count": 8 },
    { "location": "secondary_menu",       "menu_id": null, "item_count": 0 },
    { "location": "mobile_menu",          "menu_id": 2,    "menu_name": "Primary", "item_count": 8 },
    { "location": "loggedin_account_menu","menu_id": null, "item_count": 0 },
    { "location": "footer_menu",          "menu_id": null, "item_count": 0 }
  ],
  "all_menus": [
    { "id": 3, "name": "Footer Legal", "count": 5 },
    { "id": 2, "name": "Primary",      "count": 8 }
  ]
}
Pass. — 5 locations · 3 unassigned (secondary, loggedin, footer) · Footer Legal menu has +1 from add_menu_item below
▶ TEST 30

add_menu_item

Append an item to a nav menu. Returns the new item ID. Supports nesting via parent param.

inputjson
{ "menu_id": 3, "title": "Demo (test item)", "url": "https://demo-agenticwp.racunalnicar.eu/demo/" }
responsejson
{ "success": true, "menu_id": 3, "item_id": 120, "title": "Demo (test item)", "url": "…/demo/", "parent": 0 }
Pass. — added to Footer Legal menu · visible at the bottom of every page on this site
▶ TEST 31

reorder_menu

Set menu_order for every sibling in one atomic call. Replaces the "PATCH each item" loop. Items not in the list keep their existing order.

inputjson
{ "menu_id": 3, "ordered_item_ids": [28, 120, 29, 30, 31, 32] }
responsejson
{
  "success": true, "menu_id": 3, "reordered": 6,
  "applied": [
    { "id": 28,  "menu_order": 1 },
    { "id": 120, "menu_order": 2 },
    { "id": 29,  "menu_order": 3 },
    { "id": 30,  "menu_order": 4 },
    { "id": 31,  "menu_order": 5 },
    { "id": 32,  "menu_order": 6 }
  ],
  "errors": []
}
Pass. — moved test item from end to position 2 · single atomic call · check the footer menu to see the effect
▶ TEST 32

media_upload

Upload a file to the WP media library. Local file_path = base64-uploaded by MCP. Remote url = WP server downloads. Optional alt_text + title saved at upload.

inputjson
{
  "file_path": "/home/seba/PROJECTS/AgenticWP/marketing/site/agenticwp-logo.png",
  "alt_text": "AgenticWP demo upload — created by /demo/ run 2026-04-27",
  "title":    "demo-upload-2026-04-27"
}
responsejson
{
  "success": true,
  "id": 119,
  "url":   "https://demo-agenticwp.racunalnicar.eu/wp-content/uploads/2026/04/agenticwp-logo-1.png",
  "alt_text": "…",
  "title":    "demo-upload-2026-04-27"
}
Pass. — real attachment · ID 119 · base64 round-trip from local disk · used by media_set_alt + media_set_alt_bulk below
▶ TEST 33

media_set_alt

Set alt text on an existing attachment. Single call.

inputjson
{ "media_id": 119, "alt_text": "AgenticWP logo (alt updated by media_set_alt demo)" }
responsejson
{ "success": true, "id": 119, "alt_text": "AgenticWP logo (alt updated by media_set_alt demo)" }
Pass. — alt overwritten on attachment 119 · subsequent media_set_alt_bulk overwrites again
▶ TEST 34

media_set_alt_bulk

Set alt text on N attachments in one call. List of {media_id, alt_text}.

inputjson
{ "items": [ { "media_id": 119, "alt_text": "AgenticWP logo (alt re-updated by media_set_alt_bulk)" } ] }
responsejson
{ "success": true, "updated_count": 1, "updated": { "119": "AgenticWP logo (alt re-updated by media_set_alt_bulk)" } }
Pass. — bulk path verified with 1 item · scales to N · returns per-id confirmation
▶ TEST 35

seo_audit

Cross-site SEO snapshot. Per post: rank_math_title, description, focus_keyword, robots, canonical, og_image, word_count, issues[]. Aggregate summary counts. Paginated. missing_only filter.

inputjson
{ "per_page": 5, "missing_only": true }
responsejson
{
  "total_posts": 16, "total_pages": 4,
  "summary": {
    "posts_scanned": 5, "missing_title": 5, "missing_description": 5,
    "missing_focus_keyword": 5, "missing_og_image": 5, "thin_content": 5, "noindex": 0
  },
  "items": [
    { "id": 113, "url": "…/demo/", "has_title": false, "word_count": 49,
      "issues": ["missing_title","missing_description","missing_focus_keyword","missing_og_image","thin_content"] },
    { "id": 111, "url": "…/my-account/", "word_count": 3, "issues": ["missing_title", …] },
    { "id": 110, "url": "…/checkout/",   "word_count": 2, "issues": ["missing_title", …] },
    /* … */
  ]
}
Pass. — HONEST: this Demo page itself is missing all 4 RankMath fields at scan time · would be fixed by update_postmeta_multi (Tier 3) · WooCommerce pages (cart/checkout/account) are auto-generated and rightly thin
▶ TEST 36

render_page

HTTP fetch a public URL. Returns title, size_bytes, stylesheets_count, inline_style_blocks, inline_css_total_bytes, optional snippet for a CSS selector. No auth. Lives in MCP Python — no plugin endpoint. Use to verify what visitors actually receive.

inputjson
{ "url": "https://demo-agenticwp.racunalnicar.eu/demo/" }
responsejson
{
  "url": "https://demo-agenticwp.racunalnicar.eu/demo/",
  "title": "Demo | AgenticWP",
  "size_bytes": 212822,
  "stylesheets_count": 13,
  "inline_style_blocks": 10,
  "inline_css_total_bytes": 177393,
  "snippet": null
}
Pass. — page actually loads · 213KB total · 177KB inline CSS (Elementor + Astra) · pass selector for snippet of one element
▶ TEST 37

css_has_selector

Substring-search astra-settings.custom-css for a selector. Returns {exists, count, css_total_bytes, first_rule}. Run before patch_css to catch typos or verify a block landed. Pure Python composition (no plugin endpoint).

inputjson
{ "selector": ".page-id-113 .awp-tool" }
responsejson
{
  "selector": ".page-id-113 .awp-tool",
  "exists": true, "count": 17,
  "css_total_bytes": 88171,
  "first_rule": ".page-id-113 .awp-tool { border:1px solid #2b2f36; background:#0f1116; … }"
}
Pass. — this very page’s styling block · 17 references found · proves the patch_css below applied successfully
▶ TEST 38

patch_css

Atomic named-block patch of astra-settings.custom-css. Markers: /* === BEGIN:name === */ … /* === END:name === */. Modes: replace_or_append (default, idempotent), append, replace, remove. Replaces the read-modify-write pattern that kept corrupting comma-joined selectors.

inputjson
{
  "block": "demo_page_113_styles",
  "mode":  "replace_or_append",
  "rules": ".page-id-113 .awp-tool { border:1px solid #2b2f36; … } /* + ~25 more selectors */"
}
responsejson
{ "success": true, "updated": true, "block": "demo_page_113_styles", "mode": "append", "before_bytes": 85839, "after_bytes": 88560, "delta": 2721 }
Pass. — this page’s CSS · 2721 bytes added in one atomic write · idempotent on re-run
▶ TEST 39

wp_rest_call

Generic /wp/v2/* passthrough. Use for ANY standard WP REST endpoint (pages, posts, media, users, taxonomies, menus, plugins, …) without curl. Authenticated via app password.

inputjson
{ "method": "GET", "path": "/categories", "query": { "per_page": 3 } }
responsejson
{
  "status": 200,
  "data": [
    { "id": 1, "name": "Uncategorized", "slug": "uncategorized", "taxonomy": "category", "count": 0 }
  ]
}
Pass. — passthrough to /wp-json/wp/v2/categories · GET only used here · POST/PUT/PATCH/DELETE all supported
▶ TEST 40

wc_rest_call

Generic /wc/v3/* passthrough. Use for ANY WooCommerce REST endpoint — products, orders, customers, coupons, shipping, taxes, payment gateways, settings.

inputjson
{ "method": "GET", "path": "/products/categories", "query": { "per_page": 3 } }
responsejson
{
  "status": 200,
  "data": [
    { "id": 18, "name": "Uncategorized", "slug": "uncategorized", "count": 1 }
  ]
}
Pass. — passthrough to /wp-json/wc/v3/products/categories · WooCommerce auth via plugin’s REST passthrough
⌗ TEST 41 · ELEMENTOR WRITE

create_elementor_page

Create a new WordPress page with Elementor builder enabled. Optionally pre-populate with elements. The page YOU ARE READING was created by this tool.

inputjson
{
  "title": "Demo",
  "status": "publish",
  "elements": [ /* hero container */ ]
}
responsejson
{ "success": true, "page_id": 113, "title": "Demo", "status": "publish", "url": "https://demo-agenticwp.racunalnicar.eu/demo/", "element_count": 1 }
Pass. — self-referential proof · this page exists because this tool created it · started with 1 element, now ~40+
⌗ TEST 42 · ELEMENTOR WRITE

build_elementor_section

Generate ready-to-insert Elementor JSON for a built-in component (hero, heading, text, image, button, video_bg, products_grid, icon_list, category_cards, cta_banner, spacer). Returns a complete element with unique IDs. The hero at the top of THIS PAGE was built by this tool.

inputjson
{
  "component": "hero",
  "params": {
    "title": "AgenticWP — Full Tool Demo",
    "subtitle": "Every MCP tool tested live …"
  }
}
responsejson
{ "success": true, "component": "hero", "element": { "id": "d541a2a", "elType": "container", "elements": [ /* nested heading widgets */ ] } }
Pass. — scroll up to see the actual hero · same JSON, same IDs · pure server-side generator
⌗ TEST 43 · ELEMENTOR WRITE

insert_elementor_element

Insert a new element into an Elementor page WITHOUT downloading and re-uploading the full page. Position: ‘start’ / ‘end’ (default) / ‘before’ / ‘after’ a sibling. Optionally insert into a nested container via parent_id. EVERY tool section on this page after the hero was added with this tool.

input (this very section’s call)json
{
  "page_id": 113,
  "position": "end",
  "element": { "id": "t203", "elType": "container", "elements": [ /* this html widget */ ] }
}
responsejson
{ "success": true, "page_id": 113, "post_type": "page", "element_id": "t203", "position": "end", "message": "Element inserted and re-rendered" }
Pass. — per-call lock prevents concurrent saves · invalid parent / position / sibling caught with 400 · top-level inserts must be containers · ID collisions caught
⌗ TEST 44 · ELEMENTOR WRITE

update_elementor_page

Replace the entire Elementor JSON for a page with a fresh elements array. Calls Elementor’s Document::save() so post_content + _elementor_css + cache regenerate atomically — no stale-HTML drift. Payloads >500KB rejected (filterable). Use update_elementor_element for single-widget edits to save bandwidth. We used this tool earlier in this run to bulk-add 14 sections (t101-t114) in one call.

input (the bulk-add we did)json
{
  "page_id": 113,
  "elements": [ /* hero + sect001 + t000002 + 14 new tier-1 sections */ ]
}
responsejson
{ "success": true, "page_id": 113, "element_count": 17, "size_bytes": 17157, "message": "Saved with re-render" }
Pass. — 17 elements saved at once · ~17KB · re-rendered · post_content + _elementor_css regenerated atomically
⌗ TEST 45 · ELEMENTOR WRITE

update_elementor_element

Update a single element by ID without re-uploading the whole page. Pass ‘settings’ to MERGE into existing settings, OR ‘element’ to fully replace (ID preserved). Triggers re-render. We just changed the sandbox container’s background.

inputjson
{
  "page_id": 113,
  "element_id": "sandbox1",
  "settings": { "background_color": "#1f3a23" }
}
responsejson
{ "success": true, "page_id": 113, "element_id": "sandbox1", "message": "Element updated and re-rendered" }
Pass. — scroll up to sandbox1 — was dark blue (#1a1f2a), now green tint (#1f3a23) · merge mode preserves all other settings · diff_elementor_element previews this without saving
⌗ TEST 46 · ELEMENTOR WRITE

elementor_replace

Find/replace text in the SERIALIZED Elementor JSON of a page. Use for fixing typos, replacing URLs, updating phone numbers across all widgets in one shot. Returns count of replacements. Triggers re-render. We just replaced REPLACE_ME_MARKER in sandbox1 above.

inputjson
{ "page_id": 113, "find": "REPLACE_ME_MARKER", "replace": "replaced-by-elementor_replace-2026-04-27" }
responsejson
{ "success": true, "page_id": 113, "replacements": 1, "message": "Replaced 1 occurrence(s)" }
Pass. — scroll up to sandbox1 — REPLACE_ME_MARKER is now the timestamped string · operates on serialized JSON before re-deserialization · run css_has_selector / get_elementor_element to verify
⌗ TEST 47 · ELEMENTOR WRITE

restore_elementor_revision

Roll back a page to a specific Elementor revision. Calls Document::save() so HTML + CSS re-render atomically. Creates a NEW revision capturing the pre-restore state, so the restore is itself undoable. Demoed on a separate sandbox page (id 157, status: draft) so it doesn’t touch /demo/.

script (1: create page id 157 with heading "Original state v1") → (2: update_elementor_element to "MUTATED state v2") → (3: list_elementor_revisions returns rev 158 + 159) → (4: restore rev 158)json
{ "page_id": 157, "revision_id": 158 }
responsejson
{ "success": true, "page_id": 157, "revision_id": 158, "element_count": 1, "message": "Restored revision 158 and re-rendered" }
verification (get_elementor_element after restore)json
{ "element": { "id": "sbpw1", "widgetType": "heading", "settings": { "title": "Original state v1", "header_size": "h2" } } }
Pass. — full round-trip: original → mutated → restored to original · the restore itself created revision 160 capturing the mutated state, so it’s reversible · sandbox page id 157 left as draft for inspection
⌗ TEST 48 · ELEMENTOR WRITE

restore_revision

Restore a non-Elementor page/post from a WP revision. Fetches the revision’s raw post_content and POSTs it back to the parent. WP auto-snapshots the current state as a new revision before writing, so the restore is undoable. WARNING: don’t use on Elementor pages — plain WP revisions don’t capture _elementor_data and would desync the JSON. Use restore_elementor_revision instead.

script (1: create post 162 with content v1) → (2: update to v2) → (3: update to v3) → (4: list_revisions returns rev 164 (v3) + rev 163 (v2)) → (5: restore rev 163)json
{ "post_id": 162, "post_type": "posts", "revision_id": 163 }
responsejson
{ "success": true, "post_id": 162, "restored_from_revision": 163, "bytes_written": 55, "modified": "2026-04-27T21:32:58" }
verification (GET /posts/162)json
{ "content": { "raw": "MUTATED content v2 — changed for restore_revision demo." } }
Pass. — round-trip: v1 → v2 → v3 → restored to v2 · sandbox post id 162 left as draft for inspection · restore itself created a new revision capturing v3
⚠ TEST 49 · HIGH RISK

update_astra_settings

Merge-update the astra-settings option. Only keys you provide are touched; all others remain. Use for header builder, colors, typography, footer. We added a scoped marker key (not a real Astra setting) so this couldn’t visibly affect the theme — proves the merge path without altering real config.

inputjson
{ "settings": { "agenticwp_demo_marker": "demo-tier3-2026-04-27" } }
responsejson
{ "success": true, "updated": true, "keys_updated": ["agenticwp_demo_marker"], "total_keys": 59 }
Pass. — 59 total Astra keys after merge · only ours added · for real edits use header-html-1, button-h-color, sticky-header-on-devices, etc. · marker cleared at end of run
⚠ TEST 50 · HIGH RISK

bulk_update_products

Update many WooCommerce products in one call. Allowed fields: name, description, status, regular_price, sale_price, sku, stock_quantity, stock_status, manage_stock, weight, dimensions, tax_status, featured, catalog_visibility, menu_order, category_ids, tag_ids, image_id, gallery_image_ids, meta_data. We touched only product 118 (the test surface) — price 9.99 → 12.99, stock 0 → 7.

inputjson
{ "updates": [{ "id": 118, "regular_price": "12.99", "stock_quantity": 7, "manage_stock": true }] }
responsejson
{ "success": true, "updated_count": 1, "updated_ids": [118], "error_count": 0, "errors": [] }
Pass. — scoped to test product · per-id success/error tracked · price reverted at end of run
⚠ TEST 51 · HIGH RISK

bulk_update_pages

Apply prepend / append / replace ops to many pages in one call. Per-entry errors don’t abort the batch; each result reported independently. Re-run-safe when you control your markers. Demoed by appending content to throwaway page 168 (created and trashed within this run).

inputjson
{ "updates": [{ "id": 168, "append": "<p>APPENDED by bulk_update_pages at 21:38</p>" }] }
responsejson
{
  "total": 1, "ok": 1, "failed": 0,
  "results": [
    { "id": 168, "ok": true, "bytes_before": 44, "bytes_after": 89 }
  ]
}
Pass. — throwaway page 168 created (status: draft) · content went 44 → 89 bytes after append · page deleted at end of run
⚠ TEST 52 · HIGH RISK

update_postmeta_multi

Set meta on MANY posts in one call. Each entry: {post_id, meta: {key: value}}. Use for batch SEO updates across pages/posts (RankMath fields on every product, OG images on a sitemap section, etc.). Scoped to demo post 113 + sandbox post 162; cleaned up at end.

inputjson
{
  "updates": [
    { "post_id": 113, "meta": { "agenticwp_demo_multi_a": "tier3-from-multi" } },
    { "post_id": 162, "meta": { "agenticwp_demo_multi_b": "tier3-from-multi" } }
  ]
}
responsejson
{
  "success": true,
  "results": [
    { "post_id": 113, "updated_count": 1, "meta": { "agenticwp_demo_multi_a": "tier3-from-multi" } },
    { "post_id": 162, "updated_count": 1, "meta": { "agenticwp_demo_multi_b": "tier3-from-multi" } }
  ]
}
Pass. — 2 posts · 1 key each · per-post results returned · keys deleted at end of run
⚠ TEST 53 · HIGH RISK

patch_css — full mode round-trip

The patch_css block at run start (Tier 1 t137) used mode=’replace_or_append’. Here we exercised the full mode set on a separate sandbox block.

step 1: append (creates the block)json
{ "block": "demo_tier3_sandbox", "mode": "replace_or_append", "rules": ".awp-tier3-marker { display: none; }" }
→ { "mode": "append", "before_bytes": 88560, "after_bytes": 88753, "delta": +193 }
step 2: replace (overwrites the block)json
{ "block": "demo_tier3_sandbox", "mode": "replace", "rules": ".awp-tier3-marker { display: block; color: #79c0ff; }" }
→ { "mode": "replace", "before_bytes": 88753, "after_bytes": 88744, "delta": -9 }
step 3: remove (deletes the block + its markers)json
{ "block": "demo_tier3_sandbox", "mode": "remove" }
→ { "mode": "remove", "before_bytes": 88744, "after_bytes": 88560, "delta": -184 }
Pass. — full round-trip · final byte count == start byte count (88560) · zero drift · proves the mode set is reversible
⊘ TEST 54 · SKIPPED

apply_mcp_update

Downloads the latest MCP zip from the manifest, verifies SHA-256, backs up the running install to ../backups/AgenticWP-<ver>-<stamp>/, then copies new files over the install dir. Refuses if the dir lacks server.py. Tells the user to restart — Python cannot replace its own running bytecode.

input (would be)json
{}
output (would be)json
{ "success": true, "backed_up_to": "…/backups/AgenticWP-3.9.3-…/", "installed_version": "3.9.5", "message": "restart MCP server to load new code" }
Skipped. — HONEST: this tool mutates the DEVELOPER’s local MCP install at /home/seba/PROJECTS/AgenticWP, not WordPress. Running it would replace the very Python process generating this page mid-flight. Tested separately during real upgrades. The check_mcp_update demo at the top (Tier 1 t102) shows the manifest-fetch path that this tool uses to decide what to download.
⊘ TEST 55 · SKIPPED

rollback_mcp_update

Restores the most recent backup from ../backups/. Inverse of apply_mcp_update. Same restart caveat — caller must restart MCP after rollback.

input (would be)json
{}
output (would be)json
{ "success": true, "restored_from": "…/backups/AgenticWP-3.9.3-…/", "running_version_pending_restart": "3.9.3" }
Skipped. — same scope as apply_mcp_update — pure local file operations on the MCP install · zero WP impact
ℹ TEST 56 · NOT RUN

check_mcp_update

Listed in tier 4 for completeness — it’s the read-only counterpart of apply / rollback. It WAS run live (see t102 above), so this slot is just a pointer.

notejson
scroll up to t102 (Tier 1 read-only) for the live output. Pairs with apply_mcp_update + rollback_mcp_update as the read/apply/rollback triad.
Info. — same tool, different framing · listed here to keep the "every tool covered" promise visible
Scroll to Top