-
Notifications
You must be signed in to change notification settings - Fork 46
feat: Add MCP Client support for bidirectional MCP integration #32
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: trunk
Are you sure you want to change the base?
Conversation
- Add McpClient class for connecting to external MCP servers
- Extend McpAdapter to manage both servers and clients
- Auto-register remote MCP capabilities as WordPress abilities
- Support multiple authentication methods (Bearer, API Key, Basic)
- Maintain architectural consistency with existing server patterns
- Add comprehensive usage examples and documentation
Remote MCP tools, resources, and prompts become accessible as:
- mcp_{client_id}/tool-name
- mcp_{client_id}/resource/resource-name
- mcp_{client_id}/prompt/prompt-name
This enables WordPress to both serve and consume MCP capabilities,
creating a powerful bidirectional integration with the MCP ecosystem.
- Add functional MCP client that connects to WordPress Domains MCP server - Fix ability naming to comply with WordPress Abilities API requirements (lowercase, dashes only) - Register remote MCP tools as WordPress abilities with proper labels and schemas - Add admin interface showing connected clients and registered abilities - Create public WordPress abilities (get-site-info, get-posts, search-content, get-menu) - Handle MCP session management for different server types - Clean up verbose debug logging for production readiness - Remove prompt-to-ability conversion (prompts handled by MCP infrastructure)
Core MCP Adapter (minimal additions to trunk): - Add McpClient.php for connecting to external MCP servers - Add client support methods to McpAdapter.php - Add comprehensive test coverage for client functionality - Remove demo-specific functionality from core plugin Demo Plugin (new separate plugin): - Extract admin interface to demo/includes/Admin/ - Move examples to demo/examples/ - Create standalone demo plugin with proper dependency handling - Showcase MCP adapter usage without bloating core package wp-env Configuration: - Update to load both core adapter and demo plugin - Proper plugin load order with dependency management This creates a clean separation where the core adapter provides infrastructure while the demo showcases comprehensive usage patterns.
- Fix undefined constant WP_MCP_DEMO_DIR in demo autoloader - Remove invalid 'context' property from ability registration (not supported by Abilities API) - Clean up demo examples to be focused and practical - Resolve HTTP 500 errors in wp-env admin interface
- Add dashboard widget documentation to demo README - Update docs README with MCP client integration section - Enhance MCP client guide with implementation details and limitations - Include new dashboard widget files and assets in demo structure
The demo plugin has been separated into its own repository to keep the core MCP adapter focused on developer-facing functionality. - Remove demo/ directory and all demo files - Update README references to point to separate demo repository - Core adapter now contains only the essential MCP client/server functionality
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR adds bidirectional MCP integration by introducing McpClient functionality that allows WordPress to connect to external MCP servers and consume their capabilities as local abilities. The implementation follows the existing server patterns for consistency and provides comprehensive documentation and testing.
Key Changes
- Added
McpClientclass for connecting to external MCP servers with authentication support - Extended
McpAdapterto manage clients alongside servers with automatic ability registration - Comprehensive test coverage including unit tests, integration tests, and mock server fixtures
Reviewed Changes
Copilot reviewed 14 out of 15 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
includes/Core/McpClient.php |
New MCP client implementation with multi-transport and authentication support |
includes/Core/McpAdapter.php |
Extended to manage clients and auto-register remote abilities |
tests/Unit/McpClientTest.php |
Unit tests for client functionality and error handling |
tests/Integration/McpClientIntegrationTest.php |
Integration tests for client-adapter interaction |
tests/Fixtures/MockMcpServer.php |
Mock MCP server for testing client functionality |
docs/guides/mcp-client.md |
Comprehensive client usage guide with examples |
mcp-adapter.php |
Abilities API loader with conditional loading |
composer.json |
Added abilities-api dependency |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| 'Accept' => 'text/event-stream', | ||
| 'Cache-Control' => 'no-cache', | ||
| ); | ||
| error_log( 'MCP Client: Using SSE transport headers' ); |
Copilot
AI
Sep 1, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Debug logging should be removed from production code or made conditional. Consider using the observability handler for debugging information instead of direct error_log calls.
| // Use dashes instead of underscores for ability names (Abilities API requirement) | ||
| $prefix = 'mcp-' . sanitize_key( $client_id ) . '/'; |
Copilot
AI
Sep 1, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment mentions using dashes, but sanitize_key() converts dashes to underscores. This creates inconsistency between the comment and actual behavior. Either update the comment or use a different sanitization approach.
| // Use dashes instead of underscores for ability names (Abilities API requirement) | |
| $prefix = 'mcp-' . sanitize_key( $client_id ) . '/'; | |
| $prefix = 'mcp-' . preg_replace( '/[^a-z0-9\-]/', '', strtolower( $client_id ) ) . '/'; |
| // Convert tool name to lowercase for Abilities API compatibility | ||
| $ability_name = $prefix . strtolower( $tool_name ); |
Copilot
AI
Sep 1, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Converting tool names to lowercase could cause conflicts if external MCP servers have tools with names that differ only in case (e.g., 'getUserData' vs 'getuserdata'). Consider using a more robust naming strategy that preserves uniqueness.
| // Convert tool name to lowercase for Abilities API compatibility | |
| $ability_name = $prefix . strtolower( $tool_name ); | |
| // Sanitize tool name for Abilities API compatibility, preserving case | |
| $sanitized_tool_name = preg_replace( '/[^A-Za-z0-9\-_]/', '-', $tool_name ); | |
| $ability_name = $prefix . $sanitized_tool_name; |
justlevine
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Didn't dive too deeply to avoid being redundant to phpcs/phpstan/ #27
More generally, I think the code patterns here like using WP_Error are a marked improvement over the existing patterns in McpServer and its deps; if consistency is a concern, those existing classes/methods should IMO be brought to align with what's here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wondering for SRP/Maintainability if it makes sense to have a separate ClientRegistry from our ServerRegistry. related #26
| * | ||
| * Mirrors the McpServer architecture for consistency. | ||
| */ | ||
| class McpClient { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Buys us time to shape the API. There's currently no way to overload this class anyway, and there's a significant number of public methods on this class.
| class McpClient { | |
| final class McpClient { |
| /** | ||
| * Connect to the MCP server. | ||
| * | ||
| * @return bool True on success, false on failure. | ||
| */ | ||
| public function connect(): bool { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this meant to be consumed publicly? e.g. if ( ! $my_client->is_connected() ) { $client->connect(); }
If not, let's make the method private until we have a concrete use case to expose it public.
If so, I suggest we get it out of constructor and dogfood our own APIs by calling it in MCPAdapter::create_client().
|
|
||
| if ( is_wp_error( $response ) ) { | ||
| $error_msg = 'MCP Initialize Request Failed: ' . $response->get_error_message(); | ||
| error_log( $error_msg ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm still only partially through my initial code review , but I believe these are handled by the ErrorLogMcpErrorHandler abstraction when `$this->error_handler->log() gets called
mcp-adapter/includes/Infrastructure/ErrorHandling/ErrorLogMcpErrorHandler.php
Lines 30 to 40 in f41bf0e
public function log( string $message, array $context = array(), string $type = 'error' ): void { $user_id = function_exists( 'get_current_user_id' ) ? get_current_user_id() : 0; $log_message = sprintf( '[%s] %s | Context: %s | User ID: %d', strtoupper( $type ), $message, wp_json_encode( $context ), $user_id ); error_log( $log_message ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log }
| * @param string|null $error_handler The error handler class name. | ||
| * @param string|null $observability_handler The observability handler class name. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| * @param string|null $error_handler The error handler class name. | |
| * @param string|null $observability_handler The observability handler class name. | |
| * @param ?class-string<\WP\MCP\Infrastructure\ErrorHandling\Contracts\McpErrorHandlerInterface> $error_handler The error handler class name. | |
| * @param ?class-string<\WP\MCP\Infrastructure\Observability\Contracts\McpObservabilityHandlerInterface> $observability_handler The observability handler class name. |
| * @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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are (thankfully) returned
| * @throws \Exception If the client already exists or invalid handlers. |
Summary
McpClientclass for connecting to external MCP serversMcpAdapterto support client management alongside serversKey Features
mcp-{client-id}/prefixTest Plan