convertkit
/
vendor
/
wordpress
/
mcp-adapter
/
includes
/
Domain
/
Utils
/
AbilityArgumentNormalizer.php
AbilityArgumentNormalizer.php in Kit (formerly ConvertKit) – Email Newsletter, Email Marketing, Membership, Subscribers and Landing Pages 3.4.3, at vendor/wordpress/mcp-adapter/includes/Domain/Utils/AbilityArgumentNormalizer.php
| 1 | <?php |
| 2 | /** |
| 3 | * Normalizes ability arguments for MCP protocol compatibility. |
| 4 | * |
| 5 | * @package McpAdapter |
| 6 | */ |
| 7 | |
| 8 | declare( strict_types=1 ); |
| 9 | |
| 10 | namespace WP\MCP\Domain\Utils; |
| 11 | |
| 12 | /** |
| 13 | * Normalizes ability arguments between MCP and WordPress Abilities API. |
| 14 | * |
| 15 | * MCP clients send {} (empty object) for tools without arguments. |
| 16 | * PHP decodes this as [] (empty array). |
| 17 | * |
| 18 | * Abilities without an input_schema expect null, not an empty array. |
| 19 | * Abilities with an input_schema usually expect an empty array, never null: |
| 20 | * PHP's [] satisfies an empty object or empty array schema, but null fails |
| 21 | * validation as "not of type <schema type>" (object, array, and so on). Two |
| 22 | * exceptions keep null: a schema whose type explicitly permits null (an |
| 23 | * explicit null value is passed through), and a schema with a top-level |
| 24 | * default, where null lets the Abilities API apply that default for both a |
| 25 | * null and an empty {} input. In both, null is the author's declared intent. |
| 26 | * |
| 27 | * @since 0.5.0 |
| 28 | */ |
| 29 | class AbilityArgumentNormalizer { |
| 30 | |
| 31 | /** |
| 32 | * Normalize parameters for an ability based on its input schema. |
| 33 | * |
| 34 | * No input schema: empty arrays are converted to null, so abilities that |
| 35 | * take no parameters see null. |
| 36 | * Has input schema: null or an empty array normalizes to an empty array so a |
| 37 | * zero-argument call passes schema validation instead of failing as "not of |
| 38 | * type <schema type>" (object, array, and so on). Two exceptions return null: |
| 39 | * a top-level default (both null and {} return null so the Abilities API |
| 40 | * applies the default) and a type that explicitly permits null (an explicit |
| 41 | * null is passed through). |
| 42 | * |
| 43 | * @param \WP_Ability $ability The ability to normalize parameters for. |
| 44 | * @param mixed $parameters The parameters to normalize. |
| 45 | * |
| 46 | * @return mixed Normalized parameters (null when no schema and params are empty; empty array when a schema is present and params are empty or null, unless the schema declares a top-level default or its type permits null, in which case null is returned). |
| 47 | * @since 0.5.0 |
| 48 | * @since 0.6.0 Empty or null parameters for schema-defining abilities normalize to an empty array, except when the schema declares a top-level default (honored for both null and empty {} input) or its type explicitly permits null. |
| 49 | */ |
| 50 | public static function normalize( \WP_Ability $ability, $parameters ) { |
| 51 | $input_schema = $ability->get_input_schema(); |
| 52 | |
| 53 | // No schema: an empty {} means "no arguments" -> null. |
| 54 | if ( empty( $input_schema ) ) { |
| 55 | return is_array( $parameters ) && empty( $parameters ) ? null : $parameters; |
| 56 | } |
| 57 | |
| 58 | // Has schema, missing/empty argument set (null or {}). |
| 59 | if ( null === $parameters || array() === $parameters ) { |
| 60 | // A top-level default is the author's declared "no input" value. |
| 61 | // Return null for BOTH null and {} so WP_Ability::normalize_input() |
| 62 | // applies the default -- it fills the default only when input is null. |
| 63 | // An MCP client sends {} to mean "no arguments", so {} must honor the |
| 64 | // default too, not just an omitted parameter. |
| 65 | if ( array_key_exists( 'default', $input_schema ) ) { |
| 66 | return null; |
| 67 | } |
| 68 | |
| 69 | // An explicit null from the client is kept only when the schema's |
| 70 | // type permits null; {} stays [] as an empty object. |
| 71 | if ( null === $parameters && self::schema_permits_null( $input_schema ) ) { |
| 72 | return null; |
| 73 | } |
| 74 | |
| 75 | // Otherwise use [], which satisfies an empty object or array schema |
| 76 | // so a zero-argument call validates. null never validates on its own. |
| 77 | return array(); |
| 78 | } |
| 79 | |
| 80 | return $parameters; |
| 81 | } |
| 82 | |
| 83 | /** |
| 84 | * Whether the schema's top-level type explicitly permits null. |
| 85 | * |
| 86 | * Only an explicit top-level type is honored (JSON Schema "null", or a type |
| 87 | * array containing "null"). Composition keywords that also permit null |
| 88 | * (anyOf/oneOf/enum/const) are not inspected; such a schema falls through to |
| 89 | * [], which still validates when object or array is among the allowed forms. |
| 90 | * A schema with no type is treated as not permitting null, so a zero-argument |
| 91 | * call still normalizes to [] for callbacks that expect an array. |
| 92 | * |
| 93 | * @param array<string,mixed> $input_schema The ability input schema. |
| 94 | * |
| 95 | * @return bool True when null is a valid top-level value for the schema. |
| 96 | * @since 0.6.0 |
| 97 | */ |
| 98 | private static function schema_permits_null( array $input_schema ): bool { |
| 99 | $type = $input_schema['type'] ?? null; |
| 100 | |
| 101 | if ( is_array( $type ) ) { |
| 102 | return in_array( 'null', $type, true ); |
| 103 | } |
| 104 | |
| 105 | return 'null' === $type; |
| 106 | } |
| 107 | } |
| 108 |