Skip to content

Commit a9acca6

Browse files
committed
Merge branch 'main' into feature/docs
2 parents 30401e5 + e6300fd commit a9acca6

15 files changed

+408
-48
lines changed

ai-command.php

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace WP_CLI\AiCommand;
44

5+
use WP_CLI\AiCommand\ToolRepository\CollectionToolRepository;
56
use WP_CLI;
67

78
if ( ! class_exists( '\WP_CLI' ) ) {
@@ -14,4 +15,32 @@
1415
require_once $ai_command_autoloader;
1516
}
1617

17-
WP_CLI::add_command( 'ai', AiCommand::class );
18+
WP_CLI::add_command( 'ai', static function ( $args, $assoc_args ) {
19+
$server = new MCP\Server();
20+
$client = new MCP\Client($server);
21+
22+
$tools = new ToolCollection();
23+
24+
// TODO Register your tool here and add it to the collection
25+
26+
$image_tools = new ImageTools($client);
27+
28+
foreach($image_tools->get_tools() as $tool){
29+
$tools->add($tool);
30+
}
31+
32+
33+
// WordPress REST calls
34+
$rest_tools = new MapRESTtoMCP();
35+
36+
foreach( $rest_tools->map_rest_to_mcp() as $tool ) {
37+
$tools->add( $tool );
38+
}
39+
40+
$ai_command = new AiCommand(
41+
new CollectionToolRepository( $tools ),
42+
$server,
43+
$client
44+
);
45+
$ai_command( $args, $assoc_args );
46+
} );

settings/route-additions.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
return [
4+
'post' => [
5+
'POST' => [
6+
'defaults' => [
7+
'status' => 'private',
8+
],
9+
],
10+
],
11+
];

src/AiCommand.php

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace WP_CLI\AiCommand;
44

5+
use WP_CLI\AiCommand\ToolRepository\CollectionToolRepository;
56
use WP_CLI\AiCommand\Tools\FileTools;
67
use WP_CLI\AiCommand\Tools\URLTools;
78
use WP_CLI;
@@ -23,6 +24,14 @@
2324
*/
2425
class AiCommand extends WP_CLI_Command {
2526

27+
public function __construct(
28+
private CollectionToolRepository $tools,
29+
private WP_CLI\AiCommand\MCP\Server $server,
30+
private WP_CLI\AiCommand\MCP\Client $client
31+
) {
32+
parent::__construct();
33+
}
34+
2635
/**
2736
* Greets the world.
2837
*
@@ -47,20 +56,25 @@ class AiCommand extends WP_CLI_Command {
4756
* @param array $assoc_args Associative array of associative arguments.
4857
*/
4958
public function __invoke( $args, $assoc_args ) {
50-
$server = new MCP\Server();
51-
$client = new MCP\Client($server);
52-
53-
$this->register_tools($server, $client);
59+
$this->register_tools($this->server);
60+
$this->register_resources($this->server);
5461

55-
$this->register_resources($server);
56-
57-
$result = $client->call_ai_service_with_prompt( $args[0] );
62+
$result = $this->client->call_ai_service_with_prompt( $args[0] );
5863

5964
WP_CLI::success( $result );
6065
}
6166

6267
// Register tools for AI processing
63-
private function register_tools($server, $client) {
68+
private function register_tools($server) : void {
69+
$filters = apply_filters( 'wp_cli/ai_command/command/filters', [] );
70+
71+
foreach( $this->tools->find_all( $filters ) as $tool ) {
72+
$server->register_tool( $tool->get_data() );
73+
}
74+
75+
return;
76+
77+
// TODO move this
6478
$server->register_tool(
6579
[
6680
'name' => 'list_tools',
@@ -89,8 +103,10 @@ private function register_tools($server, $client) {
89103
]
90104
);
91105

92-
$map_rest_to_mcp = new MapRESTtoMCP();
93-
$map_rest_to_mcp->map_rest_to_mcp( $server );
106+
107+
108+
new FileTools( $server );
109+
new URLTools( $server );
94110

95111
$server->register_tool(
96112
[

src/Collection.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace WP_CLI\AiCommand;
6+
7+
use Countable;
8+
use Iterator;
9+
10+
abstract class Collection implements Iterator, Countable
11+
{
12+
13+
protected array $data = [];
14+
15+
public function next(): void
16+
{
17+
next($this->data);
18+
}
19+
20+
public function key(): int
21+
{
22+
return (int)key($this->data);
23+
}
24+
25+
public function valid(): bool
26+
{
27+
return key($this->data) !== null;
28+
}
29+
30+
public function rewind(): void
31+
{
32+
reset($this->data);
33+
}
34+
35+
public function count(): int
36+
{
37+
return count($this->data);
38+
}
39+
40+
}

src/Entity/Tool.php

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace WP_CLI\AiCommand\Entity;
6+
7+
use InvalidArgumentException;
8+
9+
final class Tool
10+
{
11+
12+
public function __construct(
13+
private array $data,
14+
private array $tags = []
15+
) {
16+
$this->validate();
17+
}
18+
19+
private function validate(): void
20+
{
21+
foreach ($this->tags as $tag) {
22+
if ( ! preg_match('/^[a-z][a-z-]+$/', $tag)) {
23+
throw new InvalidArgumentException('Tags can only contain [a-z] and -.');
24+
}
25+
}
26+
}
27+
28+
public function get_name(): string
29+
{
30+
return $this->data['name'];
31+
}
32+
33+
public function get_tags(): array
34+
{
35+
return $this->tags;
36+
}
37+
38+
public function get_data(): array
39+
{
40+
return $this->data;
41+
}
42+
43+
}

src/ImageTools.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
namespace WP_CLI\AiCommand;
4+
use WP_CLI\AiCommand\Entity\Tool;
5+
6+
class ImageTools {
7+
8+
private $client;
9+
10+
public function __construct($client) {
11+
$this->client = $client;
12+
}
13+
14+
public function get_tools(){
15+
return [
16+
$this->image_generation_tool()
17+
];
18+
}
19+
20+
public function image_generation_tool() {
21+
return new Tool(
22+
[
23+
'name' => 'generate_image',
24+
'description' => 'Generates an image',
25+
'inputSchema' => [
26+
'type' => 'object',
27+
'properties' => [
28+
'prompt' => [
29+
'type' => 'string',
30+
'description' => 'The prompt for generating the image.',
31+
],
32+
],
33+
'required' => [ 'prompt' ],
34+
],
35+
'callable' => function ( $params ) {
36+
37+
return $this->client->get_image_from_ai_service( $params['prompt'] );
38+
},
39+
]
40+
);
41+
}
42+
43+
44+
45+
}

src/MCP/Client.php

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,10 @@ static function () {
107107
rename( $filename, $filename . '.' . $extension );
108108
$filename .= '.' . $extension;
109109

110-
file_put_contents( $filename, $image_blob->get_binary_data() );
110+
// file_put_contents( $filename, $image_blob->get_binary_data() );
111111

112112
$image_url = $filename;
113+
$image_id = \WP_CLI\AiCommand\MediaManager::upload_to_media_library($image_url);
113114
}
114115

115116
break;
@@ -120,6 +121,8 @@ static function () {
120121
// TODO: Save as file or so.
121122
break;
122123
}
124+
125+
return $image_id || 'no image found';
123126
}
124127

125128
// See https://github.com/felixarntz/ai-services/blob/main/docs/Accessing-AI-Services-in-PHP.md for further processing.
@@ -188,17 +191,17 @@ static function () {
188191

189192
\WP_CLI::debug( 'Making request...' . print_r( $contents, true ), 'ai' );
190193

191-
if ( $service->get_service_slug() === 'openai' ) {
192-
$model = 'gpt-4o';
193-
} else {
194-
$model = 'gemini-2.0-flash';
195-
}
194+
// if ( $service->get_service_slug() === 'openai' ) {
195+
// $model = 'gpt-4o';
196+
// } else {
197+
// $model = 'gemini-2.0-flash';
198+
// }
196199

197200
$candidates = $service
198201
->get_model(
199202
[
200203
'feature' => 'text-generation',
201-
'model' => $model,
204+
// 'model' => $model,
202205
'tools' => $tools,
203206
'capabilities' => [
204207
AI_Capability::MULTIMODAL_INPUT,
@@ -227,9 +230,7 @@ static function () {
227230
} elseif ( $part instanceof Function_Call_Part ) {
228231
$function_result = $this->{$part->get_name()}( $part->get_args() );
229232

230-
// Capture the function name here
231-
$function_name = $part->get_name();
232-
echo "Output generated with the '$function_name' tool:\n"; // Log the function name
233+
WP_CLI::debug( 'Calling Tool: ' . $part->get_name(), 'mcp_server' );
233234

234235
// Odd limitation of add_function_response_part().
235236
if ( ! is_array( $function_result ) ) {

src/MapRESTtoMCP.php

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
namespace WP_CLI\AiCommand;
44

5+
use WP_CLI\AiCommand\Entity\Tool;
56
use WP_CLI;
6-
use WP_CLI\AiCommand\MCP\Server;
77
use WP_REST_Request;
88

99

@@ -75,9 +75,10 @@ protected function sanitize_type( $type) {
7575

7676
}
7777

78-
public function map_rest_to_mcp( Server $mcp_server ) {
78+
public function map_rest_to_mcp() : array {
7979
$server = rest_get_server();
8080
$routes = $server->get_routes();
81+
$tools = [];
8182

8283
foreach ( $routes as $route => $endpoints ) {
8384
foreach ( $endpoints as $endpoint ) {
@@ -92,19 +93,21 @@ public function map_rest_to_mcp( Server $mcp_server ) {
9293
continue;
9394
}
9495

95-
$tool = [
96+
$tool = new Tool( [
9697
'name' => $information->get_sanitized_route_name(),
9798
'description' => $this->generate_description( $information ),
9899
'inputSchema' => $this->args_to_schema( $endpoint['args'] ),
99100
'callable' => function ( $inputs ) use ( $route, $method_name, $server ){
100101
return $this->rest_callable( $inputs, $route, $method_name, $server );
101102
},
102-
];
103+
] );
103104

104-
$mcp_server->register_tool($tool);
105+
$tools[] = $tool;
105106
}
106107
}
107108
}
109+
110+
return $tools;
108111
}
109112

110113
/**

src/MediaManager.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
namespace WP_CLI\AiCommand;
4+
5+
class MediaManager {
6+
7+
public static function upload_to_media_library($media_path) {
8+
// Get WordPress upload directory information
9+
$upload_dir = wp_upload_dir();
10+
11+
// Get the file name from the path
12+
$file_name = basename($media_path);
13+
14+
// Copy file to the upload directory
15+
$new_file_path = $upload_dir['path'] . '/' . $file_name;
16+
copy($media_path, $new_file_path);
17+
18+
// Prepare attachment data
19+
$wp_filetype = wp_check_filetype($file_name, null);
20+
$attachment = array(
21+
'post_mime_type' => $wp_filetype['type'],
22+
'post_title' => sanitize_file_name($file_name),
23+
'post_content' => '',
24+
'post_status' => 'inherit'
25+
);
26+
27+
// Insert the attachment
28+
$attach_id = wp_insert_attachment($attachment, $new_file_path);
29+
30+
// Generate attachment metadata
31+
require_once(ABSPATH . 'wp-admin/includes/image.php');
32+
$attach_data = wp_generate_attachment_metadata($attach_id, $new_file_path);
33+
wp_update_attachment_metadata($attach_id, $attach_data);
34+
35+
return $attach_id;
36+
37+
}
38+
}

0 commit comments

Comments
 (0)