Method

A Repeatable Debugging Workflow for MCP Adapter Issues

⏱ 14 min

Every confusing MCP Adapter problem you’ll hit falls into one of three buckets: the ability never registered in the first place, it registered but a permission check is rejecting the caller, or it’s executing and failing partway through. Almost all wasted debugging time comes from not knowing which bucket you’re in before you start changing code. This lesson builds the workflow the rest of the course keeps applying: reproduce the failure on demand, check what the log actually says, inspect the server with a real tool instead of guessing, then isolate which of the three buckets you’re actually dealing with.

What you'll learn in this lesson
Reproducing a failure with wp eval, not through an AI client
Removing the client and the model from the loop so you're debugging PHP, not a conversation.
Checking the error log before changing anything
What WP_DEBUG_LOG surfaces that a normal request hides.
Inspecting the server with MCP Inspector
Seeing exactly what a client would see, before blaming the client.
The three-bucket isolation method
Registration, permission, or execution, in that order, every time.
Prerequisites

At least one registered ability and a working MCP server (Courses 1 and 2). A local environment where you can enable WP_DEBUG and WP_DEBUG_LOG, and Node.js available for npx if you want to run MCP Inspector later in this course.

Step 1: Reproduce it without the AI client in the loop

The first mistake is debugging through the AI client that reported the problem. A chat client’s error message is often a paraphrase of a paraphrase, by the time you’re reading “the tool failed,” you’ve lost the actual JSON-RPC error and the actual PHP state that produced it. Reproduce the same call directly, with WP-CLI, so the only variable is your own code:

# File: terminal
wp eval '
$ability = wp_get_ability( "my-plugin/my-ability" );
var_dump( $ability );
if ( $ability ) {
	var_dump( $ability->execute( array( /* the exact args the client sent */ ) ) );
}
'

If wp_get_ability() itself returns null, you already know the problem is registration, not execution, and you can stop looking at your execute_callback entirely. That’s the whole point of reproducing outside the client: it tells you which bucket you’re in within the first minute, instead of after an hour of trial and error.

Testing as the wrong WordPress user

wp eval runs as whichever user WP-CLI is configured to act as, often none at all in a bare install. If the ability’s permission_callback checks current_user_can(), a bare wp eval will fail that check even when the ability is registered correctly. Use wp eval-file with an explicit wp_set_current_user() call, or wp shell --user=, so you’re testing as the same user your AI client actually authenticates as.

Step 2: Check the error log before changing any code

WordPress’s own debug log, not your imagination, is the fastest way to find out what actually happened. Turn it on if it isn’t already:

// File: wp-config.php
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );
define( 'WP_DEBUG_DISPLAY', false );

Then reproduce the failure and read wp-content/debug.log directly:

# File: terminal
tail -n 100 wp-content/debug.log | grep -i -E "abilit|mcp|doing_it_wrong"

A _doing_it_wrong() notice is easy to miss because WordPress core only surfaces it when WP_DEBUG is on, it does not appear in a normal request. Several of the real registration failures covered later in this course produce exactly this kind of notice, invisible until you turn debugging on. If you’ve been developing with WP_DEBUG off, turn it on before you conclude a registration “just silently fails for no reason.”

The adapter's own error handler also writes here

By default the MCP Adapter’s ErrorLogMcpErrorHandler writes execution failures to this same PHP error log with structured context (which tool, which user, what error). Lesson 7 covers reading and extending it in detail, for now just know it’s one more reason to check the log first.

Step 3: Inspect the server with a real tool, not a guess

Once you know the ability registers and the log is clean, the next step is confirming what an MCP client would actually see, without trusting the client’s own UI to render it accurately. The MCP Inspector, the Model Context Protocol project’s own testing tool, gives you that directly:

# File: terminal
npx @modelcontextprotocol/inspector \
  wp --path=/path/to/your/wordpress/site mcp-adapter serve \
  --server=my-plugin-content-server --user=admin

Inspector shows you the exact tool list, the exact schema for each tool, and lets you call one manually with arguments you control. Lesson 6 goes deep on Inspector; for this workflow, the important habit is using it before assuming a client-reported failure is a WordPress bug at all. Plenty of confusing “MCP Adapter is broken” reports turn out to be a specific client’s own quirks in how it renders a schema or handles an empty response, not the adapter.

Step 4: Isolate the failing layer

Work through these in order, every time
1
Registration
Does wp_has_ability() or wp_get_ability() confirm it exists at all? If not, stop here, nothing downstream matters yet.
2
Permission
Call the ability as the exact user your client authenticates as. A false from permission_callback looks identical to a missing ability from the outside.
3
Execution
If it registers and permission passes, the failure is inside execute_callback or the schema validation around it. Now you're debugging normal PHP.

Working through these in this specific order matters because the symptoms overlap. A tool that “doesn’t show up” in an AI client could be a genuine registration failure, or it could be an ability that registered fine but is being filtered out of that particular server’s tool list, or it could be a permission check quietly rejecting the connected user before the client ever renders it as a tool at all. Checking registration first, with wp_get_ability() directly, rules out the entire first category in seconds.

Test it: build a one-line triage command

A command worth keeping in your notes, since you’ll run some version of it constantly while working through this course:

# File: terminal
wp eval '
$name = "my-plugin/my-ability";
echo "registered: " . ( wp_has_ability( $name ) ? "yes" : "no" ) . PHP_EOL;
$ability = wp_get_ability( $name );
if ( $ability ) {
	echo "permission: " . ( $ability->permission_callback() ? "pass" : "fail" ) . PHP_EOL;
}
'

Run it, read the two lines it prints, and you already know which of the next three modules of this course you need.

Assuming a fix worked because the AI client stopped complaining

An AI client retrying, rephrasing, or silently giving up on a failed tool call can look a lot like success from the outside. Always confirm the underlying state change directly (the post exists, the option changed, the row got inserted), the same discipline the testing lessons in Course 2 already established, not just the absence of a visible error in the client.

Recap

Reproduce failures with wp eval so you’re debugging PHP directly, not a client’s paraphrase of an error. Check wp-content/debug.log with WP_DEBUG_LOG enabled before changing any code, _doing_it_wrong() notices from the Abilities API are invisible without it. Use MCP Inspector to confirm what a client would actually see instead of trusting a specific client’s rendering of a failure. Then isolate registration, then permission, then execution, in that order, every time. The next three lessons are all registration-layer problems, the most common place this workflow actually lands.

Resources & further reading

Why wp_register_ability() Returns NULL →