CINQ WP MCP
MCP server for WordPress sites built from ACF flexible content blocks: let Claude build pages, settings and menus from your theme's blocks.
Install
No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:
wp plugin install https://github.com/agencecinq/cinq-wp-mcp/archive/refs/heads/main.zipA WordPress plugin that turns a site built from ACF flexible content blocks into an MCP server, so an AI client such as Claude can build and edit its pages, posts, site-wide settings and menus — with the site's own blocks.
It is written for the way CINQ builds sites (the
wp-cinquante-et-un starter theme:
ACF field groups declared in PHP, pages assembled from a blocks flexible content field),
but nothing in it is specific to one site. Install it, and it reads the theme: every
flexible content field, every layout, every field and its constraints are discovered from
ACF at runtime. A layout added to the theme shows up in Claude without touching the plugin.
What a site may want to add — descriptions of its blocks, editorial rules, the page
templates of its design — goes in an optional mcp.json at the theme root, modeled on
theme.json.
Requirements
| Component | Version | Why |
|---|---|---|
| WordPress | 6.9+ | the Abilities API ships with core since 6.9 |
| PHP | 8.1+ | |
| MCP Adapter | 0.6+ | MCP transport and server registration |
| ACF Pro or Secure Custom Fields | the blocks are ACF flexible content fields |
The plugin cannot be activated without these, and deactivates itself if ACF goes away
later. The free ACF does not include flexible content, so it is refused too. (The
Requires Plugins header cannot express "ACF Pro or SCF", so the check is done on
activation instead.)
Install
wp plugin install https://github.com/WordPress/mcp-adapter/releases/latest/download/mcp-adapter.zip --activate
wp plugin install https://github.com/agencecinq/cinq-wp-mcp/releases/latest/download/cinq-wp-mcp.zip --activate
Updates come from GitHub releases through the regular Plugins screen: the plugin's
Update URI points at this repository, and WordPress is offered each new release within
twelve hours.
Settings → CINQ WP MCP shows the endpoint, what was found in the theme, where the configuration comes from, and the connected applications.
Connect Claude
The MCP endpoint is https://<site>/wp-json/cinq-wp-mcp/mcp.
OAuth (recommended)
In Claude, add a custom connector with that URL and leave authentication on OAuth. Claude discovers the server, registers itself, and opens the site: sign in with your own WordPress account and allow the connection. Each person connects as themselves, with their own capabilities, and appears as the author of their changes in the revisions.
Access is revoked per application from Settings → CINQ WP MCP → Connected apps, or by each user from their profile.
The plugin is its own OAuth 2.1 authorization server, as the MCP specification expects: metadata (RFC 8414, RFC 9728), dynamic client registration (RFC 7591), PKCE S256 mandatory, public clients only, one-hour access tokens, thirty-day refresh tokens rotated at each use (a replayed one revokes the whole chain), revocation (RFC 7009). Tokens are stored as SHA-256 hashes and only open the MCP endpoint.
Registration accepts Claude's callbacks and loopback addresses (desktop and command-line
clients); add others with settings.oauth.redirectUris.
OAuth discovery needs
https://<site>/.well-known/…to reach WordPress. On a site installed in a subdirectory, or behind a server that keeps/.well-known/for itself, use an application password.
Application password
-
In your WordPress profile, create an application password.
-
In Claude, set the connector's authentication to None and add one request header:
Header Value AuthorizationBasic+ base64 oflogin:application passwordX-Api-KeyorX-Auth-Tokenlogin:application password
These three names are on the list of headers Claude accepts without approval. They are only
read on the MCP endpoint, and checked by WordPress core
(wp_authenticate_application_password()). One connector means one shared account.
curl -H 'X-Api-Key: LOGIN:APPLICATION PASSWORD' -H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' \
'https://<site>/wp-json/cinq-wp-mcp/mcp'
How the theme is read
Zones. Every ACF flexible content field is a zone, identified by the dotted path of
field names that leads to it: blocks on pages, archive_posts.blocks inside the blog
archive's options page. A zone applies where its field group's location rules say. The
primary zone of a content type is the one its blocks argument writes to — the root zone
named blocks when there is one.
Layouts reserved for a content type. When the theme hides a layout on some content type
through acf/prepare_field (as the editor would), the plugin asks the theme the same
question and refuses the write with a message saying where the block belongs.
Page templates. When a zone's location rules require a page template
(page_template == page-templates/blocks-page.php), writing blocks to a page assigns it, so
the page does not render empty. When they only exclude some (!= styleguide, the
starter's case), nothing is touched.
Validation. A write is validated whole before anything is saved and every problem comes
back at once with its path (blocks[2].content.heading: expected one of h1, h2, h3).
Required fields with a default are filled with it, as the editor would.
The tools
Names take the configured prefix (site- by default).
| Tool | |
|---|---|
list-block-types |
zones and their block types, per content type or options page |
get-block-schema |
JSON Schema of block types |
list-page-templates |
design templates as block stacks (only when the theme declares some) |
get-content-schema |
the fields a content type owns outside its zones |
list-content · get-content |
find and read content, blocks included, in the write shape |
create-content · update-content |
create or update, blocks and fields included |
edit-blocks |
append, insert, replace, update, move, remove one block — on a post or an options page |
delete-content |
trash, restore, delete |
search-media · upload-media |
media library |
list-terms |
taxonomy terms |
list-options-pages · get-options · update-options |
ACF options pages (when the theme has some) |
list-menus · get-menu · update-menu · assign-menu-locations |
navigation menus; item IDs are kept on update |
The SEO title and description are written through Yoast, Rank Math or SEOPress, whichever is active.
mcp.json
Optional. At the theme root, merged like theme.json: plugin defaults < parent theme <
child theme < cinq_wp_mcp_config filter < overrides saved in the admin. Objects merge
key by key, lists replace each other, templates merge by name. A file that fails validation
is ignored whole and its errors are shown in the admin.
{
"$schema": "https://raw.githubusercontent.com/agencecinq/cinq-wp-mcp/main/schemas/mcp.schema.json",
"version": 1,
"settings": {
"server": {
"title": "Acme",
"toolPrefix": "acme",
"instructions": "Write in French, formal tone. Create content as drafts."
},
"postTypes": { "exclude": ["styleguide"] },
"zones": { "labels": { "archive_posts.blocks": "Blog archive" } },
"optionsPages": { "include": ["options-theme", "archive-post"] },
"menus": { "locations": ["main", "footer"] },
"pageTemplate": "auto",
"seo": "auto",
"oauth": { "redirectUris": [] }
},
"blocks": {
"hero": { "description": "Opening of a page, once, always first." },
"form": { "hidden": true }
},
"templates": [
{
"name": "landing",
"title": "Landing page",
"source": "https://www.figma.com/design/…",
"sections": [
{ "label": "Hero", "block": "hero" },
{ "label": "Pricing", "block": "cards_grid", "status": "approx" },
{ "label": "Booking", "status": "missing", "note": "No embed block yet." }
]
}
]
}
settings.server.instructionsis appended to the server description: it is where the editorial rules for the AI go.blocks.<layout>annotates a layout (description,guidance) or hides it (hidden).templatescan also live one per file inmcp/templates/<name>.json. Section statuses:exact(the block is the section),approx(nearest block),theme(rendered outside the stack),missing(no block yet: Claude says so instead of approximating). Templates are re-checked against the live theme and a block that no longer exists is flagged.
The full format is schemas/mcp.schema.json; with $schema set,
editors autocomplete it.
Development
composer install
composer test # standalone harness, no WordPress needed
composer lint # WordPress Coding Standards
The harness loads real theme ACF registrations from tests/fixtures/ — the starter theme
and agencecinq.com — into in-memory stubs, and runs discovery, schemas, mapping, editing,
configuration, OAuth and updates against them. Refresh a fixture with
bin/sync-fixtures <name> <path to the theme>.
Release: bump Version and VERSION in cinq-wp-mcp.php, then push a v* tag. The
workflow tests, builds cinq-wp-mcp.zip and attaches it to a GitHub release.
Limits
- Gutenberg blocks (including ACF blocks) are not handled: the plugin works with flexible content.
- Polylang and WPML are not handled: content is created in the default language.
- Widgets and the Customizer are out of scope.
License
MIT © CINQ