'AI Builder', 'description' => 'WP AI Builder abilities for page building with HTML, Tailwind CSS, and native WordPress blocks.', ) ); } /** * Register WordPress abilities for MCP exposure (runs on wp_abilities_api_init). */ public function register_abilities() { $this->register_html_to_blocks(); $this->register_blocks_to_html(); $this->register_read_selection(); $this->register_update_selection(); } private function register_html_to_blocks() { Compat::register_ability( 'wp-ai-builder/html-to-blocks', array( 'label' => 'HTML to Blocks', 'description' => 'Convert HTML content with Tailwind CSS classes or serialized WordPress block markup into block data for page building.', 'category' => 'ai-builder', 'input_schema' => array( 'type' => 'object', 'properties' => array( 'html' => array( 'type' => 'string', 'description' => 'HTML content with Tailwind CSS classes or serialized WordPress block markup to convert to block data.', ), ), 'required' => array( 'html' ), ), 'output_schema' => array( 'type' => 'array', 'items' => array( 'type' => 'object', 'properties' => array( 'name' => array( 'type' => 'string' ), 'attributes' => array( 'type' => 'object' ), 'innerBlocks' => array( 'type' => 'array' ), ), ), ), 'execute_callback' => function ( $input ) { $html = $input['html'] ?? ''; if ( empty( $html ) ) { return array(); } return HtmlToBlocks::convert( $html ); }, 'permission_callback' => function () { return Security::can_edit_posts(); }, 'meta' => array( 'mcp' => array( 'public' => true ), ), ) ); } private function register_blocks_to_html() { Compat::register_ability( 'wp-ai-builder/blocks-to-html', array( 'label' => 'Blocks to HTML', 'description' => 'Convert block data arrays back to HTML. Supports tailwind/html blocks and native WordPress blocks. Useful for reading existing page content so it can be understood and modified.', 'category' => 'ai-builder', 'input_schema' => array( 'type' => 'object', 'properties' => array( 'blocks' => array( 'type' => 'array', 'description' => 'Array of block data objects with name, attributes, and innerBlocks.', 'items' => array( 'type' => 'object', 'properties' => array( 'name' => array( 'type' => 'string' ), 'attributes' => array( 'type' => 'object' ), 'innerBlocks' => array( 'type' => 'array' ), ), ), ), ), 'required' => array( 'blocks' ), ), 'output_schema' => array( 'type' => 'object', 'properties' => array( 'html' => array( 'type' => 'string' ), ), ), 'execute_callback' => function ( $input ) { $blocks = $input['blocks'] ?? array(); return array( 'html' => BlocksToHtml::convert_blocks( $blocks ) ); }, 'permission_callback' => function () { return Security::can_edit_posts(); }, 'meta' => array( 'mcp' => array( 'public' => true ), ), ) ); } private function register_read_selection() { Compat::register_ability( 'wp-ai-builder/read-selection', array( 'label' => 'Read Selected Blocks', 'description' => 'Read the blocks currently selected by the user in the block editor. Returns block data and its HTML representation. The user must have selected blocks.', 'category' => 'ai-builder', 'input_schema' => array( 'type' => 'object', 'properties' => array(), ), 'output_schema' => array( 'type' => 'object', 'properties' => array( 'post_id' => array( 'type' => 'integer' ), 'indices' => array( 'type' => 'array' ), 'blocks' => array( 'type' => 'array' ), 'html' => array( 'type' => 'string' ), ), ), 'execute_callback' => function ( $input ) { $selection = SelectionBridge::read_selection(); if ( ! $selection ) { return array( 'success' => false, 'message' => 'No blocks selected. Please select one or more blocks in the WordPress editor to read or update them.', ); } if ( ! SelectionBridge::is_fresh( $selection ) ) { return array( 'success' => false, 'message' => 'The selection snapshot expired. Reselect the block or explicitly refresh the selection, then try again.', ); } $blocks = $selection['blocks'] ?? array(); $html = ! empty( $selection['html'] ) ? $selection['html'] : BlocksToHtml::convert_blocks( $blocks ); return array( 'post_id' => $selection['post_id'] ?? 0, 'indices' => $selection['indices'] ?? array(), 'blocks' => $blocks, 'html' => $html, ); }, 'permission_callback' => function () { return Security::can_edit_posts(); }, 'meta' => array( 'mcp' => array( 'public' => true ), ), ) ); } private function register_update_selection() { Compat::register_ability( 'wp-ai-builder/update-selection', array( 'label' => 'Update Selected Blocks', 'description' => 'Replace the currently selected blocks on the page with new block data. Updates the post content live. The editor will refresh to show changes.', 'category' => 'ai-builder', 'input_schema' => array( 'type' => 'object', 'properties' => array( 'blocks' => array( 'type' => 'array', 'description' => 'New block data arrays to replace the selected blocks with.', 'items' => array( 'type' => 'object', 'properties' => array( 'name' => array( 'type' => 'string' ), 'attributes' => array( 'type' => 'object' ), 'innerBlocks' => array( 'type' => 'array' ), ), ), ), 'html' => array( 'type' => 'string', 'description' => 'Optional: raw HTML to convert to blocks first, then update the page.', ), ), ), 'output_schema' => array( 'type' => 'object', 'properties' => array( 'success' => array( 'type' => 'boolean' ), 'html' => array( 'type' => 'string' ), 'blocks' => array( 'type' => 'array' ), 'message' => array( 'type' => 'string' ), ), ), 'execute_callback' => function ( $input ) { $selection = SelectionBridge::read_selection(); if ( ! $selection ) { return array( 'success' => false, 'message' => 'No blocks selected. Select blocks in the WordPress editor.', ); } if ( ! SelectionBridge::is_fresh( $selection ) ) { return array( 'success' => false, 'message' => 'The selection snapshot expired. Reselect the block or explicitly refresh the selection, then try again.', ); } $post_id = $selection['post_id'] ?? 0; $indices = $selection['indices'] ?? array(); if ( ! $post_id ) { return array( 'success' => false, 'message' => 'Invalid selection data. Post ID is missing.', ); } if ( ! Security::can_edit_post( intval( $post_id ) ) ) { return array( 'success' => false, 'message' => 'Sorry, you are not allowed to edit this post.', ); } // Get new blocks: either direct block data or convert from HTML. $blocks = $input['blocks'] ?? null; if ( ! $blocks && ! empty( $input['html'] ) ) { $blocks = HtmlToBlocks::convert( $input['html'] ); } if ( ! $blocks ) { return array( 'success' => false, 'message' => 'No blocks or HTML provided to update with.', ); } // Normalize blocks to WP parser format for serialize_blocks(). $blocks = BlocksToHtml::normalize_blocks( $blocks ); // Read the current post content. $post = get_post( $post_id ); if ( ! $post ) { return array( 'success' => false, 'message' => 'Post not found.', ); } // Parse existing blocks. $existing_blocks = parse_blocks( $post->post_content ); if ( empty( $existing_blocks ) ) { // Empty post — use the new blocks directly. $existing_blocks = $blocks; } elseif ( ! empty( $indices ) ) { // Replace blocks at the given indices. sort( $indices, SORT_NUMERIC ); $indices = array_reverse( $indices ); foreach ( $indices as $idx ) { if ( isset( $existing_blocks[ $idx ] ) ) { array_splice( $existing_blocks, $idx, 1, $blocks ); break; } } // Fallback: replace at first selected index. if ( ! in_array( $blocks, $existing_blocks, true ) ) { $first_idx = min( $indices ); if ( isset( $existing_blocks[ $first_idx ] ) ) { array_splice( $existing_blocks, $first_idx, count( $indices ), $blocks ); } } } else { // No indices available — replace entire post content. $existing_blocks = $blocks; } $new_content = serialize_blocks( $existing_blocks ); $updated = wp_update_post( array( 'ID' => $post_id, 'post_content' => $new_content, ), true ); if ( is_wp_error( $updated ) ) { return array( 'success' => false, 'message' => $updated->get_error_message(), ); } // Store the result for the JS editor to pick up. $result_html = BlocksToHtml::convert_blocks( $blocks ); SelectionBridge::store_result( array( 'post_id' => $post_id, 'clientIds' => $selection['clientIds'] ?? array(), 'blocks' => $blocks, 'html' => $result_html, 'indices' => $indices, 'post_content' => $new_content, 'replacement_html' => serialize_blocks( $blocks ), ) ); return array( 'success' => true, 'html' => $result_html, 'blocks' => $blocks, 'message' => 'Blocks updated successfully. The editor will refresh to show the changes.', ); }, 'permission_callback' => function () { return Security::can_edit_posts(); }, 'meta' => array( 'mcp' => array( 'public' => true ), ), ) ); } }