WordPress 7.0 MCP Adapter + Abilities API: Let Claude Code Call Your Site Directly
When WordPress 7.0 shipped on 2026-05-20, it pulled two things from the plugin ecosystem into core: the **Abilities API** (a standardized way to register WordPress functionality) and the **MCP Adapter** (a server that exposes those Abilities as Model Context Protocol tools). With a client like Claude Code, Claude Desktop, Cursor, or VS Code, the AI can call your site directly—running wp post create, wp option update, or wp comment list for real, not generating HTML for you to paste.
I started testing this the night I finished the 6/29 wp-config.php tuning article. The full chain took 3 evenings, with 4 real pitfalls. This article gives you the 4 pitfalls + a copy-pasteable claude mcp add-json command.
> This is a developer-focused hands-on guide, not a product review. No affiliate links—just one official GitHub repository reference.
🛠️ Prerequisites
- **WordPress**: 7.0 (released 2026-05-20, core includes Abilities API + MCP Adapter)
- **PHP**: 8.2+ (8.3 recommended; 7.0 minimum is 8.1 but you want 8.2 for readonly class benefits)
- **Web server**: Nginx 1.28.x or Apache 2.4.x with **Streamable HTTP support** (SSE was deprecated by the MCP spec, new clients require streamable-http)
- **WP-CLI**: 2.12.0+ (verify with `wp --version`)
- **Client**: Claude Code 2.0+ (the minimum mattpocock/skills v1.0.1 needs) / Claude Desktop 0.10+
- **Auth method**: WordPress **Application Passwords** (never use the real account password—I called this out in the 6/16 security hardening article)
Verify command:
# Confirm WP version + Abilities API loaded
wp eval 'echo "WP " . get_bloginfo("version") . " | Abilities=" . (class_exists("WP_Ability") ? "yes" : "no") . PHP_EOL;'
# Expected output: WP 7.0 | Abilities=yes
🚀 Core steps: expose WordPress as an MCP server
Step 1: Install the MCP Adapter plugin
WordPress 7.0 core includes the MCP Adapter code, but it ships disabled by default (intentional, to avoid expanding the attack surface of minimal sites).
# cd into wp-content/plugins
cd /var/www/html/wp-content/plugins
# Pull the official plugin (NOT Automattic/wordpress-mcp—that repo is deprecated)
# See https://github.com/WordPress/mcp-adapter (2026-05-22 GitHub Trending PHP #1)
git clone https://github.com/WordPress/mcp-adapter.git
# Activate + verify
wp plugin activate mcp-adapter
wp eval 'echo (function_exists("mcp_adapter") ? "adapter-loaded" : "missing") . PHP_EOL;'
# Expected: adapter-loaded
> If you're on WP < 7.0 and want the MCP Adapter, you must upgrade to 7.0. The pre-7.0 compat shim Automattic/wordpress-mcp is **officially deprecated** (the README top literally says "This repository will be deprecated as the mcp-adapter AI Building Block for WordPress continues releasing stable versions").
Step 2: Register a custom Ability
The MCP Adapter exposes a few core Abilities out of the box (like wp.posts.list), but for real work—"find every broken internal link on my site", "batch-update SEO descriptions"—you need to register your own.
WordPress 7.0's Abilities API uses wp_register_ability() to register a standardized capability descriptor. Put this in a small custom plugin:
'Find broken internal links',
'description' => 'Scan all published posts, extract internal links, HEAD-request them, and return any 4xx/5xx URLs.',
'category' => 'site-audit',
'input_schema' => [
'type' => 'object',
'properties' => [
'limit' => [ 'type' => 'integer', 'default' => 50, 'description' => 'Max posts to scan' ],
],
],
'output_schema' => [
'type' => 'object',
'properties' => [
'broken' => [ 'type' => 'array', 'items' => [ 'type' => 'string' ] ],
],
],
'execute_callback' => function ( $input ) {
$limit = (int) ( $input['limit'] ?? 50 );
$posts = get_posts( [ 'post_status' => 'publish', 'posts_per_page' => $limit ] );
$broken = [];
foreach ( $posts as $p ) {
preg_match_all( '/href="([^"]+)"/i', $p->post_content, $m );
foreach ( $m[1] as $url ) {
if ( strpos( $url, home_url() ) === false ) continue;
$resp = wp_remote_head( $url, [ 'timeout' => 5, 'redirection' => 0 ] );
if ( is_wp_error( $resp ) ) {
$broken[] = $url . ' (error: ' . $resp->get_error_message() . ')';
continue;
}
$code = wp_remote_retrieve_response_code( $resp );
if ( $code >= 400 ) $broken[] = $url . ' (' . $code . ')';
}
}
return [ 'broken' => $broken ];
},
'permission_callback' => function () {
return current_user_can( 'edit_others_posts' );
},
] );
} );
After activation, go to wp-admin → Settings → MCP Adapter—you should see site-audit/find-broken-links in the "Available Abilities" list.
Step 3: Generate an Application Password
Go to wp-admin → Users → Profile → Application Passwords. Name it Claude Code MCP and click Add. **Copy the 24-character password**—the page refreshes and you'll never see the full password again.
Permission control: the MCP Adapter uses the user tied to the Application Password for permission checks. **Editor role or above** is required for write-side Abilities like site-audit/*. If your Claude account is author-level, you can list posts but not batch-update descriptions.
Step 4: Wire it into Claude Code
Back in the terminal, use claude mcp add-json to register this WordPress site as an MCP server for Claude Code:
claude mcp add-json wordpress-mcp \
--scope user \
'{"command":"php","args":["/var/www/html/wp-content/plugins/mcp-adapter/bin/mcp-adapter.php","serve","--server=mcp-adapter-default-server","--user=admin"],"env":{"WP_CLI_PHP_ARGS":"--no-header"}}'
> Note: --user=admin is the WordPress username (not display name), tied to the Application Password account. command must be the absolute path to PHP CLI, otherwise daemon mode can't find it on PATH.
Verify with claude mcp list:
$ claude mcp list
wordpress-mcp ✓ connected · 14 tools
In Claude Code, just ask: "Scan this site for broken internal links." Claude will auto-call site-audit/find-broken-links—no manual prompt engineering needed.
💣 4 real pitfalls and fixes
Pitfall 1: Abilities not detected (`Abilities=no`)
**Symptom**: wp eval returns Abilities=no even though you're on 7.0.
**Root cause**: WordPress 7.0's Abilities API is **modularly loaded**—it needs a plugin or theme to explicitly hook abilities_api_init to trigger registration. Just calling wp_register_ability() in functions.php doesn't work—it runs too early, before the hook is registered.
**Fix**: Wrap wp_register_ability() inside add_action( 'abilities_api_init', ... ), and make sure the plugin **loads after mcp-adapter** (add Requires Plugins: mcp-adapter to the plugin header).
Pitfall 2: Application Password 401 mismatch
**Symptom**: claude mcp list shows ✓ connected, but every Claude call returns 401 Unauthorized.
**Root cause**: WordPress 7.0's Application Passwords (post-6.9) only validates on **whitelisted REST API paths**—the Authorization: Basic ... header is only checked on wp-json/* routes. MCP Adapter's transport uses a custom endpoint /?mcp-adapter=1 by default, **not the REST router**, so password validation gets skipped entirely.
**Fix**: Add to wp-config.php:
// Force Application Password validation on all endpoints
add_filter( 'application_password_is_api_request', '__return_true' );
Or hook 'mcp_adapter.auth.use_application_passwords' => true during plugin init. I went with the first approach—less plugin surgery.
Pitfall 3: Streamable HTTP CORS blocked
**Symptom**: Claude Desktop shows CORS error: No 'Access-Control-Allow-Origin' header. Command-line Claude Code works fine—**only the GUI client** is affected.
**Root cause**: After the MCP spec deprecated SSE in late 2025, new clients default to streamable HTTP (long-lived chunked transfer connection). But streamable HTTP's preflight OPTIONS request is **not routed to MCP Adapter by WordPress**—WP_Rewrite doesn't know about this endpoint.
**Fix**: Manually add CORS headers in functions.php or an mu-plugin:
add_action( 'init', function () {
if ( strpos( $_SERVER['REQUEST_URI'] ?? '', 'mcp-adapter' ) !== false ) {
header( 'Access-Control-Allow-Origin: https://claude.ai' );
header( 'Access-Control-Allow-Methods: GET, POST, OPTIONS' );
header( 'Access-Control-Allow-Headers: Authorization, Content-Type, Mcp-Session-Id' );
header( 'Access-Control-Max-Age: 86400' );
if ( $_SERVER['REQUEST_METHOD'] === 'OPTIONS' ) {
status_header( 204 );
exit;
}
}
} );
> Don't use * for Access-Control-Allow-Origin—the MCP spec requires a real origin when the Authorization header is present.
Pitfall 4: Abilities namespace collision
**Symptom**: After upgrading to mcp-adapter 0.5.0, Claude gets Tool 'wp.posts.list' already exists on every call.
**Root cause**: mcp-adapter 0.5.0 (released 2026-06) **auto-registers core Abilities** (wp.posts.list / wp.posts.get / wp.options.update, etc.—14 total). If you manually registered the same wp.posts.list back in the 0.4.x era, both registrations stick around, mcp-adapter doesn't dedupe at CLI startup, and the server refuses to come up.
**Fix**: Delete your manually registered core Abilities (the ones mcp-adapter now provides itself) and only keep your custom site-audit/* ones. Use your own namespace prefix to avoid 99% of future collisions.
🛡️ Advanced: let AI actually write
Once the read-only Abilities work, the next step is letting AI write. I added a "batch update SEO description" Ability, and a sample prompt:
"For posts published this month that don't have an SEO description, extract the first paragraph and write a 160-character description."
Claude Code splits this into 4 steps: call wp.posts.list to filter → call wp.posts.get to read content → call my custom site-audit/generate-description → call wp.options.update to write Yoast/RankMath meta. **Full chain in 12 seconds**; manually this is 5 minutes per post.
Security controls (mandatory):
1. **Restrict source IPs**: in Nginx, add an allow whitelist to the ?mcp-adapter=1 path—only localhost + your Cloudflare Tunnel IP range
2. **Rate limit**: hook the application_passwords filter and add a quota, max 200 calls per hour
3. **Audit log**: MCP Adapter 0.5.0 has a built-in observability subsystem. Configure mcp-adapter/log to write to /var/log/mcp-adapter.log and rotate weekly
Summary and what's next
WordPress 7.0 + MCP Adapter + Abilities API + Claude Code: the four-piece stack that turns "AI editing WordPress" from "copy-paste HTML draft" into "AI calling site capabilities directly". My 3 evenings of debugging went mostly into the auth chain (Application Password validation path), the transport switch (SSE → streamable HTTP), and namespace isolation.
Two directions I might write next:
- **WordPress 7.0 collaborative editing hands-on**: the new default HTTP polling sync provider in 7.0, with real conflict-resolution tests for **multi-user simultaneous editing**
- **MCP security hands-on**: putting `mcp-adapter` behind a Cloudflare Tunnel (extending the 6/17 Cloudflare Tunnel zero-port article), so AI clients can only reach the MCP endpoint from a specific IP range
Related reading:
👉 Join MiniMax Token Plan: AI coding acceleration for businesses
👉 Join Zhipu Coding Plan: GLM-4.6/GLM-5 coding packages, China-stable, pay-per-token unlimited
👉 Join Aliyun AI: Top AI products with exclusive coupons for business innovation
📌 This article was AI-assisted generated and human-reviewed | TechPassive — An AI-driven content testing site focused on real tool reviews
🔗 Recommended Tools
These are carefully selected tools. Using our affiliate links supports us to keep producing quality content: