Real Authentication

How Authentication Actually Works in the MCP Adapter

⏱ 12 min

If you searched for how to secure a WordPress MCP server and landed on material talking about OAuth 2.1 redirect URIs, client registration, or authorization code flows, that material is not describing the plugin this track builds on. The MCP Adapter (github.com/WordPress/mcp-adapter) has no OAuth implementation. What it actually uses is WordPress’s existing authentication surface: Application Passwords for remote clients, and cookie/nonce authentication for logged-in browser sessions. This lesson walks through both, and why that’s genuinely a reasonable design, not a missing feature.

What you'll learn in this lesson
Why OAuth is not part of the MCP Adapter
Where the OAuth assumption comes from, and the different product it actually describes.
Application Passwords, the real remote auth mechanism
Basic Auth over HTTPS, resolved to a real WordPress user.
Cookie/nonce auth for browser-based sessions
How an already-logged-in admin session authenticates MCP requests.
The transport-level permission_callback
The default is_user_logged_in() gate every request passes through first.
Prerequisites

A working MCP server built with the MCP Adapter (Course 2), and at least one MCP client connected to it (Course 3). This lesson is conceptual, you won’t change any code yet.

Step 1: Where the OAuth assumption comes from

WordPress.com offers a separate, proprietary, hosted MCP product for its own platform, and that product does use OAuth. It is a different codebase, a different vendor relationship, and out of scope for anything self-hosted. The open-source MCP Adapter plugin, the one you install on your own WordPress site and the one every other lesson in this track uses, was never built with an OAuth flow. Confusing the two leads people to go looking for a client ID, a client secret, and a redirect URI setting that does not exist in this plugin, and to conclude (wrongly) that the adapter’s security model is somehow behind other MCP server implementations. It isn’t behind, it’s just built on a different, already-existing WordPress mechanism.

Two different products, do not conflate them

If you are evaluating WordPress.com’s hosted MCP offering specifically, its OAuth model is a legitimate thing to research on its own terms. But if you’re running the self-hosted MCP Adapter plugin covered in this track, there is no OAuth surface to configure, secure, or audit. Don’t spend time hardening a flow that isn’t there.

Step 2: Application Passwords, the real remote auth mechanism

For any MCP client connecting over HTTP (Claude Desktop’s remote connector, Cursor, a custom script), the adapter relies on WordPress core’s built-in Application Passwords feature, available since WordPress 5.6. An application password is a randomly generated credential tied to one specific WordPress user account. The client sends it as HTTP Basic Auth:

Authorization: Basic base64(username:xxxx xxxx xxxx xxxx xxxx xxxx)

WordPress core resolves that header to a real, existing WP_User, exactly the user who generated the password. There is no separate “MCP identity”, the agent is authenticating as a specific WordPress user, with that user’s exact roles and capabilities. This single fact is the foundation for the entire permissions module later in this course.

Application Passwords require HTTPS in practice

Basic Auth sends credentials in a reversible, base64-encoded form on every request. Over plain HTTP this is trivially interceptable. WordPress core will refuse to expose the Application Passwords UI at all on a non-HTTPS site (localhost is exempted for development). Never point a real AI client at an HTTP-only WordPress install.

Step 3: Cookie/nonce auth for browser-based sessions

Not every MCP interaction is a separate remote client. If your setup involves a browser-based tool acting on behalf of a user who is already logged into wp-admin, the adapter can rely on WordPress’s existing cookie authentication plus a nonce, the same mechanism the core REST API has used for years. The request carries the browser’s authentication cookies and an X-WP-Nonce header instead of an Authorization header. This path only makes sense for same-origin, browser-context tools, not for a desktop AI client talking to a remote site, that’s what Application Passwords are for.

Step 4: The transport-level gate every request passes through first

Before any individual ability’s own permission_callback runs, the MCP Adapter checks a server-wide transport permission callback. Unless you override it, that default is:

is_user_logged_in()

In practice this means: whichever authentication method resolved the request (an Application Password’s Basic Auth, or a cookie/nonce pair) must have produced a real, logged-in WordPress user, or the request is rejected before any tool is even considered. Course docs (docs/guides/transport-permissions.md in the adapter repository) cover how to replace this default with your own logic, which the permissions module later in this course builds on directly.

Remote AI clients authenticate with Application Passwords
HTTP Basic Auth, resolved to one specific WordPress user account.
Browser-context tools can use cookie/nonce auth
The same mechanism the core REST API already uses for logged-in sessions.
Every request passes a transport-level check first
Default is_user_logged_in(), replaceable with custom logic.
There is no adapter-native OAuth, session token, or client registry
The security boundary is entirely WordPress's existing user and capability system.

Test it: confirm which mechanism your connection is actually using

On your existing MCP server from Course 2, check which header your connected client is actually sending. A quick way, temporarily log the incoming authorization state from inside a transport permission callback:

// File: my-plugin.php
add_filter( 'mcp_adapter_default_server_config', function ( $config ) {
	$config['transport_permission_callback'] = function ( \WP_REST_Request $request ) {
		error_log( 'MCP auth header present: ' . ( $request->get_header( 'authorization' ) ? 'yes' : 'no' ) );
		error_log( 'MCP current user: ' . get_current_user_id() );
		return is_user_logged_in();
	};
	return $config;
} );

Make a request from your connected AI client, then check your site’s error log. You should see a real, non-zero user ID, confirming the request resolved to an actual WordPress account, not an anonymous or synthetic one.

Assuming 'authenticated' means 'safe'

Passing the transport-level is_user_logged_in() check only proves the request is tied to some real WordPress user. It says nothing about what that user is allowed to do. That’s the job of each ability’s own permission_callback, covered starting in the next module. Authentication and authorization are two separate questions, and this course treats them that way throughout.

Recap

The MCP Adapter has no OAuth flow, remote clients authenticate with WordPress Application Passwords over HTTPS, and browser-context tools can use cookie/nonce auth. Every request also passes a transport-level permission check first, is_user_logged_in() by default. The identity behind any MCP connection is always a real WordPress user account, which is exactly why the next lesson focuses on how you generate, name, and rotate those Application Passwords deliberately, rather than treating them as an afterthought.

Resources & further reading

Generating and Rotating Application Passwords for AI Clients →