PK     hV0]ç:Ype  e  
  module.phpnu „[µü¤        <?php

namespace Elementor\Modules\Mcp;

use Elementor\Core\Base\Module as BaseModule;
use Elementor\Plugin;
use Elementor\Core\Experiments\Manager as ExperimentsManager;
use WP\MCP\Core\McpAdapter;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class Module extends BaseModule {
	const EXPERIMENT_NAME = 'e_wp_abilities_api';

	public function get_name() {
		return 'mcp';
	}

	public static function is_active() {
		return class_exists( McpAdapter::class ) &&
			function_exists( 'wp_register_ability' ) &&
			Plugin::instance()->experiments->is_feature_active( self::EXPERIMENT_NAME );
	}

	public static function get_experimental_data() {
		return [
			'name' => self::EXPERIMENT_NAME,
			'title' => __( 'Elementor MCP WP Abilities API', 'elementor' ),
			'description' => __( 'Enable Elementor MCP WP Abilities API. Requirements: 1. WordPress 7.0 or higher. 2. Create an application password for your agent user. 3. Add to your MCP config: {url: "https://<your-site-url>/wp-json/elementor/mcp", headers: {Authorization: "Basic <base64(user:application-password)>"}}', 'elementor' ),
			'hidden' => true,
			'default' => ExperimentsManager::STATE_INACTIVE,
		];
	}

	public function __construct() {
		parent::__construct();

		if ( ! $this->is_active() ) {
			return;
		}

		McpAdapter::instance();

		add_action( 'wp_abilities_api_categories_init', [ $this, 'register_ability_category' ] );
		add_action( 'wp_abilities_api_init', [ $this, 'register_abilities' ] );
		add_action( 'mcp_adapter_init', [ $this, 'register_server' ] );
	}

	public function register_ability_category() {
		if ( ! function_exists( 'wp_register_ability_category' ) ) {
			return;
		}

		wp_register_ability_category(
			'elementor',
			[
				'label' => __( 'Elementor', 'elementor' ),
				'description' => __( 'Elementor page builder data, global classes, and variables.', 'elementor' ),
			]
		);
	}

	public function register_abilities() {
		if ( ! function_exists( 'wp_register_ability' ) ) {
			return;
		}

		( new Abilities\List_Pages_Ability() )->register();
		( new Abilities\Get_Structure_Ability() )->register();
		( new Abilities\Update_Settings_Ability() )->register();
		( new Abilities\Create_Page_Ability() )->register();
		( new Abilities\Get_Globals_Ability() )->register();
	}

	public function register_server( $adapter ) {
		if ( ! $adapter instanceof McpAdapter ) {
			return;
		}

		$result = $adapter->create_server(
			'elementor-mcp-server',
			'elementor',
			'mcp',
			'Elementor MCP',
			'Read and modify Elementor Editor abilities.',
			'v1.0.0',
			[ \WP\MCP\Transport\HttpTransport::class ],
			\WP\MCP\Infrastructure\ErrorHandling\ErrorLogMcpErrorHandler::class,
			\WP\MCP\Infrastructure\Observability\NullMcpObservabilityHandler::class,
			[
				'elementor/list-pages',
				'elementor/get-page-structure',
				'elementor/update-page-settings',
				'elementor/create-page',
				'elementor/get-globals',
			],
			[],
			[]
		);

		if ( is_wp_error( $result ) ) {
			// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
			error_log( sprintf( '[Elementor MCP] Server registration failed: %s', $result->get_error_message() ) );
			return;
		}
	}
}
PK     hV0]xê¾è„  „  !  abilities/get-globals-ability.phpnu „[µü¤        <?php

namespace Elementor\Modules\Mcp\Abilities;

use Elementor\Modules\GlobalClasses\Global_Classes_Repository;
use Elementor\Modules\Variables\Services\Batch_Operations\Batch_Processor;
use Elementor\Modules\Variables\Services\Variables_Service;
use Elementor\Modules\Variables\Storage\Variables_Repository;
use Elementor\Plugin;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class Get_Globals_Ability extends Abstract_Ability {

	protected function get_ability_id(): string {
		return 'elementor/get-globals';
	}

	protected function get_definition(): Ability_Definition {
		return new Ability_Definition(
			__( 'Get Elementor Globals', 'elementor' ),
			__( 'Returns site-wide Elementor design data: global classes (shared CSS classes from the kit) and variables (design tokens such as colors and fonts tied to the active kit). Use when you need kit-level styling context, not a single page tree.', 'elementor' ),
			'elementor',
			[
				'type' => 'object',
				'properties' => [
					'global_classes' => [
						'type' => 'object',
						'description' => 'Global class definitions and order from the active kit.',
					],
					'variables' => [
						'type' => 'object',
						'description' => 'Variables list, total count, and watermark from the active kit.',
					],
				],
			],
			[
				'annotations' => [
					'readonly' => true,
					'idempotent' => true,
					'destructive' => false,
				],
			],
			function () {
				return current_user_can( 'edit_posts' );
			}
			// No input_schema â€” this ability takes no input.
		);
	}

	public function execute( $input = [] ) {
		$kit = Plugin::$instance->kits_manager->get_active_kit();

		$classes_payload = Global_Classes_Repository::make( $kit )->all()->get();

		$variables_service = new Variables_Service(
			new Variables_Repository( $kit ),
			new Batch_Processor()
		);

		$variables_payload = $variables_service->load();

		return [
			'global_classes' => $classes_payload,
			'variables' => [
				'variables' => $variables_payload['data'] ?? [],
				'total' => isset( $variables_payload['data'] ) ? count( $variables_payload['data'] ) : 0,
				'watermark' => $variables_payload['watermark'] ?? null,
			],
		];
	}
}
PK     hV0]É»1…â  â  !  abilities/create-page-ability.phpnu „[µü¤        <?php

namespace Elementor\Modules\Mcp\Abilities;

use Elementor\Plugin;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class Create_Page_Ability extends Abstract_Ability {

	protected function get_ability_id(): string {
		return 'elementor/create-page';
	}

	protected function get_definition(): Ability_Definition {
		return new Ability_Definition(
			__( 'Create Elementor Page', 'elementor' ),
			__( 'Creates a new draft post or page and marks it as built with Elementor (blank canvas in the editor). Use when the user wants a new layout shell to design in Elementor. Returns the new post ID and edit URL.', 'elementor' ),
			'elementor',
			[
				'type' => 'object',
				'properties' => [
					'id' => [ 'type' => 'integer' ],
					'edit_url' => [ 'type' => 'string' ],
					'status' => [ 'type' => 'string' ],
					'type' => [ 'type' => 'string' ],
				],
			],
			[
				'annotations' => [
					'readonly' => false,
					'idempotent' => false,
					'destructive' => false,
				],
			],
			function () {
				return current_user_can( 'edit_posts' );
			},
			[
				'type' => 'object',
				'properties' => [
					'title' => [
						'type' => 'string',
						'description' => 'Optional title for the new post.',
					],
					'post_type' => [
						'type' => 'string',
						'description' => 'Post type slug; must support Elementor. Defaults to page.',
						'default' => 'page',
					],
				],
			]
		);
	}

	public function execute( $input = [] ) {
		$input = is_array( $input ) ? $input : [];

		$post_type = ! empty( $input['post_type'] ) ? sanitize_key( $input['post_type'] ) : 'page';

		$type_error = $this->validate_post_type( $post_type );
		if ( $type_error ) {
			return $type_error;
		}

		$permission_error = $this->check_create_permission( $post_type );
		if ( $permission_error ) {
			return $permission_error;
		}

		$title = isset( $input['title'] ) && is_string( $input['title'] ) ? $input['title'] : '';

		return $this->create_post( $post_type, $title );
	}

	private function validate_post_type( string $post_type ): ?\WP_Error {
		if ( ! post_type_exists( $post_type ) || ! post_type_supports( $post_type, 'elementor' ) ) {
			return new \WP_Error(
				'invalid_post_type',
				__( 'This post type does not support Elementor.', 'elementor' ),
				[ 'status' => \WP_Http::BAD_REQUEST ]
			);
		}

		return null;
	}

	private function check_create_permission( string $post_type ): ?\WP_Error {
		$post_type_object = get_post_type_object( $post_type );

		if ( ! $post_type_object || ! current_user_can( $post_type_object->cap->create_posts ) ) {
			return new \WP_Error(
				'rest_cannot_create',
				__( 'Sorry, you are not allowed to create posts of this type.', 'elementor' ),
				[ 'status' => \WP_Http::FORBIDDEN ]
			);
		}

		return null;
	}

	private function create_post( string $post_type, string $title ) {
		$post_id = wp_insert_post(
			[
				'post_title' => $title ? $title : __( 'Elementor Draft', 'elementor' ),
				'post_status' => 'draft',
				'post_type' => $post_type,
			],
			true
		);

		if ( is_wp_error( $post_id ) ) {
			return $post_id;
		}

		$document = Plugin::$instance->documents->get( $post_id );

		if ( ! $document ) {
			return new \WP_Error(
				'document_not_found',
				__( 'Document could not be loaded.', 'elementor' ),
				[ 'status' => \WP_Http::INTERNAL_SERVER_ERROR ]
			);
		}

		$document->set_is_built_with_elementor( true );

		return [
			'id' => (int) $post_id,
			'edit_url' => $document->get_edit_url(),
			'status' => get_post_status( $post_id ),
			'type' => $post_type,
		];
	}
}
PK     hV0]Se§8  8  %  abilities/update-settings-ability.phpnu „[µü¤        <?php

namespace Elementor\Modules\Mcp\Abilities;

use Elementor\Plugin;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class Update_Settings_Ability extends Abstract_Ability {

	protected function get_ability_id(): string {
		return 'elementor/update-page-settings';
	}

	protected function get_definition(): Ability_Definition {
		return new Ability_Definition(
			__( 'Update Elementor Page Settings', 'elementor' ),
			__( 'Updates Elementor document-level settings for a post (for example page layout, title visibility, or custom page settings). Pass only the keys you want to change. Use list-pages to resolve IDs and get-page-structure when you also need the element tree. Requires permission to edit the target post.', 'elementor' ),
			'elementor',
			[
				'type' => 'object',
				'properties' => [
					'success' => [ 'type' => 'boolean' ],
					'post_id' => [ 'type' => 'integer' ],
				],
			],
			[
				'annotations' => [
					'readonly' => false,
					'idempotent' => false,
					'destructive' => false,
				],
			],
			function () {
				return current_user_can( 'edit_posts' );
			},
			[
				'type' => 'object',
				'required' => [ 'post_id', 'settings' ],
				'properties' => [
					'post_id' => [
						'type' => 'integer',
						'description' => 'WordPress post ID of the Elementor document.',
					],
					'settings' => [
						'type' => 'object',
						'description' => 'Partial document settings object; merged into existing settings. Schema enforcement is delegated to document->save().',
						'additionalProperties' => true,
					],
				],
			]
		);
	}

	public function execute( $input = [] ) {
		$post_id = isset( $input['post_id'] ) ? absint( $input['post_id'] ) : 0;
		$settings = isset( $input['settings'] ) && is_array( $input['settings'] ) ? $input['settings'] : null;

		if ( ! $post_id ) {
			return new \WP_Error( 'invalid_post_id', __( 'A valid post_id is required.', 'elementor' ), [ 'status' => \WP_Http::BAD_REQUEST ] );
		}

		if ( null === $settings ) {
			return new \WP_Error( 'invalid_settings', __( 'The settings object is required.', 'elementor' ), [ 'status' => \WP_Http::BAD_REQUEST ] );
		}

		$document = Plugin::$instance->documents->get( $post_id );

		if ( ! $document ) {
			return new \WP_Error( 'document_not_found', __( 'Document not found.', 'elementor' ), [ 'status' => \WP_Http::NOT_FOUND ] );
		}

		if ( ! $document->is_editable_by_current_user() ) {
			return new \WP_Error( 'rest_cannot_edit', __( 'Sorry, you are not allowed to edit this document.', 'elementor' ), [ 'status' => \WP_Http::FORBIDDEN ] );
		}

		$saved = $document->save( [ 'settings' => $settings ] );

		if ( ! $saved ) {
			return new \WP_Error( 'save_failed', __( 'Could not save document settings.', 'elementor' ), [ 'status' => \WP_Http::INTERNAL_SERVER_ERROR ] );
		}

		return [
			'success' => true,
			'post_id' => $post_id,
		];
	}
}
PK     hV0]-<oŸæ  æ  #  abilities/get-structure-ability.phpnu „[µü¤        <?php

namespace Elementor\Modules\Mcp\Abilities;

use Elementor\Plugin;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class Get_Structure_Ability extends Abstract_Ability {

	protected function get_ability_id(): string {
		return 'elementor/get-page-structure';
	}

	protected function get_definition(): Ability_Definition {
		return new Ability_Definition(
			__( 'Get Elementor Page Structure', 'elementor' ),
			__( 'Returns the Elementor element tree (widgets, containers, and nested content) for a single post or page ID. Use after list-pages when you need the live JSON structure to reason about layout, widget types, or to plan edits. Only works for posts that were saved with Elementor.', 'elementor' ),
			'elementor',
			[
				'type' => 'object',
				'properties' => [
					'elements' => [
						'type' => 'array',
						'description' => 'Root-level Elementor elements for the document.',
					],
				],
			],
			[
				'annotations' => [
					'readonly' => true,
					'idempotent' => true,
					'destructive' => false,
				],
			],
			function () {
				return current_user_can( 'edit_posts' );
			},
			[
				'type' => 'object',
				'required' => [ 'post_id' ],
				'properties' => [
					'post_id' => [
						'type' => 'integer',
						'description' => 'WordPress post ID of the Elementor document.',
					],
				],
			]
		);
	}

	public function execute( $input = [] ) {
		$post_id = $this->resolve_post_id( $input );
		if ( is_wp_error( $post_id ) ) {
			return $post_id;
		}

		$document = $this->get_editable_document( $post_id );
		if ( is_wp_error( $document ) ) {
			return $document;
		}

		$elements = $document->get_elements_data();

		return [
			'elements' => is_array( $elements ) ? $elements : [],
		];
	}

	private function resolve_post_id( $input ) {
		$post_id = isset( $input['post_id'] ) ? absint( $input['post_id'] ) : 0;

		if ( ! $post_id ) {
			return new \WP_Error(
				'invalid_post_id',
				__( 'A valid post_id is required.', 'elementor' ),
				[ 'status' => \WP_Http::BAD_REQUEST ]
			);
		}

		if ( ! current_user_can( 'edit_post', $post_id ) ) {
			return new \WP_Error(
				'rest_cannot_view',
				__( 'Sorry, you are not allowed to access this document.', 'elementor' ),
				[ 'status' => \WP_Http::FORBIDDEN ]
			);
		}

		return $post_id;
	}

	private function get_editable_document( int $post_id ) {
		$document = Plugin::$instance->documents->get( $post_id );

		if ( ! $document ) {
			return new \WP_Error(
				'document_not_found',
				__( 'Document not found.', 'elementor' ),
				[ 'status' => \WP_Http::NOT_FOUND ]
			);
		}

		if ( ! $document->is_built_with_elementor() ) {
			return new \WP_Error(
				'not_elementor',
				__( 'This post is not built with Elementor.', 'elementor' ),
				[ 'status' => \WP_Http::BAD_REQUEST ]
			);
		}

		if ( ! $document->is_editable_by_current_user() ) {
			return new \WP_Error(
				'rest_cannot_view',
				__( 'Sorry, you are not allowed to edit this document.', 'elementor' ),
				[ 'status' => \WP_Http::FORBIDDEN ]
			);
		}

		return $document;
	}
}
PK     hV0]Ô´       abilities/abstract-ability.phpnu „[µü¤        <?php

namespace Elementor\Modules\Mcp\Abilities;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

abstract class Abstract_Ability {

	abstract protected function get_ability_id(): string;

	abstract protected function get_definition(): Ability_Definition;

	abstract public function execute( $input = [] );

	public function register(): void {
		$definition = $this->get_definition()->to_array();
		$definition['execute_callback'] = [ $this, 'execute' ];
		wp_register_ability( $this->get_ability_id(), $definition );
	}
}
PK     hV0]žÙéè«  «     abilities/ability-definition.phpnu „[µü¤        <?php

namespace Elementor\Modules\Mcp\Abilities;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class Ability_Definition {
	public string $label;
	public string $description;
	public string $category;
	public array $output_schema;
	public array $meta;
	/** @var callable */
	public $permission_callback;
	public array $input_schema;

	public function __construct(
		string $label,
		string $description,
		string $category,
		array $output_schema,
		array $meta,
		callable $permission_callback,
		array $input_schema = []
	) {
		$this->label = $label;
		$this->description = $description;
		$this->category = $category;
		$this->output_schema = $output_schema;
		$this->meta = $meta;
		$this->permission_callback = $permission_callback;
		$this->input_schema = $input_schema;
	}

	public function to_array(): array {
		$definition = [
			'label' => $this->label,
			'description' => $this->description,
			'category' => $this->category,
			'output_schema' => $this->output_schema,
			'meta' => $this->meta,
			'permission_callback' => $this->permission_callback,
		];

		if ( ! empty( $this->input_schema ) ) {
			$definition['input_schema'] = $this->input_schema;
		}

		return $definition;
	}
}
PK     hV0]¦ðÆÍ	  Í	     abilities/list-pages-ability.phpnu „[µü¤        <?php

namespace Elementor\Modules\Mcp\Abilities;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class List_Pages_Ability extends Abstract_Ability {

	protected function get_ability_id(): string {
		return 'elementor/list-pages';
	}

	protected function get_definition(): Ability_Definition {
		return new Ability_Definition(
			__( 'List Elementor Pages', 'elementor' ),
			__( 'Returns pages and posts built with Elementor on this WordPress site. Each item includes ID, title, status (publish/draft), URL, and post type. Use this first to discover which pages exist before fetching their structure or modifying settings.', 'elementor' ),
			'elementor',
			[
				'type' => 'array',
				'items' => [
					'type' => 'object',
					'properties' => [
						'id' => [ 'type' => 'integer' ],
						'title' => [ 'type' => 'string' ],
						'status' => [ 'type' => 'string' ],
						'url' => [ 'type' => 'string' ],
						'type' => [ 'type' => 'string' ],
					],
				],
			],
			[
				'annotations' => [
					'readonly' => true,
					'idempotent' => true,
					'destructive' => false,
				],
			],
			function () {
				return current_user_can( 'edit_posts' );
			},
			[
				'type' => 'object',
				'properties' => [
					'status' => [
						'type' => 'string',
						'enum' => [ 'publish', 'draft', 'any' ],
						'default' => 'any',
					],
					'post_type' => [
						'type' => 'string',
						'description' => 'Filter by post type. Omit for all Elementor-supported types.',
					],
				],
			]
		);
	}

	public function execute( $input = [] ) {
		$input = is_array( $input ) ? $input : [];

		$args = [
			'post_type' => get_post_types_by_support( 'elementor' ),
			'meta_key' => '_elementor_edit_mode',
			'meta_value' => 'builder',
			'post_status' => isset( $input['status'] ) ? $input['status'] : 'any',
			'fields' => 'ids',
			'posts_per_page' => -1,
			'orderby' => 'date',
			'order' => 'DESC',
			'suppress_filters' => false,
		];

		if ( ! empty( $input['post_type'] ) ) {
			$args['post_type'] = sanitize_key( $input['post_type'] );
		}

		$ids = get_posts( $args );

		$ids = array_values(
			array_filter(
				array_map( 'absint', $ids ),
				function ( $id ) {
					return $id && current_user_can( 'edit_post', $id );
				}
			)
		);

		return array_map(
			function ( $id ) {
				return [
					'id' => $id,
					'title' => get_the_title( $id ),
					'status' => get_post_status( $id ),
					'url' => (string) get_permalink( $id ),
					'type' => get_post_type( $id ),
				];
			},
			$ids
		);
	}
}
PK       hV0]ç:Ype  e  
                module.phpnu „[µü¤        PK       hV0]xê¾è„  „  !            Ÿ  abilities/get-globals-ability.phpnu „[µü¤        PK       hV0]É»1…â  â  !            t  abilities/create-page-ability.phpnu „[µü¤        PK       hV0]Se§8  8  %            §#  abilities/update-settings-ability.phpnu „[µü¤        PK       hV0]-<oŸæ  æ  #            4/  abilities/get-structure-ability.phpnu „[µü¤        PK       hV0]Ô´                 m;  abilities/abstract-ability.phpnu „[µü¤        PK       hV0]žÙéè«  «               Á=  abilities/ability-definition.phpnu „[µü¤        PK       hV0]¦ðÆÍ	  Í	               ¼B  abilities/list-pages-ability.phpnu „[µü¤        PK      ò  ÙL    