close

Make WordPress Core

Opened 10 days ago

Last modified 6 days ago

#64955 assigned enhancement

Abilities API: Add schema compiler for AI tool calling compatibility

Reported by: gziolo's profile gziolo Owned by: gziolo's profile gziolo
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:

  1. required predominantly 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.
  2. additionalProperties defaults to true when absent; OpenAI requires false on every object in strict mode.
  3. oneOf is used; OpenAI rejects it (only anyOf accepted), Anthropic normalizes it to anyOf.
  4. Validation keywords (minimum, maxLength, pattern, format) are stripped or rejected by AI providers, though they remain enforced by WordPress's validators.
  5. WordPress-only keywords (context, readonly, arg_options) are meaningless to AI consumers.
  6. definitions and $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 required from draft-03 per-property to draft-04 array syntax.
  • Sets additionalProperties: false on all object nodes.
  • Converts oneOf to anyOf.
  • Relocates validation keywords (minimum, maxLength, etc.) into property description text as soft guidance for the model, while WordPress's validators continue to enforce them.
  • Strips WordPress-only keywords (context, readonly, arg_options).
  • Maps definitions$defs in output for AI providers that expect the modern keyword name.
  • Accepts a $target parameter 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_keywords filter to rest_get_allowed_schema_keywords().
  • Add definitions and $ref to the allowed keywords list for output passthrough.
  • Integrate the compiler into the AI Client with the $target derived from the active provider plugin.

No changes to existing validation behavior on either client or server.

Change History (4)

#1 Image @gziolo
10 days ago

  • Owner set to gziolo
  • Status changed from new to assigned

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-contentmy_plugin__translate_content) but passes input_schema verbatim to FunctionDeclaration in 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.

required keyword syntax

WordPress 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 the required array — optional fields must use "type": ["string", "null"] union types.

A schema using 'required' => true on 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.

additionalProperties default

JSON Schema and WordPress default additionalProperties to true when absent. OpenAI mandates additionalProperties: false on every object in strict mode. Anthropic strongly recommends it.

oneOf vs anyOf

WordPress supports both (#51025, WP 5.6). OpenAI rejects oneOf — only anyOf accepted. Anthropic normalizes oneOf to anyOf. Both providers reject oneOf, allOf, and anyOf at the root level of a tool's input_schema. Converting oneOf to anyOf is safe for generation since anyOf is semantically more permissive — the canonical validators still enforce oneOf semantics on both client and server.

Validation keywords

Keywords like minimum, maximum, minLength, maxLength, pattern, multipleOf, and most format values are fully supported by WordPress's validators but unsupported by AI providers' constrained decoding. Anthropic's SDK strips them and injects constraint descriptions into description. OpenAI rejects them in strict mode. Google's Gemini API has broader support including numeric constraints.

The compiler relocates these to description text as soft guidance. Enforcement remains with WordPress's validators — if an AI model produces a value outside the minimum/maximum range, the client-side or server-side validator catches it.

$ref / definitions gap

WordPress does not support $ref or definitions/$defs for schema composition. AI providers support internal $ref references and use $defs (draft 2019-09 keyword name), while draft-04 uses definitions. WordPress schemas are already fully inlined, so no $ref resolution 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-side ajv-draft-04 validator recognizes definitions but not $defs. The compiler maps definitions$defs in AI-facing output. Similarly, canonical schemas should use single-value enum rather than const (draft-06), since const doesn't exist in draft-04. AI providers accept both forms.

WordPress-only keywords

context (view/edit/embed array), readonly (lowercase — see #56152), and arg_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/abilities package validates schemas client-side using ajv-draft-04 with coerceTypes: false — stricter than WordPress PHP, which performs type juggling. The header comment in validation.ts explicitly states the design intent:

Rules are configured to support the intersection of common rules between JSON Schema draft-04, WordPress (a subset of JSON Schema draft-04), and various providers like OpenAI and Anthropic.

Both server-side and client-side abilities are validated on the client. executeAbility() in api.ts validates input and output unconditionally when schemas are present. Server-side abilities fetched by @wordpress/core-abilities carry their schemas into the client-side store, creating a double-validation path: client-side AJV first, then server-side rest_validate_value_from_schema when 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 executes

Because 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 to required, converting oneOf to anyOf — 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)

  1. Normalize required: Walk all object nodes. Collect per-property required: true flags (draft-03) and merge with any existing required array (draft-04). For AI targets, add all remaining properties to required and convert their types to ["original_type", "null"] unions. Remove per-property required booleans.
  2. Set additionalProperties: false: Recursively on every object node, including nested objects inside properties, items, and anyOf sub-schemas.
  3. Convert oneOf to anyOf: Direct keyword replacement. Sub-schema array unchanged.
  4. Relocate validation keywords to descriptions: For each property with minimum, maximum, minLength, maxLength, pattern, multipleOf, or unsupported format values, append a human-readable constraint summary to the property's description. Then remove the keywords.
  5. Strip WordPress-only keywords: Remove context, readonly, arg_options, and any other WordPress-specific annotations.
  6. Map definitions to $defs: If the schema uses definitions (draft-04), rename it to $defs (draft 2019-09) in the output. AI providers expect the modern keyword name.
  7. Validate structural limits: Check for total properties, nesting levels, and enum values (OpenAI limits). Emit _doing_it_wrong() warnings if exceeded.

Provider-specific $target behavior

  • OpenAI: Strictest. Strip all validation keywords, enforce additionalProperties: false, put all properties in required. Strip format entirely.
  • Anthropic: Moderately strict. Same structural requirements, but allows some optional properties (up to optional parameters, parameters with union types across all strict tools). May preserve some format values.
  • Google Gemini: Most permissive. Supports numeric constraints and does not require 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 strict flag to rest_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 required normalization (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 via wp_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_Resolver should call wp_compile_ability_schema_for_ai() on each ability's input_schema before passing it to the PHP AI Client SDK. The $target should 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_keywords filter

Add 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 definitions and $ref without patching core.

No dependencies. Parallel with Steps 1-2.

Step 4: Add definitions and $ref to the allowed keywords list

Add these to rest_get_allowed_schema_keywords() for output passthrough — not validation resolution. definitions is the draft-04 keyword compatible with the client-side ajv-draft-04 validator; the compiler maps it to $defs in AI-facing output. $ref is supported by AJV draft-04 natively.

AI providers also support $defs (draft 2019-09) and const (draft-06), but neither exists in draft-04 and the client-side validator does not recognize them. Canonical ability schemas should use definitions and single-value enum respectively.

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:

  • oneOf used where anyOf would suffice → suggest anyOf.
  • additionalProperties absent on object schemas → suggest setting it explicitly.
  • Per-property 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.
  • readonly used instead of readOnly → note the casing deviation (#56152).

Advisory notices, not validation errors.

Depends on Step 1.

Step 6: Introduce schema_version for opt-in strict authoring

Add an optional schema_version key to ability registration args. Abilities declaring schema_version: 2 opt into stricter conventions:

  • Draft-03 per-property required: true is rejected at registration time.
  • additionalProperties must be explicitly set on all object nodes.
  • oneOf is rejected (use anyOf instead).
  • The compiler can skip most transformation steps for v2 schemas.

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 its tools/list handler. Does not require coordination with core releases.

Future considerations (not blocking)

  • General-purpose wp_validate_json_schema() function. rest_validate_value_from_schema now serves the Abilities API in contexts unrelated to the REST API. A wp_validate_json_schema( $value, $schema, $args ) wrapper would reflect this broader role and provide a natural home for strict-mode validation tied to schema_version. Initially a thin passthrough.
  • $ref resolution in the validator. Teaching the validator to resolve internal $ref references would unlock schema composition for validation, not just output passthrough. Significant change — should be its own tracked effort.
  • Client-side schema compilation for browser-agent protocols. Since executing any ability routes through the REST API where server-side compilation applies, a JavaScript compiler is not needed today. As WebMCP matures, it may need to expose client-side ability schemas directly to browser-based AI agents. The PHP compiler's test fixtures should be portable to JavaScript when this need arises.
  • Schema compilation in the PHP AI Client SDK. If the SDK eventually gains its own schema normalization, the WP AI Client could delegate to it. The SDK currently treats schemas as opaque payloads; changing this is a separate decision.
  • Structural limit warnings. OpenAI limits schemas for properties, nesting levels, and enum values. Worth checking at registration time but limits may change. Implement as soft warnings.
  • readOnly casing alignment. #56152 tracks this. Not blocking since both forms are stripped by the compiler.

Image

This ticket was mentioned in Slack in #core-ai by gziolo. View the logs.


10 days ago

#3 Image @gziolo
7 days ago

  • Milestone changed from Awaiting Review to Future Release

#4 Image @gziolo
6 days ago

  • Keywords 2nd-opinion added
Note: See TracTickets for help on using tickets.