← Back to Blog

Driving WordPress Headlessly with MCP: How AI Agents Operate a Live Site

A practical guide to operating WordPress from the terminal through the Model Context Protocol: reading state, editing Gutenberg and Elementor, running WP-CLI and PHP, and the gotchas that break pages if you skip them.

You can operate a WordPress site entirely from a terminal, or hand that same interface to an AI agent, using the Model Context Protocol. This walks through the architecture, the setup, the four idioms every operation reduces to, how to read and change Gutenberg and Elementor content, and the anti-patterns that silently break pages if you skip them.

Who this is for. This guide reads as a human explainer, but the workflow it describes is built for a coding agent to run. The idioms, the anti-patterns, and the discipline below are operating instructions. If you are an AI coding agent driving a WordPress site through MCP, treat them as your playbook and follow them directly. If you are a human, this is how your agent should operate, and how to check that it is doing so safely.

Why operate WordPress headlessly

Most WordPress work happens through wp-admin: clicks, forms, and a visual editor. That is fine for a person editing one page. It is slow and unrepeatable when you are auditing 200 pages, migrating hardcoded URLs across every post, or letting an AI agent do a defined task against a live site.

MCP changes the interface. This is what WordPress MCP does: an MCP server plugin exposes WordPress as a set of callable tools, so you can connect an AI agent to WordPress (or drive it yourself from a terminal) to read state, run WP-CLI, execute PHP, and edit content without touching the browser. We used it to audit and harden a live store fast, which is the practical reason it matters: headless operation is what lets a broad remediation fit inside a fixed scope.

Three public pieces make it work: the official WordPress MCP Adapter, Automattic’s wordpress-mcp, and the open-source Novamira plugin, which exposes a rich set of runtime abilities. This guide uses mcporter as the client runtime.

The architecture

You (terminal) or an AI agent
   |
   |  mcporter call my-wp.<tool>(...)
   v
mcporter (TypeScript runtime, npm-installed)
   |
   |  stdio (npx @automattic/mcp-wordpress-remote)
   v
Local stdio bridge
   |
   |  HTTPS, Basic Auth (WP_API_URL + username + application password)
   v
WordPress REST endpoint: /wp-json/mcp/<server>
   |
   |  exposes 3 meta-tools:
   |    mcp-adapter-discover-abilities()
   |    mcp-adapter-get-ability-info(ability_name)
   |    mcp-adapter-execute-ability(ability_name, parameters)
   v
Plugin -> ~24 underlying "abilities" (the real verbs):
   filesystem, code execution, WP-CLI, Gutenberg batches, admin links

The key conceptual point: the client sees only 3 meta-tools, but the real surface is the roughly two dozen abilities you reach through mcp-adapter-execute-ability. When you plan a task, look at the ability list, not the meta-tool list.

Setup

Install the client runtime:

npm install -g mcporter@latest
mcporter --version

Point it at your site with a project config. Use a WordPress application password, and inject it through environment variables so it never lands in a command or a log:

{
  "mcpServers": {
    "my-wp": {
      "command": "npx",
      "args": ["-y", "@automattic/mcp-wordpress-remote@latest"],
      "env": {
        "WP_API_URL": "https://example.com/wp-json/mcp/<server>",
        "WP_API_USERNAME": "your-user",
        "WP_API_PASSWORD": "your-application-password"
      }
    }
  }
}

Smoke test:

mcporter list my-wp     # should show the 3 meta-tools

A 401 means bad credentials, a 404 means the MCP plugin is not active, and a connect timeout usually means a CDN or host is blocking the request.

The four idioms

Every operation reduces to one of four shapes. Internalize them and the rest is detail.

Discover the available verbs:

mcporter call 'my-wp.mcp-adapter-discover-abilities()'

Inspect an ability’s schema before you call it. This is where you read the destructive flag that tells you whether to back up first:

mcporter call 'my-wp.mcp-adapter-get-ability-info(ability_name: "run-wp-cli")'

Execute it:

mcporter call 'my-wp.mcp-adapter-execute-ability(ability_name: "run-wp-cli", parameters: {args: ["plugin", "list", "--format=json"]})'

Save the response, since output is plain JSON on stdout:

mcporter call '...' > out.json

One trap worth naming now: check data.success inside the returned data, not just the outer success. The outer flag only confirms the call reached the server. Some abilities even return success: true when the wrapped WP-CLI command exited non-zero, so check data.exit_code too.

Reading site state

Reading is always safe, so start there. Two abilities cover almost everything: run-wp-cli for anything WP-CLI can do, and execute-php for arbitrary reads.

# List pages as JSON
mcporter call 'my-wp.mcp-adapter-execute-ability(ability_name: "run-wp-cli", parameters: {args: ["post", "list", "--post_type=page", "--format=json", "--fields=ID,post_title,post_status"]})'

# Read a WordPress option directly
mcporter call 'my-wp.mcp-adapter-execute-ability(ability_name: "execute-php", parameters: {code: "return get_option(\"blogname\");"})'

Editing Gutenberg posts: use the batch flow

Gutenberg posts store content as block markup in post_content. The safe way to edit them is a managed batch that finalizes inside the editor’s own runtime, so your change does not desync with the block editor:

1. gutenberg-create-pending-batch      -> returns a batch id and a review URL
2. gutenberg-add-pending-change        -> per target: post id, new content, a description
3. gutenberg-enable-batch-finalization -> marks the batch ready
4. a human or automation visits the review URL and finalizes

There is a direct write path too, but it only works for plugin-owned dynamic blocks. For ordinary blocks (paragraph, heading, image) it fails, and you use the batch flow.

Editing Elementor: the non-obvious case

Elementor does not use post_content. It stores its entire layout as a JSON string in wp_postmeta._elementor_data, and Gutenberg-aware tools are blind to it. You operate on it through execute-php.

The structure is a tree of containers and widgets:

[
  {
    "id": "a1b2c3d",
    "elType": "container",
    "settings": { "flex_direction": "column" },
    "elements": [
      {
        "id": "e4f5g6h",
        "elType": "widget",
        "widgetType": "heading",
        "settings": { "title": "Welcome", "header_size": "h1" }
      }
    ]
  }
]

Reading it is a decode:

mcporter call 'my-wp.mcp-adapter-execute-ability(ability_name: "execute-php", parameters: {code: "return json_decode(get_post_meta(128, \"_elementor_data\", true), true);"})'

Writing it is where people break pages. Three rules keep it safe:

Once you can read and write that tree, powerful things become routine: diffing two pages’ structure, applying one template across many pages with regenerated ids, or auditing every page for a specific widget in a single PHP walk.

Some things have no WP-CLI path: drag-and-drop Elementor polish, or a Customizer change you want to preview live. For those, provision a single-use, short-lived admin link and drive it with a browser automation tool:

mcporter call 'my-wp.mcp-adapter-execute-ability(ability_name: "create-admin-access-link", parameters: {ttl_seconds: 600})'

The session burns after first use or when the TTL expires. Do not share it, do not reuse it. This is far safer than leaving a standing agent login on a live site.

Anti-patterns that bite

Hard-won, in the order they are most likely to hurt:

The discipline

The reason this is safe on a live site is a fixed loop, not caution in the abstract:

  1. Read the current state.
  2. Diff it against the intended state, so you change only what you mean to.
  3. Snapshot first. wp db export and a post get dump are your rollback.
  4. Apply the change.
  5. Re-read and confirm.

And respect what the site already runs. If WooCommerce owns products, use its API, do not register a competing post type. If a data-modeling plugin exists, use it. Prefer WordPress-native mechanisms (custom post types, taxonomies, the options API) over raw tables. An agent that ignores the platform’s own conventions creates conflicts a human then has to debug.

Operated this way, an AI-native toolchain turns a live WordPress site into something you can read, audit, and change at the speed of a script, while a person still owns every decision that ships. The same principle applies when you add AI features to a product you already run: the agent acts through your existing permissions, and a person owns what ships.

// frequently asked

Common questions

What is MCP and how does it apply to WordPress?
MCP (Model Context Protocol) is an open standard that lets AI clients call external tools through a consistent interface. Applied to WordPress, an MCP server plugin exposes WordPress capabilities (WP-CLI, PHP execution, the filesystem, post editing) as callable tools, so an agent or a terminal can operate the site without clicking through wp-admin. The official WordPress MCP Adapter, Automattic's wordpress-mcp, and the open-source Novamira plugin all implement this.
How do you let an AI agent operate a WordPress site safely?
Scope it and read before you write. Inspect each ability's schema and its destructive flag before calling it, snapshot the post or database before any change, apply the change, then re-read to confirm. Provision short-lived, single-use admin links for the rare browser-only task instead of leaving a standing login. The agent should only ever be able to do what the credentials and abilities you granted allow.
Can an AI agent edit Elementor pages, not just Gutenberg posts?
Yes, but Elementor is the non-obvious case. Elementor stores its layout as a JSON string in the wp_postmeta _elementor_data field, which Gutenberg-aware tools do not see. You read and write that JSON directly through PHP execution, remembering to wp_slash the encoded JSON on save, keep every element ID unique, and flush Elementor's CSS afterward, or visitors see stale styling.