Skip to content

xfiveco/xfive-mcp

Repository files navigation

xfive-mcp — WordPress MCP server

Exposes a focused set of WordPress content-management tools over the Model Context Protocol (MCP) so AI assistants can read and write posts, pages, ACF fields, options, menus, and media.

The canonical reference for each tool is its PHP class in inc/Abilities/ — read the get_description(), get_input_schema(), and get_output_schema() methods. Each tool also returns a hint field in its output payload that points to likely next actions or common failure causes.

Design principles

  • Few, sharp tools. No overlapping primitives. Block-level mutation tools were removed in favor of one path: read full content, modify the markup string, write full content back.
  • Soft-fail with hints. Where useful, tools return null/empty results plus a hint string instead of erroring — so the agent can chain follow-up actions without a round-trip through error handling.
  • Schema-first. Always call block-schema for each Gutenberg block before composing markup. Wrong attributes are silently dropped.

Available tools

All tools register under the xfive-mcp server with REST namespace xfive-mcp/v1 and route mcp. Each is grouped by category.

Tools follow a read/write-symmetry rule: anything a write tool can set, a read tool can return — so content can be migrated between sites without silently dropping fields. IDs of attachments and terms are site-local; tool hints flag them so the agent remaps when migrating.

Blocks (read-only)

Tool Purpose
block-schema Get the registration schema for a block: attributes, supports, renderMode/seedAs (how to seed the comment), and nesting info (usesInnerBlocks, allowedBlocks, parent). Call BEFORE writing block markup.
block-tree Parse a post's post_content into a Gutenberg block tree. attrs_only drops innerHTML/innerContent; depth summarizes blocks past the limit as {blockName, childCount, truncated}.

Posts

Tool Purpose
post-list Enumerate posts of one or more post types, with status, search, taxonomy + meta_query filters, pagination, ordering, and a fields mode (ids / summary / full).
post-by-title Look up a post by exact title + post_type (optional post_status). Returns post_id: null (not an error) when nothing matches, plus the matched title/slug/status/type.
post-get-content Read the raw post_content markup string (+ post_id, length).
post-get-meta Read post meta plus the full post object for migration (title, slug, status, type, excerpt, parent, menu_order, author, date, modified, comment/ping status, password, featured_image_id).
post-create Create a post / page / CPT in one call: every wp_insert_post field plus _thumbnail_id (featured image), meta_input, and tax_input (assign terms). Returns the new post_id.
post-update Update post-level fields (pass-through to wp_update_post) plus _thumbnail_id, meta_input, tax_input.
post-update-content Replace post_content with new Gutenberg markup. The primary tool for ALL block edits.
post-meta-update Set/delete arbitrary core post meta (update_post_meta; pass null to delete). For ACF use acf-field-update.
post-trash Move a post to the trash, or restore it (action: "trash" | "untrash"). Permanent deletion is intentionally not supported.

Media

Tool Purpose
media-upload Bring an EXTERNAL file into the library from a remote URL or local filesystem path. Optionally set alt, title, caption, description, post_parent. Returns attachment id + url.
media-migrate Push an existing library file from THIS site to a remote WordPress site (e.g. local → staging), server-to-server. The source attachment's alt/title/caption/description are carried over (override-able). The agent passes only attachment_id — file bytes never travel through the agent. Returns the remote attachment id + url. See Media migration below for required config.

Menus

Tool Purpose
nav-menu-list List all menus (or one) with their items, theme locations, and per-item object_id/type/parent/classes/target. Round-trips with nav-menu-create.
nav-menu-create Create a nav menu, optionally assign to a theme location, optionally seed items (custom links, posts, taxonomies, nested via parent_index).

ACF

Tool Purpose
acf-field-get Read ACF field values for a post / term / user / options page. Reverses textarea render markup (raw); expands image/file/gallery fields to {id,url,filename} in a media map (expand_media, default on).
acf-field-update Update one or more ACF fields (object_id: numeric post ID, "term_{id}", "user_{id}", or "option"). Returns read-after-write values; an already-equal value counts as updated, not failed.
acf-field-schema Read the STRUCTURE (not values) of ACF field groups for a target: field names/keys/types, nested sub_fields/layouts (repeater/group/flexible), and choices. Use before writing complex fields.

Options & theme mods

Tool Purpose
options-get Read wp_options rows (type: "option") or theme mods (type: "theme_mod"); for theme mods, omit names to return all. Read counterpart to options-update.
options-update Bulk-update wp_options rows (type: "option") or theme mods (type: "theme_mod").

Widgets

Tool Purpose
widgets-list List all sidebars and the widgets assigned to each (widget_id, type, settings).
widget-add Add a widget to a sidebar (type + settings, optional position).
widget-update Update a widget's settings (merge, or replace).
widget-remove Remove a widget from a sidebar (and delete its stored settings).

Editing block content — the only flow

There are no partial-block mutation tools (block-add / update / replace / move / remove were removed; index-based mutation was fragile). To edit any block content:

  1. Optional: block-tree (inspect current structure) or post-get-content (raw markup string).
  2. block-schema for every block type you're about to write (validates attribute shape).
  3. post-update-content with the full new Gutenberg markup.

Round-trip cost is small in practice and produces predictable, debuggable diffs.

Media migration (media-migrate)

media-migrate follows the WP Migrate DB Pro push model: it runs on the source site (e.g. local), reads an attachment from disk, and POSTs it server-to-server to the remote site's built-in wp/v2/media REST endpoint. The agent passes only attachment_id, so file bytes never pass through the agent's context.

Because the source site makes the outbound HTTP call itself (it is not the authenticated MCP caller), it must hold the remote's credentials. Configure them once on the source site's wp-config.php:

define( 'XFIVE_MCP_REMOTE_URL', 'https://staging.example.com' );
define( 'XFIVE_MCP_REMOTE_USER', 'your-remote-username' );
define( 'XFIVE_MCP_REMOTE_APP_PASSWORD', 'xxxx xxxx xxxx xxxx xxxx xxxx' );

The application password is generated on the remote site at Users → Profile → Application Passwords; that user needs the upload_files capability. The remote site must be reachable over HTTP from the source server.

Notes:

  • Allowed types: jpeg, png, gif, webp, svg, mp4, webm, ogg, mov.
  • The file is sent in a single request, so large files (video) are bound by the remote's upload_max_filesize, post_max_size, and memory_limit (HTTP 413 indicates the limit was hit).
  • The remote regenerates its own thumbnail/size variants; only the original file is transferred.

Authentication

The MCP REST endpoint uses HTTP Basic Auth with WordPress application passwords. For local development, define MCP_OPEN truthy in wp-config.php to bypass auth and run as the first administrator user (already configured in this site).

define( 'MCP_OPEN', true );

Client configuration

The server is reached over HTTP via the @automattic/mcp-wordpress-remote proxy, which bridges stdio MCP clients to the WordPress REST endpoint.

Claude Code (.mcp.json)

Drop a .mcp.json at the project root:

{
  "mcpServers": {
    "xfive-mcp-chisel": {
      "type": "http",
      "url": "http://chisel-ai-mcp-test.test/wp-json/xfive-mcp/mcp"
    }
  }
}

WP_API_URL points at this plugin's route (xfive-mcp/v1 namespace → mcp route).

Authentication options

  • MCP_OPEN mode (local dev). When define( 'MCP_OPEN', true ) is set in wp-config.php, the endpoint accepts unauthenticated requests and runs as the first admin. Omit WP_API_USERNAME / WP_API_PASSWORD — they're not needed.

  • Application passwords (staging / shared). Generate one at Users → Profile → Application Passwords and add:

    "env": {
      "WP_API_URL": "http://your-site.test/wp-json/xfive-mcp/mcp",
      "WP_API_USERNAME": "your-username",
      "WP_API_PASSWORD": "your-application-password"
    }

Other coding tools

Other MCP-capable clients (Codex, Cursor, Windsurf, Cline, Zed, etc.) use the same proxy command / args / env block, but each reads it from its own config file and may wrap it in a different top-level schema. Check the tool's MCP docs for the exact filename and structure — the inner server definition is portable.

Architecture

inc/
├── Abilities/             # One class per MCP tool, all extend AbilitiesBase
│   ├── AbilitiesBase.php
│   ├── BlockSchema.php
│   ├── BlockTree.php
│   ├── PostList.php
│   ├── PostByTitle.php
│   ├── PostCreate.php
│   ├── PostUpdate.php
│   ├── PostUpdateContent.php
│   ├── PostGetContent.php
│   ├── PostGetMeta.php
│   ├── PostMetaUpdate.php
│   ├── PostTrash.php
│   ├── MediaUpload.php
│   ├── MediaMigrate.php
│   ├── NavMenuList.php
│   ├── NavMenuCreate.php
│   ├── AcfFieldGet.php
│   ├── AcfFieldUpdate.php
│   ├── AcfFieldSchema.php
│   ├── OptionsGet.php
│   ├── OptionsUpdate.php
│   └── Widget{sList,Add,Update,Remove}.php
├── WP/
│   ├── Plugin.php         # Bootstrap (singleton)
│   ├── Abilities.php      # Registers each ability with wp_register_ability()
│   └── MCP.php            # Creates the MCP server via mcp_adapter_init
└── Trait/
    ├── Singleton.php
    └── Config.php         # Inventory of which tools live in which category

Tool registration is data-driven from Trait/Config.php: add an entry there, create the matching class in inc/Abilities/, and Abilities::register_abilities() picks it up on wp_abilities_api_init.

About

MCP server with Wordpress Abilities API

Resources

License

Stars

1 star

Watchers

1 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages