Opened 10 days ago
Last modified 6 days ago
#64955 assigned enhancement
Abilities API: Add schema compiler for AI tool calling compatibility
| Reported by: |
|
Owned by: |
|
|---|---|---|---|
| Milestone: | Future Release | Priority: | normal |
| Severity: | normal | Version: | 6.9 |
| Component: | AI | Keywords: | abilities ai-client 2nd-opinion |
| Focuses: | rest-api | Cc: |
Description
The Abilities API's input_schema and output_schema use WordPress's draft-04-style JSON Schema conventions. When these schemas are consumed by AI agents — via the AI Client, the MCP adapter, or browser-based agents — they must conform to the constrained JSON Schema subset that AI providers accept for strict tool calling / structured outputs.
AI providers like OpenAI, Anthropic, and Google implement constrained decoding — they compile tool schemas into a grammar that restricts token generation at inference time. This means they only accept structural keywords (type, properties, required, enum, anyOf) and reject or strip validation keywords (minimum, maxLength, pattern, format). No provider conforms to any single JSON Schema draft; their supported subset is roughly "draft-04 structural keywords plus $defs and const from later drafts, minus all value-level assertion keywords."
WordPress's JSON Schema conventions diverge from this subset in six areas:
requiredpredominantly uses draft-03 per-property syntax ('required' => true); AI providers only understand the draft-04 array syntax. The client-side AJV validator also does not enforce draft-03 syntax.additionalPropertiesdefaults totruewhen absent; OpenAI requiresfalseon every object in strict mode.oneOfis used; OpenAI rejects it (onlyanyOfaccepted), Anthropic normalizes it toanyOf.- Validation keywords (
minimum,maxLength,pattern,format) are stripped or rejected by AI providers, though they remain enforced by WordPress's validators. - WordPress-only keywords (
context,readonly,arg_options) are meaningless to AI consumers. definitionsand$ref(draft-04 schema composition keywords) are not in the allowed keywords list, preventing their use in ability schemas.
Currently, no component in the WordPress AI stack transforms schemas for provider compatibility. The AI Client passes ability schemas verbatim to the PHP AI Client SDK, which treats them as opaque payloads. The three featured provider plugins (OpenAI, Anthropic, Google) handle authentication and API formatting but do not transform schemas. This means ability schemas authored by plugin developers flow through to AI provider APIs unchanged, where they may be rejected or silently degraded.
Server-side abilities are validated on both client (AJV draft-04, with no type coercion) and server (rest_validate_value_from_schema, with type juggling), creating a natural strictness gradient: AI compiled schema (strictest) → client-side AJV (strict) → server-side PHP (most permissive). Because the compiler's transformations only narrow what's accepted, values conforming to the compiled schema automatically pass both existing validators without any changes to either validation layer.
Proposed solution
Introduce wp_compile_ability_schema_for_ai() — a public function that transforms a canonical ability schema into an AI-compatible variant at the output boundary:
- Normalizes
requiredfrom draft-03 per-property to draft-04 array syntax. - Sets
additionalProperties: falseon all object nodes. - Converts
oneOftoanyOf. - Relocates validation keywords (
minimum,maxLength, etc.) into propertydescriptiontext as soft guidance for the model, while WordPress's validators continue to enforce them. - Strips WordPress-only keywords (
context,readonly,arg_options). - Maps
definitions→$defsin output for AI providers that expect the modern keyword name. - Accepts a
$targetparameter for provider-specific behavior (openai,anthropic,google,default). - Is filterable via
wp_ability_schema_compiled.
The compiler is a server-side concern, invoked at AI-facing boundaries: the AI Client (before PHP AI Client SDK hand-off), and optionally by standalone adapters like the MCP adapter. The canonical schema remains unchanged for REST API consumers. The compiled schema never reaches the client-side store.
Additionally:
- Add a
wp_rest_allowed_schema_keywordsfilter to rest_get_allowed_schema_keywords(). - Add
definitionsand$refto the allowed keywords list for output passthrough. - Integrate the compiler into the AI Client with the
$targetderived from the active provider plugin.
No changes to existing validation behavior on either client or server.
Related
- #64591 — Add WP AI Client and corresponding connectors screen
- #56152 —
readonlyvsreadOnlycasing - #51025 —
anyOfandoneOfsupport (landed in WP 5.6) - #48818 —
requiredv4 array syntax support (landed in WP 5.5) - #38531 — Array/object schema validation and sanitization
- REST API Schema Handbook
- OpenAI Structured Outputs — Supported schemas and strict mode requirements
- Anthropic Structured Outputs — JSON Schema limitations
- Google Gemini Function Declarations — Schema support
- MCP Tools Specification — Tool schema format
I did extensive research into how JSON schemas flow through the WordPress AI stack, what each AI provider actually accepts, and how the client-side validation layer interacts with the server-side one. The findings below informed the ticket description above. I'm sharing the full technical detail here so the community can verify the claims and correct anything I got wrong — particularly around the provider plugin internals, AI client, or MCP adapter.
How schemas flow today
No component in the WordPress AI stack transforms ability schemas for AI provider compatibility. Here's the path:
wp_register_ability( input_schema ) ← WordPress draft-04 style │ WP_Abilities_Registry │ ┌────┼──────────────────────┬───────────────────────┐ │ │ │ │ ▼ ▼ ▼ ▼ REST API WP AI Client MCP Adapter (/wp-abilities/v1/) (Prompt Builder) (/wp-json/mcp/...) │ │ │ │ PHP AI Client SDK MCP tools/list │ (opaque pass-through) (limited fixes) │ │ │ │ Provider Plugin │ │ (no schema transform) │ ▼ ▼ ▼ Canonical Schema AI Provider API MCP Client (unchanged) (may reject/strip) (may reject/strip)The WP AI Client converts ability names to provider-safe tool names (e.g.,
my-plugin/translate-content→my_plugin__translate_content) but passesinput_schemaverbatim toFunctionDeclarationin the PHP AI Client SDK. The SDK treats schemas as opaque payloads. The three provider plugins (OpenAI, Anthropic, Google) extend the SDK's base model classes and handle API formatting but include no schema normalization logic. The MCP adapter performs limited structural fixes (empty schema normalization, non-object wrapping for MCP compliance) but no keyword-level transformation.Incompatibility details
The ticket lists six incompatibilities. Here's the deeper technical context for each.
requiredkeyword syntaxWordPress supports both draft-03 per-property
required: true(predominant since WP 4.7) and draft-04 array syntax (added in WP 5.5). AI providers only understand v4 syntax. OpenAI further requires that all properties appear in therequiredarray — optional fields must use"type": ["string", "null"]union types.A schema using
'required' => trueon individual properties passes WordPress server-side validation but the requirement is invisible to both AI providers and the client-side AJV validator (which only recognizes v4 array syntax). Abilities using draft-03 syntax effectively have no required-field enforcement on the client.additionalPropertiesdefaultJSON Schema and WordPress default
additionalPropertiestotruewhen absent. OpenAI mandatesadditionalProperties: falseon every object in strict mode. Anthropic strongly recommends it.oneOfvsanyOfWordPress supports both (#51025, WP 5.6). OpenAI rejects
oneOf— onlyanyOfaccepted. Anthropic normalizesoneOftoanyOf. Both providers rejectoneOf,allOf, andanyOfat the root level of a tool'sinput_schema. ConvertingoneOftoanyOfis safe for generation sinceanyOfis semantically more permissive — the canonical validators still enforceoneOfsemantics on both client and server.Validation keywords
Keywords like
minimum,maximum,minLength,maxLength,pattern,multipleOf, and mostformatvalues are fully supported by WordPress's validators but unsupported by AI providers' constrained decoding. Anthropic's SDK strips them and injects constraint descriptions intodescription. OpenAI rejects them in strict mode. Google's Gemini API has broader support including numeric constraints.The compiler relocates these to
descriptiontext as soft guidance. Enforcement remains with WordPress's validators — if an AI model produces a value outside theminimum/maximumrange, the client-side or server-side validator catches it.$ref/definitionsgapWordPress does not support
$refordefinitions/$defsfor schema composition. AI providers support internal$refreferences and use$defs(draft 2019-09 keyword name), while draft-04 usesdefinitions. WordPress schemas are already fully inlined, so no$refresolution is needed in the compiler — but these keywords aren't in the allowed keywords list and get silently stripped.Canonical ability schemas should use
definitions(draft-04) rather than$defs(draft 2019-09), because the client-sideajv-draft-04validator recognizesdefinitionsbut not$defs. The compiler mapsdefinitions→$defsin AI-facing output. Similarly, canonical schemas should use single-valueenumrather thanconst(draft-06), sinceconstdoesn't exist in draft-04. AI providers accept both forms.WordPress-only keywords
context(view/edit/embed array),readonly(lowercase — see #56152), andarg_options(escape hatch for custom callbacks, stripped by `get_public_item_schema()`). Meaningless to AI providers.Client-side validation and the strictness gradient
The
@wordpress/abilitiespackage validates schemas client-side usingajv-draft-04withcoerceTypes: false— stricter than WordPress PHP, which performs type juggling. The header comment invalidation.tsexplicitly states the design intent:Both server-side and client-side abilities are validated on the client.
executeAbility()inapi.tsvalidates input and output unconditionally when schemas are present. Server-side abilities fetched by@wordpress/core-abilitiescarry their schemas into the client-side store, creating a double-validation path: client-side AJV first, then server-siderest_validate_value_from_schemawhen the REST API call executes.This creates a natural strictness gradient:
AI Provider Schema (strictest) ← canonical schema compiled for AI consumption │ │ AI model generates values conforming to this ▼ Client-side AJV draft-04 (strict) ← validates against canonical schema │ │ values pass because compiled schema is a strict subset ▼ Server-side PHP validator (most permissive) ← validates against canonical schema │ │ values pass because PHP is even more permissive than AJV ▼ Ability callback executesBecause values flow from the strictest layer to the most permissive, any value that satisfies the compiled AI schema automatically passes both validators. The compiler's transformations only narrow what's accepted — setting
additionalProperties: false, adding all properties torequired, convertingoneOftoanyOf— so the compiled schema is inherently safe for the double-validation path. The canonical schema in the client-side store never needs to change.Proposed compiler architecture
The compiler sits at every boundary where schemas leave WordPress for AI consumption:
wp_register_ability( input_schema ) ← WordPress draft-04 style │ WP_Abilities_Registry │ ┌────┼──────────────────────┬───────────────────────┐ │ │ │ │ ▼ ▼ ▼ ▼ REST API WP AI Client MCP Adapter (/wp-abilities/v1/) (Prompt Builder) (standalone plugin) │ │ │ │ compile_schema() compile_schema() │ │ │ ▼ ▼ ▼ Canonical Schema AI-Strict Schema AI-Strict Schema (unchanged for REST (before SDK hand-off) (in tools/list) API consumers)Compiler transformations (in order)
required: Walk all object nodes. Collect per-propertyrequired: trueflags (draft-03) and merge with any existingrequiredarray (draft-04). For AI targets, add all remaining properties torequiredand convert their types to["original_type", "null"]unions. Remove per-propertyrequiredbooleans.additionalProperties: false: Recursively on every object node, including nested objects insideproperties,items, andanyOfsub-schemas.oneOftoanyOf: Direct keyword replacement. Sub-schema array unchanged.minimum,maximum,minLength,maxLength,pattern,multipleOf, or unsupportedformatvalues, append a human-readable constraint summary to the property'sdescription. Then remove the keywords.context,readonly,arg_options, and any other WordPress-specific annotations.definitionsto$defs: If the schema usesdefinitions(draft-04), rename it to$defs(draft 2019-09) in the output. AI providers expect the modern keyword name._doing_it_wrong()warnings if exceeded.Provider-specific
$targetbehavioradditionalProperties: false, put all properties inrequired. Stripformatentirely.formatvalues.additionalProperties: false. The compiler can preserve more keywords.default: Targets the most restrictive common subset (effectively OpenAI's constraints), or whatever else we decide as a better approach.Why this approach over alternatives
Alternative A: Dual-mode validator in core. Add a
strictflag torest_validate_value_from_schema. Invasive — touches the most critical validation path in WordPress, risks regressions in every REST API endpoint, and couples AI provider requirements to core's validation logic. AI provider requirements change faster than WordPress release cycles. Rejected.Alternative B: Require ability authors to write AI-compatible schemas directly. Shifts complexity to every plugin author, guarantees inconsistency, and doesn't help with
requirednormalization (which requires semantic changes like null-union types). Existing core abilities would all need manual rewrites. Rejected.Alternative C: Add schema transformation to the PHP AI Client SDK or provider plugins. Fixes the WP AI Client path but not MCP adapter, WebMCP, or any future adapter. Places WordPress-specific schema knowledge inside a provider-agnostic SDK that treats schemas as opaque payloads. Rejected.
Alternative D: Schema compiler as a shared core utility (proposed). Pure, testable, filterable function alongside the Abilities API. Changes no validation behavior. All AI-facing components call the same function. The strictness gradient guarantees compiled schemas are safe for the existing double-validation path. Accepted.
Implementation steps
Steps are ordered by dependency. Steps within the same group can be done in parallel.
Step 1: Ship the schema compiler function (PHP)
Introduce
wp_compile_ability_schema_for_ai( $schema, $target = 'default' )in WordPress core, alongside the Abilities API. Purely additive — transforms schemas on output, changes no validation behavior. Filterable viawp_ability_schema_compiled.No dependencies. Enables all downstream work.
Step 2: Integrate compiler into the WP AI Client
The WP AI Client's
Ability_Function_Resolvershould callwp_compile_ability_schema_for_ai()on each ability'sinput_schemabefore passing it to the PHP AI Client SDK. The$targetshould be derived from the active provider plugin.Highest-impact integration point — covers the primary path through which WordPress sites interact with AI providers.
Depends on Step 1.
Step 3: Add
wp_rest_allowed_schema_keywordsfilterAdd a filter hook to rest_get_allowed_schema_keywords():
apply_filters( 'wp_rest_allowed_schema_keywords', $keywords, $context );Minimal core change with broad utility beyond the Abilities API. Enables plugins to pass through keywords like
definitionsand$refwithout patching core.No dependencies. Parallel with Steps 1-2.
Step 4: Add
definitionsand$refto the allowed keywords listAdd these to rest_get_allowed_schema_keywords() for output passthrough — not validation resolution.
definitionsis the draft-04 keyword compatible with the client-sideajv-draft-04validator; the compiler maps it to$defsin AI-facing output.$refis supported by AJV draft-04 natively.AI providers also support
$defs(draft 2019-09) andconst(draft-06), but neither exists in draft-04 and the client-side validator does not recognize them. Canonical ability schemas should usedefinitionsand single-valueenumrespectively.Depends on Step 3, or can be done directly in core alongside it.
Step 5: Emit authoring guidance via
_doing_it_wrong()When abilities are registered, check schemas for patterns that cause AI compatibility issues:
oneOfused whereanyOfwould suffice → suggestanyOf.additionalPropertiesabsent on object schemas → suggest setting it explicitly.required: true(draft-03) → suggest v4 array syntax. This syntax is also not enforced by the client-side AJV validator, so switching improves client-side validation coverage.readonlyused instead ofreadOnly→ note the casing deviation (#56152).Advisory notices, not validation errors.
Depends on Step 1.
Step 6: Introduce
schema_versionfor opt-in strict authoringAdd an optional
schema_versionkey to ability registration args. Abilities declaringschema_version: 2opt into stricter conventions:required: trueis rejected at registration time.additionalPropertiesmust be explicitly set on all object nodes.oneOfis rejected (useanyOfinstead).Migration path: new abilities adopt v2, existing abilities continue on implicit v1.
Depends on Steps 1 and 5.
Adjacent: MCP adapter integration
The MCP adapter is a standalone plugin. Once Step 1 lands, it should call
wp_compile_ability_schema_for_ai()in itstools/listhandler. Does not require coordination with core releases.Future considerations (not blocking)
wp_validate_json_schema()function.rest_validate_value_from_schemanow serves the Abilities API in contexts unrelated to the REST API. Awp_validate_json_schema( $value, $schema, $args )wrapper would reflect this broader role and provide a natural home for strict-mode validation tied toschema_version. Initially a thin passthrough.$refresolution in the validator. Teaching the validator to resolve internal$refreferences would unlock schema composition for validation, not just output passthrough. Significant change — should be its own tracked effort.readOnlycasing alignment. #56152 tracks this. Not blocking since both forms are stripped by the compiler.