From 5d9bc1248645553e7c46be89d8cc611825086053 Mon Sep 17 00:00:00 2001 From: Tome Pajkovski Date: Sun, 16 Mar 2025 17:54:24 +0100 Subject: [PATCH 1/7] adding the base image generation tool --- ai-command.php | 13 +++++++++---- src/ImageTools.php | 11 ++--------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/ai-command.php b/ai-command.php index 8df738a..7621937 100644 --- a/ai-command.php +++ b/ai-command.php @@ -23,10 +23,15 @@ // TODO Register your tool here and add it to the collection - $all_tools = [ - ...(new ImageTools($client))->get_tools(), - ...(new MapRESTtoMCP())->map_rest_to_mcp(), - ]; + $image_tools = new ImageTools(); + + foreach($image_tools->get_tools() as $tool){ + $tools->add($tool); + } + + + // WordPress REST calls + $rest_tools = new MapRESTtoMCP(); foreach ($all_tools as $tool) { $tools->add($tool); diff --git a/src/ImageTools.php b/src/ImageTools.php index d68dbe5..2ad6a05 100644 --- a/src/ImageTools.php +++ b/src/ImageTools.php @@ -1,15 +1,9 @@ client = $client; - } public function get_tools(){ return [ @@ -32,9 +26,8 @@ public function image_generation_tool() { ], 'required' => [ 'prompt' ], ], - 'callable' => function ( $params ) { - - return $this->client->get_image_from_ai_service( $params['prompt'] ); + 'callable' => function ( $params ) use ( $client ) { + return $client->get_image_from_ai_service( $params['prompt'] ); }, ] ); From 38be99737812ced354f7375704cc3022b53af340 Mon Sep 17 00:00:00 2001 From: Tome Pajkovski Date: Mon, 17 Mar 2025 08:12:10 +0100 Subject: [PATCH 2/7] refactoring the generate an image too --- ai-command.php | 4 ++-- src/ImageTools.php | 15 ++++++++++----- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/ai-command.php b/ai-command.php index 7621937..624550d 100644 --- a/ai-command.php +++ b/ai-command.php @@ -23,7 +23,7 @@ // TODO Register your tool here and add it to the collection - $image_tools = new ImageTools(); + $image_tools = new ImageTools($client); foreach($image_tools->get_tools() as $tool){ $tools->add($tool); @@ -33,7 +33,7 @@ // WordPress REST calls $rest_tools = new MapRESTtoMCP(); - foreach ($all_tools as $tool) { + foreach ($rest_tools as $tool) { $tools->add($tool); } diff --git a/src/ImageTools.php b/src/ImageTools.php index 2ad6a05..cea63d2 100644 --- a/src/ImageTools.php +++ b/src/ImageTools.php @@ -1,9 +1,17 @@ client = $client; + } + public function get_tools(){ return [ @@ -26,13 +34,10 @@ public function image_generation_tool() { ], 'required' => [ 'prompt' ], ], - 'callable' => function ( $params ) use ( $client ) { - return $client->get_image_from_ai_service( $params['prompt'] ); + 'callable' => function ( $params ) { + return $this->client->get_image_from_ai_service( $params['prompt'] ); }, ] ); } - - - } \ No newline at end of file From 7560b753db9de84ae8a7e8c3a7218171f73ac09a Mon Sep 17 00:00:00 2001 From: Tome Pajkovski Date: Mon, 17 Mar 2025 08:52:08 +0100 Subject: [PATCH 3/7] working image modification --- ai-command.php | 25 +++++++++- src/AiCommand.php | 70 ++++++++++++++++++++++++++ src/ImageTools.php | 40 +++++++++++++-- src/MCP/Client.php | 122 +++++++++++++++++++++++++++++++++++++++++++++ src/MCP/Server.php | 18 ++++++- 5 files changed, 270 insertions(+), 5 deletions(-) diff --git a/ai-command.php b/ai-command.php index 624550d..7932f2b 100644 --- a/ai-command.php +++ b/ai-command.php @@ -23,7 +23,7 @@ // TODO Register your tool here and add it to the collection - $image_tools = new ImageTools($client); + $image_tools = new ImageTools($client, $server); foreach($image_tools->get_tools() as $tool){ $tools->add($tool); @@ -44,3 +44,26 @@ ); $ai_command( $args, $assoc_args ); } ); + + + +if(!function_exists('\WP_CLI\AiCommand\custom_tome_log')) { + function custom_tome_log( $message, $data = '' ) { + + $log = trailingslashit( dirname(__FILE__)) . 'log/'; + if ( ! is_dir( $log ) ) { + mkdir( $log ); + } + + $file = $log . date( 'Y-m-d' ) . '.log'; + if ( ! is_file( $file ) ) { + file_put_contents( $file, '' ); + } + if ( ! empty( $data ) ) { + $message = array( $message => $data ); + } + $data_string = print_r( $message, true ) . "\n"; + file_put_contents( $file, $data_string, FILE_APPEND ); + } + +} \ No newline at end of file diff --git a/src/AiCommand.php b/src/AiCommand.php index 28f0c2f..d7e3057 100644 --- a/src/AiCommand.php +++ b/src/AiCommand.php @@ -72,6 +72,8 @@ private function register_tools($server) : void { $server->register_tool( $tool->get_data() ); } + $this->register_media_resources($server); + return; // TODO move this @@ -238,4 +240,72 @@ private function register_resources($server) { 'filePath' => './products.json', // Data will be fetched from products.json ]); } + + protected function register_media_resources( $server ) { + + $args = array( + 'post_type' => 'attachment', + 'post_status' => 'inherit', + 'posts_per_page' => - 1, + ); + + $media_items = get_posts( $args ); + + foreach ( $media_items as $media ) { + + $media_id = $media->ID; + $media_url = wp_get_attachment_url( $media_id ); + $media_type = get_post_mime_type( $media_id ); + $media_title = get_the_title( $media_id ); + + $server->register_resource( + [ + 'name' => 'media_' . $media_id, + 'uri' => 'media://' . $media_id, + 'description' => $media_title, + 'mimeType' => $media_type, + 'callable' => function () use ( $media_id, $media_url, $media_type ) { + $data = [ + 'id' => $media_id, + 'url' => $media_url, + 'filepath' => get_attached_file( $media_id ), + 'alt' => get_post_meta( $media_id, '_wp_attachment_image_alt', true ), + 'mime_type' => $media_type, + 'metadata' => wp_get_attachment_metadata( $media_id ), + ]; + + return $data; + }, + ] + ); + } + + // Also register a media collection resource + $server->register_resource( + [ + 'name' => 'media_collection', + 'uri' => 'data://media', + 'description' => 'Collection of all media items', + 'mimeType' => 'application/json', + 'callable' => function () { + + $args = array( + 'post_type' => 'attachment', + 'post_status' => 'inherit', + 'posts_per_page' => - 1, + 'fields' => 'ids', + ); + + $media_ids = get_posts( $args ); + $media_map = []; + + foreach ( $media_ids as $id ) { + $media_map[ $id ] = 'media://' . $id; + } + + return $media_map; + }, + ] + ); + } } diff --git a/src/ImageTools.php b/src/ImageTools.php index cea63d2..b98f068 100644 --- a/src/ImageTools.php +++ b/src/ImageTools.php @@ -2,20 +2,24 @@ namespace WP_CLI\AiCommand; use WP_CLI\AiCommand\Entity\Tool; +use WP_CLI\AiCommand\custom_tome_log; class ImageTools { - public $client; + protected $client; + protected $server; - public function __construct($client) { + public function __construct($client, $server) { $this->client = $client; + $this->server = $server; } public function get_tools(){ return [ - $this->image_generation_tool() + $this->image_generation_tool(), + $this->image_modification_tool() ]; } @@ -40,4 +44,34 @@ public function image_generation_tool() { ] ); } + + public function image_modification_tool() { + + return new Tool( + [ + 'name' => 'modify_image', + 'description' => 'Modifies an image with a given image id and prompt.', + 'inputSchema' => [ + 'type' => 'object', + 'properties' => [ + 'prompt' => [ + 'type' => 'string', + 'description' => 'The prompt for generating the image.', + ], + 'media_id' => [ + 'type' => 'integer', + 'description' => 'the id of the media element', + ], + ], + 'required' => [ 'prompt', 'media_id' ], + ], + 'callable' => function ( $params ) { + $media_uri = 'media://' . $params['media_id']; + $media_resource = $this->server->get_resource_data( $media_uri ); + return $this->client->modify_image_with_ai( $params['prompt'], $media_resource ); + }, + ] + ); + + } } \ No newline at end of file diff --git a/src/MCP/Client.php b/src/MCP/Client.php index 2316bd0..d570feb 100644 --- a/src/MCP/Client.php +++ b/src/MCP/Client.php @@ -309,4 +309,126 @@ static function () { WP_CLI::error( $e->getMessage() ); } } + + public function modify_image_with_ai($prompt, $media_element) { + + + $mime_type = $media_element['mime_type']; + $image_path = $media_element['filepath']; + $image_contents = file_get_contents($image_path); + + // Convert image to base64 + $base64_image = base64_encode($image_contents); + + // API Configuration + $api_key = 'AIzaSyBqCwJnOiF_muJ9vjAg7gc7xKS6o_Zxtok'; + $model = 'gemini-2.0-flash-exp'; + $api_url = "https://generativelanguage.googleapis.com/v1beta/models/{$model}:generateContent?key={$api_key}"; + + // Prepare request payload + $payload = [ + 'contents' => [ + [ + 'role' => 'user', + 'parts' => [ + [ + 'text' => $prompt + ], + [ + 'inline_data' => [ + 'mime_type' => $mime_type, + 'data' => $base64_image + ] + ] + ] + ] + ], + 'generationConfig' => [ + 'responseModalities' => ['TEXT', 'IMAGE'] + ] + ]; + + // Convert payload to JSON + $json_payload = json_encode($payload); + + // Set up cURL request + $ch = curl_init($api_url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $json_payload); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'Content-Type: application/json', + 'Content-Length: ' . strlen($json_payload) + ]); + + // Execute request + $response = curl_exec($ch); + $error = curl_error($ch); + $status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + // Handle errors + if ($error) { + WP_CLI::error("cURL Error: " . $error); + return false; + } + + if ($status_code >= 400) { + WP_CLI::error("API Error (Status $status_code): " . $response); + return false; + } + + // Process response + $response_data = json_decode($response, true); + + // Check for valid response + if (empty($response_data) || !isset($response_data['candidates'][0]['content']['parts'])) { + WP_CLI::error("Invalid API response format"); + return false; + } + + // Extract image data from response + $image_data = null; + foreach ($response_data['candidates'][0]['content']['parts'] as $part) { + if (isset($part['inlineData'])) { + $image_data = $part['inlineData']['data']; + $response_mime_type = $part['inlineData']['mimeType']; + break; + } + } + + if (!$image_data) { + WP_CLI::error("No image data in response"); + return false; + } + + // Decode base64 image + $binary_data = base64_decode($image_data); + if (false === $binary_data) { + WP_CLI::error("Failed to decode image data"); + return false; + } + + // Create temporary file for the image + $extension = explode('/', $response_mime_type)[1] ?? 'jpg'; + $filename = tempnam('/tmp', 'ai-generated-image'); + rename($filename, $filename . '.' . $extension); + $filename .= '.' . $extension; + + // Save image to the file + if (!file_put_contents($filename, $binary_data)) { + WP_CLI::error("Failed to save image to temporary file"); + return false; + } + + // Upload to media library + $image_id = \WP_CLI\AiCommand\MediaManager::upload_to_media_library($filename); + + if ($image_id) { + WP_CLI::success('Image generated with ID: ' . $image_id); + return $image_id; + } + + return false; +} } diff --git a/src/MCP/Server.php b/src/MCP/Server.php index 0bc3124..cff2080 100644 --- a/src/MCP/Server.php +++ b/src/MCP/Server.php @@ -211,10 +211,16 @@ private function read_resource( $uri ) { ]; } - private function get_resource_data( $mcp_resource ) { + public function get_resource_data( $mcp_resource ) { // Replace this with your actual logic to access the resource data // based on the resource definition. + if ( str_starts_with( $mcp_resource, 'media://' ) ) { + return $this->get_media_data( $mcp_resource ); + } + + + // Example: If the resource is a file, read the file contents. if ( isset( $mcp_resource['filePath'] ) ) { return file_get_contents( $mcp_resource['filePath'] ); @@ -230,6 +236,16 @@ private function get_resource_data( $mcp_resource ) { throw new Exception( 'Unable to access resource data.' ); } + private function get_media_data( $mcp_resource ) { + + foreach ( $this->resources as $resource ) { + if ( $resource['uri'] === $mcp_resource ) { + $callback_response = $resource['callable'](); + return $callback_response; + } + } + } + // TODO: use a dedicated JSON schema validator library private function validate_input( $input, $schema ): array { $errors = []; From b1519d3fefffcf892b94c499db76180293d7224b Mon Sep 17 00:00:00 2001 From: Tome Pajkovski Date: Mon, 17 Mar 2025 09:13:01 +0100 Subject: [PATCH 4/7] adding the image modification functionality --- src/MCP/Client.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/MCP/Client.php b/src/MCP/Client.php index d570feb..f7b7411 100644 --- a/src/MCP/Client.php +++ b/src/MCP/Client.php @@ -321,8 +321,8 @@ public function modify_image_with_ai($prompt, $media_element) { $base64_image = base64_encode($image_contents); // API Configuration - $api_key = 'AIzaSyBqCwJnOiF_muJ9vjAg7gc7xKS6o_Zxtok'; - $model = 'gemini-2.0-flash-exp'; + $api_key = ""; + $model = 'gemini-2.0-flash-exp'; $api_url = "https://generativelanguage.googleapis.com/v1beta/models/{$model}:generateContent?key={$api_key}"; // Prepare request payload From 80c442fb120b92317640f5b4be699fa70baa3ddd Mon Sep 17 00:00:00 2001 From: Tome Pajkovski Date: Mon, 17 Mar 2025 09:44:53 +0100 Subject: [PATCH 5/7] the image modification works --- src/ImageTools.php | 2 ++ src/MCP/Client.php | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/ImageTools.php b/src/ImageTools.php index b98f068..a7df76e 100644 --- a/src/ImageTools.php +++ b/src/ImageTools.php @@ -68,6 +68,8 @@ public function image_modification_tool() { 'callable' => function ( $params ) { $media_uri = 'media://' . $params['media_id']; $media_resource = $this->server->get_resource_data( $media_uri ); + + \WP_CLI\AiCommand\custom_tome_log($media_resource); return $this->client->modify_image_with_ai( $params['prompt'], $media_resource ); }, ] diff --git a/src/MCP/Client.php b/src/MCP/Client.php index f7b7411..2b50cb6 100644 --- a/src/MCP/Client.php +++ b/src/MCP/Client.php @@ -321,7 +321,11 @@ public function modify_image_with_ai($prompt, $media_element) { $base64_image = base64_encode($image_contents); // API Configuration - $api_key = ""; + $api_key = get_option('ais_google_api_key'); + + if(!$api_key) { + WP_CLI::error("Gemini API Key is not available"); + } $model = 'gemini-2.0-flash-exp'; $api_url = "https://generativelanguage.googleapis.com/v1beta/models/{$model}:generateContent?key={$api_key}"; From b3d25c90acc51420607f843c0b8a3fc9b5abcf2d Mon Sep 17 00:00:00 2001 From: Tome Pajkovski Date: Mon, 17 Mar 2025 09:51:09 +0100 Subject: [PATCH 6/7] removing logging --- ai-command.php | 25 +------------------------ src/ImageTools.php | 4 +--- 2 files changed, 2 insertions(+), 27 deletions(-) diff --git a/ai-command.php b/ai-command.php index 7932f2b..dcde411 100644 --- a/ai-command.php +++ b/ai-command.php @@ -43,27 +43,4 @@ $client ); $ai_command( $args, $assoc_args ); -} ); - - - -if(!function_exists('\WP_CLI\AiCommand\custom_tome_log')) { - function custom_tome_log( $message, $data = '' ) { - - $log = trailingslashit( dirname(__FILE__)) . 'log/'; - if ( ! is_dir( $log ) ) { - mkdir( $log ); - } - - $file = $log . date( 'Y-m-d' ) . '.log'; - if ( ! is_file( $file ) ) { - file_put_contents( $file, '' ); - } - if ( ! empty( $data ) ) { - $message = array( $message => $data ); - } - $data_string = print_r( $message, true ) . "\n"; - file_put_contents( $file, $data_string, FILE_APPEND ); - } - -} \ No newline at end of file +} ); \ No newline at end of file diff --git a/src/ImageTools.php b/src/ImageTools.php index a7df76e..fe70e70 100644 --- a/src/ImageTools.php +++ b/src/ImageTools.php @@ -59,7 +59,7 @@ public function image_modification_tool() { 'description' => 'The prompt for generating the image.', ], 'media_id' => [ - 'type' => 'integer', + 'type' => 'string', 'description' => 'the id of the media element', ], ], @@ -68,8 +68,6 @@ public function image_modification_tool() { 'callable' => function ( $params ) { $media_uri = 'media://' . $params['media_id']; $media_resource = $this->server->get_resource_data( $media_uri ); - - \WP_CLI\AiCommand\custom_tome_log($media_resource); return $this->client->modify_image_with_ai( $params['prompt'], $media_resource ); }, ] From 39c3a85be2f7b6ece715b8a0d2563e4a50784021 Mon Sep 17 00:00:00 2001 From: Jan-Willem Oostendorp Date: Mon, 17 Mar 2025 10:15:47 +0100 Subject: [PATCH 7/7] Fixed format. --- ai-command.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ai-command.php b/ai-command.php index dcde411..7615640 100644 --- a/ai-command.php +++ b/ai-command.php @@ -30,8 +30,8 @@ } - // WordPress REST calls - $rest_tools = new MapRESTtoMCP(); + // WordPress REST calls TODO; make this class compatible with the new Tool class. + $rest_tools = (new MapRESTtoMCP())->map_rest_to_mcp(); foreach ($rest_tools as $tool) { $tools->add($tool); @@ -43,4 +43,4 @@ $client ); $ai_command( $args, $assoc_args ); -} ); \ No newline at end of file +} );