Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,23 @@ To install the latest development version of this package, use the following com
wp package install mcp-wp/ai-command:dev-main
```

Right now, the plugin requires a WordPress site with the [AI Services plugin](https://wordpress.org/plugins/ai-services) installed.
This package uses the **WP AI Client** (available in WordPress 7.0+ or via the AI plugin) for AI functionality.

### Configuration

Configure credentials for AI providers:

```bash
# Configure credentials for AI providers
wp ai credentials set openai sk-proj-YOUR-API-KEY
wp ai credentials set anthropic sk-ant-YOUR-API-KEY
wp ai credentials set google YOUR-GOOGLE-API-KEY

# List configured credentials
wp ai credentials list
```

Credentials are stored in the WordPress database and can also be managed through the WordPress admin settings screen.

### Reporting a bug

Expand Down
9 changes: 5 additions & 4 deletions ai-command.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
require_once __DIR__ . '/vendor/autoload.php';
}

WP_CLI::add_command( 'ai', AiCommand::class );
WP_CLI::add_command( 'mcp prompt', AiCommand::class );
WP_CLI::add_command( 'mcp', McpCommand::class );
WP_CLI::add_command( 'mcp server', McpServerCommand::class );
WP_CLI::add_command( 'ai', \McpWp\AiCommand\AiCommand::class );
WP_CLI::add_command( 'ai credentials', \McpWp\AiCommand\CredentialsCommand::class );
WP_CLI::add_command( 'mcp prompt', \McpWp\AiCommand\AiCommand::class );
WP_CLI::add_command( 'mcp', \McpWp\AiCommand\McpCommand::class );
WP_CLI::add_command( 'mcp server', \McpWp\AiCommand\McpServerCommand::class );
5 changes: 4 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"roave/security-advisories": "dev-latest",
"wp-cli/extension-command": "^2.1",
"wp-cli/wp-cli-tests": "^v4.3.9",
"wpackagist-plugin/ai-services": "^0.6.0"
"wordpress/wp-ai-client": "^0.2.1"
},
"repositories":[
{
Expand Down Expand Up @@ -49,6 +49,9 @@
"bundled": false,
"commands": [
"ai",
"ai credentials list",
"ai credentials set",
"ai credentials delete",
"mcp prompt",
"mcp server add",
"mcp server list",
Expand Down
12 changes: 12 additions & 0 deletions features/ai-credentials.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Feature: AI Credentials command
Scenario: Credentials management with WP AI Client
Given a WP installation

When I try `wp ai credentials list`
Then STDERR should contain:
"""
The WP AI Client is not available.
"""

# TODO: Add tests for when WP AI Client is available
# This would require installing the AI plugin or WordPress 7.0+
11 changes: 2 additions & 9 deletions features/ai.feature
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Feature: AI command
Scenario: Missing AI Services plugin
Scenario: Missing WP AI Client
When I try `wp ai "Hello World"`
Then STDERR should contain:
"""
Expand All @@ -16,12 +16,5 @@ Feature: AI command
When I try `wp ai "Hello World"`
Then STDERR should contain:
"""
This command currently requires the AI Services plugin.
"""

When I run `wp plugin install ai-services --activate`
When I try `wp ai "Hello World"`
Then STDERR should contain:
"""
No service satisfying the given arguments is registered and available.
This command requires the WP AI Client.
"""
144 changes: 144 additions & 0 deletions src/AI/WpAiClient.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
<?php

namespace McpWp\AiCommand\AI;

use Exception;
use InvalidArgumentException;
use WP_CLI;
use function cli\menu;
use function cli\prompt;

/**
* WP AI Client wrapper class.
*
* Provides an adapter for the WP AI Client library.
*
* @phpstan-type ToolDefinition array{name: string, description: string|null, parameters: array<string, array<string, mixed>>, server: string, callback: callable}
*/
class WpAiClient {
private bool $needs_approval = true;

/**
* @param array $tools List of tools.
* @param bool $approval_mode Whether tool usage needs to be approved.
* @param string|null $service Service to use.
* @param string|null $model Model to use.
*
* @phpstan-param ToolDefinition[] $tools
*/
public function __construct(
private readonly array $tools,
private readonly bool $approval_mode,
private readonly ?string $service,
private readonly ?string $model
) {}

/**
* Calls a given tool.
*
* @param string $tool_name Tool name.
* @param mixed $tool_args Tool args.
* @return mixed
*/
private function call_tool( string $tool_name, mixed $tool_args ): mixed {
foreach ( $this->tools as $tool ) {
if ( $tool_name === $tool['name'] ) {
return call_user_func( $tool['callback'], $tool_args );
}
}

throw new InvalidArgumentException( 'Tool "' . $tool_name . '" not found.' );
}

/**
* Returns the name of the server a given tool is coming from.
*
* @param string $tool_name Tool name.
* @return mixed
*/
private function get_tool_server_name( string $tool_name ): mixed {
foreach ( $this->tools as $tool ) {
if ( $tool_name === $tool['name'] ) {
return $tool['server'];
}
}

throw new InvalidArgumentException( 'Tool "' . $tool_name . '" not found.' );
}

/**
* Calls AI service with a prompt.
*
* @param string $prompt The prompt to send.
*/
public function call_ai_service_with_prompt( string $prompt ): void {
try {
// Initialize WP AI Client if not already done.
if ( ! did_action( 'init' ) ) {
do_action( 'init' );
}

\WordPress\AI_Client\AI_Client::init();

// Create a prompt builder.
$prompt_builder = \WordPress\AI_Client\AI_Client::prompt( $prompt );

// Apply model preference if specified.
if ( $this->service && $this->model ) {
$prompt_builder = $prompt_builder->using_model_preference( [ $this->service, $this->model ] );
} elseif ( $this->model ) {
// If only model is specified without a service, try common providers.
// This provides a reasonable fallback that works with most configurations.
// The WP AI Client will automatically use the first available provider
// that has the specified model and is properly configured.
$prompt_builder = $prompt_builder->using_model_preference(
[ 'anthropic', $this->model ],
[ 'openai', $this->model ],
[ 'google', $this->model ]
);
}

// Generate text response.
$text = $prompt_builder->generate_text();

// Output the response.
WP_CLI::line( WP_CLI::colorize( "%G$text%n" ) );

// Keep the session open for follow-up questions.
$this->continue_conversation( $prompt, $text );

} catch ( Exception $e ) {
WP_CLI::error( $e->getMessage() );
}
}

/**
* Continues the conversation with follow-up prompts.
*
* @param string $initial_prompt The initial prompt.
* @param string $response The AI response.
*/
private function continue_conversation( string $initial_prompt, string $response ): void {
$user_response = prompt( '', false, '' );

if ( empty( $user_response ) ) {
return;
}

try {
$prompt_builder = \WordPress\AI_Client\AI_Client::prompt( $user_response );

if ( $this->service && $this->model ) {
$prompt_builder = $prompt_builder->using_model_preference( [ $this->service, $this->model ] );
}

$text = $prompt_builder->generate_text();

WP_CLI::line( WP_CLI::colorize( "%G$text%n" ) );

$this->continue_conversation( $user_response, $text );
} catch ( Exception $e ) {
WP_CLI::error( $e->getMessage() );
}
}
}
55 changes: 15 additions & 40 deletions src/AiCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,15 @@

namespace McpWp\AiCommand;

use Mcp\Client\ClientSession;
use McpWp\AiCommand\AI\AiClient;
use McpWp\AiCommand\MCP\Client;
use McpWp\AiCommand\Utils\CliLogger;
use McpWp\AiCommand\Utils\McpConfig;
use McpWp\MCP\Servers\WordPress\WordPress;
use McpWp\AiCommand\AI\WpAiClient;
use WP_CLI;
use WP_CLI\Utils;
use WP_CLI_Command;

/**
* AI command class.
*
* Allows interacting with an LLM using MCP.
*
* @phpstan-import-type ToolDefinition from AiClient
* Allows interacting with an LLM using the WP AI Client.
*/
class AiCommand extends WP_CLI_Command {

Expand All @@ -29,38 +22,26 @@ class AiCommand extends WP_CLI_Command {
* <prompt>
* : AI prompt.
*
* [--skip-builtin-servers[=<server>]]
* : Skip loading the built-in servers for WP-CLI and the current WordPress site.
* Can be set to 'all' (skip both), 'cli' (skip the WP-CLI server),
* or 'wp' (skip the WordPress server).
*
* [--skip-wordpress]
* : Run command without loading WordPress. (Not implemented yet)
*
* [--approval-mode]
* : Approve tool usage before running.
*
* [--service=<service>]
* : Manually specify the AI service to use.
* Depends on the available AI services.
* Examples: 'google', 'anthropic', 'openai'.
*
* [--model=<model>]
* : Manually specify the LLM model that should be used.
* Depends on the available AI services.
* Examples: 'gemini-2.0-flash', 'gpt-4o'.
* Examples: 'gemini-2.0-flash', 'gpt-4o', 'claude-sonnet-4-5'.
*
* ## EXAMPLES
*
* # Get data from WordPress
* $ wp ai "What are the titles of my last three posts?"
* - Hello world
* - My awesome post
* - Another post
* # Ask a simple question
* $ wp ai "Explain WordPress in one sentence"
* WordPress is a free and open-source content management system...
*
* # Interact with multiple MCP servers.
* $ wp ai "Take file foo.txt and create a new blog post from it"
* Success: Blog post created.
* # Use a specific model
* $ wp ai "Summarize the history of WordPress" --model=gpt-4o
* WordPress was created in 2003...
*
* @when before_wp_load
*
Expand All @@ -75,21 +56,15 @@ public function __invoke( array $args, array $assoc_args ): void {
WP_CLI::error( 'Not implemented yet.' );
}

if ( ! function_exists( '\ai_services' ) ) {
WP_CLI::error( 'This command currently requires the AI Services plugin. You can install it with `wp plugin install ai-services --activate`.' );
// Ensure WP AI Client is available.
if ( ! class_exists( '\WordPress\AI_Client\AI_Client' ) ) {
WP_CLI::error( 'This command requires the WP AI Client. Please ensure WordPress 7.0+ or the AI plugin is installed and activated.' );
}

$skip_builtin_servers = Utils\get_flag_value( $assoc_args, 'skip-builtin-servers', 'all' );

$sessions = $this->get_sessions( $with_wordpress && 'cli' === $skip_builtin_servers, 'wp' === $skip_builtin_servers );
$tools = $this->get_tools( $sessions );

$approval_mode = (bool) Utils\get_flag_value( $assoc_args, 'approval-mode', false );
$service = Utils\get_flag_value( $assoc_args, 'service' );
$model = Utils\get_flag_value( $assoc_args, 'model' );

$ai_client = new AiClient( $tools, $approval_mode, $service, $model );
$service = Utils\get_flag_value( $assoc_args, 'service' );
$model = Utils\get_flag_value( $assoc_args, 'model' );

$ai_client = new WpAiClient( [], false, $service, $model );
$ai_client->call_ai_service_with_prompt( $args[0] );
}

Expand Down
Loading
Loading