The MCP Adapter automatically creates a default server that provides core MCP functionality for WordPress abilities. This server acts as a bridge between AI agents and WordPress, allowing them to discover and execute WordPress abilities through the Model Context Protocol.
The MCP Adapter supports two approaches for exposing WordPress abilities to AI agents. You can use both at the same time — they serve different purposes.
The default server is created automatically when the plugin loads. It exposes three meta-tools that let AI agents dynamically discover and execute abilities with meta.public=true or an explicit meta.mcp.public=true. An explicit meta.mcp.public=false opts an otherwise public ability out of MCP:
mcp-adapter-discover-abilities— Lists all publicly available abilitiesmcp-adapter-get-ability-info— Retrieves the full schema for a specific abilitymcp-adapter-execute-ability— Executes an ability with provided parameters
Note: These are the MCP tool names that AI agents use when calling
tools/call. The underlying WordPress abilities are registered with/separators (e.g.,mcp-adapter/discover-abilities), butMcpNameSanitizerconverts/to-during registration so that tool names comply with the MCP specification character set. See Tool naming for details.
The AI agent navigates this layered interface: discover what is available, inspect what it needs, then execute. This keeps the MCP tools/list response small (only 3 tool schemas) regardless of how many abilities exist on the site.
The default server also auto-discovers public abilities with mcp.type set to resource or prompt, exposing them as MCP resources and prompts alongside the three meta-tools.
Best for:
- General-purpose AI integration where the set of abilities changes over time
- Sites with many plugins registering abilities — no server reconfiguration needed
- Scenarios where context window efficiency matters (only 3 tool schemas sent to the AI)
A custom server is created explicitly via the mcp_adapter_init hook. Each ability you list is registered as a standalone MCP tool, resource, or prompt with its own schema visible directly in tools/list:
add_action( 'mcp_adapter_init', function ( $adapter ) {
$adapter->create_server(
'content-server',
'mcp',
'content-server',
'Content Server',
'Focused content management tools',
'1.0.0',
array( \WP\MCP\Transport\HttpTransport::class ),
\WP\MCP\Infrastructure\ErrorHandling\ErrorLogMcpErrorHandler::class,
null, // observability handler
array( 'my-plugin/create-post', 'my-plugin/update-post' ), // tools
array(), // resources
array() // prompts
);
} );The AI agent sees my-plugin-create-post and my-plugin-update-post as individual tools with their full input schemas — no discovery step required.
Best for:
- Focused integrations exposing a small, well-defined set of tools
- Cases where AI agents need dedicated schemas with rich parameter descriptions
- When you want the AI to call tools directly without the discover-then-execute indirection
| Aspect | Default Server | Custom Server |
|---|---|---|
| Creation | Automatic on plugin load | Manual via mcp_adapter_init hook |
| Tool visibility | 3 meta-tools; abilities discovered at runtime | Each ability is a separate MCP tool |
| AI interaction | Discover → Inspect → Execute (3 steps) | Call tool directly (1 step) |
| Scalability | Unlimited abilities without growing tools/list |
Each tool adds to tools/list response |
| Schema detail | AI fetches schemas on demand via get-ability-info |
Full schemas visible immediately |
| Configuration | Zero-config; abilities opt in with meta.public=true |
Explicit ability list per server |
| Auto-discovery | Public resources and prompts are auto-discovered | Only explicitly listed components |
| Transport | HTTP (REST API) by default; STDIO via WP-CLI | Any transport you configure |
If you only want custom servers, disable the default server with the mcp_adapter_create_default_server filter. This filter is applied in McpAdapter::maybe_create_default_server() before any default abilities or the default server factory are registered:
add_filter( 'mcp_adapter_create_default_server', '__return_false' );When disabled, the three built-in meta-tools (mcp-adapter/discover-abilities, mcp-adapter/get-ability-info, mcp-adapter/execute-ability) are not registered and the default server endpoint is not created.
Use the mcp_adapter_default_server_config filter to modify the default server's configuration before it is created. The filter receives the full configuration array and must return an array — it is merged with defaults via wp_parse_args():
add_filter( 'mcp_adapter_default_server_config', function ( $config ) {
// Change the server name
$config['server_name'] = 'My Site MCP Server';
// Add a custom tool alongside the 3 meta-tools
$config['tools'][] = 'my-plugin/quick-search';
// Use a custom error handler
$config['error_handler'] = \MyPlugin\CustomErrorHandler::class;
return $config;
} );See the full default configuration below for all available keys.
When using the HTTP REST API transport, MCP clients must follow the session protocol defined by the MCP specification. The STDIO transport (WP-CLI) does not use HTTP sessions — session lifecycle is tied to the WP-CLI process instead.
Every HTTP client must complete an initialization handshake before sending any other MCP request:
- Initialize — Send a
POSTrequest with theinitializeJSON-RPC method. NoMcp-Session-Idheader is needed for this first request. - Capture the session ID — The response includes an
Mcp-Session-Idheader containing a UUID. Store this value. - Include the header on every subsequent request — All following
POSTandDELETErequests must include theMcp-Session-Idheader with the stored value. (The MCP specification also requires the header onGETrequests for SSE streaming, but SSE is not yet implemented —GETcurrently returns405 Method Not Allowed.) - Terminate when done — Send a
DELETErequest with theMcp-Session-Idheader to clean up the session.
# 1. Initialize and capture the session ID
SESSION_ID=$(curl -s -D - -X POST \
"https://yoursite.com/wp-json/mcp/mcp-adapter-default-server" \
--user "username:application_password" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"my-client","version":"1.0.0"}}}' \
| grep -i 'mcp-session-id' | awk '{print $2}' | tr -d '\r')
echo "Session ID: $SESSION_ID"
# 2. Send initialized notification (tells server the client is ready for requests)
curl -s -X POST \
"https://yoursite.com/wp-json/mcp/mcp-adapter-default-server" \
--user "username:application_password" \
-H "Content-Type: application/json" \
-H "Mcp-Session-Id: $SESSION_ID" \
-d '{"jsonrpc":"2.0","method":"notifications/initialized"}'
# 3. Use the session ID for subsequent requests
curl -s -X POST \
"https://yoursite.com/wp-json/mcp/mcp-adapter-default-server" \
--user "username:application_password" \
-H "Content-Type: application/json" \
-H "Mcp-Session-Id: $SESSION_ID" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'
# 4. Terminate the session when done
curl -s -X DELETE \
"https://yoursite.com/wp-json/mcp/mcp-adapter-default-server" \
--user "username:application_password" \
-H "Mcp-Session-Id: $SESSION_ID"| Condition | JSON-RPC Error Code | HTTP Status | Message |
|---|---|---|---|
Missing Mcp-Session-Id header on a non-initialize request |
-32600 (Invalid Request) |
400 | Missing Mcp-Session-Id header |
| Invalid or expired session ID | -32602 (Invalid Params) |
200 | Invalid or expired session |
| User not authenticated | -32010 (Unauthorized) |
401 | User not authenticated |
Two filters control session behavior:
mcp_adapter_session_max_per_user — Maximum number of concurrent sessions a single user can have on the current site. When the limit is reached the oldest session is automatically evicted. Default: 32.
// Allow at most 5 concurrent sessions per user on the current site
add_filter( 'mcp_adapter_session_max_per_user', function () {
return 5;
} );mcp_adapter_session_inactivity_timeout — Number of seconds a session can remain idle before it is considered expired. Default: DAY_IN_SECONDS (86 400 seconds / 24 hours).
// Expire sessions after 1 hour of inactivity
add_filter( 'mcp_adapter_session_inactivity_timeout', function () {
return HOUR_IN_SECONDS;
} );mcp_adapter_session_activity_update_interval — Minimum number of seconds between activity timestamp updates for an active session. Updating the timestamp on every request adds a database write; this interval throttles those writes. Default: 60 seconds.
// Update activity timestamp at most every 5 minutes
add_filter( 'mcp_adapter_session_activity_update_interval', function () {
return 5 * MINUTE_IN_SECONDS;
} );Sessions are stored in user meta and are cleaned up automatically when a new session is created or an existing session is validated.
The STDIO transport used by WP-CLI does not require HTTP sessions. Each wp mcp-adapter serve invocation runs as a single process with its own lifecycle, so session management is unnecessary:
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' | \
wp mcp-adapter serve --user=admin --server=mcp-adapter-default-server- Server ID:
mcp-adapter-default-server - Endpoint:
/wp-json/mcp/mcp-adapter-default-server - Transport: HTTP (MCP Streamable HTTP compliant)
- Authentication: Requires logged-in WordPress user with
readcapability (customizable via filters)
$wordpress_defaults = array(
'server_id' => 'mcp-adapter-default-server',
'server_route_namespace' => 'mcp',
'server_route' => 'mcp-adapter-default-server',
'server_name' => 'MCP Adapter Default Server',
'server_description' => 'Default MCP server for WordPress abilities discovery and execution',
'server_version' => 'v1.0.0',
'mcp_transports' => array( HttpTransport::class ),
'error_handler' => ErrorLogMcpErrorHandler::class,
'observability_handler' => NullMcpObservabilityHandler::class,
'tools' => array(
'mcp-adapter/discover-abilities',
'mcp-adapter/get-ability-info',
'mcp-adapter/execute-ability',
),
'resources' => array(),
'prompts' => array(),
);The default server includes three core abilities that provide MCP functionality:
The MCP Adapter uses a layered tooling approach where a small set of meta-abilities provides access to all WordPress abilities, solving the "too many tools problem" that affects MCP servers.
The Problem: In traditional MCP implementations, each capability would be exposed as a separate tool. When an AI agent connects, it requests tools/list and receives every tool's complete schema (name, description, input parameters, etc.). With dozens or hundreds of tools, this creates several issues:
- Context Window Bloat: Tool schemas consume significant portions of the AI's context window before any actual work begins
- Decision Paralysis: AI agents struggle to choose the right tool from an overwhelming list of options
- Scalability Limits: The system becomes unwieldy as the number of tools grows
The Solution: Rather than exposing each WordPress ability as a separate MCP tool, the default server exposes just three strategic meta-abilities that act as a gateway:
- Discover (
mcp-adapter/discover-abilities) - Lists all available WordPress abilities - Get Info (
mcp-adapter/get-ability-info) - Retrieves detailed schema for any specific ability - Execute (
mcp-adapter/execute-ability) - Executes any ability with provided parameters
This layered approach provides several key benefits:
- Minimal Context Consumption: Only 3 tool schemas are sent to the AI agent, regardless of how many WordPress abilities exist
- Dynamic Capability Discovery: WordPress plugins can register unlimited abilities without MCP server reconfiguration
- Progressive Information Loading: The AI only fetches detailed schemas for abilities it actually needs
- Cleaner Decision-Making: The AI navigates a simple, structured interface rather than choosing from hundreds of tools
- Future-Proof Scalability: New abilities are automatically discoverable through the existing gateway tools
The AI agent uses these three tools in combination to systematically explore and interact with the WordPress abilities ecosystem: first discovering what's available, then getting detailed information about relevant abilities, and finally executing the chosen actions.
Purpose: Lists all WordPress abilities that are publicly available via MCP.
MCP Method: tools/list
Security:
- Requires authenticated WordPress user
- Requires
readcapability (customizable viamcp_adapter_discover_abilities_capabilityfilter) - Only returns abilities with effective MCP public exposure
Behavior:
- Scans all registered WordPress abilities
- Excludes abilities starting with
mcp-adapter/(prevents self-referencing) - Filters to only include abilities exposed by
meta.public=trueormeta.mcp.public=true - Returns ability name, label, and description for each public ability
Output Format:
{
"abilities": [
{
"name": "my-plugin/create-post",
"label": "Create Post",
"description": "Creates a new WordPress post"
}
]
}Annotations:
readOnlyHint:true(does not modify data)destructiveHint:false(safe operation)idempotentHint:true(consistent results)openWorldHint:false(works with known abilities only)
Purpose: Provides detailed information about a specific WordPress ability.
MCP Method: tools/call with tool name mcp-adapter-get-ability-info
Input Parameters:
ability_name(required): The full name of the ability to query
Security:
- Requires authenticated WordPress user
- Requires
readcapability (customizable viamcp_adapter_get_ability_info_capabilityfilter) - Only works with abilities that have effective MCP public exposure
- Returns
ability_not_public_mcperror for non-public abilities
Output Format:
{
"name": "my-plugin/create-post",
"label": "Create Post",
"description": "Creates a new WordPress post",
"input_schema": {
"type": "object",
"properties": {...}
},
"output_schema": {...},
"meta": {...}
}Annotations:
readOnlyHint:true(does not modify data)destructiveHint:false(safe operation)idempotentHint:true(consistent results)openWorldHint:false(works with known abilities only)
Purpose: Executes any WordPress ability with provided parameters.
MCP Method: tools/call with tool name mcp-adapter-execute-ability
Input Parameters:
ability_name(required): The full name of the ability to executeparameters(required): Object containing parameters to pass to the ability
Security:
- Requires authenticated WordPress user
- Requires
readcapability (customizable viamcp_adapter_execute_ability_capabilityfilter) - Only executes abilities that have effective MCP public exposure
- Performs additional permission check on the target ability itself
- Double-checks permissions before execution as additional security layer
Execution Flow:
- Validates user authentication and capabilities
- Checks whether the target ability has effective MCP public exposure
- Verifies target ability exists
- Calls the target ability's permission callback
- Executes the target ability with provided parameters
- Returns structured response with success/error status
Output Format:
{
"success": true,
"data": {
// Result from the executed ability
}
}Error Format:
{
"success": false,
"error": "Error message describing what went wrong"
}Annotations:
readOnlyHint:false(may modify data depending on executed ability)openWorldHint:true(can execute any registered ability)
The default server implements a metadata-driven security model:
- Default Secure: Abilities are NOT accessible via MCP by default
- Explicit Opt-in: Abilities must set
meta.public=trueormeta.mcp.public=trueto be accessible - Granular Control:
meta.mcp.publiccan override the high-level public setting for MCP only
Example of Public MCP Ability:
wp_register_ability('my-plugin/safe-tool', [
'label' => 'Safe Tool',
'description' => 'A safe tool for MCP access',
'category' => 'site',
'execute_callback' => 'my_safe_callback',
'permission_callback' => function() {
return current_user_can('read');
},
'meta' => [
'public' => true, // This makes it accessible to clients, including MCP
]
]);All core abilities require:
- WordPress Authentication: User must be logged in (
is_user_logged_in()) - Capability Check: User must have required capability (default:
read) - MCP Exposure Check: Target ability must have effective MCP public exposure
You can customize required capabilities using WordPress filters:
// Require 'edit_posts' for discovering abilities
add_filter('mcp_adapter_discover_abilities_capability', function() {
return 'edit_posts';
});
// Require 'manage_options' for getting ability info
add_filter('mcp_adapter_get_ability_info_capability', function() {
return 'manage_options';
});
// Require 'publish_posts' for executing abilities
add_filter('mcp_adapter_execute_ability_capability', function() {
return 'publish_posts';
});# List all available tools
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' | \
wp mcp-adapter serve --user=admin --server=mcp-adapter-default-server
# Get info about a specific ability
echo '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"mcp-adapter-get-ability-info","arguments":{"ability_name":"my-plugin/create-post"}}}' | \
wp mcp-adapter serve --user=admin --server=mcp-adapter-default-server
# Execute an ability
echo '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"mcp-adapter-execute-ability","arguments":{"ability_name":"my-plugin/create-post","parameters":{"title":"Test Post","content":"Hello World"}}}}' | \
wp mcp-adapter serve --user=admin --server=mcp-adapter-default-server# Test with curl (requires authentication)
curl -X POST "https://yoursite.com/wp-json/mcp/mcp-adapter-default-server" \
--user "username:application_password" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'The default server uses structured error handling:
authentication_required: User not logged ininsufficient_capability: User lacks required WordPress capabilityability_not_found: Requested ability doesn't existability_not_public_mcp: Ability is not exposed via MCPmissing_ability_name: Required ability name parameter missing
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32008,
"message": "Permission denied: User lacks required capability: read"
}
}- Secure by Default: Only set
meta.public=truefor abilities that should be client-accessible; usemeta.mcp.public=falseto opt out of MCP specifically - Proper Permissions: Implement appropriate permission callbacks for your abilities
- Clear Documentation: Provide good labels and descriptions for your abilities
- Input Validation: Use proper input schemas to validate parameters
- User Management: Only grant MCP access to trusted users
- Capability Review: Regularly review which users have the required capabilities
- Monitor Usage: Use error logging to monitor MCP usage and potential security issues
- Custom Filters: Use capability filters to tighten security if needed
- Check that abilities have
meta.public=trueormeta.mcp.public=true, without an MCP-specific opt-out - Verify user is authenticated and has required capabilities
- Ensure abilities are properly registered during
wp_abilities_api_init
- Verify user authentication (logged in)
- Check user has required capability (default:
read) - Confirm the ability has public exposure and does not set
meta.mcp.public=false
- Ensure ability is registered before MCP server initialization
- Check ability name spelling and format
- Verify ability registration happens during
wp_abilities_api_initaction
- Creating Abilities - Learn how to create MCP-compatible abilities
- Transport Permissions - Customize server-wide authentication
- Error Handling - Implement custom error management