PluginProbe ʕ •ᴥ•ʔ
Booking for Appointments and Events Calendar – Amelia / 2.4.6
Booking for Appointments and Events Calendar – Amelia v2.4.6
2.4.7 2.4.6 2.4.5 2.4.4 2.4.3 2.4.2 2.4.1 2.4 trunk 1.2.1 1.2.10 1.2.11 1.2.12 1.2.13 1.2.14 1.2.15 1.2.16 1.2.17 1.2.18 1.2.19 1.2.2 1.2.20 1.2.21 1.2.22 1.2.23 1.2.24 1.2.25 1.2.26 1.2.27 1.2.28 1.2.29 1.2.3 1.2.30 1.2.31 1.2.32 1.2.33 1.2.34 1.2.35 1.2.36 1.2.37 1.2.38 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 2.0 2.0.1 2.0.2 2.1 2.1.1 2.1.2 2.1.3 2.2 2.2.1 2.3
ameliabooking / vendor / wordpress / mcp-adapter / includes / Abilities / ExecuteAbilityAbility.php
ameliabooking / vendor / wordpress / mcp-adapter / includes / Abilities Last commit date
DiscoverAbilitiesAbility.php 2 months ago ExecuteAbilityAbility.php 2 months ago GetAbilityInfoAbility.php 2 months ago McpAbilityHelperTrait.php 2 months ago
ExecuteAbilityAbility.php
238 lines
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 mcp.public=true metadata 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