Production Realities

MCP Adapter Limitations and Workarounds

⏱ 14 min

Most of this course has shown you what the MCP Adapter does well. This lesson is the honest inventory of what it doesn’t do at all, as of this writing, so you stop looking for a setting that doesn’t exist and start building the workaround instead. None of this is a criticism of the project: the MCP Adapter is an actively developing plugin built by the WordPress AI team, and gaps like these are exactly what an early-stage piece of infrastructure looks like before a wider ecosystem of add-ons fills them in. Treat this lesson as a map of “build it yourself for now,” not “avoid this tool.”

What you'll learn in this lesson
What the adapter genuinely does not include
No OAuth, no built-in rate limiting, no RBAC beyond core capabilities, no built-in audit trail.
Why Application Passwords and cookie/nonce are the only auth options
And what that means for machine-to-machine integrations.
The real, working custom patterns from Course 4
Applied specifically to the gaps this lesson names.
Where to watch for this changing
The actual public roadmap, not speculation.
Prerequisites

This lesson assumes you completed MCP Security & Authentication for WordPress, since every workaround here is a direct application of that course’s custom rate-limiting and capability patterns to the specific gaps named below.

Step 1: No OAuth, at all

The MCP Adapter supports exactly two authentication mechanisms: WordPress Application Passwords, and cookie/nonce authentication for same-origin browser contexts. There is no OAuth flow built in, anywhere in the adapter. If a client or an integration partner asks “does this support OAuth,” the honest answer today is no, and the workaround is the same one Course 4 built: an Application Password issued per integration, treated with the same operational discipline (rotation, per-integration issuance, revocation on offboarding) an OAuth token would get.

// File: my-plugin.php
// There is no OAuth handshake to hook into. This is the entire auth surface:
// Application Passwords (wp-admin > Users > Profile, or the REST endpoint),
// or an authenticated cookie session for same-origin requests.

For genuinely external, third-party OAuth-expecting integrations, the honest path is a thin OAuth-to-Application-Password bridge you build and control yourself, not a setting inside the adapter.

Step 2: No built-in rate limiting

The adapter enforces no request-per-minute or request-per-user ceiling anywhere. Nothing stops a misbehaving or compromised client from calling an ability in a tight loop. Course 4’s transient-based rate limiter is the real answer, applied inside your own permission_callback:

// File: my-plugin.php
'permission_callback' => function () {
	$user_id = get_current_user_id();
	$key     = "my_plugin_rl_{$user_id}";
	$count   = (int) get_transient( $key );

	if ( $count >= 30 ) {
		return new WP_Error( 'rate_limited', __( 'Too many requests, try again shortly.', 'my-plugin' ) );
	}

	set_transient( $key, $count + 1, MINUTE_IN_SECONDS );
	return current_user_can( 'edit_posts' );
},

This lives entirely in your ability’s own code, the adapter has no concept of rate limiting to configure or override.

Step 3: No RBAC beyond core capabilities

The adapter doesn’t add a role or permission system of its own, permission_callback is just PHP, and whatever authorization model it expresses is entirely up to you. That’s usually fine, since WordPress’s own roles and capabilities are already a real RBAC system, but if you need something finer-grained (an “MCP client tier” concept that doesn’t map to an existing WordPress role) you build it as a custom capability, exactly as Course 4 covered:

// File: my-plugin.php
add_action( 'init', function () {
	$role = get_role( 'editor' );
	$role->add_cap( 'my_plugin_use_mcp_partner_tier' );
} );

Then check it, and only it, in the relevant abilities’ permission_callbacks. There’s no separate “MCP permissions” system to configure, it’s the same capability system you’ve always had.

Step 4: No built-in audit trail

Nothing in the adapter itself persists a durable, queryable log of “which user, via which client, called which ability, with what input, when.” The ErrorLogMcpErrorHandler default writes failures to the PHP error log, and the observability handler (Lesson 3) gives you a hook to build metrics, but neither one is a compliance-grade audit trail by itself. Build that as a custom observability handler that writes to a dedicated database table instead of a transient bucket, exactly the pattern Course 4 introduced for security event logging:

// File: my-plugin.php
class My_Plugin_Audit_Log_Handler implements \WP\MCP\Infrastructure\Observability\Contracts\McpObservabilityHandlerInterface {
	public function record_event( string $event, array $tags = array(), ?float $duration_ms = null ): void {
		global $wpdb;
		$wpdb->insert( "{$wpdb->prefix}mcp_audit_log", array(
			'event_time' => current_time( 'mysql' ),
			'user_id'    => get_current_user_id(),
			'event'      => $event,
			'ability'    => $tags['ability'] ?? null,
			'tags'       => wp_json_encode( $tags ),
		) );
	}
}
Gap, workaround, and where the pattern comes from
What’s missingWorkaroundCovered in depth
OAuthDisciplined Application Password issuance and rotationCourse 4
Rate limitingTransient counters inside permission_callbackCourse 4
Fine-grained RBACCustom capabilities added to roles, checked in permission_callbackCourse 4
Audit trailCustom McpObservabilityHandlerInterface writing to a real tableThis course, Lesson 3

Test it

Confirm your rate limiter actually engages by calling an ability more than the threshold in under a minute through a real MCP client, and confirm the 31st call returns the rate_limited error rather than executing:

for i in $(seq 1 32); do
  wp-env run cli wp eval 'var_dump( wp_get_ability( "my-plugin/some-ability" )->has_permission( array() ) );'
done

The final few calls in that loop should return false or a WP_Error, not true.

These are current limitations, not permanent ones

The MCP Adapter is a young, actively maintained project. Treat this lesson’s contents as accurate today and worth re-checking against the project’s own changelog and the make.wordpress.org/ai/ roadmap before assuming any specific gap still exists a year from now.

Recap

As of this writing, the MCP Adapter has no OAuth, no built-in rate limiting, no RBAC system beyond WordPress’s own capabilities, and no persistent audit trail out of the box. Every one of those gaps has a real, working custom pattern, Application Password discipline, transient-based rate limiting, custom capabilities, and a database-backed observability handler, all of which Course 4 and this course have already built. None of this is a reason to avoid the adapter; it’s the current shape of an early-stage piece of infrastructure.

Resources & further reading

← Building Reusable Ability Libraries CI/CD Pipelines for MCP Plugins →