diff --git a/.wp-env.json b/.wp-env.json
index 3e92575..2525740 100644
--- a/.wp-env.json
+++ b/.wp-env.json
@@ -1,7 +1,7 @@
{
"$schema": "https://schemas.wp.org/trunk/wp-env.json",
"core": null,
- "plugins": ["WordPress/abilities-api", "./."],
+ "plugins": ["WordPress/abilities-api", "./.", "./demo"],
"env": {
"development": {
"config": {
diff --git a/README.md b/README.md
index 2ac2cf0..2414fdd 100644
--- a/README.md
+++ b/README.md
@@ -2,15 +2,18 @@
[*Part of the **AI Building Blocks for WordPress** initiative*](https://make.wordpress.org/ai/2025/07/17/ai-building-blocks)
-A canonical plugin for WordPress that provides the adapter for the WordPress Abilities API, enabling WordPress abilities to be exposed as
-MCP (Model Context Protocol) tools, resources, and prompts. This adapter serves as the foundation for integrating
-WordPress capabilities with AI agents through the MCP specification.
+A canonical plugin for WordPress that provides bidirectional integration with the Model Context Protocol (MCP). It enables WordPress abilities to be exposed as MCP tools, resources, and prompts, **and** allows WordPress to connect to external MCP servers and use their capabilities as local abilities. This adapter serves as the foundation for integrating WordPress capabilities with AI agents through the MCP specification.
+
+**Includes Demo Plugin**: A complete demonstration plugin showcasing real-world usage patterns, admin interface, and practical examples.
## Overview
-The MCP Adapter bridges the gap between WordPress's Abilities API and the Model Context Protocol (MCP), allowing
-WordPress applications to expose their functionality to AI agents in a standardized, secure, and extensible way. It
-provides a clean abstraction layer that converts WordPress abilities into MCP-compatible interfaces.
+The MCP Adapter bridges the gap between WordPress's Abilities API and the Model Context Protocol (MCP), enabling bidirectional integration:
+
+**Server Mode**: Expose WordPress abilities to AI agents as MCP tools, resources, and prompts.
+**Client Mode**: Connect to external MCP servers and use their capabilities as local WordPress abilities.
+
+This provides a clean abstraction layer that seamlessly integrates WordPress with the broader MCP ecosystem.
**Built for Extensibility**: The adapter ships with production-ready REST API and streaming transport protocols, plus a
default error handling system. However, it's designed to be easily extended - create custom transport protocols for
@@ -21,8 +24,14 @@ systems.
### Core Functionality
+**MCP Server Features:**
- **Ability-to-MCP Conversion**: Automatically converts WordPress abilities into MCP tools, resources, and prompts
- **Multi-Server Management**: Create and manage multiple MCP servers with unique configurations
+
+**MCP Client Features:**
+- **Remote MCP Integration**: Connect to external MCP servers and consume their capabilities
+- **Ability Registration**: Remote MCP tools, resources, and prompts become local WordPress abilities
+- **Multi-Client Management**: Connect to multiple external MCP servers simultaneously
- **Extensible Transport Layer**:
- **Built-in Transports**: REST API (`RestTransport`) and Streaming (`StreamableTransport`) protocols included
- **Custom Transport Support**: Implement `McpTransportInterface` to create custom communication protocols
@@ -379,6 +388,8 @@ if ( ! defined( 'WP_MCP_VERSION' ) || version_compare( WP_MCP_VERSION, '0.1.0',
## Basic Usage
+**Note**: For complete working examples, see the included [demo plugin](demo/) which provides practical implementations and an admin interface for testing MCP functionality.
+
### Creating an MCP Server
To create an MCP server, register a callback function to the `mcp_adapter_init` action hook. This callback function can accept one parameter, `$adapter`, which is an instance of the McpAdapter class that is used to create the MCP server.
@@ -404,6 +415,76 @@ add_action('mcp_adapter_init', function($adapter) {
});
```
+### Creating an MCP Client
+
+To connect to external MCP servers, register a callback function to the `mcp_client_init` action hook:
+
+```php
+add_action('mcp_client_init', function($adapter) {
+ $adapter->create_client(
+ 'ai-service', // Unique client identifier
+ 'https://api.example.com/mcp', // External MCP server URL
+ [ // Client configuration
+ 'auth' => [
+ 'type' => 'bearer',
+ 'token' => 'your-api-token',
+ ],
+ 'timeout' => 30,
+ ]
+ );
+});
+```
+
+Once connected, remote MCP capabilities automatically become available as WordPress abilities:
+
+```php
+// Remote MCP tool becomes a WordPress ability
+$result = wp_execute_ability('mcp_ai-service/analyze-content', [
+ 'content' => 'Text to analyze',
+ 'type' => 'sentiment'
+]);
+
+// Remote MCP resource becomes a WordPress ability
+$data = wp_execute_ability('mcp_ai-service/resource/user-profile', []);
+
+// Remote MCP prompt becomes a WordPress ability
+$prompt = wp_execute_ability('mcp_ai-service/prompt/seo-analysis', [
+ 'url' => 'https://example.com'
+]);
+```
+
+### Client Authentication
+
+The MCP client supports multiple authentication methods:
+
+```php
+// Bearer token authentication
+$config = [
+ 'auth' => [
+ 'type' => 'bearer',
+ 'token' => 'your-bearer-token'
+ ]
+];
+
+// API key authentication
+$config = [
+ 'auth' => [
+ 'type' => 'api_key',
+ 'key' => 'your-api-key',
+ 'header' => 'X-API-Key' // Optional, defaults to X-API-Key
+ ]
+];
+
+// Basic authentication
+$config = [
+ 'auth' => [
+ 'type' => 'basic',
+ 'username' => 'your-username',
+ 'password' => 'your-password'
+ ]
+];
+```
+
## Advanced Usage
### Custom Transport Implementation
diff --git a/composer.json b/composer.json
index 5814100..23fb981 100644
--- a/composer.json
+++ b/composer.json
@@ -70,7 +70,8 @@
}
},
"require": {
- "php": "^7.4 || ^8.0"
+ "php": "^7.4 || ^8.0",
+ "wordpress/abilities-api": "^0.1.0"
},
"require-dev": {
"automattic/vipwpcs": "^3.0",
diff --git a/composer.lock b/composer.lock
index f3841f8..a3913bc 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,8 +4,83 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "152ced40e029fe53fa4aec344dec806e",
- "packages": [],
+ "content-hash": "b04501b17efd97e00f51fb9b20aa64b1",
+ "packages": [
+ {
+ "name": "wordpress/abilities-api",
+ "version": "v0.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/WordPress/abilities-api.git",
+ "reference": "e0423b48ff6f2b92b2677c731d6ca1ccc493f97f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/WordPress/abilities-api/zipball/e0423b48ff6f2b92b2677c731d6ca1ccc493f97f",
+ "reference": "e0423b48ff6f2b92b2677c731d6ca1ccc493f97f",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.4 | ^8"
+ },
+ "require-dev": {
+ "automattic/vipwpcs": "^3.0",
+ "dealerdirect/phpcodesniffer-composer-installer": "^1.0",
+ "php-coveralls/php-coveralls": "^2.5",
+ "phpcompatibility/php-compatibility": "10.x-dev as 9.99.99",
+ "phpcompatibility/phpcompatibility-wp": "^2.1",
+ "phpstan/extension-installer": "^1.3",
+ "phpstan/phpstan": "^2.1.22",
+ "phpstan/phpstan-deprecation-rules": "^2.0.3",
+ "phpstan/phpstan-phpunit": "^2.0.3",
+ "phpunit/phpunit": "^8.5|^9.6",
+ "slevomat/coding-standard": "^8.0",
+ "squizlabs/php_codesniffer": "^3.9",
+ "szepeviktor/phpstan-wordpress": "^2.0.2",
+ "wp-coding-standards/wpcs": "^3.1",
+ "wp-phpunit/wp-phpunit": "^6.5",
+ "wpackagist-plugin/plugin-check": "^1.6",
+ "yoast/phpunit-polyfills": "^4.0"
+ },
+ "type": "library",
+ "extra": {
+ "installer-paths": {
+ "vendor/{$vendor}/{$name}/": [
+ "wpackagist-plugin/plugin-check"
+ ]
+ }
+ },
+ "autoload": {
+ "files": [
+ "includes/bootstrap.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "GPL-2.0-or-later"
+ ],
+ "authors": [
+ {
+ "name": "WordPress AI Team",
+ "homepage": "https://make.wordpress.org/ai/"
+ }
+ ],
+ "description": "AI Abilities for WordPress.",
+ "homepage": "https://github.com/WordPress/abilities-api",
+ "keywords": [
+ "abilities",
+ "ai",
+ "api",
+ "llm",
+ "wordpress"
+ ],
+ "support": {
+ "issues": "https://github.com/WordPress/abilities-api/issues",
+ "source": "https://github.com/WordPress/abilities-api"
+ },
+ "time": "2025-08-29T04:47:50+00:00"
+ }
+ ],
"packages-dev": [
{
"name": "automattic/vipwpcs",
diff --git a/demo/README.md b/demo/README.md
new file mode 100644
index 0000000..0b97164
--- /dev/null
+++ b/demo/README.md
@@ -0,0 +1,121 @@
+# MCP Adapter Demo Plugin
+
+A demonstration plugin showcasing the MCP Adapter functionality with practical examples and an admin interface for testing MCP server and client capabilities.
+
+## Overview
+
+This demo plugin provides:
+
+- **Admin Interface**: WordPress admin page for managing and testing MCP servers and clients
+- **Working Examples**: Practical implementation examples in the `examples/` directory
+- **Demo Abilities**: Sample WordPress abilities that demonstrate MCP integration patterns
+- **Real-world Usage**: Production-ready code patterns you can use in your own plugins
+
+## Features
+
+### Admin Interface
+
+The demo plugin adds an admin page under **Settings > MCP Settings** that provides:
+
+- **Server Management**: Create and configure MCP servers
+- **Client Management**: Connect to external MCP servers
+- **Testing Tools**: Interactive testing of MCP functionality
+- **Real-time Monitoring**: View server status and connection details
+
+### Dashboard Widget
+
+A clean dashboard widget that displays MCP status information:
+
+- **Server Status**: List of configured MCP servers with capability counts (tools, resources, prompts)
+- **Client Connections**: Connected MCP clients with connection status and server URLs
+- **Quick Actions**: Direct access to MCP settings and manual refresh functionality
+- **Core WordPress Styling**: Matches WordPress dashboard widgets exactly using minimal custom CSS
+
+### Demo Abilities
+
+The plugin registers several example abilities that demonstrate different MCP integration patterns:
+
+- **Tool Examples**: Interactive abilities that perform actions
+- **Resource Examples**: Data-providing abilities for information access
+- **Prompt Examples**: Template abilities for generating recommendations
+
+### Examples Directory
+
+The `examples/` directory contains standalone PHP files demonstrating:
+
+- Server setup and configuration
+- Client connection and usage
+- Error handling and observability
+- Transport customization
+- Real-world integration patterns
+
+## Installation
+
+### Requirements
+
+- WordPress 6.8+
+- MCP Adapter plugin (installed and activated)
+- PHP 7.4+
+
+### Installation Steps
+
+1. **Install MCP Adapter**: Ensure the main MCP Adapter plugin is installed and activated first
+2. **Install Demo Plugin**: Install this demo plugin
+3. **Activate Plugin**: Activate the MCP Adapter Demo plugin
+4. **Access Admin Interface**: Navigate to **Settings > MCP Settings** in your WordPress admin
+
+## Usage
+
+### Testing MCP Functionality
+
+1. Navigate to **Settings > MCP Settings** in WordPress admin
+2. Use the interface to create test servers and clients
+3. Monitor connections and test MCP operations
+4. Review the examples directory for code implementation details
+
+### Using as Reference
+
+The demo plugin serves as a reference implementation for:
+
+- **Plugin Structure**: How to structure an MCP-enabled plugin
+- **Ability Registration**: Proper patterns for registering WordPress abilities
+- **Server Configuration**: Best practices for MCP server setup
+- **Client Integration**: How to connect to external MCP servers
+- **Error Handling**: Robust error handling patterns
+- **Admin Integration**: Creating WordPress admin interfaces for MCP management
+
+## Examples Directory
+
+See the [examples README](examples/README.md) for detailed information about the included code examples and how to use them in your own projects.
+
+## Code Structure
+
+```
+demo/
+├── mcp-adapter-demo.php # Main plugin file
+├── includes/
+│ ├── DemoPlugin.php # Core plugin class
+│ ├── Autoloader.php # PSR-4 autoloader
+│ └── Admin/
+│ ├── McpTestPage.php # Admin interface implementation
+│ ├── DashboardWidget.php # Dashboard widget functionality
+│ └── assets/ # CSS and JavaScript files
+└── examples/ # Standalone usage examples
+```
+
+## Development
+
+This demo plugin follows WordPress coding standards and best practices:
+
+- **PSR-4 Autoloading**: Proper namespace and class loading
+- **WordPress Hooks**: Correct use of WordPress action and filter hooks
+- **Security**: Proper nonce handling and capability checks
+- **Error Handling**: Graceful error handling and user feedback
+
+## Contributing
+
+This demo plugin is part of the MCP Adapter project. For contributing guidelines, see the main [MCP Adapter repository](https://github.com/WordPress/mcp-adapter).
+
+## License
+
+GPL-2.0-or-later - same as WordPress and the main MCP Adapter plugin.
\ No newline at end of file
diff --git a/demo/examples/README.md b/demo/examples/README.md
new file mode 100644
index 0000000..31c6a3d
--- /dev/null
+++ b/demo/examples/README.md
@@ -0,0 +1,179 @@
+# MCP Adapter Examples
+
+This directory contains standalone PHP files demonstrating various MCP Adapter usage patterns and advanced features. Each example is self-contained and includes detailed comments explaining the implementation.
+
+## Overview
+
+These examples showcase practical implementation patterns for:
+
+- **Server Configuration**: Creating and configuring MCP servers
+- **Client Integration**: Connecting to external MCP services
+- **Custom Transport**: Implementing specialized communication protocols
+- **Observability**: Adding monitoring and metrics collection
+- **Error Handling**: Robust error management strategies
+- **Advanced Patterns**: Production-ready implementation techniques
+
+## Examples
+
+### [`server-usage.php`](server-usage.php)
+
+Demonstrates how to create and configure MCP servers with various options:
+
+- **Basic Server Creation**: Simple server setup with minimal configuration
+- **Advanced Configuration**: Servers with custom transports and error handlers
+- **Ability Exposure**: How to selectively expose WordPress abilities as MCP tools
+- **Multiple Servers**: Managing multiple servers with different configurations
+
+**Key Concepts:**
+- Server registration using `mcp_adapter_init` action
+- Transport method selection
+- Error handler configuration
+- Ability filtering and organization
+
+### [`client-usage.php`](client-usage.php)
+
+Shows how to connect to external MCP servers and use their capabilities:
+
+- **Basic Client Connection**: Connecting to public MCP services
+- **Authentication Patterns**: Bearer tokens, API keys, and basic auth
+- **Remote Capability Usage**: Using remote tools, resources, and prompts as local abilities
+- **Error Handling**: Graceful handling of connection failures
+
+**Key Concepts:**
+- Client registration using `mcp_client_init` action
+- Authentication configuration options
+- Automatic ability registration for remote capabilities
+- Connection monitoring and error recovery
+
+**Real Example:** Connects to WordPress Domains MCP service for domain search functionality.
+
+### [`observability-example.php`](observability-example.php)
+
+Demonstrates custom monitoring and metrics collection for MCP operations:
+
+- **Custom Observability Handler**: Creating handlers that integrate with your monitoring systems
+- **Event Tracking**: Recording MCP events and operations
+- **Performance Metrics**: Timing and performance measurement
+- **Integration Patterns**: Connecting with external monitoring tools
+
+**Key Concepts:**
+- Implementing `McpObservabilityHandlerInterface`
+- Event recording and context management
+- Performance timing and metrics
+- Custom logging and alerting integration
+
+**Use Cases:**
+- Integration with DataDog, New Relic, or custom analytics
+- Performance monitoring and optimization
+- Debugging and troubleshooting MCP operations
+
+### [`monitored-transport.php`](monitored-transport.php)
+
+Shows how to create custom transport implementations with built-in monitoring:
+
+- **Transport Wrapper Pattern**: Extending existing transports with additional functionality
+- **Request/Response Logging**: Detailed logging of MCP communications
+- **Performance Monitoring**: Built-in timing and performance tracking
+- **Custom Protocol Implementation**: Foundation for specialized transport protocols
+
+**Key Concepts:**
+- Extending base transport classes
+- Implementing monitoring at the transport layer
+- Request and response interception
+- Custom routing and protocol handling
+
+**Use Cases:**
+- Performance debugging and optimization
+- Compliance and audit logging
+- Integration with existing API gateway infrastructure
+- Custom authentication and security requirements
+
+## Usage Patterns
+
+### 1. Development and Testing
+
+During development, these examples help you:
+
+- **Understand Integration Points**: See where MCP Adapter hooks into WordPress
+- **Test Different Configurations**: Experiment with various server and client setups
+- **Debug Issues**: Use monitoring examples to troubleshoot problems
+- **Validate Functionality**: Ensure your MCP integration works correctly
+
+### 2. Production Implementation
+
+For production deployments:
+
+- **Reference Implementation**: Use examples as starting points for your own code
+- **Best Practices**: Follow established patterns for error handling and monitoring
+- **Security Patterns**: Implement proper authentication and authorization
+- **Performance Optimization**: Use monitoring examples to identify bottlenecks
+
+### 3. Custom Development
+
+When building custom functionality:
+
+- **Transport Development**: Use monitored transport as a foundation for custom protocols
+- **Observability Integration**: Adapt observability examples for your monitoring stack
+- **Error Handling**: Implement robust error management following example patterns
+- **Testing Strategies**: Use examples to validate your custom implementations
+
+## Getting Started
+
+### Prerequisites
+
+- WordPress 6.8+
+- MCP Adapter plugin installed and activated
+- MCP Adapter Demo plugin activated (provides the context these examples run in)
+- PHP 7.4+
+
+### Running the Examples
+
+1. **Install Dependencies**: Ensure MCP Adapter and Demo plugin are active
+2. **Review Code**: Examine the example files to understand the patterns
+3. **Copy and Modify**: Use examples as starting points for your own implementations
+4. **Test Integration**: Use the demo plugin admin interface to test your configurations
+
+### Customization
+
+Each example includes commented sections showing:
+
+- **Configuration Options**: Different ways to configure servers and clients
+- **Extension Points**: Where to add your own custom logic
+- **Integration Patterns**: How to connect with your existing WordPress functionality
+- **Error Scenarios**: How to handle various failure conditions
+
+## Integration with Demo Plugin
+
+These examples work in conjunction with the demo plugin's admin interface:
+
+- **Live Testing**: Use the admin interface to test server and client configurations
+- **Real-time Monitoring**: See the results of observability examples in action
+- **Interactive Debugging**: Use the interface to trigger and observe MCP operations
+- **Configuration Validation**: Verify that your custom configurations work correctly
+
+## Best Practices
+
+### Development
+
+- **Start Simple**: Begin with basic server or client examples
+- **Incremental Enhancement**: Add monitoring and error handling gradually
+- **Test Thoroughly**: Use the demo plugin interface to validate functionality
+- **Document Changes**: Keep track of customizations for maintenance
+
+### Production
+
+- **Security First**: Implement proper authentication and authorization
+- **Monitor Everything**: Use observability examples for production monitoring
+- **Error Resilience**: Implement comprehensive error handling
+- **Performance Tracking**: Monitor timing and resource usage
+
+### Maintenance
+
+- **Version Compatibility**: Test examples with MCP Adapter updates
+- **Documentation**: Keep implementation documentation current
+- **Monitoring**: Maintain observability for ongoing operations
+- **Security Updates**: Regular review of authentication and security patterns
+
+## Contributing
+
+These examples are maintained as part of the MCP Adapter project. To contribute improvements or additional examples, see the main [MCP Adapter repository](https://github.com/WordPress/mcp-adapter).
\ No newline at end of file
diff --git a/demo/examples/client-usage.php b/demo/examples/client-usage.php
new file mode 100644
index 0000000..92800a4
--- /dev/null
+++ b/demo/examples/client-usage.php
@@ -0,0 +1,51 @@
+create_client(
+ 'wpcom-domains',
+ 'https://wpcom-domains-mcp.a8cai.workers.dev/mcp',
+ array(
+ 'timeout' => 30,
+ )
+ );
+
+ if ( is_wp_error( $client ) ) {
+ error_log( 'Failed to connect to WordPress Domains: ' . $client->get_error_message() );
+ return;
+ }
+
+ // Remote tools are automatically registered as WordPress abilities:
+ // - mcp-wpcom-domains/searchdomains
+ // - mcp-wpcom-domains/checkdomainavailability
+ // - mcp-wpcom-domains/getsuggestedtlds
+} );
+
+// Example 2: Client with authentication
+/*
+add_action( 'mcp_client_init', function( $adapter ) {
+ $client = $adapter->create_client(
+ 'my-service',
+ 'https://api.example.com/mcp',
+ array(
+ 'auth' => array(
+ 'type' => 'bearer',
+ 'token' => 'your-api-token-here',
+ ),
+ 'timeout' => 30,
+ )
+ );
+} );
+*/
+
+// Example 3: Custom permission control
+add_action( 'init', function() {
+ add_filter( 'mcp_client_permission', function( $allowed ) {
+ return current_user_can( 'edit_others_posts' );
+ } );
+} );
\ No newline at end of file
diff --git a/demo/examples/monitored-transport.php b/demo/examples/monitored-transport.php
new file mode 100644
index 0000000..0eb1e40
--- /dev/null
+++ b/demo/examples/monitored-transport.php
@@ -0,0 +1,48 @@
+get_request_method() . ' ' . $context->get_endpoint() );
+
+ // Call parent handler
+ $response = parent::handle( $context );
+
+ // Log response timing
+ $duration = ( microtime( true ) - $start_time ) * 1000;
+ error_log( "MCP Response: {$response->get_status()} in {$duration}ms" );
+
+ return $response;
+ }
+}
+
+// Example: How to use monitored transport (not active)
+add_action( 'mcp_adapter_init', function( $adapter ) {
+ $adapter->create_server(
+ 'monitored-server',
+ 'mcp',
+ 'monitored',
+ 'Monitored Server',
+ 'Server with request/response logging',
+ '1.0.0',
+ array(
+ MonitoredRestTransport::class, // Custom transport with logging
+ ),
+ null,
+ null,
+ array(),
+ array(),
+ array()
+ );
+} );
+*/
\ No newline at end of file
diff --git a/demo/examples/observability-example.php b/demo/examples/observability-example.php
new file mode 100644
index 0000000..d15e1a2
--- /dev/null
+++ b/demo/examples/observability-example.php
@@ -0,0 +1,40 @@
+create_server(
+ 'monitored-server',
+ 'mcp',
+ 'monitored',
+ 'Monitored Server',
+ 'Server with custom observability tracking',
+ '1.0.0',
+ array(
+ \WP\MCP\Transport\Http\RestTransport::class,
+ ),
+ null,
+ SimpleMcpObservabilityHandler::class, // custom observability
+ array(),
+ array(),
+ array()
+ );
+} );
+*/
\ No newline at end of file
diff --git a/demo/examples/server-usage.php b/demo/examples/server-usage.php
new file mode 100644
index 0000000..5f41488
--- /dev/null
+++ b/demo/examples/server-usage.php
@@ -0,0 +1,55 @@
+create_server(
+ 'custom-server',
+ 'mcp',
+ 'custom',
+ 'Custom Server',
+ 'Example of creating a custom MCP server',
+ '1.0.0',
+ array(
+ \WP\MCP\Transport\Http\RestTransport::class,
+ ),
+ null,
+ null,
+ array( 'your-ability-name' ), // specify abilities to expose
+ array(), // resources
+ array() // prompts
+ );
+} );
+*/
+
+// Example 2: Server with custom permissions (commented out)
+/*
+add_action( 'mcp_adapter_init', function( $adapter ) {
+ $adapter->create_server(
+ 'admin-server',
+ 'mcp',
+ 'admin',
+ 'Admin Server',
+ 'Admin-only MCP server',
+ '1.0.0',
+ array(
+ \WP\MCP\Transport\Http\RestTransport::class,
+ ),
+ null,
+ null,
+ array(),
+ array(),
+ array(),
+ function() { return current_user_can( 'manage_options' ); } // admin only
+ );
+} );
+*/
\ No newline at end of file
diff --git a/demo/includes/Admin/DashboardWidget.php b/demo/includes/Admin/DashboardWidget.php
new file mode 100644
index 0000000..eb24152
--- /dev/null
+++ b/demo/includes/Admin/DashboardWidget.php
@@ -0,0 +1,253 @@
+ wp_create_nonce( 'mcp_dashboard_nonce' ),
+ 'ajaxurl' => admin_url( 'admin-ajax.php' ),
+ ) );
+ }
+
+ /**
+ * Render the widget content.
+ */
+ public function render_widget_content(): void {
+ $servers = $this->get_mcp_servers();
+ $clients = $this->get_mcp_clients();
+ ?>
+
+ get_servers() ) {
+ $adapter->mcp_adapter_init();
+ }
+
+ $mcp_servers = $adapter->get_servers();
+ if ( is_array( $mcp_servers ) ) {
+ foreach ( $mcp_servers as $server_id => $server ) {
+ if ( $server ) {
+ $servers[ $server_id ] = array(
+ 'name' => $server->get_server_name(),
+ 'description' => $server->get_server_description(),
+ 'tools_count' => count( $server->get_tools() ),
+ 'resources_count' => count( $server->get_resources() ),
+ 'prompts_count' => count( $server->get_prompts() ),
+ );
+ }
+ }
+ }
+ }
+ }
+
+ // Cache for 2 minutes
+ set_transient( $cache_key, $servers, 2 * MINUTE_IN_SECONDS );
+
+ return $servers;
+ }
+
+ /**
+ * Get MCP clients information.
+ *
+ * @return array MCP clients data.
+ */
+ private function get_mcp_clients(): array {
+ $cache_key = 'mcp_dashboard_clients_' . get_current_user_id();
+ $cached_data = get_transient( $cache_key );
+
+ if ( false !== $cached_data ) {
+ return $cached_data;
+ }
+
+ $clients = array();
+
+ // Check if MCP Adapter is available
+ if ( class_exists( '\WP\MCP\Core\McpAdapter' ) ) {
+ $adapter = \WP\MCP\Core\McpAdapter::instance();
+ if ( $adapter ) {
+ // Ensure MCP adapter has been initialized
+ $adapter->mcp_adapter_init();
+
+ $mcp_clients = $adapter->get_clients();
+ if ( is_array( $mcp_clients ) ) {
+ foreach ( $mcp_clients as $client_id => $client ) {
+ if ( $client ) {
+ $clients[ $client_id ] = array(
+ 'server_url' => $client->get_server_url(),
+ 'connected' => $client->is_connected(),
+ );
+ }
+ }
+ }
+ }
+ }
+
+ // Cache for 1 minute
+ set_transient( $cache_key, $clients, MINUTE_IN_SECONDS );
+
+ return $clients;
+ }
+
+ /**
+ * Handle AJAX refresh request.
+ */
+ public function handle_ajax_refresh(): void {
+ check_ajax_referer( 'mcp_dashboard_nonce', 'nonce' );
+
+ if ( ! current_user_can( 'read' ) ) {
+ wp_die( 'Unauthorized' );
+ }
+
+ // Clear cache to force fresh data
+ $this->clear_cache();
+
+ // Get fresh data
+ $servers = $this->get_mcp_servers();
+ $clients = $this->get_mcp_clients();
+
+ wp_send_json_success( array(
+ 'servers' => $servers,
+ 'clients' => $clients,
+ ) );
+ }
+
+ /**
+ * Clear widget cache.
+ */
+ public function clear_cache(): void {
+ $user_id = get_current_user_id();
+ delete_transient( 'mcp_dashboard_servers_' . $user_id );
+ delete_transient( 'mcp_dashboard_clients_' . $user_id );
+ }
+}
\ No newline at end of file
diff --git a/demo/includes/Admin/McpAdminPage.php b/demo/includes/Admin/McpAdminPage.php
new file mode 100644
index 0000000..aff58c6
--- /dev/null
+++ b/demo/includes/Admin/McpAdminPage.php
@@ -0,0 +1,977 @@
+ wp_create_nonce( 'mcp_nonce' ),
+ 'ajaxurl' => admin_url( 'admin-ajax.php' ),
+ ) );
+
+ wp_enqueue_style(
+ 'mcp-admin',
+ plugins_url( 'assets/mcp-admin.css', __FILE__ ),
+ array(),
+ '1.0.0'
+ );
+ }
+
+ /**
+ * Add admin page to WordPress menu.
+ */
+ public function add_admin_page(): void {
+ add_options_page(
+ 'MCP Servers & Clients',
+ 'MCP',
+ 'manage_options',
+ 'mcp-settings',
+ array( $this, 'render_page' )
+ );
+ }
+
+ /**
+ * Register settings.
+ */
+ public function register_settings(): void {
+ register_setting( 'mcp_settings', 'mcp_servers', array(
+ 'type' => 'array',
+ 'default' => array(),
+ ) );
+ }
+
+ /**
+ * Render the settings page.
+ */
+ public function render_page(): void {
+ $servers = get_option( 'mcp_servers', array() );
+ ?>
+
+
MCP Integration
+
Manage bidirectional MCP integration: connect to external MCP servers and expose WordPress capabilities as MCP servers.
+
+ get_registered_mcp_servers(); ?>
+
+ Currently running MCP servers with get_available_abilities() ); ?> available abilities and get_registered_mcp_clients() ); ?> active client connections.
+
+
+
+
+
+
+
MCP Client Connections
+
External MCP servers that WordPress connects to as a client. Remote tools, resources, and prompts become available as WordPress abilities.
+
+ get_registered_mcp_clients(); ?>
+
+
+
+
No MCP client connections are currently active. Client connections will appear here once they are configured via code.
+
+
+
+ $client_info ) : ?>
+
+
+
+
+
Server URL:
+
Status:
+
+ ✅ Connected
+
+ ❌ Disconnected
+
+
+
+
+
+
+
Remote MCP Capabilities Available as WordPress Abilities:
+ get_client_registered_abilities( $client_id );
+ if ( ! empty( $registered_abilities ) ) : ?>
+
+
+
+
+
+ ()
+
+
+
+
+
+
+
+
+
No abilities registered yet. MCP tools, resources, and prompts from this server are automatically registered with the mcp-/ prefix.
+
+
+
+
+
+
+
+
+
+
+
+
MCP client connections to external servers are configured programmatically using the mcp_client_init action hook.
+
+
How to Configure MCP Clients:
+
+ Edit your theme's functions.php or create a custom plugin
+ Use the mcp_client_init action hook
+ Create clients using $adapter->create_client()
+ Remote MCP capabilities become WordPress abilities automatically
+
+
+
Example:
+
add_action( 'mcp_client_init', function( $adapter ) {
+ $client = $adapter->create_client(
+ 'wpcom-domains', // Client ID
+ 'https://wpcom-domains-mcp.a8cai.workers.dev/mcp', // External MCP server URL
+ array(
+ 'timeout' => 30, // Configuration options
+ )
+ );
+} );
+
+
See examples/client-usage.php for complete working examples.
+
+
+
+
+
+
+
WordPress MCP Servers
+
MCP servers that your WordPress site exposes for external MCP clients to connect to:
+
+
+
+
No MCP servers are currently exposed. MCP servers will appear here once they are configured via code.
+
+
+
+ $server_info ) : ?>
+
+
+
+
+
+
+
+
Server Endpoint
+
+
Server URL:
+
Protocol: HTTP JSON-RPC
+
Authentication: WordPress REST API (optional)
+
Version:
+
+
+
+
+
+
Connect External MCP Clients:
+
{
+ "mcpServers": {
+ "": {
+ "serverUrl": ""
+ }
+ }
+}
+
+
+
+
+
+
+
+
+
+
+
+
MCP servers and their exposed capabilities are configured programmatically using the mcp_adapter_init action hook.
+
+
How to Configure MCP Servers:
+
+ Edit your theme's functions.php or create a custom plugin
+ Use the mcp_adapter_init action hook
+ Register abilities using wp_register_ability()
+ Create MCP servers using $adapter->create_server()
+
+
+
Example:
+
add_action( 'mcp_adapter_init', function( $adapter ) {
+ $adapter->create_server(
+ 'my-server', // Server ID
+ 'mcp', // Namespace
+ 'my-endpoint', // Route
+ 'My Server', // Name
+ 'Description', // Description
+ '1.0.0', // Version
+ [ RestTransport::class ],
+ null, null,
+ [ 'my_tool_ability' ], // Tools to expose
+ [ 'my_resource_ability' ], // Resources to expose
+ [ 'my_prompt_ability' ] // Prompts to expose
+ );
+} );
+
+
See examples/server-usage.php for complete working examples.
+
+
+
+
+
+
+
+ $auth_type );
+ switch ( $auth_type ) {
+ case 'bearer':
+ $auth['token'] = sanitize_text_field( $_POST['bearer_token'] ?? '' );
+ break;
+ case 'api_key':
+ $auth['key'] = sanitize_text_field( $_POST['api_key'] ?? '' );
+ $auth['header'] = sanitize_text_field( $_POST['api_header'] ?? 'X-API-Key' );
+ break;
+ case 'basic':
+ $auth['username'] = sanitize_text_field( $_POST['username'] ?? '' );
+ $auth['password'] = sanitize_text_field( $_POST['password'] ?? '' );
+ break;
+ }
+
+ // Save server config
+ $servers = get_option( 'mcp_servers', array() );
+ $servers[ $server_id ] = array(
+ 'name' => $server_name,
+ 'url' => $server_url,
+ 'auth' => $auth,
+ 'timeout' => $timeout,
+ 'ssl_verify' => (bool) $ssl_verify, // Ensure proper boolean
+ );
+
+ update_option( 'mcp_servers', $servers );
+
+ wp_send_json_success( 'Server saved successfully.' );
+ }
+
+ /**
+ * Handle AJAX delete server request.
+ */
+ public function handle_ajax_delete_server(): void {
+ check_ajax_referer( 'mcp_nonce' );
+
+ if ( ! current_user_can( 'manage_options' ) ) {
+ wp_die( 'Unauthorized' );
+ }
+
+ $server_id = sanitize_text_field( $_POST['server_id'] ?? '' );
+
+ if ( empty( $server_id ) ) {
+ wp_send_json_error( 'Server ID is required.' );
+ }
+
+ $servers = get_option( 'mcp_servers', array() );
+ if ( ! isset( $servers[ $server_id ] ) ) {
+ wp_send_json_error( 'Server not found.' );
+ }
+
+ unset( $servers[ $server_id ] );
+ update_option( 'mcp_servers', $servers );
+
+ wp_send_json_success( 'Server deleted successfully.' );
+ }
+
+ /**
+ * Handle AJAX get server request.
+ */
+ public function handle_ajax_get_server(): void {
+ check_ajax_referer( 'mcp_nonce' );
+
+ if ( ! current_user_can( 'manage_options' ) ) {
+ wp_die( 'Unauthorized' );
+ }
+
+ $server_id = sanitize_text_field( $_POST['server_id'] ?? '' );
+
+ if ( empty( $server_id ) ) {
+ wp_send_json_error( 'Server ID is required.' );
+ }
+
+ $servers = get_option( 'mcp_servers', array() );
+ if ( ! isset( $servers[ $server_id ] ) ) {
+ wp_send_json_error( 'Server not found.' );
+ }
+
+ wp_send_json_success( $servers[ $server_id ] );
+ }
+
+ /**
+ * Handle AJAX test request.
+ */
+ public function handle_ajax_test(): void {
+ check_ajax_referer( 'mcp_nonce' );
+
+ if ( ! current_user_can( 'manage_options' ) ) {
+ wp_die( 'Unauthorized' );
+ }
+
+ $server_url = sanitize_url( $_POST['server_url'] ?? '' );
+ $client_id = sanitize_text_field( $_POST['client_id'] ?? '' );
+
+ if ( empty( $server_url ) || empty( $client_id ) ) {
+ wp_send_json_error( 'Server URL and Client ID are required. Received: server_url="' . $server_url . '", client_id="' . $client_id . '"' );
+ }
+
+ try {
+ // Create a test client during the proper action hook
+ $test_client = null;
+ $client_error = null;
+
+ // Create a temporary hook to create our test client
+ $test_hook = function( $adapter ) use ( $client_id, $server_url, &$test_client, &$client_error ) {
+ $client = $adapter->create_client(
+ $client_id,
+ $server_url,
+ array(
+ 'timeout' => 10,
+ 'ssl_verify' => true, // Use proper SSL for HTTPS
+ )
+ );
+
+ if ( is_wp_error( $client ) ) {
+ $client_error = $client->get_error_message();
+ } else {
+ $test_client = $client;
+ }
+ };
+
+ // Add the temporary hook
+ add_action( 'mcp_client_init', $test_hook );
+
+ // Trigger the mcp_client_init action to create our test client
+ $adapter = McpAdapter::instance();
+ if ( ! $adapter ) {
+ wp_send_json_error( 'MCP Adapter is not available. Please check if the adapter is properly initialized.' );
+ }
+
+ // Manually trigger the client init action for testing
+ do_action( 'mcp_client_init', $adapter );
+
+ // Remove the temporary hook
+ remove_action( 'mcp_client_init', $test_hook );
+
+ // Check if client creation failed
+ if ( $client_error ) {
+ wp_send_json_error( 'Failed to create client: ' . $client_error );
+ }
+
+ if ( ! $test_client ) {
+ wp_send_json_error( 'Failed to create test client.' );
+ }
+
+ $client = $test_client;
+
+ $output = 'Connection Test Results ';
+
+ // Test connection
+ $output .= 'Connected: ' . ( $client->is_connected() ? '✅ Yes' : '❌ No' ) . '
';
+ $output .= 'Server URL: ' . esc_html( $client->get_server_url() ) . '
';
+ $output .= 'Client ID: ' . esc_html( $client->get_client_id() ) . '
';
+
+ if ( ! $client->is_connected() ) {
+ wp_send_json_error( $output . '❌ Failed to connect to server. Make sure the MCP server is running.
' );
+ }
+
+ // List tools
+ $tools = $client->list_tools();
+ $output .= 'Available Tools ';
+ if ( is_wp_error( $tools ) ) {
+ $output .= '❌ Error listing tools: ' . esc_html( $tools->get_error_message() ) . '
';
+ } else {
+ $tools_list = $tools['tools'] ?? array();
+ if ( empty( $tools_list ) ) {
+ $output .= 'No tools available
';
+ } else {
+ $output .= '';
+ foreach ( $tools_list as $tool ) {
+ $output .= '' . esc_html( $tool['name'] ) . ' ';
+ if ( isset( $tool['description'] ) ) {
+ $output .= ': ' . esc_html( $tool['description'] );
+ }
+ $output .= ' ';
+ }
+ $output .= ' ';
+ }
+ }
+
+ // List resources
+ $resources = $client->list_resources();
+ $output .= 'Available Resources ';
+ if ( is_wp_error( $resources ) ) {
+ $output .= '❌ Error listing resources: ' . esc_html( $resources->get_error_message() ) . '
';
+ } else {
+ $resources_list = $resources['resources'] ?? array();
+ if ( empty( $resources_list ) ) {
+ $output .= 'No resources available
';
+ } else {
+ $output .= '';
+ foreach ( $resources_list as $resource ) {
+ $output .= '' . esc_html( $resource['uri'] ) . ' ';
+ if ( isset( $resource['description'] ) ) {
+ $output .= ': ' . esc_html( $resource['description'] );
+ }
+ $output .= ' ';
+ }
+ $output .= ' ';
+ }
+ }
+
+ // List prompts
+ $prompts = $client->list_prompts();
+ $output .= 'Available Prompts ';
+ if ( is_wp_error( $prompts ) ) {
+ $output .= '❌ Error listing prompts: ' . esc_html( $prompts->get_error_message() ) . '
';
+ } else {
+ $prompts_list = $prompts['prompts'] ?? array();
+ if ( empty( $prompts_list ) ) {
+ $output .= 'No prompts available
';
+ } else {
+ $output .= '';
+ foreach ( $prompts_list as $prompt ) {
+ $output .= '' . esc_html( $prompt['name'] ) . ' ';
+ if ( isset( $prompt['description'] ) ) {
+ $output .= ': ' . esc_html( $prompt['description'] );
+ }
+ $output .= ' ';
+ }
+ $output .= ' ';
+ }
+ }
+
+ wp_send_json_success( $output );
+
+ } catch ( \Throwable $e ) {
+ wp_send_json_error( 'Exception: ' . $e->getMessage() );
+ }
+ }
+
+
+ /**
+ * Get all registered MCP clients.
+ *
+ * @return array Array of registered MCP clients with their information.
+ */
+ private function get_registered_mcp_clients(): array {
+ $adapter = \WP\MCP\Core\McpAdapter::instance();
+ if ( ! $adapter ) {
+ return array();
+ }
+
+ // Ensure MCP adapter has been initialized (this triggers mcp_client_init)
+ $adapter->mcp_adapter_init();
+
+ $clients = $adapter->get_clients();
+ $client_info = array();
+
+ foreach ( $clients as $client_id => $client ) {
+ if ( ! $client ) {
+ continue;
+ }
+
+ $client_info[ $client_id ] = array(
+ 'id' => $client_id,
+ 'server_url' => $client->get_server_url(),
+ 'connected' => $client->is_connected(),
+ 'capabilities' => $client->get_capabilities(),
+ );
+ }
+
+ return $client_info;
+ }
+
+ /**
+ * Get all registered MCP servers.
+ *
+ * @return array Array of registered MCP servers with their information.
+ */
+ private function get_registered_mcp_servers(): array {
+ $adapter = \WP\MCP\Core\McpAdapter::instance();
+ if ( ! $adapter ) {
+ return array();
+ }
+
+ // Ensure MCP adapter has been initialized
+ if ( ! $adapter->get_servers() ) {
+ $adapter->mcp_adapter_init();
+ }
+
+ $servers = $adapter->get_servers();
+ $server_info = array();
+
+ foreach ( $servers as $server_id => $server ) {
+ if ( ! $server ) {
+ continue;
+ }
+
+ $server_info[ $server_id ] = array(
+ 'id' => $server_id,
+ 'name' => $server->get_server_name(),
+ 'description' => $server->get_server_description(),
+ 'version' => $server->get_server_version(),
+ 'endpoint' => home_url( '/wp-json/' . $server->get_server_route_namespace() . '/' . $server->get_server_route() . '/' ),
+ 'namespace' => $server->get_server_route_namespace(),
+ 'route' => $server->get_server_route(),
+ 'tools_count' => count( $server->get_tools() ),
+ 'resources_count' => count( $server->get_resources() ),
+ 'prompts_count' => count( $server->get_prompts() ),
+ );
+ }
+
+ return $server_info;
+ }
+
+ /**
+ * Get available WordPress abilities.
+ *
+ * @return array Array of abilities with their information.
+ */
+ private function get_available_abilities(): array {
+ // Check if the Abilities API is available
+ if ( ! function_exists( 'wp_get_abilities' ) ) {
+ // Fallback to mock data if API not available
+ return array(
+ 'get_site_info' => array(
+ 'description' => 'Get basic WordPress site information (registered by MCP plugin)',
+ 'type' => 'tool',
+ ),
+ );
+ }
+
+ // Get real abilities from the Abilities API
+ $registered_abilities = wp_get_abilities();
+ $abilities = array();
+
+ foreach ( $registered_abilities as $ability ) {
+ $ability_name = $ability->get_name();
+ $abilities[ $ability_name ] = array(
+ 'description' => $ability->get_description() ?? 'No description available',
+ 'type' => 'tool', // Default to tool type
+ );
+ }
+
+ // If no abilities are registered yet, show what should be available
+ if ( empty( $abilities ) ) {
+ $abilities['get_site_info'] = array(
+ 'description' => 'Get basic WordPress site information (will be registered when MCP server initializes)',
+ 'type' => 'tool',
+ );
+ }
+
+ return $abilities;
+ }
+
+ /**
+ * Handle AJAX check server status request.
+ */
+ public function handle_ajax_check_server_status(): void {
+ check_ajax_referer( 'mcp_nonce' );
+
+ if ( ! current_user_can( 'manage_options' ) ) {
+ wp_die( 'Unauthorized' );
+ }
+
+ $server_id = sanitize_text_field( $_POST['server_id'] ?? '' );
+
+ if ( empty( $server_id ) ) {
+ wp_send_json_error( 'Server ID is required.' );
+ }
+
+ $servers = get_option( 'mcp_servers', array() );
+ if ( ! isset( $servers[ $server_id ] ) ) {
+ wp_send_json_error( 'Server not found.' );
+ }
+
+ $server = $servers[ $server_id ];
+
+ // Debug output array
+ $debug_info = array(
+ 'server_id' => $server_id,
+ 'server_config' => $server,
+ 'steps' => array(),
+ );
+
+ try {
+ $debug_info['steps'][] = 'Starting connection test...';
+
+ // Create a test client during the proper action hook
+ $test_client = null;
+ $client_error = null;
+
+ // Create a temporary hook to create our test client
+ $test_hook = function( $adapter ) use ( $server_id, $server, &$test_client, &$client_error, &$debug_info ) {
+ $debug_info['steps'][] = 'Inside mcp_client_init hook';
+
+ $config = array(
+ 'timeout' => intval( $server['timeout'] ?? 30 ),
+ 'ssl_verify' => isset( $server['ssl_verify'] ) ? (bool) $server['ssl_verify'] : true, // Proper boolean with default
+ );
+
+ // Add auth if configured
+ if ( ! empty( $server['auth'] ) && $server['auth']['type'] !== 'none' ) {
+ $config['auth'] = $server['auth'];
+ }
+
+ $debug_info['client_config'] = $config;
+ $debug_info['steps'][] = 'Calling adapter->create_client()';
+
+ $client = $adapter->create_client(
+ 'status-check-' . $server_id,
+ $server['url'],
+ $config
+ );
+
+ if ( is_wp_error( $client ) ) {
+ $client_error = $client->get_error_message();
+ $debug_info['client_creation_error'] = $client_error;
+ $debug_info['steps'][] = 'Client creation failed: ' . $client_error;
+ } else {
+ $test_client = $client;
+ $debug_info['steps'][] = 'Client created successfully';
+ }
+ };
+
+ // Add the temporary hook
+ add_action( 'mcp_client_init', $test_hook );
+ $debug_info['steps'][] = 'Added temporary hook';
+
+ // Trigger the mcp_client_init action to create our test client
+ $adapter = McpAdapter::instance();
+ if ( ! $adapter ) {
+ $debug_info['steps'][] = 'MCP Adapter not available';
+ wp_send_json_error( 'MCP Adapter is not available. Debug: ' . wp_json_encode( $debug_info ) );
+ }
+ $debug_info['steps'][] = 'Got MCP Adapter instance';
+
+ // Manually trigger the client init action for testing
+ do_action( 'mcp_client_init', $adapter );
+ $debug_info['steps'][] = 'Triggered mcp_client_init action';
+
+ // Remove the temporary hook
+ remove_action( 'mcp_client_init', $test_hook );
+ $debug_info['steps'][] = 'Removed temporary hook';
+
+ // Check if client creation failed
+ if ( $client_error ) {
+ $debug_info['steps'][] = 'Client creation failed: ' . $client_error;
+ wp_send_json_error( 'Failed to connect: ' . $client_error . 'Debug Info: ' . print_r( $debug_info, true ) . ' ' );
+ }
+
+ if ( ! $test_client ) {
+ $debug_info['steps'][] = 'No client created';
+ wp_send_json_error( 'Failed to create client.Debug Info: ' . print_r( $debug_info, true ) . ' ' );
+ }
+
+ $debug_info['steps'][] = 'Client created, checking connection status';
+ $debug_info['client_connected'] = $test_client->is_connected();
+ $debug_info['client_capabilities'] = $test_client->get_capabilities();
+
+ // Check connection
+ if ( ! $test_client->is_connected() ) {
+ $debug_info['steps'][] = 'Client not connected';
+
+ // Capture recent error log entries for debugging
+ $debug_info['recent_logs'] = $this->get_recent_mcp_logs();
+
+ wp_send_json_error( 'Server is not responding.Debug Info: ' . print_r( $debug_info, true ) . ' ' );
+ }
+
+ // Get capabilities
+ $tools = $test_client->list_tools();
+ $resources = $test_client->list_resources();
+ $prompts = $test_client->list_prompts();
+
+ $response_data = array(
+ 'connected' => true,
+ 'tools' => is_wp_error( $tools ) ? array() : ( $tools['tools'] ?? array() ),
+ 'resources' => is_wp_error( $resources ) ? array() : ( $resources['resources'] ?? array() ),
+ 'prompts' => is_wp_error( $prompts ) ? array() : ( $prompts['prompts'] ?? array() ),
+ );
+
+ wp_send_json_success( $response_data );
+
+ } catch ( \Throwable $e ) {
+ $debug_info['exception'] = $e->getMessage();
+ $debug_info['recent_logs'] = $this->get_recent_mcp_logs();
+
+ // Provide specific error context for server status checks
+ $error_details = array();
+
+ // Common connection issues and their explanations
+ if ( strpos( $e->getMessage(), 'cURL error' ) !== false ) {
+ if ( strpos( $e->getMessage(), 'Could not resolve host' ) !== false ) {
+ $error_details['reason'] = 'DNS Resolution Failed';
+ $error_details['explanation'] = 'The server hostname could not be resolved. Check if the URL is correct and the server is accessible.';
+ } elseif ( strpos( $e->getMessage(), 'Connection refused' ) !== false ) {
+ $error_details['reason'] = 'Connection Refused';
+ $error_details['explanation'] = 'The server is not accepting connections. The MCP server may be down or not running on the specified port.';
+ } elseif ( strpos( $e->getMessage(), 'timeout' ) !== false ) {
+ $error_details['reason'] = 'Connection Timeout';
+ $error_details['explanation'] = 'The server took too long to respond. The server may be slow or overloaded.';
+ } elseif ( strpos( $e->getMessage(), 'SSL' ) !== false ) {
+ $error_details['reason'] = 'SSL/TLS Error';
+ $error_details['explanation'] = 'There was an issue with the secure connection. Try disabling SSL verification for local development servers.';
+ } else {
+ $error_details['reason'] = 'Network Error';
+ $error_details['explanation'] = 'A network-level error occurred: ' . $e->getMessage();
+ }
+ } elseif ( strpos( $e->getMessage(), 'Invalid JSON' ) !== false ) {
+ $error_details['reason'] = 'Invalid Response Format';
+ $error_details['explanation'] = 'The server returned an invalid JSON response. This may not be a valid MCP server.';
+ } elseif ( strpos( $e->getMessage(), 'HTTP' ) !== false ) {
+ $error_details['reason'] = 'HTTP Error';
+ $error_details['explanation'] = 'The server returned an HTTP error: ' . $e->getMessage();
+ } else {
+ $error_details['reason'] = 'Unknown Error';
+ $error_details['explanation'] = $e->getMessage();
+ }
+
+ $error_context = array(
+ 'server_id' => $server_id,
+ 'server_url' => $server['url'] ?? 'Unknown',
+ 'error_details' => $error_details,
+ 'full_exception' => $e->getMessage(),
+ );
+
+ error_log( 'MCP Server Status Check Failed: ' . print_r( $error_context, true ) );
+
+ $error_message = $error_details['reason'] . ': ' . $error_details['explanation'];
+ wp_send_json_error( $error_message );
+ }
+ }
+
+ /**
+ * Get recent MCP-related log entries for debugging.
+ *
+ * @return array Recent log entries.
+ */
+ private function get_recent_mcp_logs(): array {
+ $logs = array();
+
+ // Try to read from WordPress debug log
+ $log_file = WP_CONTENT_DIR . '/debug.log';
+ if ( file_exists( $log_file ) && is_readable( $log_file ) ) {
+ $log_content = file_get_contents( $log_file );
+ $log_lines = explode( "\n", $log_content );
+
+ // Get the last 20 lines that contain "MCP"
+ $mcp_lines = array_filter( $log_lines, function( $line ) {
+ return stripos( $line, 'mcp' ) !== false;
+ } );
+
+ $logs = array_slice( array_values( $mcp_lines ), -10 ); // Last 10 MCP entries
+ }
+
+ // If no debug log or no MCP entries, return a placeholder
+ if ( empty( $logs ) ) {
+ $logs[] = 'No recent MCP log entries found. WordPress debug logging may not be enabled.';
+ }
+
+ return $logs;
+ }
+
+ /**
+ * Get WordPress abilities that were registered from a specific MCP client.
+ *
+ * @param string $client_id The MCP client ID.
+ * @return array Array of abilities registered from this client.
+ */
+ private function get_client_registered_abilities( string $client_id ): array {
+ if ( ! function_exists( 'wp_get_abilities' ) ) {
+ return array();
+ }
+
+ $all_abilities = wp_get_abilities();
+ $client_abilities = array();
+
+ // Create the expected prefix for this client's abilities
+ $prefix = 'mcp-' . sanitize_key( $client_id ) . '/';
+
+ foreach ( $all_abilities as $ability ) {
+ $ability_name = $ability->get_name();
+
+ // Check if this ability was registered from the specified client
+ if ( strpos( $ability_name, $prefix ) === 0 ) {
+ $client_abilities[] = array(
+ 'name' => $ability_name,
+ 'label' => $ability->get_label(),
+ 'description' => $ability->get_description(),
+ );
+ }
+ }
+
+ return $client_abilities;
+ }
+}
\ No newline at end of file
diff --git a/demo/includes/Admin/assets/dashboard-widget.css b/demo/includes/Admin/assets/dashboard-widget.css
new file mode 100644
index 0000000..3306213
--- /dev/null
+++ b/demo/includes/Admin/assets/dashboard-widget.css
@@ -0,0 +1,61 @@
+/**
+ * MCP Dashboard Widget - Minimal WordPress Core Styles
+ */
+
+/* Server ID styling */
+#mcp-widget .mcp-server-id {
+ color: #646970;
+ font-weight: 400;
+ font-size: 11px;
+}
+
+/* Stats styling */
+#mcp-widget .mcp-stats {
+ color: #646970;
+ font-size: 11px;
+}
+
+/* Connection status */
+#mcp-widget .mcp-connected {
+ color: #00a32a;
+ font-weight: 600;
+}
+
+#mcp-widget .mcp-disconnected {
+ color: #d63638;
+ font-weight: 600;
+}
+
+/* Actions */
+#mcp-widget .mcp-actions {
+ margin-top: 12px;
+ padding-top: 12px;
+ border-top: 1px solid #f0f0f1;
+}
+
+#mcp-widget .mcp-actions .button {
+ margin-right: 6px;
+}
+
+/* Loading state */
+#mcp-widget.loading {
+ opacity: 0.6;
+ pointer-events: none;
+}
+
+.mcp-loading-spinner {
+ display: inline-block;
+ width: 16px;
+ height: 16px;
+ border: 2px solid #c3c4c7;
+ border-radius: 50%;
+ border-top-color: #0073aa;
+ animation: spin 1s linear infinite;
+ margin-right: 6px;
+}
+
+@keyframes spin {
+ to {
+ transform: rotate(360deg);
+ }
+}
\ No newline at end of file
diff --git a/demo/includes/Admin/assets/dashboard-widget.js b/demo/includes/Admin/assets/dashboard-widget.js
new file mode 100644
index 0000000..7d6aa68
--- /dev/null
+++ b/demo/includes/Admin/assets/dashboard-widget.js
@@ -0,0 +1,206 @@
+/**
+ * MCP Dashboard Widget JavaScript
+ */
+
+(function($) {
+ 'use strict';
+
+ /**
+ * Refresh dashboard widget data
+ */
+ window.mcpDashboardRefresh = function() {
+ var $widget = $('#mcp_demo_overview');
+ var $button = $('.mcp-refresh');
+ var $widgetContent = $widget.find('#mcp-widget');
+
+ // Show loading state
+ $button.prop('disabled', true).text('Refreshing...');
+ $widgetContent.addClass('loading');
+
+ // Add loading spinner to button
+ $button.prepend(' ');
+
+ $.ajax({
+ url: mcpDashboard.ajaxurl,
+ type: 'POST',
+ data: {
+ action: 'mcp_dashboard_refresh',
+ nonce: mcpDashboard.nonce
+ },
+ success: function(response) {
+ if (response.success && response.data) {
+ updateWidgetContent(response.data);
+ showNotification('MCP status refreshed', 'success');
+ } else {
+ showNotification('Failed to refresh MCP status', 'error');
+ }
+ },
+ error: function(xhr, status, error) {
+ console.error('MCP refresh failed:', error);
+ showNotification('Network error while refreshing', 'error');
+ },
+ complete: function() {
+ // Remove loading state
+ $button.prop('disabled', false).text('Refresh');
+ $button.find('.mcp-loading-spinner').remove();
+ $widgetContent.removeClass('loading');
+ }
+ });
+ };
+
+ /**
+ * Update widget content with fresh MCP data
+ */
+ function updateWidgetContent(data) {
+ var $widget = $('#mcp_demo_overview .inside #mcp-widget');
+
+ // Update servers section
+ if (data.servers !== undefined) {
+ var $serversBlock = $widget.find('#mcp-servers');
+ var $serversList = $serversBlock.find('ul');
+
+ if (Object.keys(data.servers).length > 0) {
+ if (!$serversList.length) {
+ $serversBlock.find('p').remove();
+ $serversBlock.append('');
+ $serversList = $serversBlock.find('ul');
+ }
+
+ $serversList.empty();
+ $.each(data.servers, function(serverId, server) {
+ var $item = $('' +
+ '' + escapeHtml(server.name) + ' ' +
+ '(' + escapeHtml(serverId) + ') ' +
+ '' +
+ 'Tools: ' + (server.tools_count || 0) + ', ' +
+ 'Resources: ' + (server.resources_count || 0) + ', ' +
+ 'Prompts: ' + (server.prompts_count || 0) +
+ ' ' +
+ ' ');
+ $serversList.append($item);
+ });
+ } else {
+ $serversList.remove();
+ if (!$serversBlock.find('p').length) {
+ $serversBlock.append('No MCP servers are currently configured.
');
+ }
+ }
+ }
+
+ // Update clients section
+ if (data.clients !== undefined) {
+ var $clientsBlock = $widget.find('#mcp-clients');
+ var $clientsList = $clientsBlock.find('ul');
+
+ if (Object.keys(data.clients).length > 0) {
+ if (!$clientsList.length) {
+ $clientsBlock.find('p').remove();
+ $clientsBlock.append('');
+ $clientsList = $clientsBlock.find('ul');
+ }
+
+ $clientsList.empty();
+ $.each(data.clients, function(clientId, client) {
+ var statusClass = client.connected ? 'mcp-connected' : 'mcp-disconnected';
+ var statusText = client.connected ? '[Connected]' : '[Disconnected]';
+
+ var $item = $('' +
+ '' + escapeHtml(clientId) + ' ' +
+ '' + statusText + ' ' +
+ '' + escapeHtml(client.server_url || '') + '' +
+ ' ');
+ $clientsList.append($item);
+ });
+ } else {
+ $clientsList.remove();
+ if (!$clientsBlock.find('p').length) {
+ $clientsBlock.append('No MCP clients are currently connected.
');
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Show notification message
+ */
+ function showNotification(message, type) {
+ // Create notification element
+ var $notification = $('' +
+ message +
+ '× ' +
+ '
');
+
+ // Add notification styles if not already present
+ if (!$('#mcp-notification-styles').length) {
+ $('').appendTo('head');
+ }
+
+ // Add to page and auto-remove
+ $('body').append($notification);
+
+ // Handle close button
+ $notification.find('.mcp-notification-close').on('click', function() {
+ $notification.fadeOut(300, function() { $(this).remove(); });
+ });
+
+ // Auto-remove after 4 seconds
+ setTimeout(function() {
+ if ($notification.is(':visible')) {
+ $notification.fadeOut(300, function() { $(this).remove(); });
+ }
+ }, 4000);
+ }
+
+ /**
+ * Truncate text to specified word count
+ */
+ function truncateText(text, wordCount) {
+ var words = text.split(' ');
+ if (words.length <= wordCount) {
+ return text;
+ }
+ return words.slice(0, wordCount).join(' ') + '...';
+ }
+
+ /**
+ * Escape HTML to prevent XSS
+ */
+ function escapeHtml(text) {
+ var div = document.createElement('div');
+ div.textContent = text;
+ return div.innerHTML;
+ }
+
+ // Initialize when DOM is ready
+ $(document).ready(function() {
+ // Auto-refresh every 5 minutes
+ setInterval(function() {
+ if ($('#mcp_demo_overview').length && document.hasFocus()) {
+ mcpDashboardRefresh();
+ }
+ }, 300000); // 5 minutes
+
+ // Add keyboard shortcut for refresh (Ctrl/Cmd + Shift + R when widget is visible)
+ $(document).on('keydown', function(e) {
+ if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.which === 82) { // Ctrl/Cmd + Shift + R
+ if ($('#mcp_demo_overview').is(':visible')) {
+ e.preventDefault();
+ mcpDashboardRefresh();
+ }
+ }
+ });
+
+ console.log('MCP Dashboard Widget initialized');
+ });
+
+})(jQuery);
\ No newline at end of file
diff --git a/demo/includes/Admin/assets/mcp-admin.css b/demo/includes/Admin/assets/mcp-admin.css
new file mode 100644
index 0000000..858d752
--- /dev/null
+++ b/demo/includes/Admin/assets/mcp-admin.css
@@ -0,0 +1,274 @@
+/**
+ * MCP Admin Styles - WordPress Core Compatible
+ */
+
+.tab-content {
+ display: none;
+}
+
+.tab-content.active {
+ display: block;
+}
+
+/* Server items using WP postbox style */
+.server-item {
+ background: #fff;
+ border: 1px solid #ccd0d4;
+ box-shadow: 0 1px 1px rgba(0,0,0,.04);
+ margin: 20px 0;
+}
+
+.server-header {
+ border-bottom: 1px solid #eee;
+ padding: 8px 12px;
+ background: #fafafa;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.server-header h3 {
+ margin: 0;
+ font-size: 14px;
+ line-height: 1.4;
+ color: #23282d;
+ flex-grow: 1;
+}
+
+.server-actions {
+ display: flex;
+ gap: 4px;
+ flex-shrink: 0;
+}
+
+.server-actions button {
+ margin: 0;
+}
+
+.server-details {
+ padding: 12px;
+}
+
+.status-indicator {
+ padding: 1px 5px;
+ font-size: 11px;
+ font-weight: 600;
+ text-transform: uppercase;
+}
+
+.status-connected {
+ color: #00a32a;
+}
+
+.status-disconnected {
+ color: #d63638;
+}
+
+.status-unknown {
+ color: #dba617;
+}
+
+/* Abilities using WP list table style */
+.ability-item {
+ background: #f9f9f9;
+ border: 1px solid #ddd;
+ margin: 0;
+ padding: 8px 12px;
+ border-top: none;
+}
+
+.ability-item:first-child {
+ border-top: 1px solid #ddd;
+}
+
+.ability-name {
+ font-weight: 600;
+ color: #23282d;
+}
+
+.ability-type {
+ font-size: 11px;
+ color: #666;
+ text-transform: uppercase;
+ margin-left: 8px;
+}
+
+.ability-description {
+ margin-top: 3px;
+ font-size: 13px;
+ color: #666;
+ font-style: italic;
+}
+
+/* WordPress-style sections */
+.mcp-section {
+ background: #fff;
+ border: 1px solid #ccd0d4;
+ box-shadow: 0 1px 1px rgba(0,0,0,.04);
+ margin: 20px 0;
+}
+
+.mcp-section-header {
+ background: #fafafa;
+ padding: 8px 12px;
+ border-bottom: 1px solid #eee;
+ font-size: 14px;
+ font-weight: 600;
+ color: #23282d;
+}
+
+.mcp-section-content {
+ padding: 12px;
+}
+
+/* Stats using WordPress dashboard style */
+.mcp-stats {
+ margin-bottom: 20px;
+}
+
+.mcp-stats:after {
+ content: "";
+ display: table;
+ clear: both;
+}
+
+.mcp-stat {
+ float: left;
+ margin: 0 20px 0 0;
+ padding: 0;
+ text-align: center;
+ min-width: 80px;
+}
+
+.mcp-stat-number {
+ font-size: 32px;
+ font-weight: 400;
+ color: #23282d;
+ line-height: 1.2;
+}
+
+.mcp-stat-label {
+ font-size: 13px;
+ color: #666;
+}
+
+/* Form sections */
+.mcp-form-section {
+ margin: 20px 0;
+ padding: 12px;
+ background: #f9f9f9;
+ border: 1px solid #ddd;
+}
+
+.mcp-form-section h4 {
+ margin-top: 0;
+ margin-bottom: 10px;
+ font-size: 14px;
+ color: #23282d;
+}
+
+/* Server endpoint info */
+.mcp-server-info {
+ background: #f9f9f9;
+ border: 1px solid #ddd;
+ padding: 12px;
+ margin: 15px 0;
+}
+
+.server-endpoint-info h4 {
+ margin-top: 0;
+ margin-bottom: 10px;
+ color: #23282d;
+ font-size: 14px;
+ border-bottom: 1px solid #ddd;
+ padding-bottom: 5px;
+}
+
+.endpoint-details {
+ margin: 10px 0;
+}
+
+.endpoint-details p {
+ margin: 5px 0;
+ font-size: 13px;
+}
+
+.endpoint-details code {
+ background: #f1f1f1;
+ color: #d63638;
+ padding: 2px 4px;
+ font-family: Consolas, Monaco, monospace;
+ font-size: 12px;
+}
+
+.connection-instructions {
+ margin-top: 15px;
+ padding-top: 10px;
+ border-top: 1px solid #ddd;
+}
+
+.connection-instructions h5 {
+ color: #23282d;
+ margin-bottom: 8px;
+ font-size: 13px;
+}
+
+.connection-instructions pre {
+ background: #f9f9f9;
+ border: 1px solid #ddd;
+ padding: 10px;
+ overflow-x: auto;
+ font-family: Consolas, Monaco, monospace;
+ font-size: 11px;
+ line-height: 1.4;
+ color: #23282d;
+}
+
+.connection-instructions code {
+ color: inherit;
+ background: transparent;
+}
+
+/* Capabilities sections */
+.capabilities-section {
+ margin: 10px 0;
+ padding: 8px 0;
+ border-bottom: 1px solid #eee;
+}
+
+.capabilities-section:last-child {
+ border-bottom: none;
+}
+
+.capabilities-section h5 {
+ margin: 0 0 8px 0;
+ color: #23282d;
+ font-size: 13px;
+ font-weight: 600;
+}
+
+.capability-item {
+ background: #f9f9f9;
+ border: 1px solid #ddd;
+ padding: 6px 8px;
+ margin: 3px 0;
+}
+
+.capability-item strong {
+ color: #0073aa;
+ font-size: 12px;
+}
+
+.capability-desc {
+ font-size: 11px;
+ color: #666;
+ margin-top: 2px;
+ font-style: italic;
+}
+
+.server-capabilities {
+ background: #f9f9f9;
+ border: 1px solid #ddd;
+ margin-top: 10px;
+ padding: 8px;
+}
\ No newline at end of file
diff --git a/demo/includes/Admin/assets/mcp-admin.js b/demo/includes/Admin/assets/mcp-admin.js
new file mode 100644
index 0000000..1677988
--- /dev/null
+++ b/demo/includes/Admin/assets/mcp-admin.js
@@ -0,0 +1,353 @@
+/**
+ * MCP Admin JavaScript
+ */
+jQuery(document).ready(function($) {
+
+ // Tab functionality
+ window.showTab = function(tabName) {
+ // Hide all tabs
+ $('.tab-content').hide();
+ $('.nav-tab').removeClass('nav-tab-active');
+
+ // Show selected tab
+ $('#' + tabName + '-tab').show();
+ $('a[href="#' + tabName + '"]').addClass('nav-tab-active');
+ };
+
+ // Server form functions
+ window.showAddServerForm = function() {
+ $('#server-form').show();
+ $('#form-title').text('Add New Server');
+ $('#mcp-server-form')[0].reset();
+ $('#server-id').val('');
+ };
+
+ window.hideServerForm = function() {
+ $('#server-form').hide();
+ };
+
+ window.toggleAuthFields = function() {
+ var authType = $('#auth-type').val();
+ $('#bearer-token-row, #api-key-row, #basic-auth-row').hide();
+
+ if (authType === 'bearer') {
+ $('#bearer-token-row').show();
+ } else if (authType === 'api_key') {
+ $('#api-key-row').show();
+ } else if (authType === 'basic') {
+ $('#basic-auth-row').show();
+ }
+ };
+
+ window.loadServerDetails = function(serverId) {
+ if (!serverId) return;
+
+ $.ajax({
+ url: ajaxurl,
+ type: 'POST',
+ data: {
+ action: 'get_mcp_server',
+ server_id: serverId,
+ _ajax_nonce: mcpAdmin.nonce
+ },
+ success: function(response) {
+ if (response.success) {
+ var server = response.data;
+ $('input[name="server_url"]').val(server.url);
+ $('input[name="client_id"]').val('wordpress-' + serverId);
+ }
+ }
+ });
+ };
+
+ // Test form handler
+ $('#mcp-test-form').on('submit', function(e) {
+ e.preventDefault();
+
+ var $results = $('#mcp-test-results');
+ $results.html('Testing connection...
');
+
+ var serverUrl = $('#mcp-test-form input[name="server_url"]').val();
+ var clientId = $('#mcp-test-form input[name="client_id"]').val();
+
+ console.log('Debug: Server URL =', serverUrl);
+ console.log('Debug: Client ID =', clientId);
+
+ if (!serverUrl || !clientId) {
+ $results.html('Error: Please fill in both Server URL and Client ID
');
+ return;
+ }
+
+ $.ajax({
+ url: ajaxurl,
+ type: 'POST',
+ data: {
+ action: 'test_mcp_client',
+ server_url: serverUrl,
+ client_id: clientId,
+ _ajax_nonce: mcpAdmin.nonce
+ },
+ success: function(response) {
+ if (response.success) {
+ $results.html('Success!
' + response.data + '
');
+ } else {
+ $results.html('Error: ' + response.data + '
');
+ }
+ },
+ error: function() {
+ $results.html('');
+ }
+ });
+ });
+
+ // Server form handler
+ $('#mcp-server-form').on('submit', function(e) {
+ e.preventDefault();
+
+ var formData = {
+ action: 'save_mcp_server',
+ server_id: $('#server-id').val(),
+ server_name: $('#server-name').val(),
+ server_url: $('#server-url').val(),
+ auth_type: $('#auth-type').val(),
+ bearer_token: $('#bearer-token').val(),
+ api_key: $('#api-key').val(),
+ api_header: $('#api-header').val(),
+ username: $('#username').val(),
+ password: $('#password').val(),
+ timeout: $('#timeout').val(),
+ ssl_verify: $('#ssl-verify').is(':checked') ? 1 : 0,
+ _ajax_nonce: mcpAdmin.nonce
+ };
+
+ $.ajax({
+ url: ajaxurl,
+ type: 'POST',
+ data: formData,
+ success: function(response) {
+ if (response.success) {
+ location.reload();
+ } else {
+ alert('Error: ' + response.data);
+ }
+ },
+ error: function() {
+ alert('AJAX request failed');
+ }
+ });
+ });
+
+ // Edit server handler
+ $(document).on('click', '.edit-server', function() {
+ var serverId = $(this).closest('.server-item').data('server-id');
+ // Load server data and populate form
+ $.ajax({
+ url: ajaxurl,
+ type: 'POST',
+ data: {
+ action: 'get_mcp_server',
+ server_id: serverId,
+ _ajax_nonce: mcpAdmin.nonce
+ },
+ success: function(response) {
+ if (response.success) {
+ var server = response.data;
+ $('#server-id').val(serverId);
+ $('#server-name').val(server.name);
+ $('#server-url').val(server.url);
+ $('#auth-type').val(server.auth.type || 'none');
+ toggleAuthFields();
+ $('#bearer-token').val(server.auth.token || '');
+ $('#api-key').val(server.auth.key || '');
+ $('#api-header').val(server.auth.header || 'X-API-Key');
+ $('#username').val(server.auth.username || '');
+ $('#password').val(server.auth.password || '');
+ $('#timeout').val(server.timeout || 30);
+ $('#ssl-verify').prop('checked', server.ssl_verify !== false);
+ $('#form-title').text('Edit Server: ' + server.name);
+ $('#server-form').show();
+ }
+ }
+ });
+ });
+
+ // Delete server handler
+ $(document).on('click', '.delete-server', function() {
+ if (confirm('Are you sure you want to delete this server?')) {
+ var serverId = $(this).closest('.server-item').data('server-id');
+ $.ajax({
+ url: ajaxurl,
+ type: 'POST',
+ data: {
+ action: 'delete_mcp_server',
+ server_id: serverId,
+ _ajax_nonce: mcpAdmin.nonce
+ },
+ success: function(response) {
+ if (response.success) {
+ location.reload();
+ } else {
+ alert('Error: ' + response.data);
+ }
+ }
+ });
+ }
+ });
+
+ // Server status checking
+ window.checkServerStatus = function(serverId) {
+ var $statusIndicator = $('#status-' + serverId);
+ var $capabilities = $('#capabilities-' + serverId);
+ var $serverItem = $statusIndicator.closest('.server-item');
+
+ // Clear any previous error messages
+ $serverItem.find('.connection-error').remove();
+
+ $statusIndicator.removeClass('status-connected status-disconnected status-unknown')
+ .addClass('status-unknown').text('Checking...');
+
+ $.ajax({
+ url: ajaxurl,
+ type: 'POST',
+ data: {
+ action: 'check_server_status',
+ server_id: serverId,
+ _ajax_nonce: mcpAdmin.nonce
+ },
+ success: function(response) {
+ if (response.success) {
+ var data = response.data;
+
+ // Update status
+ $statusIndicator.removeClass('status-unknown')
+ .addClass('status-connected').text('Connected');
+
+ // Show capabilities section
+ $capabilities.show();
+
+ // Update tools
+ var toolsHtml = '';
+ if (data.tools && data.tools.length > 0) {
+ data.tools.forEach(function(tool) {
+ toolsHtml += '';
+ toolsHtml += '
' + escapeHtml(tool.name) + ' ';
+ if (tool.description) {
+ toolsHtml += '
' + escapeHtml(tool.description) + '
';
+ }
+ toolsHtml += '
';
+ });
+ } else {
+ toolsHtml = 'No tools available ';
+ }
+ $('#tools-' + serverId).html(toolsHtml);
+
+ // Update resources
+ var resourcesHtml = '';
+ if (data.resources && data.resources.length > 0) {
+ data.resources.forEach(function(resource) {
+ resourcesHtml += '';
+ resourcesHtml += '
' + escapeHtml(resource.uri) + ' ';
+ if (resource.description) {
+ resourcesHtml += '
' + escapeHtml(resource.description) + '
';
+ }
+ resourcesHtml += '
';
+ });
+ } else {
+ resourcesHtml = 'No resources available ';
+ }
+ $('#resources-' + serverId).html(resourcesHtml);
+
+ // Update prompts
+ var promptsHtml = '';
+ if (data.prompts && data.prompts.length > 0) {
+ data.prompts.forEach(function(prompt) {
+ promptsHtml += '';
+ promptsHtml += '
' + escapeHtml(prompt.name) + ' ';
+ if (prompt.description) {
+ promptsHtml += '
' + escapeHtml(prompt.description) + '
';
+ }
+ promptsHtml += '
';
+ });
+ } else {
+ promptsHtml = 'No prompts available ';
+ }
+ $('#prompts-' + serverId).html(promptsHtml);
+
+ } else {
+ $statusIndicator.removeClass('status-unknown')
+ .addClass('status-disconnected').text('Failed');
+ $capabilities.hide();
+
+ // Show detailed error message (don't escape HTML for debug output)
+ var $errorMsg = $('Connection Failed: ' + response.data + '
');
+ $capabilities.after($errorMsg);
+
+ // Remove error after 10 seconds
+ setTimeout(function() {
+ $errorMsg.fadeOut();
+ }, 10000);
+ }
+ },
+ error: function(xhr, status, error) {
+ $statusIndicator.removeClass('status-unknown')
+ .addClass('status-disconnected').text('Error');
+ $capabilities.hide();
+
+ // Show AJAX error message
+ var errorMessage = 'Network request failed';
+ if (status === 'timeout') {
+ errorMessage = 'Request timeout - server took too long to respond';
+ } else if (status === 'error') {
+ errorMessage = 'Network error - check your internet connection';
+ } else if (status === 'parsererror') {
+ errorMessage = 'Server returned invalid data';
+ }
+
+ var $errorMsg = $('AJAX Error: ' + errorMessage + '
');
+ $capabilities.after($errorMsg);
+
+ // Remove error after 10 seconds
+ setTimeout(function() {
+ $errorMsg.fadeOut();
+ }, 10000);
+ }
+ });
+ };
+
+ // Helper function to escape HTML
+ function escapeHtml(unsafe) {
+ return unsafe
+ .replace(/&/g, "&")
+ .replace(//g, ">")
+ .replace(/"/g, """)
+ .replace(/'/g, "'");
+ }
+
+ // Abilities export handler
+ $('#export-abilities-form').on('submit', function(e) {
+ e.preventDefault();
+
+ var $results = $('#export-results');
+ $results.html('Exporting abilities...
');
+
+ $.ajax({
+ url: ajaxurl,
+ type: 'POST',
+ data: {
+ action: 'export_abilities_mcp',
+ _ajax_nonce: mcpAdmin.nonce
+ },
+ success: function(response) {
+ if (response.success) {
+ $results.html('Success!
' + response.data + '
');
+ } else {
+ $results.html('Error: ' + response.data + '
');
+ }
+ },
+ error: function() {
+ $results.html('');
+ }
+ });
+ });
+});
\ No newline at end of file
diff --git a/demo/includes/Autoloader.php b/demo/includes/Autoloader.php
new file mode 100644
index 0000000..f787770
--- /dev/null
+++ b/demo/includes/Autoloader.php
@@ -0,0 +1,44 @@
+setup();
+ }
+
+ return self::$instance;
+ }
+
+ /**
+ * Setup the demo plugin.
+ */
+ private function setup(): void {
+ // Initialize admin interface
+ if ( is_admin() ) {
+ $admin_page = new McpAdminPage();
+ $admin_page->init();
+
+ $dashboard_widget = new DashboardWidget();
+ $dashboard_widget->init();
+ }
+
+ // Register demo abilities during the correct action
+ add_action( 'abilities_api_init', array( $this, 'register_demo_abilities' ) );
+
+ // Set up demo MCP servers
+ add_action( 'mcp_adapter_init', array( $this, 'setup_demo_servers' ) );
+
+ // Load examples early so their hooks are registered before mcp_adapter_init fires
+ $this->load_examples();
+ }
+
+ /**
+ * Register demo abilities during abilities_api_init.
+ */
+ public function register_demo_abilities(): void {
+ if ( ! function_exists( 'wp_register_ability' ) ) {
+ return;
+ }
+
+ // Content Management Server abilities
+ wp_register_ability(
+ 'demo-content/get-featured-posts',
+ array(
+ 'label' => 'Get Featured Posts',
+ 'description' => 'Get posts marked as featured or sticky',
+ 'input_schema' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'limit' => array(
+ 'type' => 'integer',
+ 'description' => 'Number of posts to return',
+ 'default' => 5,
+ 'maximum' => 20,
+ ),
+ ),
+ ),
+ 'output_schema' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'posts' => array( 'type' => 'array' ),
+ ),
+ ),
+ 'execute_callback' => function ( $args ) {
+ $limit = min( intval( $args['limit'] ?? 5 ), 20 );
+ $posts = get_posts( array(
+ 'numberposts' => $limit,
+ 'post_status' => 'publish',
+ 'orderby' => 'date',
+ 'order' => 'DESC',
+ ) );
+
+ $result = array();
+ foreach ( $posts as $post ) {
+ $result[] = array(
+ 'id' => $post->ID,
+ 'title' => $post->post_title,
+ 'excerpt' => wp_trim_words( $post->post_excerpt ?: $post->post_content, 20 ),
+ 'url' => get_permalink( $post ),
+ );
+ }
+ return array( 'posts' => $result );
+ },
+ 'permission_callback' => function () {
+ return true;
+ },
+ )
+ );
+
+ wp_register_ability(
+ 'demo-content/update-post-status',
+ array(
+ 'label' => 'Update Post Status',
+ 'description' => 'Update the status of a WordPress post',
+ 'input_schema' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'post_id' => array(
+ 'type' => 'integer',
+ 'description' => 'Post ID to update',
+ ),
+ 'status' => array(
+ 'type' => 'string',
+ 'enum' => array( 'draft', 'publish', 'private', 'pending' ),
+ 'description' => 'New post status',
+ ),
+ ),
+ 'required' => array( 'post_id', 'status' ),
+ ),
+ 'output_schema' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'success' => array( 'type' => 'boolean' ),
+ ),
+ ),
+ 'execute_callback' => function ( $args ) {
+ $result = wp_update_post( array(
+ 'ID' => $args['post_id'],
+ 'post_status' => $args['status'],
+ ) );
+ return array( 'success' => ! is_wp_error( $result ) );
+ },
+ 'permission_callback' => function () {
+ return current_user_can( 'edit_posts' );
+ },
+ )
+ );
+
+ // Analytics Server abilities
+ wp_register_ability(
+ 'demo-analytics/get-site-metrics',
+ array(
+ 'label' => 'Get Site Metrics',
+ 'description' => 'Get basic site metrics and statistics',
+ 'input_schema' => array(
+ 'type' => 'object',
+ 'properties' => array(),
+ ),
+ 'output_schema' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'metrics' => array( 'type' => 'object' ),
+ ),
+ ),
+ 'execute_callback' => function ( $args ) {
+ return array(
+ 'metrics' => array(
+ 'total_posts' => wp_count_posts()->publish,
+ 'total_pages' => wp_count_posts( 'page' )->publish,
+ 'total_comments' => wp_count_comments()->approved,
+ 'total_users' => count_users()['total_users'],
+ ),
+ );
+ },
+ 'permission_callback' => function () {
+ return true;
+ },
+ )
+ );
+
+ wp_register_ability(
+ 'demo-analytics/get-popular-posts',
+ array(
+ 'label' => 'Get Popular Posts',
+ 'description' => 'Get posts with most comments',
+ 'input_schema' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'limit' => array(
+ 'type' => 'integer',
+ 'default' => 10,
+ 'maximum' => 50,
+ ),
+ ),
+ ),
+ 'output_schema' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'posts' => array( 'type' => 'array' ),
+ ),
+ ),
+ 'execute_callback' => function ( $args ) {
+ $limit = min( intval( $args['limit'] ?? 10 ), 50 );
+ $posts = get_posts( array(
+ 'numberposts' => $limit,
+ 'orderby' => 'comment_count',
+ 'order' => 'DESC',
+ 'post_status' => 'publish',
+ ) );
+
+ $result = array();
+ foreach ( $posts as $post ) {
+ $result[] = array(
+ 'id' => $post->ID,
+ 'title' => $post->post_title,
+ 'url' => get_permalink( $post ),
+ 'comment_count' => intval( $post->comment_count ),
+ );
+ }
+ return array( 'posts' => $result );
+ },
+ 'permission_callback' => function () {
+ return true;
+ },
+ )
+ );
+
+ // Resource abilities for Content Management Server
+ wp_register_ability(
+ 'demo-content/resource-recent-posts',
+ array(
+ 'label' => 'Recent Posts Resource',
+ 'description' => 'Get recent posts as a resource',
+ 'meta' => array(
+ 'uri' => 'mcp://content-server/recent-posts',
+ ),
+ 'input_schema' => array(
+ 'type' => 'object',
+ 'properties' => array(),
+ ),
+ 'output_schema' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'contents' => array( 'type' => 'array' ),
+ ),
+ ),
+ 'execute_callback' => function ( $args ) {
+ $posts = get_posts( array(
+ 'numberposts' => 10,
+ 'post_status' => 'publish',
+ ) );
+ return array(
+ 'contents' => array(
+ array(
+ 'type' => 'text',
+ 'text' => wp_json_encode( $posts ),
+ ),
+ ),
+ );
+ },
+ 'permission_callback' => function () {
+ return true;
+ },
+ )
+ );
+
+ wp_register_ability(
+ 'demo-content/resource-categories',
+ array(
+ 'label' => 'Categories Resource',
+ 'description' => 'Get WordPress categories as a resource',
+ 'meta' => array(
+ 'uri' => 'mcp://content-server/categories',
+ ),
+ 'input_schema' => array(
+ 'type' => 'object',
+ 'properties' => array(),
+ ),
+ 'output_schema' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'contents' => array( 'type' => 'array' ),
+ ),
+ ),
+ 'execute_callback' => function ( $args ) {
+ $categories = get_categories();
+ return array(
+ 'contents' => array(
+ array(
+ 'type' => 'text',
+ 'text' => wp_json_encode( $categories ),
+ ),
+ ),
+ );
+ },
+ 'permission_callback' => function () {
+ return true;
+ },
+ )
+ );
+
+ // Resource abilities for Analytics Server
+ wp_register_ability(
+ 'demo-analytics/resource-engagement-data',
+ array(
+ 'label' => 'Engagement Data Resource',
+ 'description' => 'Get site engagement data as a resource',
+ 'meta' => array(
+ 'uri' => 'mcp://analytics-server/engagement-data',
+ ),
+ 'input_schema' => array(
+ 'type' => 'object',
+ 'properties' => array(),
+ ),
+ 'output_schema' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'contents' => array( 'type' => 'array' ),
+ ),
+ ),
+ 'execute_callback' => function ( $args ) {
+ $engagement_data = array(
+ 'total_posts' => wp_count_posts()->publish,
+ 'total_comments' => wp_count_comments()->approved,
+ 'avg_comments_per_post' => wp_count_posts()->publish > 0 ?
+ round( wp_count_comments()->approved / wp_count_posts()->publish, 2 ) : 0,
+ );
+ return array(
+ 'contents' => array(
+ array(
+ 'type' => 'text',
+ 'text' => wp_json_encode( $engagement_data ),
+ ),
+ ),
+ );
+ },
+ 'permission_callback' => function () {
+ return true;
+ },
+ )
+ );
+
+ wp_register_ability(
+ 'demo-analytics/resource-traffic-sources',
+ array(
+ 'label' => 'Traffic Sources Resource',
+ 'description' => 'Get traffic source information as a resource',
+ 'meta' => array(
+ 'uri' => 'mcp://analytics-server/traffic-sources',
+ ),
+ 'input_schema' => array(
+ 'type' => 'object',
+ 'properties' => array(),
+ ),
+ 'output_schema' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'contents' => array( 'type' => 'array' ),
+ ),
+ ),
+ 'execute_callback' => function ( $args ) {
+ $traffic_data = array(
+ 'direct' => '45%',
+ 'search' => '35%',
+ 'social' => '15%',
+ 'referral' => '5%',
+ 'note' => 'Simulated traffic data for demo purposes',
+ );
+ return array(
+ 'contents' => array(
+ array(
+ 'type' => 'text',
+ 'text' => wp_json_encode( $traffic_data ),
+ ),
+ ),
+ );
+ },
+ 'permission_callback' => function () {
+ return true;
+ },
+ )
+ );
+
+ // Prompt abilities for Content Management Server
+ wp_register_ability(
+ 'demo-content/prompt-post-ideas',
+ array(
+ 'label' => 'Generate Post Ideas',
+ 'description' => 'Generate blog post ideas based on categories and topics',
+ 'input_schema' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'category' => array(
+ 'type' => 'string',
+ 'description' => 'Content category or topic',
+ 'default' => 'general',
+ ),
+ 'count' => array(
+ 'type' => 'integer',
+ 'description' => 'Number of ideas to generate',
+ 'default' => 5,
+ 'maximum' => 10,
+ ),
+ ),
+ ),
+ 'output_schema' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'prompt' => array( 'type' => 'string' ),
+ 'suggestions' => array( 'type' => 'array' ),
+ ),
+ ),
+ 'execute_callback' => function ( $args ) {
+ $category = sanitize_text_field( $args['category'] ?? 'general' );
+ $count = min( intval( $args['count'] ?? 5 ), 10 );
+
+ $base_ideas = array(
+ 'How to optimize your ' . $category . ' strategy',
+ 'The ultimate guide to ' . $category,
+ 'Common mistakes in ' . $category . ' and how to avoid them',
+ 'Latest trends in ' . $category . ' for 2024',
+ 'Beginner\'s guide to ' . $category,
+ 'Advanced ' . $category . ' techniques',
+ 'Case study: Success in ' . $category,
+ 'Tools and resources for ' . $category,
+ 'Future of ' . $category . ' industry',
+ 'Expert insights on ' . $category,
+ );
+
+ $suggestions = array_slice( $base_ideas, 0, $count );
+
+ return array(
+ 'prompt' => 'Here are ' . $count . ' blog post ideas for the ' . $category . ' category:',
+ 'suggestions' => $suggestions,
+ );
+ },
+ 'permission_callback' => function () {
+ return true;
+ },
+ )
+ );
+
+ wp_register_ability(
+ 'demo-content/prompt-content-audit',
+ array(
+ 'label' => 'Content Audit Checklist',
+ 'description' => 'Generate a content audit checklist for WordPress sites',
+ 'input_schema' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'focus_area' => array(
+ 'type' => 'string',
+ 'enum' => array( 'seo', 'readability', 'engagement', 'technical' ),
+ 'description' => 'Area to focus the audit on',
+ 'default' => 'seo',
+ ),
+ ),
+ ),
+ 'output_schema' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'prompt' => array( 'type' => 'string' ),
+ 'checklist' => array( 'type' => 'array' ),
+ ),
+ ),
+ 'execute_callback' => function ( $args ) {
+ $focus = sanitize_text_field( $args['focus_area'] ?? 'seo' );
+
+ $checklists = array(
+ 'seo' => array(
+ 'Check title tags are unique and descriptive',
+ 'Verify meta descriptions are compelling',
+ 'Ensure proper heading structure (H1, H2, H3)',
+ 'Review internal linking strategy',
+ 'Check for broken links',
+ 'Verify image alt text',
+ 'Review URL structure',
+ 'Check for duplicate content',
+ ),
+ 'readability' => array(
+ 'Review paragraph length and structure',
+ 'Check for clear headings and subheadings',
+ 'Ensure proper use of bullet points and lists',
+ 'Review sentence length and complexity',
+ 'Check for consistent tone and voice',
+ 'Verify proper formatting and typography',
+ 'Review content flow and transitions',
+ 'Check for grammar and spelling errors',
+ ),
+ 'engagement' => array(
+ 'Review call-to-action placement',
+ 'Check for engaging introductions',
+ 'Verify social sharing options',
+ 'Review comment moderation and responses',
+ 'Check for multimedia content integration',
+ 'Review related posts suggestions',
+ 'Verify newsletter signup placement',
+ 'Check for interactive elements',
+ ),
+ 'technical' => array(
+ 'Check page loading speed',
+ 'Verify mobile responsiveness',
+ 'Review image optimization',
+ 'Check for proper schema markup',
+ 'Verify SSL certificate',
+ 'Review caching configuration',
+ 'Check for 404 errors',
+ 'Verify backup systems',
+ ),
+ );
+
+ return array(
+ 'prompt' => 'Content audit checklist focused on ' . $focus . ':',
+ 'checklist' => $checklists[ $focus ] ?? $checklists['seo'],
+ );
+ },
+ 'permission_callback' => function () {
+ return current_user_can( 'edit_posts' );
+ },
+ )
+ );
+
+ // Prompt abilities for Analytics Server
+ wp_register_ability(
+ 'demo-analytics/prompt-performance-report',
+ array(
+ 'label' => 'Performance Report Template',
+ 'description' => 'Generate a site performance report template with current metrics',
+ 'input_schema' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'period' => array(
+ 'type' => 'string',
+ 'enum' => array( 'weekly', 'monthly', 'quarterly' ),
+ 'description' => 'Reporting period',
+ 'default' => 'monthly',
+ ),
+ ),
+ ),
+ 'output_schema' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'prompt' => array( 'type' => 'string' ),
+ 'template' => array( 'type' => 'string' ),
+ 'metrics' => array( 'type' => 'object' ),
+ ),
+ ),
+ 'execute_callback' => function ( $args ) {
+ $period = sanitize_text_field( $args['period'] ?? 'monthly' );
+
+ $current_metrics = array(
+ 'total_posts' => wp_count_posts()->publish,
+ 'total_pages' => wp_count_posts( 'page' )->publish,
+ 'total_comments' => wp_count_comments()->approved,
+ 'total_users' => count_users()['total_users'],
+ );
+
+ $template = "# " . ucfirst( $period ) . " Performance Report\n\n" .
+ "## Site Overview\n" .
+ "- Published Posts: {$current_metrics['total_posts']}\n" .
+ "- Published Pages: {$current_metrics['total_pages']}\n" .
+ "- Approved Comments: {$current_metrics['total_comments']}\n" .
+ "- Total Users: {$current_metrics['total_users']}\n\n" .
+ "## Key Metrics\n" .
+ "- Traffic Growth: [To be updated]\n" .
+ "- Engagement Rate: [To be updated]\n" .
+ "- Conversion Rate: [To be updated]\n\n" .
+ "## Content Performance\n" .
+ "- Top Performing Posts: [To be updated]\n" .
+ "- Most Shared Content: [To be updated]\n\n" .
+ "## Recommendations\n" .
+ "- [Action item 1]\n" .
+ "- [Action item 2]\n" .
+ "- [Action item 3]";
+
+ return array(
+ 'prompt' => 'Generated ' . $period . ' performance report template with current site metrics:',
+ 'template' => $template,
+ 'metrics' => $current_metrics,
+ );
+ },
+ 'permission_callback' => function () {
+ return current_user_can( 'read' );
+ },
+ )
+ );
+
+ wp_register_ability(
+ 'demo-analytics/prompt-growth-insights',
+ array(
+ 'label' => 'Growth Insights Generator',
+ 'description' => 'Generate growth insights and recommendations based on site data',
+ 'input_schema' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'focus' => array(
+ 'type' => 'string',
+ 'enum' => array( 'content', 'engagement', 'seo', 'social' ),
+ 'description' => 'Focus area for growth insights',
+ 'default' => 'content',
+ ),
+ ),
+ ),
+ 'output_schema' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'prompt' => array( 'type' => 'string' ),
+ 'insights' => array( 'type' => 'array' ),
+ 'actions' => array( 'type' => 'array' ),
+ ),
+ ),
+ 'execute_callback' => function ( $args ) {
+ $focus = sanitize_text_field( $args['focus'] ?? 'content' );
+
+ $insights_map = array(
+ 'content' => array(
+ 'insights' => array(
+ 'Your site has ' . wp_count_posts()->publish . ' published posts',
+ 'Average comments per post: ' . (wp_count_posts()->publish > 0 ?
+ round( wp_count_comments()->approved / wp_count_posts()->publish, 1 ) : 0),
+ 'Content consistency drives engagement',
+ ),
+ 'actions' => array(
+ 'Maintain regular publishing schedule',
+ 'Focus on evergreen content topics',
+ 'Repurpose high-performing content',
+ 'Create content series or themes',
+ ),
+ ),
+ 'engagement' => array(
+ 'insights' => array(
+ 'Total approved comments: ' . wp_count_comments()->approved,
+ 'User base: ' . count_users()['total_users'] . ' registered users',
+ 'Engagement builds community loyalty',
+ ),
+ 'actions' => array(
+ 'Respond promptly to comments',
+ 'Ask questions in your content',
+ 'Create interactive content',
+ 'Encourage user-generated content',
+ ),
+ ),
+ 'seo' => array(
+ 'insights' => array(
+ 'Content volume supports SEO authority',
+ 'Internal linking opportunities available',
+ 'Regular updates improve search rankings',
+ ),
+ 'actions' => array(
+ 'Optimize title tags and meta descriptions',
+ 'Improve internal linking structure',
+ 'Focus on long-tail keywords',
+ 'Update and refresh old content',
+ ),
+ ),
+ 'social' => array(
+ 'insights' => array(
+ 'Quality content drives social shares',
+ 'Consistent posting builds audience',
+ 'Visual content performs better on social',
+ ),
+ 'actions' => array(
+ 'Create shareable graphics for posts',
+ 'Engage with your audience on social platforms',
+ 'Share behind-the-scenes content',
+ 'Use relevant hashtags strategically',
+ ),
+ ),
+ );
+
+ $data = $insights_map[ $focus ] ?? $insights_map['content'];
+
+ return array(
+ 'prompt' => 'Growth insights and recommendations for ' . $focus . ':',
+ 'insights' => $data['insights'],
+ 'actions' => $data['actions'],
+ );
+ },
+ 'permission_callback' => function () {
+ return current_user_can( 'read' );
+ },
+ )
+ );
+ }
+
+ /**
+ * Set up demo MCP servers.
+ */
+ public function setup_demo_servers( McpAdapter $adapter ): void {
+ // Server 1: Content Management Server
+ $adapter->create_server(
+ 'content-server',
+ 'mcp',
+ 'content',
+ 'Content Management Server',
+ 'Demo MCP server for content management operations',
+ '1.0.0',
+ array(
+ \WP\MCP\Transport\Http\RestTransport::class,
+ ),
+ null,
+ null,
+ array(
+ 'demo-content/get-featured-posts',
+ 'demo-content/update-post-status',
+ ),
+ array(
+ 'demo-content/resource-recent-posts',
+ 'demo-content/resource-categories',
+ ),
+ array(
+ 'demo-content/prompt-post-ideas',
+ 'demo-content/prompt-content-audit',
+ ),
+ function() { return true; } // Public access
+ );
+
+ // Server 2: Analytics Server
+ $adapter->create_server(
+ 'analytics-server',
+ 'mcp',
+ 'analytics',
+ 'Analytics Server',
+ 'Demo MCP server for site analytics and metrics',
+ '1.0.0',
+ array(
+ \WP\MCP\Transport\Http\RestTransport::class,
+ ),
+ null,
+ null,
+ array(
+ 'demo-analytics/get-site-metrics',
+ 'demo-analytics/get-popular-posts',
+ ),
+ array(
+ 'demo-analytics/resource-engagement-data',
+ 'demo-analytics/resource-traffic-sources',
+ ),
+ array(
+ 'demo-analytics/prompt-performance-report',
+ 'demo-analytics/prompt-growth-insights',
+ ),
+ function() { return current_user_can( 'read' ); }
+ );
+ }
+
+ /**
+ * Load examples.
+ */
+ private function load_examples(): void {
+ $plugin_dir = plugin_dir_path( __DIR__ );
+
+ // Load server examples
+ $server_examples_file = $plugin_dir . 'examples/server-usage.php';
+ if ( file_exists( $server_examples_file ) ) {
+ include_once $server_examples_file;
+ }
+
+ // Load client examples
+ $client_examples_file = $plugin_dir . 'examples/client-usage.php';
+ if ( file_exists( $client_examples_file ) ) {
+ include_once $client_examples_file;
+ }
+ }
+}
\ No newline at end of file
diff --git a/demo/mcp-adapter-demo.php b/demo/mcp-adapter-demo.php
new file mode 100644
index 0000000..0fd7818
--- /dev/null
+++ b/demo/mcp-adapter-demo.php
@@ -0,0 +1,70 @@
+MCP Adapter Demo requires the MCP Adapter plugin to be installed and activated.
';
+ });
+ return;
+ }
+
+ // Initialize demo plugin now that core is available
+ require_once __DIR__ . '/includes/Autoloader.php';
+ if ( \WP\MCP\Demo\Autoloader::autoload() ) {
+ \WP\MCP\Demo\DemoPlugin::instance();
+ }
+ }, 20 ); // Load after core adapter
+ return;
+}
+
+/**
+ * Define the demo plugin constants.
+ */
+function constants(): void {
+ define( 'WP_MCP_DEMO_DIR', plugin_dir_path( __FILE__ ) );
+ define( 'WP_MCP_DEMO_VERSION', '0.1.0' );
+}
+
+constants();
+
+require_once __DIR__ . '/includes/Autoloader.php';
+
+// Initialize autoloader
+if ( ! \WP\MCP\Demo\Autoloader::autoload() ) {
+ return;
+}
+
+// Initialize the demo plugin
+\WP\MCP\Demo\DemoPlugin::instance();
\ No newline at end of file
diff --git a/docs/README.md b/docs/README.md
index 6288200..094d7dd 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -74,6 +74,12 @@ Detailed API documentation and specifications.
2. Start with [Transport Permissions](guides/transport-permissions.md) for custom authentication
3. Follow [Custom Transports](guides/custom-transports.md) for specialized protocols
+### I Want to Connect to External MCP Servers
+
+1. Read the [MCP Client Guide](guides/mcp-client.md) for connecting to external services
+2. Review [Authentication Methods](guides/mcp-client.md#authentication-methods) for secure connections
+3. Check [Real-World Examples](guides/mcp-client.md#real-world-examples) for practical implementations
+
### I'm Having Issues
1. Check [Common Issues](troubleshooting/common-issues.md) for your specific problem
@@ -90,6 +96,7 @@ Detailed API documentation and specifications.
### Core Concepts
- **MCP Protocol Integration**: How WordPress abilities become AI-accessible tools, resources, and prompts
+- **MCP Client Integration**: Connect WordPress to external MCP servers and consume their capabilities
- **Transport Layers**: REST API, streaming, and custom communication protocols
- **Error Handling**: Production-ready error management and monitoring
- **Security & Permissions**: Proper authentication and authorization patterns with [Transport Permissions](guides/transport-permissions.md)
@@ -105,6 +112,7 @@ Detailed API documentation and specifications.
- **Content Management**: AI-powered post creation and management
- **Data Access**: Exposing WordPress data as MCP resources
+- **External Service Integration**: Connecting to weather APIs, news services, and domain registrars
- **Guidance Systems**: AI advisory prompts for SEO, performance, and strategy
- **Custom Integrations**: Product-specific MCP implementations
@@ -157,6 +165,28 @@ wp_register_ability( 'my-plugin/seo-recommendations', [
]);
```
+### External MCP Client Connection
+
+```php
+// Connect to external MCP server and use its capabilities
+add_action( 'mcp_client_init', function( $adapter ) {
+ $client = $adapter->create_client(
+ 'weather-service',
+ 'https://api.weather.com/mcp',
+ [
+ 'auth' => [
+ 'type' => 'api_key',
+ 'key' => 'your-api-key',
+ ],
+ ]
+ );
+
+ // Remote tools become WordPress abilities:
+ // - mcp-weather-service/current-weather
+ // - mcp-weather-service/forecast
+});
+```
+
## Best Practices Covered
### Development
diff --git a/docs/architecture/overview.md b/docs/architecture/overview.md
index 22e12a7..8b9e0ec 100644
--- a/docs/architecture/overview.md
+++ b/docs/architecture/overview.md
@@ -6,15 +6,26 @@ the design decisions that enable flexible, scalable MCP integration with WordPre
## Table of Contents
1. [System Architecture](#system-architecture)
-2. [Component Relationships](#component-relationships)
-3. [Data Flow](#data-flow)
-4. [Design Patterns](#design-patterns)
-5. [Extension Points](#extension-points)
+2. [Bidirectional Architecture](#bidirectional-architecture)
+3. [Component Relationships](#component-relationships)
+4. [Data Flow](#data-flow)
+5. [Design Patterns](#design-patterns)
+6. [Extension Points](#extension-points)
## System Architecture
The MCP Adapter follows a layered architecture that cleanly separates concerns and provides multiple extension points
-for customization.
+for customization. The system supports **bidirectional MCP integration**: WordPress can both expose its abilities as MCP servers and connect to external MCP servers as a client.
+
+## Bidirectional Architecture
+
+The MCP Adapter provides two primary integration modes:
+
+### Server Mode (Exposing WordPress Abilities)
+WordPress abilities are exposed to external MCP clients through standardized MCP protocols.
+
+### Client Mode (Consuming External MCP Servers)
+WordPress connects to external MCP servers and consumes their tools, resources, and prompts as local abilities.
### High-Level Architecture
@@ -98,6 +109,35 @@ flowchart TD
class database,users,content,plugins wordpressStyle
```
+### Bidirectional Integration Flow
+
+The MCP Adapter now supports bidirectional integration, allowing both server and client modes:
+
+```mermaid
+sequenceDiagram
+ participant ExternalServer as External MCP Server
+ participant McpClient as WordPress MCP Client
+ participant AbilitiesAPI as WordPress Abilities API
+ participant McpServer as WordPress MCP Server
+ participant ExternalClient as External MCP Client
+
+ Note over ExternalServer, ExternalClient: Bidirectional MCP Integration
+
+ %% Client Mode Flow
+ ExternalServer->>McpClient: Expose tools/resources/prompts
+ McpClient->>AbilitiesAPI: Auto-register remote capabilities as local abilities
+ Note right of AbilitiesAPI: Remote MCP tools become WordPress abilities with mcp-{client-id}/ prefix
+
+ %% Server Mode Flow
+ AbilitiesAPI->>McpServer: Local abilities registered
+ McpServer->>ExternalClient: Expose as MCP tools/resources/prompts
+ ExternalClient->>McpServer: Execute MCP requests
+ McpServer->>AbilitiesAPI: Execute local abilities
+
+ %% Unified Experience
+ Note over McpClient, McpServer: Both remote and local abilities use the same WordPress API
+```
+
### Core Component Interaction
The following diagram shows how the main components interact within the MCP Adapter system:
@@ -157,6 +197,16 @@ sequenceDiagram
- Validation coordination
- Request routing to appropriate components
+#### McpClient (Client Instance)
+
+- **Purpose**: Individual MCP client connecting to external MCP servers
+- **Responsibilities**:
+ - External server connection management
+ - Authentication handling (bearer, API key, basic)
+ - Remote capability discovery and registration
+ - Automatic ability registration with `mcp-{client-id}/` prefix
+ - Connection health monitoring and error handling
+
#### Transport Layer
- **Purpose**: Communication protocol implementation with dependency injection
@@ -544,6 +594,63 @@ class RegisterAbilityAsMcpPrompt {
- Simplified testing and mocking
- Enhanced performance for complex prompts
+### Client Pattern (External MCP Integration)
+
+The MCP Client pattern enables consuming external MCP servers:
+
+```php
+use WP\MCP\Core\McpClient;
+use WP\MCP\Core\McpAdapter;
+
+class McpClient {
+ private string $client_id;
+ private string $server_url;
+ private array $config;
+
+ public function connect(): WP_Error|bool {
+ // Establish connection to external MCP server
+ $response = $this->discover_capabilities();
+
+ if ( is_wp_error( $response ) ) {
+ return $response;
+ }
+
+ // Auto-register remote capabilities as WordPress abilities
+ $this->register_remote_abilities( $response['tools'], $response['resources'], $response['prompts'] );
+
+ return true;
+ }
+
+ private function register_remote_abilities( array $tools, array $resources, array $prompts ): void {
+ foreach ( $tools as $tool ) {
+ wp_register_ability( "mcp-{$this->client_id}/{$tool['name']}", [
+ 'label' => $tool['description'],
+ 'description' => $tool['description'],
+ 'input_schema' => $tool['inputSchema'] ?? [],
+ 'execute_callback' => function( $input ) use ( $tool ) {
+ return $this->execute_remote_tool( $tool['name'], $input );
+ },
+ 'permission_callback' => function() {
+ return apply_filters( 'mcp_client_permission', true, $this->client_id );
+ }
+ ]);
+ }
+ }
+}
+
+// Usage in McpAdapter
+add_action( 'mcp_client_init', function( $adapter ) {
+ $client = $adapter->create_client( 'wpcom-domains', 'https://wpcom-domains-mcp.a8cai.workers.dev/mcp' );
+ // Remote tools automatically become: mcp-wpcom-domains/searchdomains, etc.
+});
+```
+
+**Benefits of Client Pattern**:
+- Seamless integration of external MCP capabilities
+- Automatic ability registration with namespacing
+- Consistent WordPress API usage for remote and local abilities
+- Built-in error handling and connection management
+
### Adapter Pattern (WordPress Integration)
The core adapter pattern bridges WordPress abilities and MCP protocols:
@@ -697,6 +804,46 @@ class McpToolValidator {
}
```
+### Custom MCP Client Integration
+
+Create clients for specialized external MCP servers:
+
+```php
+use WP\MCP\Core\McpAdapter;
+
+// Connect to WordPress Domains MCP server
+add_action( 'mcp_client_init', function( $adapter ) {
+ $client = $adapter->create_client(
+ 'wpcom-domains', // Client ID
+ 'https://wpcom-domains-mcp.a8cai.workers.dev/mcp', // Server URL
+ [ // Configuration
+ 'timeout' => 30,
+ 'auth' => [ // Optional authentication
+ 'type' => 'bearer',
+ 'token' => 'your-token-here'
+ ]
+ ],
+ MyCustomErrorHandler::class, // Custom error handler
+ MyCustomObservabilityHandler::class // Custom observability
+ );
+});
+
+// Use remote capabilities as WordPress abilities
+add_action( 'init', function() {
+ // Remote MCP tools become WordPress abilities automatically
+ $result = wp_execute_ability( 'mcp-wpcom-domains/searchdomains', [
+ 'query' => 'example.com'
+ ] );
+
+ if ( ! is_wp_error( $result ) ) {
+ // Handle domain search results
+ foreach ( $result['domains'] as $domain ) {
+ echo "Available: {$domain['name']} - {$domain['price']}\n";
+ }
+ }
+});
+```
+
This architecture enables both simple integrations and complex enterprise deployments while maintaining clean separation
of concerns and extensive customization capabilities.
diff --git a/docs/getting-started/basic-examples.md b/docs/getting-started/basic-examples.md
index ec297ef..9c0c494 100644
--- a/docs/getting-started/basic-examples.md
+++ b/docs/getting-started/basic-examples.md
@@ -1,7 +1,7 @@
# Basic Examples
This guide provides simple, working examples to help you understand how to create different types of MCP components
-using the WordPress MCP Adapter.
+using the WordPress MCP Adapter, including both server and client functionality.
## Example 1: Simple Tool - Post Creator
diff --git a/docs/guides/mcp-client.md b/docs/guides/mcp-client.md
new file mode 100644
index 0000000..10532bf
--- /dev/null
+++ b/docs/guides/mcp-client.md
@@ -0,0 +1,729 @@
+# MCP Client Guide
+
+This guide covers how to use the MCP Client functionality to connect WordPress to external MCP servers and consume their capabilities as local WordPress abilities.
+
+## Table of Contents
+
+1. [Overview](#overview)
+2. [Basic Client Setup](#basic-client-setup)
+3. [Authentication Methods](#authentication-methods)
+4. [Client Configuration](#client-configuration)
+5. [Working with Remote Abilities](#working-with-remote-abilities)
+6. [Error Handling](#error-handling)
+7. [Real-World Examples](#real-world-examples)
+8. [Troubleshooting](#troubleshooting)
+
+## Overview
+
+The MCP Client enables WordPress to act as a client to external MCP servers, consuming their tools, resources, and prompts as local WordPress abilities. This creates a bidirectional integration where WordPress can both expose and consume MCP capabilities.
+
+### Key Features
+
+- **Automatic Ability Registration**: Remote MCP capabilities become WordPress abilities with the `mcp-{client-id}/` prefix
+- **Multiple Authentication Methods**: Support for bearer tokens, API keys, and basic authentication
+- **Connection Management**: Built-in connection health monitoring and error handling
+- **Permission Control**: Granular permission checking for remote capability usage
+- **Observability**: Integration with MCP Adapter's observability system
+
+## Basic Client Setup
+
+### Step 1: Create a Client Connection
+
+Use the `mcp_client_init` action hook to create client connections:
+
+```php
+add_action( 'mcp_client_init', function( $adapter ) {
+ $client = $adapter->create_client(
+ 'my-service', // Unique client identifier
+ 'https://api.example.com/mcp', // External MCP server URL
+ [ // Client configuration
+ 'timeout' => 30,
+ ]
+ );
+
+ if ( is_wp_error( $client ) ) {
+ error_log( 'Failed to create MCP client: ' . $client->get_error_message() );
+ return;
+ }
+
+ // Client is now connected and remote capabilities are being registered
+});
+```
+
+### Step 2: Automatic Initialization
+
+The `mcp_client_init` action is automatically triggered during MCP Adapter initialization. You don't need to manually trigger this action.
+
+### Step 3: Verify Connection
+
+Check that the client connection was successful:
+
+```php
+add_action( 'init', function() {
+ // Check if remote abilities were registered
+ if ( wp_get_ability( 'mcp-my-service/some-tool' ) ) {
+ echo 'Successfully connected to external MCP server!';
+ }
+}, 999 ); // Late priority to ensure abilities are registered
+```
+
+### Step 4: Understanding Ability Registration
+
+When a client connects successfully, the MCP Adapter automatically:
+
+1. **Discovers remote capabilities** by calling the MCP server's list methods
+2. **Registers tools and resources** as WordPress abilities with the prefix `mcp-{client-id}/`
+3. **Converts tool names to lowercase** for Abilities API compatibility
+4. **Sets up execution callbacks** that proxy requests to the remote MCP server
+5. **Applies permission checks** using the `mcp_client_permission` filter
+
+**Note:** Remote MCP prompts are not automatically registered as WordPress abilities. They are handled directly by the MCP server infrastructure.
+
+## Authentication Methods
+
+The MCP Client supports multiple authentication methods for connecting to external servers.
+
+### Bearer Token Authentication
+
+```php
+add_action( 'mcp_client_init', function( $adapter ) {
+ $client = $adapter->create_client(
+ 'authenticated-service',
+ 'https://api.example.com/mcp',
+ [
+ 'auth' => [
+ 'type' => 'bearer',
+ 'token' => 'your-bearer-token-here',
+ ],
+ 'timeout' => 30,
+ ]
+ );
+});
+```
+
+### API Key Authentication
+
+```php
+add_action( 'mcp_client_init', function( $adapter ) {
+ $client = $adapter->create_client(
+ 'api-key-service',
+ 'https://api.example.com/mcp',
+ [
+ 'auth' => [
+ 'type' => 'api_key',
+ 'key' => 'your-api-key-here',
+ 'header' => 'X-API-Key', // Optional, defaults to X-API-Key
+ ],
+ 'timeout' => 30,
+ ]
+ );
+});
+```
+
+### Basic Authentication
+
+```php
+add_action( 'mcp_client_init', function( $adapter ) {
+ $client = $adapter->create_client(
+ 'basic-auth-service',
+ 'https://api.example.com/mcp',
+ [
+ 'auth' => [
+ 'type' => 'basic',
+ 'username' => 'your-username',
+ 'password' => 'your-password',
+ ],
+ 'timeout' => 30,
+ ]
+ );
+});
+```
+
+### Environment-Based Authentication
+
+For security, store credentials in environment variables:
+
+```php
+add_action( 'mcp_client_init', function( $adapter ) {
+ $api_token = getenv( 'MCP_SERVICE_TOKEN' );
+
+ if ( empty( $api_token ) ) {
+ error_log( 'MCP_SERVICE_TOKEN environment variable not set' );
+ return;
+ }
+
+ $client = $adapter->create_client(
+ 'env-service',
+ 'https://api.example.com/mcp',
+ [
+ 'auth' => [
+ 'type' => 'bearer',
+ 'token' => $api_token,
+ ],
+ 'timeout' => 30,
+ ]
+ );
+});
+```
+
+## Client Configuration
+
+### Complete Configuration Example
+
+```php
+add_action( 'mcp_client_init', function( $adapter ) {
+ $client = $adapter->create_client(
+ 'full-config-service', // Client ID
+ 'https://api.example.com/mcp', // Server URL
+ [ // Configuration array
+ // Authentication
+ 'auth' => [
+ 'type' => 'bearer',
+ 'token' => 'your-token',
+ ],
+
+ // Connection settings
+ 'timeout' => 30, // Request timeout in seconds
+ 'ssl_verify' => true, // Verify SSL certificates
+ ],
+ MyCustomErrorHandler::class, // Custom error handler (optional)
+ MyCustomObservabilityHandler::class // Custom observability (optional)
+ );
+});
+```
+
+### Configuration Options
+
+| Option | Type | Default | Description |
+|--------|------|---------|-------------|
+| `auth` | array | `[]` | Authentication configuration |
+| `timeout` | int | `30` | Request timeout in seconds |
+| `ssl_verify` | bool | `true` | Whether to verify SSL certificates |
+
+**Note:** The client automatically handles connection retries and user agent strings internally. Custom retry logic and user agent configuration are not supported in the current implementation.
+
+## Working with Remote Abilities
+
+Once a client is connected, remote MCP capabilities become available as WordPress abilities.
+
+### Using Remote Tools
+
+```php
+// Remote MCP tool becomes a WordPress ability
+$result = wp_execute_ability( 'mcp-my-service/analyze-content', [
+ 'content' => 'Text to analyze',
+ 'type' => 'sentiment'
+] );
+
+if ( ! is_wp_error( $result ) ) {
+ echo "Sentiment: " . $result['sentiment'];
+ echo "Confidence: " . $result['confidence'];
+}
+```
+
+### Using Remote Resources
+
+```php
+// Remote MCP resource becomes a WordPress ability
+$data = wp_execute_ability( 'mcp-my-service/user-profile', [] );
+
+if ( ! is_wp_error( $data ) ) {
+ foreach ( $data['contents'] as $content ) {
+ if ( $content['type'] === 'text' ) {
+ $profile_data = json_decode( $content['text'], true );
+ echo "User: " . $profile_data['name'];
+ }
+ }
+}
+```
+
+### Using Remote Prompts
+
+```php
+// Remote MCP prompt becomes a WordPress ability
+$prompt = wp_execute_ability( 'mcp-my-service/seo-analysis', [
+ 'url' => 'https://example.com'
+] );
+
+if ( ! is_wp_error( $prompt ) ) {
+ echo "SEO Recommendations:\n";
+ echo $prompt['analysis'];
+}
+```
+
+### Checking Available Remote Abilities
+
+```php
+// List all abilities from a specific client
+$all_abilities = wp_get_abilities();
+$remote_abilities = array_filter( $all_abilities, function( $ability_name ) {
+ return strpos( $ability_name, 'mcp-my-service/' ) === 0;
+} );
+
+foreach ( $remote_abilities as $ability_name ) {
+ $ability = wp_get_ability( $ability_name );
+ echo "Remote ability: {$ability_name} - {$ability['label']}\n";
+}
+```
+
+## Error Handling
+
+### Client-Level Error Handling
+
+**Note:** Custom error handlers must be class names that implement the `McpErrorHandlerInterface`. The handler class will be instantiated automatically.
+
+```php
+add_action( 'mcp_client_init', function( $adapter ) {
+ $client = $adapter->create_client(
+ 'error-handled-service',
+ 'https://api.example.com/mcp',
+ [
+ 'auth' => [
+ 'type' => 'bearer',
+ 'token' => 'token',
+ ],
+ ],
+ // Custom error handler class for this client
+ MyCustomErrorHandler::class
+ );
+});
+```
+
+### Ability-Level Error Handling
+
+```php
+add_action( 'init', function() {
+ // Wrap remote ability calls in try-catch
+ try {
+ $result = wp_execute_ability( 'mcp-my-service/risky-operation', [
+ 'data' => 'some input'
+ ] );
+
+ if ( is_wp_error( $result ) ) {
+ // Handle WordPress error
+ error_log( 'Remote ability error: ' . $result->get_error_message() );
+ return;
+ }
+
+ // Process successful result
+ process_result( $result );
+
+ } catch ( Exception $e ) {
+ // Handle any other exceptions
+ error_log( 'Unexpected error in remote ability: ' . $e->getMessage() );
+ }
+});
+```
+
+### Connection Health Monitoring
+
+**Note:** The `is_connected()` method reflects the connection status at the time the client was created. It does not perform real-time health checks. For production environments, consider implementing custom health check logic.
+
+```php
+// Monitor client connection health
+add_action( 'wp_loaded', function() {
+ $adapter = \WP\MCP\Core\McpAdapter::instance();
+ $clients = $adapter->get_clients();
+
+ foreach ( $clients as $client_id => $client ) {
+ if ( ! $client->is_connected() ) {
+ error_log( "MCP client {$client_id} is disconnected" );
+
+ // Note: Automatic reconnection is not currently supported
+ // You may need to recreate the client or implement custom reconnection logic
+ error_log( "MCP client {$client_id} is disconnected - manual intervention required" );
+ }
+ }
+});
+```
+
+## Real-World Examples
+
+### Example 1: WordPress Domains Integration
+
+```php
+add_action( 'mcp_client_init', function( $adapter ) {
+ $client = $adapter->create_client(
+ 'wpcom-domains',
+ 'https://wpcom-domains-mcp.a8cai.workers.dev/mcp',
+ [
+ 'timeout' => 30,
+ ]
+ );
+
+ if ( is_wp_error( $client ) ) {
+ error_log( 'Failed to connect to WordPress Domains: ' . $client->get_error_message() );
+ return;
+ }
+
+ // Remote tools are automatically registered as WordPress abilities:
+ // - mcp-wpcom-domains/searchdomains
+ // - mcp-wpcom-domains/checkdomainavailability
+ // - mcp-wpcom-domains/getsuggestedtlds
+} );
+
+// Use the domain search functionality
+add_action( 'admin_init', function() {
+ if ( isset( $_GET['search_domain'] ) ) {
+ $domain_query = sanitize_text_field( $_GET['search_domain'] );
+
+ $results = wp_execute_ability( 'mcp-wpcom-domains/searchdomains', [
+ 'query' => $domain_query,
+ 'limit' => 10
+ ] );
+
+ if ( ! is_wp_error( $results ) ) {
+ update_option( 'domain_search_results', $results );
+ }
+ }
+} );
+```
+
+### Example 2: AI Content Analysis Service
+
+```php
+add_action( 'mcp_client_init', function( $adapter ) {
+ $client = $adapter->create_client(
+ 'ai-content-analyzer',
+ 'https://api.contentanalyzer.com/mcp',
+ [
+ 'auth' => [
+ 'type' => 'api_key',
+ 'key' => get_option( 'content_analyzer_api_key' ),
+ ],
+ 'timeout' => 60, // Content analysis may take longer
+ ]
+ );
+} );
+
+// Add content analysis to post editor
+add_action( 'add_meta_boxes', function() {
+ add_meta_box(
+ 'content_analysis',
+ 'AI Content Analysis',
+ function( $post ) {
+ if ( wp_get_ability( 'mcp-ai-content-analyzer/analyze-post' ) ) {
+ echo 'Analyze Content ';
+ echo '
';
+ } else {
+ echo 'Content analysis service not available.
';
+ }
+ },
+ 'post',
+ 'side'
+ );
+} );
+
+// AJAX handler for content analysis
+add_action( 'wp_ajax_analyze_content', function() {
+ if ( ! current_user_can( 'edit_posts' ) ) {
+ wp_die( 'Insufficient permissions' );
+ }
+
+ $post_id = intval( $_POST['post_id'] );
+ $post = get_post( $post_id );
+
+ if ( ! $post ) {
+ wp_die( 'Post not found' );
+ }
+
+ $analysis = wp_execute_ability( 'mcp-ai-content-analyzer/analyze-post', [
+ 'content' => $post->post_content,
+ 'title' => $post->post_title,
+ 'type' => 'readability'
+ ] );
+
+ if ( is_wp_error( $analysis ) ) {
+ wp_send_json_error( 'Analysis failed: ' . $analysis->get_error_message() );
+ } else {
+ wp_send_json_success( $analysis );
+ }
+} );
+```
+
+### Example 3: Multi-Service Integration
+
+```php
+add_action( 'mcp_client_init', function( $adapter ) {
+ // Connect to multiple services
+ $services = [
+ 'weather' => 'https://api.weather.com/mcp',
+ 'news' => 'https://api.newsservice.com/mcp',
+ 'finance' => 'https://api.financedata.com/mcp',
+ ];
+
+ foreach ( $services as $service_id => $service_url ) {
+ $api_key = get_option( "{$service_id}_api_key" );
+
+ if ( empty( $api_key ) ) {
+ error_log( "No API key configured for service: {$service_id}" );
+ continue;
+ }
+
+ $client = $adapter->create_client(
+ $service_id,
+ $service_url,
+ [
+ 'auth' => [
+ 'type' => 'api_key',
+ 'key' => $api_key,
+ ],
+ 'timeout' => 15,
+ ]
+ );
+
+ if ( is_wp_error( $client ) ) {
+ error_log( "Failed to connect to {$service_id}: " . $client->get_error_message() );
+ } else {
+ error_log( "Successfully connected to {$service_id} MCP service" );
+ }
+ }
+} );
+
+// Create a dashboard widget using multiple services
+add_action( 'wp_dashboard_setup', function() {
+ wp_add_dashboard_widget(
+ 'multi_service_widget',
+ 'External Data Dashboard',
+ function() {
+ // Weather data
+ $weather = wp_execute_ability( 'mcp-weather/current-weather', [
+ 'location' => 'San Francisco, CA'
+ ] );
+
+ // News headlines
+ $news = wp_execute_ability( 'mcp-news/headlines', [
+ 'category' => 'technology',
+ 'limit' => 5
+ ] );
+
+ // Stock data
+ $stocks = wp_execute_ability( 'mcp-finance/stock-quote', [
+ 'symbol' => 'AAPL'
+ ] );
+
+ echo '';
+
+ if ( ! is_wp_error( $weather ) ) {
+ echo "
Weather ";
+ echo "
San Francisco: {$weather['temperature']}°F, {$weather['conditions']}
";
+ }
+
+ if ( ! is_wp_error( $news ) ) {
+ echo "
Tech News ";
+ echo "
";
+ foreach ( array_slice( $news['articles'], 0, 3 ) as $article ) {
+ echo "{$article['title']} ";
+ }
+ echo " ";
+ }
+
+ if ( ! is_wp_error( $stocks ) ) {
+ echo "
AAPL Stock ";
+ echo "
\${$stocks['price']} ({$stocks['change']})
";
+ }
+
+ echo '
';
+ }
+ );
+} );
+```
+
+## Troubleshooting
+
+### Common Issues
+
+#### Client Not Connecting
+
+**Symptoms:**
+- No remote abilities are registered
+- Error messages in logs about connection failures
+
+**Solutions:**
+1. Verify the server URL is correct and accessible
+2. Check authentication credentials
+3. Ensure the external server supports MCP protocol
+4. Check network connectivity and firewall rules
+
+```php
+// Test connection manually
+add_action( 'init', function() {
+ if ( isset( $_GET['test_mcp_connection'] ) && current_user_can( 'manage_options' ) ) {
+ $response = wp_remote_get( 'https://api.example.com/mcp', [
+ 'timeout' => 30,
+ ] );
+
+ if ( is_wp_error( $response ) ) {
+ echo 'Connection failed: ' . $response->get_error_message();
+ } else {
+ echo 'Connection successful: ' . wp_remote_retrieve_response_code( $response );
+ }
+ exit;
+ }
+} );
+```
+
+#### Authentication Failures
+
+**Symptoms:**
+- 401 or 403 HTTP errors
+- "Authentication failed" error messages
+
+**Solutions:**
+1. Verify authentication credentials are correct
+2. Check if authentication method matches server expectations
+3. Ensure credentials have necessary permissions
+
+```php
+// Debug authentication
+add_action( 'mcp_client_init', function( $adapter ) {
+ $client = $adapter->create_client(
+ 'debug-auth',
+ 'https://api.example.com/mcp',
+ [
+ 'auth' => [
+ 'type' => 'bearer',
+ 'token' => 'your-token',
+ ],
+ ],
+ // Debug error handler class
+ MyDebugErrorHandler::class
+ );
+} );
+```
+
+#### Remote Abilities Not Working
+
+**Symptoms:**
+- Remote abilities are registered but return errors when executed
+- Unexpected response formats
+
+**Solutions:**
+1. Check if remote server is functioning properly
+2. Verify input parameters match remote server expectations
+3. Check for version compatibility issues
+
+```php
+// Test remote ability execution
+add_action( 'init', function() {
+ if ( isset( $_GET['test_remote_ability'] ) && current_user_can( 'manage_options' ) ) {
+ $result = wp_execute_ability( 'mcp-my-service/test-tool', [
+ 'test_param' => 'test_value'
+ ] );
+
+ if ( is_wp_error( $result ) ) {
+ echo 'Ability execution failed: ' . $result->get_error_message();
+ } else {
+ echo 'Ability execution successful: ' . wp_json_encode( $result );
+ }
+ exit;
+ }
+} );
+```
+
+### Performance Issues
+
+#### Slow Response Times
+
+**Solutions:**
+1. Increase timeout values for slow external services
+2. Implement caching for frequently accessed data
+3. Use async processing for non-critical operations
+
+```php
+// Implement caching for remote data
+function get_cached_remote_data( $ability_name, $params, $cache_duration = 300 ) {
+ $cache_key = 'mcp_' . md5( $ability_name . serialize( $params ) );
+
+ $cached_result = get_transient( $cache_key );
+ if ( $cached_result !== false ) {
+ return $cached_result;
+ }
+
+ $result = wp_execute_ability( $ability_name, $params );
+
+ if ( ! is_wp_error( $result ) ) {
+ set_transient( $cache_key, $result, $cache_duration );
+ }
+
+ return $result;
+}
+
+// Usage
+$weather_data = get_cached_remote_data( 'mcp-weather/current-weather', [
+ 'location' => 'San Francisco, CA'
+], 1800 ); // Cache for 30 minutes
+```
+
+### Debugging Tools
+
+#### Connection Status Check
+
+```php
+// Add admin page to check client connections
+add_action( 'admin_menu', function() {
+ add_submenu_page(
+ 'tools.php',
+ 'MCP Client Status',
+ 'MCP Clients',
+ 'manage_options',
+ 'mcp-client-status',
+ function() {
+ echo '';
+ echo '
MCP Client Status ';
+
+ $adapter = \WP\MCP\Core\McpAdapter::instance();
+ $clients = $adapter->get_clients();
+
+ echo '
';
+ echo 'Client ID Server URL Status Last Check ';
+ echo '';
+
+ foreach ( $clients as $client_id => $client ) {
+ echo '';
+ echo '' . esc_html( $client_id ) . ' ';
+ echo '' . esc_html( $client->get_server_url() ) . ' ';
+ echo '' . ( $client->is_connected() ? 'Connected' : 'Disconnected' ) . ' ';
+ echo '' . esc_html( current_time( 'mysql' ) ) . ' ';
+ echo ' ';
+ }
+
+ echo ' ';
+ echo '
';
+ echo '
';
+ }
+ );
+} );
+```
+
+## Limitations and Known Issues
+
+### Current Limitations
+
+1. **No Automatic Reconnection**: The client does not automatically reconnect if the connection is lost. You must recreate the client manually.
+
+2. **Limited Configuration Options**: Only `timeout`, `ssl_verify`, and `auth` are supported. Retry logic and user agent customization are handled internally.
+
+3. **Connection Status**: The `is_connected()` method only reflects the initial connection status, not real-time health.
+
+4. **Prompt Registration**: Remote MCP prompts are not automatically registered as WordPress abilities.
+
+5. **Error Handler Requirements**: Custom error handlers must be class names that implement `McpErrorHandlerInterface`, not callable functions.
+
+### Future Enhancements
+
+The following features may be added in future versions:
+- Automatic reconnection with exponential backoff
+- Additional configuration options (retry count, delay, user agent)
+- Real-time connection health monitoring
+- Support for registering remote prompts as abilities
+- Callable function support for error handlers
+
+## Next Steps
+
+- **Explore [Authentication Methods](#authentication-methods)** for secure connections
+- **Review [Error Handling](#error-handling)** for robust integration
+- **Check [Real-World Examples](#real-world-examples)** for practical implementations
+- **Read [Troubleshooting Guide](../troubleshooting/common-issues.md)** for problem-solving
+
+This guide should provide everything you need to successfully integrate WordPress with external MCP servers using the MCP Client functionality.
\ No newline at end of file
diff --git a/includes/Core/McpAdapter.php b/includes/Core/McpAdapter.php
index e181fda..8143a4f 100644
--- a/includes/Core/McpAdapter.php
+++ b/includes/Core/McpAdapter.php
@@ -15,7 +15,7 @@
use WP\MCP\Infrastructure\Observability\NullMcpObservabilityHandler;
/**
- * WordPress MCP Registry - Main class for managing multiple MCP servers.
+ * WordPress MCP Registry - Main class for managing multiple MCP servers and clients.
*/
class McpAdapter {
/**
@@ -53,6 +53,13 @@ class McpAdapter {
*/
private array $servers = array();
+ /**
+ * Registered clients
+ *
+ * @var \WP\MCP\Core\McpClient[]
+ */
+ private array $clients = array();
+
/**
* The has triggered init flag.
*
@@ -119,6 +126,7 @@ public function mcp_adapter_init(): void {
}
do_action( 'mcp_adapter_init', $this );
+ do_action( 'mcp_client_init', $this );
$this->has_triggered_init = true;
}
@@ -285,4 +293,201 @@ public function get_server( string $server_id ): ?McpServer {
public function get_servers(): array {
return $this->servers;
}
+
+ /**
+ * Create and register a new MCP client.
+ *
+ * @param string $client_id Unique identifier for the client.
+ * @param string $server_url MCP server URL to connect to.
+ * @param array $config Client configuration (auth, timeouts, etc.).
+ * @param string|null $error_handler The error handler class name.
+ * @param string|null $observability_handler The observability handler class name.
+ * @return \WP\MCP\Core\McpClient|\WP_Error Client instance or error.
+ * @throws \Exception If the client already exists or invalid handlers.
+ */
+ public function create_client( string $client_id, string $server_url, array $config = array(), ?string $error_handler = null, ?string $observability_handler = null ) {
+
+ // Use default handlers if not provided.
+ if ( ! $error_handler ) {
+ $error_handler = NullMcpErrorHandler::class;
+ }
+
+ if ( ! $observability_handler ) {
+ $observability_handler = NullMcpObservabilityHandler::class;
+ }
+
+ // Validate error handler class implements McpErrorHandlerInterface.
+ if ( ! in_array( McpErrorHandlerInterface::class, class_implements( $error_handler ) ?: array(), true ) ) {
+ return new \WP_Error(
+ 'invalid_error_handler',
+ esc_html__( 'Error handler class must implement the McpErrorHandlerInterface.', 'mcp-adapter' )
+ );
+ }
+
+ // Validate observability handler class implements McpObservabilityHandlerInterface.
+ if ( ! in_array( McpObservabilityHandlerInterface::class, class_implements( $observability_handler ) ?: array(), true ) ) {
+ return new \WP_Error(
+ 'invalid_observability_handler',
+ esc_html__( 'Observability handler class must implement the McpObservabilityHandlerInterface interface.', 'mcp-adapter' )
+ );
+ }
+
+ if ( ! doing_action( 'mcp_client_init' ) ) {
+ return new \WP_Error(
+ 'invalid_timing',
+ esc_html__( 'MCP Client creation must be done during mcp_client_init action.', 'mcp-adapter' )
+ );
+ }
+
+ if ( isset( $this->clients[ $client_id ] ) ) {
+ return new \WP_Error(
+ 'client_exists',
+ // translators: %s: client ID.
+ sprintf( esc_html__( 'Client with ID "%s" already exists.', 'mcp-adapter' ), esc_html( $client_id ) )
+ );
+ }
+
+ // Create client instance.
+ $client = new McpClient(
+ $client_id,
+ $server_url,
+ $config,
+ new $error_handler(),
+ new $observability_handler()
+ );
+
+ // Track client creation.
+ $observability_handler::record_event(
+ 'mcp.client.created',
+ array(
+ 'client_id' => $client_id,
+ 'server_url' => $server_url,
+ 'connected' => $client->is_connected(),
+ )
+ );
+
+ // Add client to registry.
+ $this->clients[ $client_id ] = $client;
+
+ // Register remote capabilities as local abilities.
+ $this->register_remote_abilities( $client );
+
+ return $client;
+ }
+
+ /**
+ * Get a client by ID.
+ *
+ * @param string $client_id Client ID.
+ * @return \WP\MCP\Core\McpClient|null
+ */
+ public function get_client( string $client_id ): ?McpClient {
+ return $this->clients[ $client_id ] ?? null;
+ }
+
+ /**
+ * Get all registered clients.
+ *
+ * @return \WP\MCP\Core\McpClient[]
+ */
+ public function get_clients(): array {
+ return $this->clients;
+ }
+
+ /**
+ * Register remote MCP capabilities as local WordPress abilities.
+ *
+ * @param McpClient $client MCP client instance.
+ * @return void
+ */
+ private function register_remote_abilities( McpClient $client ): void {
+ if ( ! $client->is_connected() ) {
+ return;
+ }
+
+ if ( ! function_exists( 'wp_register_ability' ) ) {
+ return;
+ }
+
+ $client_id = $client->get_client_id();
+ // Use dashes instead of underscores for ability names (Abilities API requirement)
+ $prefix = 'mcp-' . sanitize_key( $client_id ) . '/';
+
+ // Register tools as abilities.
+ $tools = $client->list_tools();
+ if ( ! is_wp_error( $tools ) && isset( $tools['tools'] ) ) {
+ foreach ( $tools['tools'] as $tool ) {
+ $tool_name = $tool['name'] ?? '';
+ if ( empty( $tool_name ) ) {
+ continue;
+ }
+
+ // Convert tool name to lowercase for Abilities API compatibility
+ $ability_name = $prefix . strtolower( $tool_name );
+
+ $result = wp_register_ability(
+ $ability_name,
+ array(
+ 'label' => ucwords( str_replace( array( '-', '_' ), ' ', $tool_name ) ),
+ 'description' => $tool['description'] ?? "Remote MCP tool: {$tool_name}",
+ 'input_schema' => $tool['inputSchema'] ?? array( 'type' => 'object' ),
+ 'output_schema' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'content' => array(
+ 'type' => 'array',
+ 'items' => array( 'type' => 'object' ),
+ ),
+ ),
+ ),
+ 'execute_callback' => function ( $args ) use ( $client, $tool_name ) {
+ return $client->call_tool( $tool_name, $args );
+ },
+ 'permission_callback' => function () {
+ return apply_filters( 'mcp_client_permission', is_user_logged_in() );
+ },
+ )
+ );
+ }
+ }
+
+ // Register resources as abilities.
+ $resources = $client->list_resources();
+ if ( ! is_wp_error( $resources ) && isset( $resources['resources'] ) ) {
+ foreach ( $resources['resources'] as $resource ) {
+ $resource_uri = $resource['uri'] ?? '';
+ if ( empty( $resource_uri ) ) {
+ continue;
+ }
+
+ $ability_name = $prefix . 'resource/' . sanitize_title( str_replace( array( '://', '/' ), '_', $resource_uri ) );
+
+ wp_register_ability(
+ $ability_name,
+ array(
+ 'label' => 'Resource: ' . basename( $resource_uri ),
+ 'description' => $resource['description'] ?? "Remote MCP resource: {$resource_uri}",
+ 'input_schema' => array( 'type' => 'object' ),
+ 'output_schema' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'contents' => array( 'type' => 'array' ),
+ ),
+ ),
+ 'execute_callback' => function ( $args ) use ( $client, $resource_uri ) {
+ return $client->read_resource( $resource_uri );
+ },
+ 'permission_callback' => function () {
+ return apply_filters( 'mcp_client_permission', is_user_logged_in() );
+ },
+ )
+ );
+ }
+ }
+
+ // Note: Remote MCP prompts are handled directly by the MCP server infrastructure
+ // rather than being converted to WordPress abilities. This matches the pattern
+ // used in McpServer.php where prompts are their own entity type.
+ }
+
}
diff --git a/includes/Core/McpClient.php b/includes/Core/McpClient.php
new file mode 100644
index 0000000..90791dc
--- /dev/null
+++ b/includes/Core/McpClient.php
@@ -0,0 +1,541 @@
+client_id = $client_id;
+ $this->server_url = rtrim( $server_url, '/' );
+ $this->config = $config;
+ $this->error_handler = $error_handler;
+ $this->observability_handler = $observability_handler;
+
+ // Detect transport protocol from URL
+ if ( strpos( $server_url, '/sse' ) !== false ) {
+ $this->transport = 'sse';
+ } elseif ( strpos( $server_url, '/mcp' ) !== false ) {
+ $this->transport = 'mcp';
+ }
+
+
+ // Auto-connect on construction
+ $this->connect();
+ }
+
+ /**
+ * Connect to the MCP server.
+ *
+ * @return bool True on success, false on failure.
+ */
+ public function connect(): bool {
+ $start_time = microtime( true );
+
+
+ try {
+ // Simple handshake request
+ $response = $this->send_request( 'initialize', array(
+ 'protocolVersion' => '2025-06-18',
+ 'capabilities' => array(
+ 'roots' => array( 'listChanged' => true ),
+ 'sampling' => array(),
+ ),
+ 'clientInfo' => array(
+ 'name' => 'WordPress MCP Client',
+ 'version' => '1.0.0',
+ ),
+ ) );
+
+ if ( is_wp_error( $response ) ) {
+ $error_msg = 'MCP Initialize Request Failed: ' . $response->get_error_message();
+ error_log( $error_msg );
+ $this->error_handler->log(
+ 'Failed to connect to MCP server',
+ array(
+ 'client_id' => $this->client_id,
+ 'server_url' => $this->server_url,
+ 'error' => $response->get_error_message(),
+ )
+ );
+ return false;
+ }
+
+ // Handle successful response
+ if ( $response ) {
+ $this->connected = true;
+ $this->capabilities = $response['capabilities'] ?? array();
+
+ // Extract session ID if provided (for DeepWiki compatibility)
+ if ( isset( $response['sessionId'] ) ) {
+ $this->session_id = $response['sessionId'];
+ }
+
+ } else {
+ return false;
+ }
+
+ // Record connection
+ $duration = ( microtime( true ) - $start_time ) * 1000;
+ $this->observability_handler::record_event(
+ 'mcp.client.connected',
+ array( 'client_id' => $this->client_id )
+ );
+ $this->observability_handler::record_timing(
+ 'mcp.client.connect_duration',
+ $duration,
+ array( 'client_id' => $this->client_id )
+ );
+
+ return true;
+
+ } catch ( \Throwable $e ) {
+ $this->error_handler->log(
+ 'Exception during MCP client connection',
+ array(
+ 'client_id' => $this->client_id,
+ 'exception' => $e->getMessage(),
+ )
+ );
+ return false;
+ }
+ }
+
+ /**
+ * Send a request to the MCP server.
+ *
+ * @param string $method MCP method.
+ * @param array $params Request parameters.
+ * @param int $request_id Request ID.
+ * @return array|\WP_Error Response or error.
+ */
+ public function send_request( string $method, array $params = array(), int $request_id = 0 ) {
+ static $request_counter = 1;
+
+ if ( 0 === $request_id ) {
+ $request_id = $request_counter++;
+ }
+
+ $request_body = array(
+ 'jsonrpc' => '2.0',
+ 'method' => $method,
+ 'params' => $params,
+ 'id' => $request_id,
+ );
+
+ // Set headers based on transport protocol
+ if ( $this->transport === 'sse' ) {
+ $headers = array(
+ 'Content-Type' => 'application/json',
+ 'Accept' => 'text/event-stream',
+ 'Cache-Control' => 'no-cache',
+ );
+ error_log( 'MCP Client: Using SSE transport headers' );
+ } else {
+ $headers = array(
+ 'Content-Type' => 'application/json',
+ 'Accept' => 'application/json, text/event-stream',
+ );
+
+ // Only add session ID for /mcp transport, but NOT for initialize requests
+ if ( $method === 'initialize' && ! $this->session_id ) {
+ $this->session_id = $this->generate_session_id();
+ }
+
+ // DeepWiki requires that initialize requests do NOT include session ID
+ if ( $this->session_id && $method !== 'initialize' ) {
+ $headers['Mcp-Session-Id'] = $this->session_id;
+ }
+ }
+
+ // Use POST for both SSE and MCP (based on working curl test)
+ $timeout = $this->config['timeout'] ?? 30;
+
+ // For SSE, use shorter timeout since it's a streaming connection
+ if ( $this->transport === 'sse' ) {
+ $timeout = min( $timeout, 5 ); // Max 5 seconds for SSE initial response
+ }
+
+ $args = array(
+ 'method' => 'POST',
+ 'headers' => $headers,
+ 'body' => wp_json_encode( $request_body ),
+ 'timeout' => $timeout,
+ 'sslverify' => $this->config['ssl_verify'] ?? true,
+ );
+
+
+ // Add authentication if configured
+ if ( isset( $this->config['auth'] ) ) {
+ $args['headers'] = array_merge( $args['headers'], $this->get_auth_headers() );
+ }
+
+
+ // Use cURL directly for SSE to handle streaming properly
+ if ( $this->transport === 'sse' ) {
+ $response = $this->send_sse_request( $this->server_url, $args );
+ } else {
+ $response = wp_remote_post( $this->server_url, $args );
+ }
+
+ if ( is_wp_error( $response ) ) {
+ return $response;
+ }
+
+ $response_body = wp_remote_retrieve_body( $response );
+ $http_code = wp_remote_retrieve_response_code( $response );
+
+ // Handle response based on transport protocol and content
+ if ( $this->transport === 'sse' || strpos( $response_body, 'event: message' ) !== false ) {
+ $decoded = $this->parse_sse_response( $response_body );
+ if ( ! $decoded ) {
+ return new \WP_Error( 'sse_parse_error', 'Failed to parse SSE response' );
+ }
+ } else {
+ $decoded = json_decode( $response_body, true );
+ }
+
+ if ( json_last_error() !== JSON_ERROR_NONE ) {
+ return new \WP_Error( 'json_error', 'Invalid JSON response' );
+ }
+
+ if ( isset( $decoded['error'] ) ) {
+ return new \WP_Error(
+ $decoded['error']['code'] ?? 'server_error',
+ $decoded['error']['message'] ?? 'Unknown server error'
+ );
+ }
+
+ return $decoded['result'] ?? $decoded;
+ }
+
+ /**
+ * Get authentication headers.
+ *
+ * @return array Auth headers.
+ */
+ private function get_auth_headers(): array {
+ $auth = $this->config['auth'];
+ $headers = array();
+
+ switch ( $auth['type'] ?? 'none' ) {
+ case 'bearer':
+ if ( isset( $auth['token'] ) ) {
+ $headers['Authorization'] = 'Bearer ' . $auth['token'];
+ }
+ break;
+
+ case 'api_key':
+ if ( isset( $auth['key'] ) ) {
+ $header_name = $auth['header'] ?? 'X-API-Key';
+ $headers[ $header_name ] = $auth['key'];
+ }
+ break;
+
+ case 'basic':
+ if ( isset( $auth['username'], $auth['password'] ) ) {
+ $credentials = base64_encode( $auth['username'] . ':' . $auth['password'] );
+ $headers['Authorization'] = 'Basic ' . $credentials;
+ }
+ break;
+ }
+
+ return $headers;
+ }
+
+ /**
+ * Execute a remote tool.
+ *
+ * @param string $tool_name Tool name.
+ * @param array $arguments Tool arguments.
+ * @return array|\WP_Error Tool result or error.
+ */
+ public function call_tool( string $tool_name, array $arguments = array() ) {
+ return $this->send_request( 'tools/call', array(
+ 'name' => $tool_name,
+ 'arguments' => $arguments,
+ ) );
+ }
+
+ /**
+ * Read a remote resource.
+ *
+ * @param string $resource_uri Resource URI.
+ * @return array|\WP_Error Resource data or error.
+ */
+ public function read_resource( string $resource_uri ) {
+ return $this->send_request( 'resources/read', array(
+ 'uri' => $resource_uri,
+ ) );
+ }
+
+ /**
+ * Get a remote prompt.
+ *
+ * @param string $prompt_name Prompt name.
+ * @param array $arguments Prompt arguments.
+ * @return array|\WP_Error Prompt data or error.
+ */
+ public function get_prompt( string $prompt_name, array $arguments = array() ) {
+ return $this->send_request( 'prompts/get', array(
+ 'name' => $prompt_name,
+ 'arguments' => $arguments,
+ ) );
+ }
+
+ /**
+ * List available tools.
+ *
+ * @return array|\WP_Error Tools list or error.
+ */
+ public function list_tools() {
+ return $this->send_request( 'tools/list' );
+ }
+
+ /**
+ * List available resources.
+ *
+ * @return array|\WP_Error Resources list or error.
+ */
+ public function list_resources() {
+ return $this->send_request( 'resources/list' );
+ }
+
+ /**
+ * List available prompts.
+ *
+ * @return array|\WP_Error Prompts list or error.
+ */
+ public function list_prompts() {
+ return $this->send_request( 'prompts/list' );
+ }
+
+ /**
+ * Get client ID.
+ *
+ * @return string Client ID.
+ */
+ public function get_client_id(): string {
+ return $this->client_id;
+ }
+
+ /**
+ * Get server URL.
+ *
+ * @return string Server URL.
+ */
+ public function get_server_url(): string {
+ return $this->server_url;
+ }
+
+ /**
+ * Check if connected.
+ *
+ * @return bool True if connected.
+ */
+ public function is_connected(): bool {
+ return $this->connected;
+ }
+
+ /**
+ * Get discovered capabilities.
+ *
+ * @return array Server capabilities.
+ */
+ public function get_capabilities(): array {
+ return $this->capabilities;
+ }
+
+ /**
+ * Send SSE request using cURL for proper streaming support.
+ *
+ * @param string $url Request URL.
+ * @param array $args Request arguments.
+ * @return array|WP_Error Response array or error.
+ */
+ private function send_sse_request( string $url, array $args ) {
+ if ( ! function_exists( 'curl_init' ) ) {
+ return new \WP_Error( 'curl_missing', 'cURL is required for SSE support' );
+ }
+
+ $ch = curl_init();
+ curl_setopt_array( $ch, array(
+ CURLOPT_URL => $url,
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_POST => true,
+ CURLOPT_POSTFIELDS => $args['body'],
+ CURLOPT_TIMEOUT => $args['timeout'],
+ CURLOPT_SSL_VERIFYPEER => $args['sslverify'],
+ CURLOPT_FOLLOWLOCATION => true,
+ CURLOPT_MAXREDIRS => 3,
+ ) );
+
+ // Set headers
+ $curl_headers = array();
+ foreach ( $args['headers'] as $key => $value ) {
+ $curl_headers[] = $key . ': ' . $value;
+ }
+ curl_setopt( $ch, CURLOPT_HTTPHEADER, $curl_headers );
+
+
+ $response_body = curl_exec( $ch );
+ $http_code = curl_getinfo( $ch, CURLINFO_HTTP_CODE );
+ $curl_error = curl_error( $ch );
+ curl_close( $ch );
+
+ if ( $curl_error ) {
+ return new \WP_Error( 'curl_error', $curl_error );
+ }
+
+ // Return in WordPress HTTP response format
+ return array(
+ 'response' => array( 'code' => $http_code ),
+ 'body' => $response_body,
+ );
+ }
+
+ /**
+ * Generate a unique session ID for MCP requests.
+ *
+ * @return string Generated session ID.
+ */
+ private function generate_session_id(): string {
+ // Try different session ID formats for DeepWiki compatibility
+ return uniqid();
+ }
+
+ /**
+ * Parse Server-Sent Events response format.
+ *
+ * @param string $sse_body SSE response body.
+ * @return array|null Parsed JSON data or null if no message found.
+ */
+ private function parse_sse_response( string $sse_body ): ?array {
+
+ $lines = explode( "\n", $sse_body );
+ $current_event = null;
+ $current_data = '';
+ $parsed_messages = array();
+
+ foreach ( $lines as $line ) {
+ $line = trim( $line );
+
+ if ( strpos( $line, 'event: ' ) === 0 ) {
+ $current_event = substr( $line, 7 );
+ } elseif ( strpos( $line, 'data: ' ) === 0 ) {
+ $current_data = substr( $line, 6 );
+
+ // If this is a message event with JSON data, parse it
+ if ( $current_event === 'message' && ! empty( $current_data ) ) {
+ $decoded = json_decode( $current_data, true );
+ if ( $decoded ) {
+ // For initialize responses, return the result
+ if ( isset( $decoded['result'] ) ) {
+ return $decoded['result'];
+ }
+ // For other responses, return the full decoded response
+ return $decoded;
+ }
+ }
+ } elseif ( empty( $line ) ) {
+ // Empty line indicates end of event
+ $current_event = null;
+ $current_data = '';
+ }
+ }
+
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/includes/Plugin.php b/includes/Plugin.php
index 07c0b8c..da05cdf 100644
--- a/includes/Plugin.php
+++ b/includes/Plugin.php
@@ -47,6 +47,7 @@ public static function instance(): self {
* Setup the plugin.
*/
private function setup(): void {
+ // Initialize the core MCP adapter
McpAdapter::instance();
}
diff --git a/mcp-adapter.php b/mcp-adapter.php
index 0669b8a..99d5f2e 100644
--- a/mcp-adapter.php
+++ b/mcp-adapter.php
@@ -44,6 +44,12 @@ function constants(): void {
}
constants();
+
+// Load the Abilities API first (only if not already loaded)
+if ( ! defined( 'WP_ABILITIES_API_DIR' ) && file_exists( __DIR__ . '/vendor/wordpress/abilities-api/abilities-api.php' ) ) {
+ require_once __DIR__ . '/vendor/wordpress/abilities-api/abilities-api.php';
+}
+
require_once __DIR__ . '/includes/Autoloader.php';
// If autoloader failed, we cannot proceed.
diff --git a/tests/Fixtures/MockMcpServer.php b/tests/Fixtures/MockMcpServer.php
new file mode 100644
index 0000000..7803a0f
--- /dev/null
+++ b/tests/Fixtures/MockMcpServer.php
@@ -0,0 +1,133 @@
+ '2024-11-05',
+ 'capabilities' => array(
+ 'tools' => array(),
+ 'prompts' => array(),
+ ),
+ 'serverInfo' => array(
+ 'name' => 'Mock Test Server',
+ 'version' => '1.0.0',
+ ),
+ );
+ }
+
+ /**
+ * Generate a mock tools list response.
+ *
+ * @return array Mock tools response.
+ */
+ public static function get_tools_response(): array {
+ return array(
+ 'tools' => array(
+ array(
+ 'name' => 'testTool',
+ 'description' => 'A test tool for unit testing',
+ 'inputSchema' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'input' => array(
+ 'type' => 'string',
+ 'description' => 'Test input parameter',
+ ),
+ ),
+ 'required' => array( 'input' ),
+ ),
+ ),
+ array(
+ 'name' => 'anotherTestTool',
+ 'description' => 'Another test tool with different schema',
+ 'inputSchema' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'count' => array(
+ 'type' => 'integer',
+ 'default' => 5,
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+
+ /**
+ * Generate a mock resources list response.
+ *
+ * @return array Mock resources response.
+ */
+ public static function get_resources_response(): array {
+ return array(
+ 'resources' => array(
+ array(
+ 'uri' => 'test://resource-1',
+ 'description' => 'First test resource',
+ 'mimeType' => 'application/json',
+ ),
+ array(
+ 'uri' => 'test://resource-2',
+ 'description' => 'Second test resource',
+ 'mimeType' => 'text/plain',
+ ),
+ ),
+ );
+ }
+
+ /**
+ * Generate a mock prompts list response.
+ *
+ * @return array Mock prompts response.
+ */
+ public static function get_prompts_response(): array {
+ return array(
+ 'prompts' => array(
+ array(
+ 'name' => 'test-prompt',
+ 'description' => 'A test prompt for unit testing',
+ 'arguments' => array(
+ array(
+ 'name' => 'topic',
+ 'description' => 'Topic for the prompt',
+ 'required' => true,
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+
+ /**
+ * Generate mock tool call response.
+ *
+ * @param string $tool_name Tool that was called.
+ * @param array $arguments Arguments passed to tool.
+ * @return array Mock tool response.
+ */
+ public static function get_tool_call_response( string $tool_name, array $arguments ): array {
+ return array(
+ 'content' => array(
+ array(
+ 'type' => 'text',
+ 'text' => "Mock response for {$tool_name} with args: " . wp_json_encode( $arguments ),
+ ),
+ ),
+ );
+ }
+}
\ No newline at end of file
diff --git a/tests/Integration/McpClientIntegrationTest.php b/tests/Integration/McpClientIntegrationTest.php
new file mode 100644
index 0000000..13c38dd
--- /dev/null
+++ b/tests/Integration/McpClientIntegrationTest.php
@@ -0,0 +1,59 @@
+create_client(
+ 'test-integration-client',
+ 'https://example.com/mcp',
+ array( 'timeout' => 5 ),
+ NullMcpErrorHandler::class,
+ NullMcpObservabilityHandler::class
+ );
+
+ // Should return a client instance (even if connection fails)
+ $this->assertInstanceOf( McpClient::class, $client );
+
+ // Adapter should track the client
+ $clients = $adapter->get_clients();
+ $this->assertArrayHasKey( 'test-integration-client', $clients );
+ $this->assertSame( $client, $clients['test-integration-client'] );
+ }
+
+ public function test_client_abilities_registration_workflow(): void {
+ // This test would need a mock MCP server to test the full workflow
+ // For now, verify the adapter has the capability to register abilities
+ $adapter = McpAdapter::instance();
+
+ $this->assertTrue( method_exists( $adapter, 'create_client' ) );
+ $this->assertTrue( method_exists( $adapter, 'get_clients' ) );
+ }
+
+ public function test_client_respects_ability_naming_conventions(): void {
+ $adapter = McpAdapter::instance();
+
+ // Verify the adapter follows the naming convention we established
+ $client = $adapter->create_client(
+ 'test-naming-client',
+ 'https://example.com/mcp',
+ array(),
+ NullMcpErrorHandler::class,
+ NullMcpObservabilityHandler::class
+ );
+
+ $this->assertInstanceOf( McpClient::class, $client );
+ $this->assertSame( 'test-naming-client', $client->get_client_id() );
+ }
+}
\ No newline at end of file
diff --git a/tests/Unit/McpClientTest.php b/tests/Unit/McpClientTest.php
new file mode 100644
index 0000000..3f952e7
--- /dev/null
+++ b/tests/Unit/McpClientTest.php
@@ -0,0 +1,93 @@
+ 30 ),
+ new NullMcpErrorHandler(),
+ new NullMcpObservabilityHandler()
+ );
+
+ $this->assertSame( 'test-client', $client->get_client_id() );
+ $this->assertSame( 'https://api.example.com/mcp', $client->get_server_url() );
+ $this->assertIsArray( $client->get_capabilities() );
+ }
+
+ public function test_it_detects_transport_protocol_from_url(): void {
+ // Test MCP transport detection
+ $mcp_client = new McpClient(
+ 'mcp-client',
+ 'https://server.com/mcp',
+ array(),
+ new NullMcpErrorHandler(),
+ new NullMcpObservabilityHandler()
+ );
+
+ // Test SSE transport detection
+ $sse_client = new McpClient(
+ 'sse-client',
+ 'https://server.com/sse',
+ array(),
+ new NullMcpErrorHandler(),
+ new NullMcpObservabilityHandler()
+ );
+
+ // We can't directly test the private transport property, but we can verify
+ // the clients were created without errors
+ $this->assertInstanceOf( McpClient::class, $mcp_client );
+ $this->assertInstanceOf( McpClient::class, $sse_client );
+ }
+
+ public function test_it_handles_connection_errors_gracefully(): void {
+ $client = new McpClient(
+ 'failing-client',
+ 'https://nonexistent-server-12345.com/mcp',
+ array( 'timeout' => 1 ),
+ new NullMcpErrorHandler(),
+ new NullMcpObservabilityHandler()
+ );
+
+ // Should handle connection failure gracefully
+ $this->assertFalse( $client->is_connected() );
+ }
+
+ public function test_it_validates_required_parameters(): void {
+ $this->expectException( \TypeError::class );
+
+ // Should throw TypeError for missing required parameters
+ new McpClient();
+ }
+
+ public function test_it_generates_unique_session_ids(): void {
+ $client1 = new McpClient(
+ 'client1',
+ 'https://server.com/mcp',
+ array(),
+ new NullMcpErrorHandler(),
+ new NullMcpObservabilityHandler()
+ );
+
+ $client2 = new McpClient(
+ 'client2',
+ 'https://server.com/mcp',
+ array(),
+ new NullMcpErrorHandler(),
+ new NullMcpObservabilityHandler()
+ );
+
+ // Different clients should exist as separate instances
+ $this->assertNotSame( $client1, $client2 );
+ }
+}
\ No newline at end of file