Securing the WordPress MCP Adapter: 8 Risks I Tested

The MCP Adapter lets AI agents like Claude talk to your WordPress site. It turns WordPress “abilities” into tools an AI can call, so you can ask an assistant to pull your orders, check SEO scores, or read site settings in plain language.

Before trusting it on a live site, I tested what it exposes on a clean WordPress. Some of what I found is not in the documentation, one surprise affects sites that never installed the adapter, and the biggest risk is not about MCP at all.

What the MCP Adapter actually does

An “ability” is a single action WordPress or a plugin registers, such as “read site info” or “delete a product.” The adapter publishes selected abilities over the Model Context Protocol so any compatible AI client can use them.

Abilities are private by default. One is reachable over the adapter only if it is marked public, and WordPress still runs that ability’s own permission check against the logged-in user before anything happens. That model is sound. The problems are in the defaults around it.

What I found testing it

Every result below was measured on 2026-10-24 on a fresh install: WordPress 7.1.3, MCP Adapter 0.7.1, with Elementor 4.3.5, WooCommerce 11.2.1, and Yoast SEO 28.7. I connected as an administrator, a shop manager, an author, and a subscriber.

A. Elementor starts an MCP server even without the adapter plugin

Check this one first. Elementor bundles the adapter and starts it for you. With only Elementor active and the MCP Adapter plugin never installed, the endpoint /wp-json/mcp/mcp-adapter-default-server was live.

Elementor runs on over 10 million sites, so many of them now serve an MCP server their owner never set up. The endpoint still requires a logged-in user, and I confirmed this on Elementor 4.3.5 only. Angie also ships the adapter. So does WP Rocket’s MCP integration: version 3.23.5.1 started this server and an OAuth one on its own, on an older bundled adapter (0.5.0). WooCommerce bundles it but did not start the server on its own.

Open /wp-json/ on your own site and search for mcp-adapter-default-server. If that route is present, your site is running an MCP server, whether or not you installed the adapter. An /mcp/ route on its own can also be an OAuth endpoint, so confirm the server route specifically.

Terminal: a curl of /wp-json lists the mcp-adapter-default-server route as live, and an anonymous POST to it returns 401

B. The three core abilities are public out of the box

On WordPress 7.1.3, three built-in abilities carry a public flag: site info, the current user’s profile, and environment info (PHP version, database version, WordPress version). The adapter treats that flag as “expose over MCP,” so these appear on the default server with no action from you.

Site info and environment info require the manage_options capability to actually run, which normally means an administrator, though a custom role can hold it too. User info returns the signed-in user’s own profile to any logged-in user. None of this is a breach on its own, but it means the default server is never truly empty.

C. Any subscriber can connect and list every tool

Connecting to the default server only needs the basic read capability. On a membership site or a store, that is every registered customer.

A subscriber who connected could list all 14 published abilities, including woocommerce/product-delete. They could not run the ones they lack permission for; the delete attempt returned “Permission denied.” The concern is disclosure: the full list of what the AI layer can do, including destructive actions, is visible to any account holder.

D. One tool runs everything, which breaks client approvals

The default server exposes just three tools, and one of them, execute-ability, runs any published ability by name. The AI client sees a single tool, not one tool per action.

This matters because some AI clients ask you to approve tools by name. In Claude Code, approving execute-ability once, for a harmless read, also approves the call that deletes a product, because it is the same tool. On the default server, that client cannot tell a read from a delete.

E. The common opt-out trick leaves a second door open

The advice you will find online is to hide a risky ability by setting its meta.mcp.public flag to false. That worked for MCP: the hidden ability returned “not found.”

But an ability marked show_in_rest is also reachable through the Abilities REST API. With the ability hidden from MCP, the same credential still ran it through its REST route and trashed a product. To close it you also need show_in_rest set to false, and even then another plugin may expose the same action through its own endpoint. The next finding explains why.

F. The credential is not scoped to MCP, and this is the big one

An AI client authenticates with a WordPress application password. People picture that as “the MCP login,” but it is not. It is a full WordPress API credential for the user behind it.

To prove it, I took the application password a client would use and skipped MCP entirely. The same password deleted a product through WooCommerce’s own REST endpoint, read every user’s email address, and changed the site title through the core REST API.

One application password from an AI client reaches the MCP server, WooCommerce REST, core REST and the Abilities REST API; locking down MCP closes only one of those doors

This reframes everything above. Locking down MCP shrinks the MCP surface, but the password still reaches WooCommerce, core, and the Abilities API directly, so none of it contains a leaked or over-privileged credential. The real security boundary is the capabilities of the user you connect as, not the tools you expose.

G. There is no audit trail by default

At the adapter’s own layer, the default server records nothing about which tool ran, for whom, or when. Your host, firewall, or an access log may still capture the raw requests, but the adapter adds no execution log of its own. WordPress also updates an application password’s “last used” time at most once a day, so even that is a coarse signal.

If an AI agent makes a change you did not expect, nothing from the adapter tells you what it did. You have to add logging yourself.

H. Prompt injection depends entirely on the model

If an ability returns text that visitors can write, such as comments or reviews, that text can carry hidden instructions to the AI. I planted a comment telling the assistant to delete two products, then asked it only to summarize the comments.

The results split by model. Three Claude models and the latest GPT model flagged the comment and refused; an older GPT model obeyed it, deleted both products, and reported the deletion as routine housekeeping. These were single runs, so treat them as observations, not guarantees. The point is that once you pre-approve execute-ability for a write-capable user, the model’s judgment is the last line of defense and it varies by model. Do not build on it; keep content-reading and write-capable sessions apart.

How to lock it down

Here is the configuration I tested and would run in production: no default server, one small server that exposes only what you need, a dedicated low-privilege user, and a log of every call.

Step 1: turn off the default server

If you do not need the auto-generated server, remove it with one filter. This also stops the copy that Elementor and other plugins start.

add_filter( 'mcp_adapter_create_default_server', '__return_false' );

In my test, the /mcp/mcp-adapter-default-server route was then gone.

Step 2: run one custom server with an explicit list

A custom server exposes only the abilities you name, and it lists each one as its own tool. That restores per-ability approval in the AI client, and it lets you require a capability of your choice to connect.

add_action( 'mcp_adapter_init', function ( $adapter ) {
    $adapter->create_server(
        'site-tools',                 // server id
        'mcp',                        // REST namespace
        'site-tools',                 // route: /wp-json/mcp/site-tools
        'Site tools (read-only)',
        'Read-only tools for the AI agent',
        'v1.0.0',
        array( \WP\MCP\Transport\HttpTransport::class ),
        \WP\MCP\Infrastructure\ErrorHandling\ErrorLogMcpErrorHandler::class,
        \WP\MCP\Infrastructure\Observability\NullMcpObservabilityHandler::class,
        array(                        // the only abilities this server exposes
            'core/get-site-info',
            'woocommerce/products-query',
            'yoast-seo/get-post-seo-data',
        ),
        array(),
        array(),
        static fn() => current_user_can( 'mcp_connect' ) // who may connect
    );
} );

In my test, this server showed three separate read-only tools, refused a user without the mcp_connect capability with a 403, and rejected any ability not on the list with “Tool not found.”

Step 3: connect as a dedicated low-privilege user

Create a WordPress user whose only job is this connection. Give it a custom role with just the capabilities the exposed abilities need, plus the mcp_connect capability from the server above. Never point an AI client at your own administrator account.

Resist the temptation to hand an agent manage_options just to make the core abilities work; that turns its password into an admin key for the whole REST API. Keep the password out of plaintext config files where you can, and revoke it the moment a client is retired.

Step 4: restrict who can even hold an application password

By default any logged-in user can create an application password for themselves, so on a store or membership site every customer can mint an API credential. Limit that to your dedicated service accounts.

add_filter( 'wp_is_application_passwords_available_for_user', function ( $available, $user ) {
    $allowed = array( 42 ); // user IDs of your MCP service accounts
    return in_array( (int) $user->ID, $allowed, true );
}, 10, 2 );

With this in place, a subscriber trying to create an application password got a 501 “not available for your account.” While you are here, disable XML-RPC if you do not use it, since application passwords work there too.

Step 5: log every call

The adapter fires a filter before each tool call. A few lines turn that into an audit log you can actually read.

add_filter( 'mcp_adapter_pre_tool_call', function ( $args, $tool_name ) {
    $log   = get_option( 'mcp_audit', array() );
    $log[] = array(
        gmdate( 'c' ),
        wp_get_current_user()->user_login,
        $tool_name,
        wp_json_encode( $args ),
        $_SERVER['REMOTE_ADDR'] ?? '',
    );
    update_option( 'mcp_audit', array_slice( $log, -500 ), false );
    return $args;
}, 10, 2 );

This filter only sees MCP calls, not the direct REST routes the same credential can reach, so keep your server access logs as the real record.

If you must stay on the default server

When you cannot move to a custom server yet, hide a risky ability on both channels at once, MCP and REST, or the REST route stays open.

add_filter( 'wp_register_ability_args', function ( $args, $name ) {
    if ( 'woocommerce/product-delete' === $name ) {
        $args['meta']['mcp']['public'] = false; // hide from MCP
        $args['meta']['show_in_rest'] = false;  // and from the REST API
    }
    return $args;
}, 10, 2 );

Exposure is not the same as authorization

It is tempting to treat the public flag as a security control. The WordPress core team’s announcement of the flag is explicit that it is not.

The public flag controls discoverability and client exposure. It does not make an ability executable without authorization, which must be enforced by the ability itself. – WordPress Core team, “A unified public exposure flag for Abilities in WordPress 7.1”

Hiding an ability reduces what an attacker can find. The wall that stops them is each ability’s permission check and the privileges of the account you connect as, as the table sums up.

Concern Default server Hardened custom server
Who can connect Any user with read Only a capability you choose
Abilities exposed All marked public Only the ones you list
Tools the AI sees One execute-ability One per ability, with read-only flags
Audit log None Yours, via a filter
Connect-user privileges Whatever account you point at it A dedicated low-privilege account

FAQs

Common questions about securing the WordPress MCP Adapter:

Do I have an MCP endpoint if I never installed the MCP Adapter?
You might. Elementor and some other plugins bundle the adapter and start a server on their own. Open /wp-json/ on your site and look for the mcp-adapter-default-server route. If it is there, you are running an MCP server whether or not you installed the adapter.
Can a regular subscriber do damage through MCP?
Not directly. A subscriber can connect and list every published ability, but each ability still checks capabilities before it runs, so a subscriber cannot execute an admin-only action. The real risk is disclosure of what the AI layer can do, plus any ability a plugin wrongly marks runnable by low-privilege users.
Is hiding an ability with meta.mcp.public enough?
No. That flag only hides the ability from MCP. The same ability is often still reachable through the REST API. To close both, also set show_in_rest to false, as shown in the hardening section.
Is an application password safe to give an AI client?
An application password is long and random, so it is not realistic to brute-force. The risk is that it is stored in plaintext in the client's config and is not limited to MCP. It works across the whole REST API with the privileges of its user. Create it for a dedicated low-privilege user and revoke it as soon as the client is retired.
Does WordPress log what the AI did?
Not by default. The default server records nothing, and the application password "last used" time updates at most once a day. Add a logger on the mcp_adapter_pre_tool_call filter to capture the user, tool, arguments, and IP of every call.
Should I disable MCP entirely if I do not use it?
Yes. If nothing on your site connects an AI agent, turn the default server off with the mcp_adapter_create_default_server filter set to false. That removes the endpoint, including copies that bundling plugins start, and leaves the abilities available for their normal uses.

Summary

The MCP Adapter’s model is sound, but its defaults are not: a server you did not ask for can be live, it exposes everything public through one tool, and it logs nothing. And the credential behind it is a full WordPress API key that reaches WooCommerce, core, and the Abilities API whether or not MCP is locked down. The account you connect as is the real boundary.

So turn off the default server, run a small custom one that exposes only what you need, connect as a dedicated low-privilege account, restrict who can hold an application password, and log every call. Then work through the WordPress hardening guide for the rest of your site.

Discussion
[  0 Comments  ]

Leave a Comment

To add code, use the buttons below. For instance, click the PHP button to insert PHP code within the shortcode. If you notice any typos, please let us know!