PluginProbe
Elementor Website Builder – more than just a page builder / 4.3.1
Elementor Website Builder – more than just a page builder v4.3.1
4.3.1 4.3.0 4.3.0-beta3 4.3.0-beta2 4.3.0-beta1 4.2.4 4.2.3 4.2.2 4.2.1 4.2.0 4.1.5 4.2.0-beta2 4.2.0-dev2 4.2.0-beta1 4.1.4 4.1.3 4.1.2 4.1.1 4.1.0 4.1.0-beta3 4.1.0-dev3 4.0.9 4.1.0-beta2 4.1.0-dev2 4.0.8 All 454 releases
elementor / vendor / wordpress / mcp-adapter / includes / Abilities / ExecuteAbilityAbility.php

ExecuteAbilityAbility.php in Elementor Website Builder – more than just a page builder 4.3.1, at vendor/wordpress/mcp-adapter/includes/Abilities/ExecuteAbilityAbility.php

238 lines 7.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Ability for executing WordPress abilities.
4 *
5 * @package McpAdapter
6 */
7
8 declare( strict_types=1 );
9
10 namespace WP\MCP\Abilities;
11
12 use WP\MCP\Domain\Utils\AbilityArgumentNormalizer;
13 use WP_Error;
14
15 /**
16 * Execute Ability - Executes a WordPress ability with provided parameters.
17 *
18 * This ability provides the primary execution layer for running any registered
19 * WordPress ability through the MCP protocol.
20 *
21 * SECURITY CONSIDERATIONS:
22 * - This ability has openWorldHint=true, allowing execution of any registered ability
23 * - Only abilities with effective MCP public exposure can be executed via default MCP server.
24 * - Requires proper WordPress capability checks for secure operation
25 * - Caller identity verification is enforced through WordPress authentication
26 *
27 * @see https://developer.wordpress.org/apis/security/ for detailed security guidance
28 */
29 final class ExecuteAbilityAbility {
30 use McpAbilityHelperTrait;
31
32 /**
33 * Register the ability.
34 */
35 public static function register(): void {
36 wp_register_ability(
37 'mcp-adapter/execute-ability',
38 array(
39 'label' => 'Execute Ability',
40 'description' => 'Execute a WordPress ability with the provided parameters. This is the primary execution layer that can run any registered ability.',
41 'category' => 'mcp-adapter',
42 'input_schema' => array(
43 'type' => 'object',
44 'properties' => array(
45 'ability_name' => array(
46 'type' => 'string',
47 'description' => 'The full name of the ability to execute',
48 ),
49 'parameters' => array(
50 'type' => 'object',
51 'description' => 'Parameters to pass to the ability',
52 ),
53 ),
54 'required' => array( 'ability_name', 'parameters' ),
55 ),
56 'output_schema' => array(
57 'type' => 'object',
58 'properties' => array(
59 'success' => array( 'type' => 'boolean' ),
60 'data' => array(
61 'type' => array(
62 'object',
63 'array',
64 'string',
65 'number',
66 'integer',
67 'boolean',
68 'null',
69 ),
70 'description' => 'The result data from the ability execution',
71 ),
72 'error' => array(
73 'type' => 'string',
74 'description' => 'Error message if execution failed',
75 ),
76 ),
77 'required' => array( 'success' ),
78 ),
79 'permission_callback' => array( self::class, 'check_permission' ),
80 'execute_callback' => array( self::class, 'execute' ),
81 'meta' => array(
82 'annotations' => array(
83 'readonly' => false,
84 'destructive' => true,
85 'idempotent' => false,
86 ),
87 ),
88 )
89 );
90 }
91
92 /**
93 * Execute the ability execution functionality.
94 *
95 * Note: Permission checks are handled by the WP_Ability::execute() framework method
96 * before this callback is invoked. This ensures all ability executions are properly
97 * authorized by the framework.
98 *
99 * @see \WP_Ability::execute()
100 *
101 * @param array $input Input parameters containing ability_name and parameters.
102 *
103 * @return array Array containing execution results.
104 */
105 public static function execute( $input = array() ): array {
106 $ability_name = $input['ability_name'] ?? '';
107 // Note: Use null coalescing instead of empty() to preserve empty arrays/objects ({} → [])
108 $parameters = $input['parameters'] ?? null;
109
110 if ( empty( $ability_name ) ) {
111 return array(
112 'success' => false,
113 'error' => 'Ability name is required',
114 );
115 }
116
117 $ability = wp_get_ability( $ability_name );
118
119 if ( ! $ability ) {
120 return array(
121 'success' => false,
122 'error' => "Ability '{$ability_name}' not found",
123 );
124 }
125
126 // Normalize parameters for ability's schema requirements
127 // Empty {} from MCP is treated as null for abilities without input schema
128 $parameters = AbilityArgumentNormalizer::normalize( $ability, $parameters );
129
130 try {
131 // Execute the ability
132 $result = $ability->execute( $parameters );
133
134 // Check if the result is a WP_Error
135 if ( is_wp_error( $result ) ) {
136 return array(
137 'success' => false,
138 'error' => $result->get_error_message(),
139 );
140 }
141
142 return array(
143 'success' => true,
144 'data' => $result,
145 );
146 } catch ( \Throwable $e ) {
147 return array(
148 'success' => false,
149 'error' => $e->getMessage(),
150 );
151 }
152 }
153
154 /**
155 * Check permissions for executing abilities.
156 *
157 * Validates user capabilities, caller identity, and MCP exposure restrictions.
158 *
159 * @param array $input Input parameters containing ability_name and parameters.
160 *
161 * @return bool|\WP_Error True if the user has permission to execute the specified ability.
162 */
163 public static function check_permission( $input = array() ) {
164 $ability_name = $input['ability_name'] ?? '';
165
166 if ( empty( $ability_name ) ) {
167 return new WP_Error( 'missing_ability_name', 'Ability name is required' );
168 }
169
170 // Validate user authentication and capabilities
171 $user_check = self::validate_user_access();
172 if ( is_wp_error( $user_check ) ) {
173 return $user_check;
174 }
175
176 // Check MCP exposure restrictions
177 $exposure_check = self::check_ability_mcp_exposure( $ability_name );
178 if ( is_wp_error( $exposure_check ) ) {
179 return $exposure_check;
180 }
181
182 // Get the target ability
183 $ability = wp_get_ability( $ability_name );
184 if ( ! $ability ) {
185 return new WP_Error( 'ability_not_found', "Ability '{$ability_name}' not found" );
186 }
187
188 // Normalize parameters for ability's schema requirements
189 // Empty {} from MCP is treated as null for abilities without input schema
190 $parameters = $input['parameters'] ?? null;
191 $parameters = AbilityArgumentNormalizer::normalize( $ability, $parameters );
192 $permission_result = $ability->check_permissions( $parameters );
193
194 // Return WP_Error as-is, or convert other values to boolean
195 if ( is_wp_error( $permission_result ) ) {
196 return $permission_result;
197 }
198
199 return (bool) $permission_result;
200 }
201
202 /**
203 * Validate user authentication and basic capabilities for execute ability.
204 *
205 * @return bool|\WP_Error True if valid, WP_Error if validation fails.
206 */
207 private static function validate_user_access() {
208 // Verify caller identity - ensure the user is authenticated
209 if ( ! is_user_logged_in() ) {
210 return new WP_Error( 'authentication_required', 'User must be authenticated to access this ability' );
211 }
212
213 /**
214 * Filters the capability required to execute abilities.
215 *
216 * This is intentionally set to 'read' as the minimum baseline capability.
217 * Each ability defines its own permission_callback that enforces the actual
218 * capability requirements for that specific operation. This filter serves
219 * only as a gate to prevent completely unauthenticated or capability-less
220 * users from reaching the ability execution layer.
221 *
222 * @since 0.3.0
223 *
224 * @param string $capability The required capability. Default 'read'.
225 */
226 $required_capability = apply_filters( 'mcp_adapter_execute_ability_capability', 'read' );
227 // phpcs:ignore WordPress.WP.Capabilities.Undetermined -- Capability is determined dynamically via filter
228 if ( ! current_user_can( $required_capability ) ) {
229 return new WP_Error(
230 'insufficient_capability',
231 sprintf( 'User lacks required capability: %s', $required_capability )
232 );
233 }
234
235 return true;
236 }
237 }
238