The WordPress MCP Adapter adds one REST endpoint to your site, /wp-json/mcp/mcp-adapter-default-server, and nearly every error it produces falls into five groups: the route is missing (rest_no_route), WordPress will not log the client in (401 with rest_forbidden), the client connects but sees no tools, the session breaks (Session not found, code -32005), or the transport itself drops.
This guide goes error by error: the exact response, the cause and the fix. Each one was reproduced on WordPress 7.1 with MCP Adapter 0.7.0 from the GitHub release zip, using curl, WP-CLI 2.12 and the @automattic/mcp-wordpress-remote proxy. Where a cause comes from an open issue rather than a test, the post says so.
Key takeaways
- Nearly every WordPress MCP Adapter error falls into five groups. The route is missing (rest_no_route), WordPress will not log the client in (401 rest_forbidden), the client sees no tools, the session breaks (-32005), or the transport drops. arvindkumar.ca
- A 404 rest_no_route means the REST API is running but nothing registered that path. Check that /wp-json/mcp lists the route, then look for an inactive plugin, a wrong path, plain permalinks, or the default server being disabled by the mcp_adapter_create_default_server filter. arvindkumar.ca
- WordPress gives every authentication failure the same 401 rest_forbidden response. Work through it in order: HTTPS, the Authorization header, the credentials, and plugins that disable Application Passwords, and test with /wp-json/wp/v2/users/me. arvindkumar.ca
- A client that looks connected but has no tools often has a proxy hiding an upstream 401 or 404, so read the proxy log. It can also be by design: the default server exposes only three discovery tools, and your own abilities need meta.public set and a valid category and schema. arvindkumar.ca
- Over HTTP, every request after initialize must send back the Mcp-Session-Id header. Since 0.7.0 it must also send MCP-Protocol-Version: 2025-11-25, and a -32005 Session not found error means the session expired, was evicted or ended, or belongs to another user, so run initialize again. arvindkumar.ca
- The claude.ai message “no MCP server was found at the provided URL” means sign-in worked but the handshake at the URL failed. Test the exact URL with curl from outside your network and look for redirects, private addresses or firewall blocks. arvindkumar.ca
- The STDIO server stops after 60 seconds because PHP’s default_socket_timeout applies to the socket Claude Code passes in. Start PHP with php -d default_socket_timeout=-1, or use HTTP instead. arvindkumar.ca
Summarised from this post, formatted for answer engines.
| What you see | Likely cause | Fix |
|---|---|---|
rest_no_route (HTTP 404) | Plugin inactive, wrong path, plain permalinks, or the default server disabled | Check /wp-json/mcp; fix the path or permalinks |
rest_forbidden (HTTP 401) | Wrong credentials, stripped Authorization header, or no HTTPS | Test with /wp-json/wp/v2/users/me; serve HTTPS |
| Connected, but no tools | The proxy hides an upstream 401 or 404, or no ability is public | Read the proxy log; set meta.public |
-32600 Missing Mcp-Session-Id header | Header not sent after initialize, or dropped by a proxy | Send it on every later request |
-32600 MCP-Protocol-Version header is required | Required since 0.7.0 | Send MCP-Protocol-Version: 2025-11-25 |
-32005 Session not found (HTTP 404) | Session expired, evicted, terminated, or owned by another user | Run initialize again; one user per client |
| “Your account was authorized, but no MCP server was found at the provided URL” | claude.ai signed in, but the handshake at the URL failed | Test the exact URL with curl from outside |
| STDIO server stops after 60 seconds | PHP’s default_socket_timeout on a socket stdin | php -d default_socket_timeout=-1 |
HPE_INVALID_CHUNK_SIZE (proxy) | Node could not parse the response framing; unconfirmed | Capture the raw response with curl -v |
Why does the endpoint return 404 rest_no_route?
WordPress answers {"code":"rest_no_route","message":"No route was found matching the URL and request method.","data":{"status":404}} when the REST API runs but nothing registered that path. First confirm the route exists (no login needed):
curl -s https://example.com/wp-json/mcp
You should see /mcp/mcp-adapter-default-server with POST, GET and DELETE. If the namespace is missing, the plugin is inactive, or something returned false on the mcp_adapter_create_default_server filter, which removes the default server and its route. The reporter of issue #367 blamed another plugin shipping its own copy of the adapter; the maintainers have not confirmed it, but 0.7.0 does deprecate bundled copies and warns when two load. If the route is listed, compare paths character by character.
Plain permalinks are the other classic cause: without rewrite rules /wp-json/ is not a REST address, and a test site returned the homepage HTML with a 200 status, which clients report as “not a valid MCP endpoint”. Pick any other permalink setting, or use https://example.com/?rest_route=/mcp/mcp-adapter-default-server, which works everywhere.
Why does a valid Application Password get 401 rest_forbidden?
Because WordPress gives every authentication failure here the same answer: {"code":"rest_forbidden","message":"Sorry, you are not allowed to do that.","data":{"status":401}}. A wrong password, an unknown username, no credentials and a correct password on a site without HTTPS all produced that identical response in testing. Check these in order:
- Is HTTPS on? Core supports Application Passwords only when
is_ssl()is true orWP_ENVIRONMENT_TYPEislocal(seewp_is_application_passwords_supported()). Otherwise the password is never checked, and your profile screen saysThe application password feature requires HTTPS, which is not enabled on this site.Test:curl -s https://example.com/wp-json/should listapplication-passwordsunderauthentication. Behind a TLS-terminating proxy, add the snippet below to wp-config.php above thewp-settings.phpline. - Did the header reach PHP? Some Apache and FastCGI setups drop
Authorization. The WordPress .htaccess carriesRewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]to pass it through; restore it if it was replaced, or ask your host. - Are the credentials right? Use the login name or email, not the display name.
curl -s https://example.com/wp-json/wp/v2/users/me --user "editor:xxxx xxxx xxxx xxxx xxxx xxxx"returns your user record when authentication works andrest_not_logged_inwhen it does not. - Did a plugin or host switch them off? Security plugins hook
wp_is_application_passwords_available, and then the section disappears from the profile entirely; the REST index test catches this too.
// Behind a proxy or load balancer that terminates TLS, tell WordPress the request was HTTPS.
if ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && 'https' === $_SERVER['HTTP_X_FORWARDED_PROTO'] ) {
$_SERVER['HTTPS'] = 'on';
}
Why is the client connected but there are no tools?
The first cause is a hidden failure. Through @automattic/mcp-wordpress-remote (version 0.5.1 was tested), a 401 or 404 from WordPress does not break the connection: the proxy answers initialize itself with "serverInfo":{"name":"WordPress MCP Remote Proxy"} and "capabilities":{"experimental":{"connectionFailed":{}}}, so the client looks connected and only the next call fails with Cannot process tools/list: WordPress connection failed during initialization, the WordPress error inside data. Set LOG_FILE and LOG_LEVEL=3 on the proxy, or open your client’s MCP log, find WordPress API error (401) or (404), and fix that.
The second is by design. The default server exposes three tools, mcp-adapter-discover-abilities, mcp-adapter-get-ability-info and mcp-adapter-execute-ability, and your abilities never appear in tools/list; the assistant must call discover first, the complaint behind issue #213. Ask it to list your site’s abilities; on WordPress 6.9 or later, core/get-site-info, core/get-user-info and core/get-environment-info should appear. Yours is missing when:
- it is not public. Abilities are private by default;
get-ability-infoanswersAbility "mysite/private-hello" is not exposed via MCP (mcp.public!=true). Set'meta' => array( 'public' => true ), or'mcp' => array( 'public' => false )insidemetato hide one from MCP only. - it never registered.
wp_register_ability()returnsnullwhencategoryis missing or unregistered, or when called outsidewp_abilities_api_init. WithWP_DEBUGon, a notice names the problem (The ability properties must contain a `category` string.); with it off, silence. - its schema fails validation. Since 0.7.0 a tool no MCP revision can represent is dropped from
tools/list, with a debug.log warning such asTool "mysite-bad-schema" is unavailable for MCP revision 2025-11-25: /inputSchema/properties/q: Expected object, got string.
A minimal public ability for a must-use plugin:
add_action( 'wp_abilities_api_init', function () {
wp_register_ability( 'mysite/public-hello', array(
'label' => 'Public hello',
'description' => 'Says hello to the current user.',
'category' => 'site',
'input_schema' => array(
'type' => 'object',
'properties' => array( 'name' => array( 'type' => 'string' ) ),
),
'execute_callback' => function ( $input ) {
$name = isset( $input['name'] ) ? sanitize_text_field( $input['name'] ) : wp_get_current_user()->display_name;
return array( 'message' => sprintf( 'Hello, %s.', $name ) );
},
'permission_callback' => function () {
return current_user_can( 'read' );
},
'meta' => array( 'public' => true ),
) );
} );
If your client needs the ability in tools/list itself, add it to the default server with the mcp_adapter_default_server_config filter; it appears as mysite-public-hello (slashes become dashes) with its own schema.
add_filter( 'mcp_adapter_default_server_config', function ( $config ) {
$config['tools'][] = 'mysite/public-hello';
return $config;
} );
Why does the server say Session not found (-32005)?
Over HTTP, initialize returns an Mcp-Session-Id response header that every later request must send back. Sessions live in user meta, capped at 32 per user and expired after 24 idle hours. The full handshake:
URL="https://example.com/wp-json/mcp/mcp-adapter-default-server"
AUTH="editor:xxxx xxxx xxxx xxxx xxxx xxxx"
SID=$(curl -s -D - -o /dev/null -X POST "$URL" --user "$AUTH" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"curl","version":"1.0"}}}' \
| grep -i '^mcp-session-id' | awk '{print $2}' | tr -d '\r')
curl -s -X POST "$URL" --user "$AUTH" \
-H "Content-Type: application/json" \
-H "Mcp-Session-Id: $SID" \
-H "MCP-Protocol-Version: 2025-11-25" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'
Three errors come out of this flow:
-32600 Invalid Request: Missing Mcp-Session-Id header(HTTP 400). The client is not sending it, or a proxy, WAF or cache strips headers it does not recognise.-32600 Invalid Request: MCP-Protocol-Version header is required for a 2025-11-25 session(HTTP 400). New in 0.7.0; only2024-11-05sessions may omit it. Clients that follow the spec send it; hand-written scripts and the adapter’s own documentation examples do not, and now fail.-32005 Session not found: Invalid or expired session(HTTP 404). The ID matches nothing for this user. It reproduces four ways: a made-up ID, a session ended withDELETE, a 33rdinitializeevicting the oldest session, and a session created by one user then sent with another user’s credentials, which happens when people share one connector configuration.
Issue #312 reports -32005 on prompts/list right after initialize on 0.5.0, with three list calls in flight. No maintainer has replied, but 0.6.0 added bounded retries around concurrent session writes, and the same parallel requests did not reproduce it on 0.7.0. Update first; if it stays intermittent, catch -32005, run initialize again and retry.
Why does claude.ai say no MCP server was found at the URL?
“Your account was authorized, but no MCP server was found at the provided URL, or your account doesn’t have access to it”, with an ofid_ reference, is a claude.ai connector message, not a WordPress one. Anthropic’s connector troubleshooting page gives the sequence: OAuth first, then an MCP handshake from Anthropic’s servers to the URL you typed. WordPress has no OAuth server and the adapter adds none (its site-owner guide says so), so an OAuth plugin on your site completed the sign-in and the handshake then failed. Usual reasons: a stray character in the URL, a redirect to https or www (HTTP clients usually drop the Authorization header when a redirect changes host), a hostname resolving to a private address, a firewall blocking Anthropic’s IP range, or an OAuth user with no WordPress account behind it. From outside your network, run curl -sI https://example.com/wp-json/mcp/mcp-adapter-default-server: anything other than 401 or 405, or any Location header, needs fixing. Issue #361 has no comments yet, so these are checks, not a diagnosis.
Why does the STDIO server stop after 60 seconds?
When Claude Code launches wp mcp-adapter serve as a local server, it hands the process a socket pair rather than a pipe. PHP’s default_socket_timeout (60 seconds by default) applies to sockets, so fgets( STDIN ) returns false after a minute of silence; the bridge reads that as end of input, logs MCP STDIO Bridge stopped and exits with code 0. Issue #308 reports it, and it reproduces with WP-CLI 2.12 and a Python socket pair: a 5-second timeout stopped the server after 5.0 seconds, and -1 kept it serving. The read loop in trunk still has no stream_set_timeout(), so start PHP with the timeout off, as in php -d default_socket_timeout=-1 /usr/local/bin/wp mcp-adapter serve --user=editor --server=mcp-adapter-default-server, or use HTTP. One more 0.7.0 change: a bare tools/list over STDIO now returns -32600 Invalid Request: The 2025 lifecycle requires initialization before this request, so send initialize first.
What should the production checklist cover?
- HTTPS end to end, with the
X-Forwarded-Protosnippet above behind a TLS-terminating proxy, and the final URL registered, never one that redirects. - One user and one Application Password per client, with the lowest role that does the job. Sessions belong to a user, so shared credentials cause
-32005, and revocation stays precise. - No caching or header rewriting on
/wp-json/mcp/.Authorization,Mcp-Session-IdandMCP-Protocol-Versionmust reach PHP unchanged, and theMcp-Session-Idresponse header must reach the client. - Abilities exposed on purpose.
meta.publiconly with a realpermission_callback,meta.mcp.public = falsefor sensitive ones, and an audit of anything promoted throughmcp_adapter_default_server_config, which is callable whether or not it is public.
Start with the two checks that cost nothing: curl -s https://example.com/wp-json/mcp proves the route exists, and users/me proves the password works. Then run the handshake above and keep the script; every error in this post shows up there before it reaches a chat window.
