这是indexloc提供的服务,不要输入任何密码
Skip to content

Conversation

@Jameswlepage
Copy link
Contributor

Summary

  • Add McpClient class for connecting to external MCP servers
  • Extend McpAdapter to support client management alongside servers
  • Automatically register remote MCP tools/resources as WordPress abilities
  • Support multiple authentication methods (Bearer, API key, Basic auth)
  • Follow existing server patterns for consistency and maintainability

Key Features

  • Bidirectional Integration: WordPress can now both expose and consume MCP capabilities
  • Automatic Ability Registration: Remote tools become local abilities with mcp-{client-id}/ prefix
  • Multiple Transport Support: SSE and standard MCP protocol support
  • Authentication: Bearer token, API key, and basic auth support
  • Error Handling: Consistent with existing server error handling patterns

Test Plan

  • Unit tests for McpClient functionality
  • Integration tests with WordPress Domains MCP server
  • Authentication method validation
  • Error handling and edge cases
  • Ability registration and execution

- 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
Copilot AI review requested due to automatic review settings September 1, 2025 00:27
@github-actions
Copy link

github-actions bot commented Sep 1, 2025

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 props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: Jameswlepage <isotropic@git.wordpress.org>
Co-authored-by: justlevine <justlevine@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

Copy link
Contributor

Copilot AI left a 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 McpClient class for connecting to external MCP servers with authentication support
  • Extended McpAdapter to 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' );
Copy link

Copilot AI Sep 1, 2025

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.

Copilot uses AI. Check for mistakes.
Comment on lines +413 to +414
// Use dashes instead of underscores for ability names (Abilities API requirement)
$prefix = 'mcp-' . sanitize_key( $client_id ) . '/';
Copy link

Copilot AI Sep 1, 2025

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.

Suggested change
// 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 ) ) . '/';

Copilot uses AI. Check for mistakes.
Comment on lines +425 to +426
// Convert tool name to lowercase for Abilities API compatibility
$ability_name = $prefix . strtolower( $tool_name );
Copy link

Copilot AI Sep 1, 2025

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.

Suggested change
// 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;

Copilot uses AI. Check for mistakes.
Copy link
Contributor

@justlevine justlevine left a 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.

Copy link
Contributor

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 {
Copy link
Contributor

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.

Suggested change
class McpClient {
final class McpClient {

Comment on lines +119 to +124
/**
* Connect to the MCP server.
*
* @return bool True on success, false on failure.
*/
public function connect(): bool {
Copy link
Contributor

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 );
Copy link
Contributor

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

  • 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
    }

Comment on lines +303 to +304
* @param string|null $error_handler The error handler class name.
* @param string|null $observability_handler The observability handler class name.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are (thankfully) returned

Suggested change
* @throws \Exception If the client already exists or invalid handlers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

4 participants