Skip to content

MCP OAuth: rewrite rules not flushed on first activation — "couldn't reach the MCP server" #8558

Description

@MathieuLamiot

Context

The feature/mcp-oauth branch adds an MCP server at /wp-json/mcp/mcp-oauth-server with a full OAuth 2.1 flow. MCP clients such as Claude Code use two RFC-mandated discovery documents to start the handshake:

  • /.well-known/oauth-protected-resource (RFC 9728) — advertises the MCP resource
  • /.well-known/oauth-authorization-server (RFC 8414) — lists all OAuth endpoints

Both documents and the OAuth flow endpoints (/oauth/authorize, /oauth/token, etc.) are served via WordPress rewrite rules that are registered and flushed during plugin activation.

Bug & Workaround

Symptom: After deploying the plugin (or on a fresh activation), MCP clients report "couldn't reach the MCP server". Both /.well-known documents and /wp-json/mcp/mcp-oauth-server return HTTP 404.

Root cause (short version): The MCP OAuth subscribers that register and flush the rewrite rules are not wired into the activation routine, so WordPress never writes the new rules to the database on first activation.

Workaround: Go to Settings → Permalinks and click Save Changes (no modification needed). This triggers flush_rewrite_rules() and immediately makes all MCP endpoints reachable.

Acceptance criteria

  • Activating WP Rocket on a clean site immediately makes /.well-known/oauth-authorization-server return a valid RFC 8414 JSON document — no manual permalink flush required.
  • GET /wp-json/mcp/mcp-oauth-server (unauthenticated) returns HTTP 401 with a WWW-Authenticate: Bearer header (not 404).
  • Connecting Claude Code to the MCP URL starts the OAuth flow without any manual setup step.
  • Existing activation behaviour (htaccess, cache dirs, capabilities, preload, performance hints) is unaffected.

Additional information

Root cause 1 — MCP subscribers absent from the activation container

Activation::activate_plugin() (inc/Engine/Activation/Activation.php) builds its own isolated Container + Event_Manager with a fixed set of service providers. McpAuthServiceProvider and McpTransportServiceProvider are not included, so mcp_auth_subscriber and mcp_auth_discovery_subscriber are never added to the activation event manager. When do_action('rocket_activation') fires, Auth/Subscriber::on_activation() is never called — meaning SecretManager::ensure_secret() is skipped and flush_rewrite_rules() is never called for the MCP OAuth rules.

Proposed fix: Add both service providers and wire both subscribers into the activation event manager, following the existing pattern for performance_hints_warmup_subscriber:

// inc/Engine/Activation/Activation.php — after the existing addServiceProvider calls
$container->addServiceProvider( new McpAuthServiceProvider() );
$container->addServiceProvider( new McpTransportServiceProvider() );
$event_manager->add_subscriber( $container->get( 'mcp_auth_subscriber' ) );
$event_manager->add_subscriber( $container->get( 'mcp_auth_discovery_subscriber' ) );

Root cause 2 — McpAdapter::instance() fires mcp_adapter_init before subscribers are registered

In the original inc/main.php, McpAdapter::instance() is called at file-load time, before plugins_loaded fires:

// top of main.php — runs at plugin require time, before any WordPress hooks
if ( class_exists( McpAdapter::class ) ) {
    McpAdapter::instance();  // fires do_action('mcp_adapter_init') immediately
}
// ...
add_action( 'plugins_loaded', 'rocket_init' );  // subscribers registered here — too late

mcp_transport_subscriber listens on mcp_adapter_init to instantiate OAuthHttpTransport and call register_rest_route(). Because the action fires before rocket_init() registers any subscribers, the REST route is never registered and every request to /wp-json/mcp/mcp-oauth-server returns 404.

Proposed fix: Move the McpAdapter::instance() call inside rocket_init(), after $wp_rocket->load(), so subscribers are hooked before the action fires:

// inc/main.php — inside rocket_init(), after $wp_rocket->load()
if ( class_exists( McpAdapter::class ) ) {
    McpAdapter::instance();
}

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions