MCP Tool Schema Validation Errors: Fix oneOf/anyOf/allOf Rejections (Provider Matrix)
MCP tool schemas using oneOf, anyOf, or allOf are being rejected by Azure, Gemini, Kimi-Code, and Backstage right now. Here's the provider rejection matrix and exact fixes.
Have broken JSON right now? Fix it free in under 1 second — no signup.
Fix My JSON →Your MCP tool schema worked fine two weeks ago. Today it's getting HTTP 400 rejected by Azure AI Foundry, Google Gemini CLI is throwing a strict schema validation error, and Kimi-Code returns moonshot flavored json schema errors on every tools/list call. Nothing in your schema changed. What happened?
The answer: the enforcement wave hit before the spec landed. The MCP RC (Release Candidate) — dropping July 28, 2026 — promotes inputSchema and outputSchema to full JSON Schema 2020-12. But providers aren't waiting. Azure, Google, MoonshotAI, Backstage, and the AJV-based SDK upgrade pipeline are all tightening validation right now, each with slightly different rules about what they'll accept and what they'll reject.
This guide covers why it's happening, which provider rejects what (with exact error messages), how to write schemas that survive every provider today, and — critically — what code breaks on July 28 that's currently working.
Why This Is Happening Now
The MCP specification has historically been loose about inputSchema. It required a JSON Schema object, but didn't strictly mandate which draft or which keywords were valid. Implementations varied: some validated anyOf, some silently stripped it, some just passed the schema through without checking.
Three things changed in 2026:
1. MCP Python SDK v2 beta shipped June 30. The v2 beta enforces JSON Schema 2020-12 for tool schemas. Servers built against v2 produce schemas with composition keywords that older clients and middleware aren't expecting. 2. Providers started pre-enforcing their own subsets. Azure AI Foundry, Google Gemini CLI, MoonshotAI, and Backstage all added or tightened schema validation in May–July 2026. Each chose a slightly different validation ruleset. The result: the same schema breaks differently on different platforms. 3. The SDK upgrade chain hit CI/CD. Claude Code Action SDK 0.2.15 upgraded its AJV validator, which applies JSON Schema 2020-12 validation to tool schemas at build time. CI pipelines that were green on 0.2.14 started failing on 0.2.15.The underlying direction is correct — JSON Schema 2020-12 is the right standard, and the July 28 RC will make oneOf/anyOf/allOf fully valid in tool schemas. But the transition is messy: providers are each enforcing a different subset of the eventual standard, and they're doing it before the standard is final.
Provider Rejection Matrix
Here's exactly what each provider rejects, the error message you'll see, and the fix:
| Provider | What it rejects | Error message | Fix |
|---|---|---|---|
| Azure AI Foundry | anyOf at root level of inputSchema | InvalidSchema: anyOf is not supported in tool input schema | Replace anyOf with a single concrete type or use oneOf with discriminator |
| Google Gemini CLI | Array types inside anyOf missing items | Schema validation error: array type requires items property | Add items: {} to every type: "array" inside anyOf branches |
| MoonshotAI Kimi-Code | Non-standard JSON Schema keywords; schemas with $defs or complex $ref chains | HTTP 400: moonshot flavored json schema error | Inline all $ref definitions; remove $defs; use flat property definitions |
| Directus / VS Code | $dynamicRef in generated schemas | Invalid schema: $dynamicRef is not a supported keyword | Replace $dynamicRef with concrete $ref pointing to $defs entry, or inline |
| Backstage (via Gemini) | Missing items on array properties inside anyOf | Gemini: Schema error — array field missing items | Add explicit items to all array fields (same fix as Gemini CLI) |
| AJV / claude-code-action 0.2.15 | Tool schemas that fail JSON Schema 2020-12 meta-schema validation | AJV validation error: schema is not valid | Run schemas through JSON Schema 2020-12 validator before committing; fix all violations |
Error Pattern 1: anyOf at Root Level
This is the most common rejection, hitting Azure AI Foundry and several enterprise gateways.
Schema that gets rejected:{
"name": "get_resource",
"description": "Fetch a resource by ID or name",
"inputSchema": {
"anyOf": [
{
"type": "object",
"properties": { "id": { "type": "string" } },
"required": ["id"]
},
{
"type": "object",
"properties": { "name": { "type": "string" } },
"required": ["name"]
}
]
}
}
Error from Azure AI Foundry:
InvalidSchema: anyOf is not supported at the root level of tool input schemas
Fix — merge into a single object with optional fields:
{
"name": "get_resource",
"description": "Fetch a resource by ID or name. Provide either id or name.",
"inputSchema": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Resource ID (use this OR name)"
},
"name": {
"type": "string",
"description": "Resource name (use this OR id)"
}
},
"minProperties": 1,
"additionalProperties": false
}
}
This flattens the anyOf union into a single object where both fields are optional but at least one is required. It loses the strict mutual-exclusion guarantee, but it works across all providers today — and after July 28 you can restore the oneOf version.
If you need to preserve the semantic distinction, add an explicit type discriminator:
{
"name": "get_resource",
"inputSchema": {
"type": "object",
"properties": {
"lookup_by": {
"type": "string",
"enum": ["id", "name"],
"description": "How to look up the resource"
},
"value": {
"type": "string",
"description": "The ID or name value"
}
},
"required": ["lookup_by", "value"],
"additionalProperties": false
}
}
Error Pattern 2: Array Types Missing items Inside anyOf
This hits Google Gemini CLI (PR #6632) and Backstage when Gemini is the underlying model.
Schema that gets rejected:{
"name": "process_items",
"inputSchema": {
"type": "object",
"properties": {
"data": {
"anyOf": [
{ "type": "string" },
{ "type": "array" }
]
}
}
}
}
Error from Gemini CLI:
Schema validation error: field 'data.anyOf[1]': array type requires 'items' property to be defined
Fix — add items to every array branch:
{
"name": "process_items",
"inputSchema": {
"type": "object",
"properties": {
"data": {
"anyOf": [
{ "type": "string" },
{
"type": "array",
"items": { "type": "string" }
}
]
}
}
}
}
If your array truly accepts mixed content, use a permissive items definition:
{
"type": "array",
"items": {}
}
An empty items: {} matches any item type. It's permissive, but it satisfies the validator and lets Gemini process the schema without errors.
import json
import jsonschema
def check_array_items(schema: dict, path: str = "") -> list[str]:
"""Find all array types missing 'items' inside anyOf/oneOf/allOf branches."""
errors = []
for keyword in ("anyOf", "oneOf", "allOf"):
if keyword in schema:
for i, branch in enumerate(schema[keyword]):
branch_path = f"{path}.{keyword}[{i}]"
if branch.get("type") == "array" and "items" not in branch:
errors.append(f"{branch_path}: array missing 'items'")
errors.extend(check_array_items(branch, branch_path))
if "properties" in schema:
for prop, prop_schema in schema["properties"].items():
errors.extend(check_array_items(prop_schema, f"{path}.{prop}"))
return errors
Run against all your tool inputSchemas
for tool in your_tools:
issues = check_array_items(tool["inputSchema"], tool["name"])
for issue in issues:
print(f"FIX REQUIRED: {issue}")
Paste individual inputSchema objects into the AI JSONMedic validator to catch this pattern instantly without writing code.
Error Pattern 3: $dynamicRef in Generated Schemas
This affects Directus (issue #25906) and any tool server using TypeScript SDK features that auto-generate schemas from TypeScript types with complex generics.
Schema that gets rejected:{
"inputSchema": {
"type": "object",
"properties": {
"filter": {
"$dynamicRef": "#filter"
}
},
"$defs": {
"filter": {
"$dynamicAnchor": "filter",
"type": "object"
}
}
}
}
Error:
Invalid schema: $dynamicRef is not a recognized keyword in the supported schema dialect
$dynamicRef is a JSON Schema 2020-12 feature for polymorphic references. The MCP pre-RC runtime validates against an older draft that doesn't support it.
Fix — replace with concrete $ref:
{
"inputSchema": {
"type": "object",
"properties": {
"filter": {
"$ref": "#/$defs/filter"
}
},
"$defs": {
"filter": {
"type": "object",
"properties": {
"field": { "type": "string" },
"operator": { "type": "string", "enum": ["eq", "ne", "gt", "lt", "in"] },
"value": {}
}
}
}
}
}
Or inline entirely if the $defs entry is only used once:
{
"inputSchema": {
"type": "object",
"properties": {
"filter": {
"type": "object",
"properties": {
"field": { "type": "string" },
"operator": { "type": "string", "enum": ["eq", "ne", "gt", "lt", "in"] },
"value": {}
}
}
}
}
}
Error Pattern 4: AJV Validation in CI/CD (claude-code-action#852)
Claude Code Action SDK 0.2.15 introduced stricter AJV validation. Any CI/CD pipeline using this SDK to validate tool definitions before invoking them will start throwing on upgrade.
Error from AJV:AJV validation error: data/tools/0/inputSchema must match JSON Schema 2020-12 meta-schema
What changed: AJV 8.x (used by SDK 0.2.15+) defaults to JSON Schema 2020-12 strict mode. It rejects:
typearrays without corresponding validators (["string", "null"]needs proper handling)- Missing
$schemadeclarations when using 2020-12 features - Schemas with unknown keywords not in the 2020-12 meta-schema
// package.json
{
"dependencies": {
"@claude/code-action": "0.2.14"
}
}
Proper fix — make your schemas explicitly 2020-12 compatible:
{
"name": "search",
"inputSchema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"query": { "type": "string" },
"limit": {
"type": ["integer", "null"],
"default": 10
},
"tags": {
"type": "array",
"items": { "type": "string" }
}
},
"required": ["query"],
"additionalProperties": false
}
}
Note: the $schema declaration is optional for MCP but helps AJV select the right validation mode.
Schema That Works Across All Providers Today
This template avoids every known rejection pattern:
{
"name": "safe_tool",
"description": "A tool with a schema that passes all current provider validators",
"inputSchema": {
"type": "object",
"properties": {
"action": {
"type": "string",
"enum": ["create", "read", "update", "delete"],
"description": "The operation to perform"
},
"resource_id": {
"type": "string",
"description": "Resource identifier (optional for create)"
},
"payload": {
"type": "object",
"description": "Request body (optional for read/delete)",
"additionalProperties": true
},
"tags": {
"type": "array",
"items": { "type": "string" },
"description": "Optional tags filter"
}
},
"required": ["action"],
"additionalProperties": false
}
}
Rules this template follows:
- No
anyOf/oneOf/allOfat root level — safe for Azure AI Foundry - All array fields declare
items— safe for Gemini CLI and Backstage - No
$dynamicRef— safe for Directus/VS Code - No
$defswith complex$refchains — safe for Kimi-Code - Uses enum for discriminated fields instead of union types — safe for AJV strict mode
additionalProperties: false— widely expected and accepted
What Changes July 28 — and What Code Breaks
The MCP RC on July 28 flips the rules. Once it lands:
What becomes valid (that isn't today):oneOf,anyOf,allOfat root level ofinputSchema— fully valid$defswith$refchains — fully valid$dynamicRefand$dynamicAnchor— fully valid (2020-12 feature)- Nested composition across multiple levels — valid and preserved
If you have middleware, a proxy, or an SDK wrapper that normalizes tool schemas by stripping composition keywords, it will silently corrupt valid schemas after July 28. Common patterns that become bugs:
// This middleware is a workaround today — it becomes a bug on July 28
function normalizeToolSchema(schema) {
// ❌ DO NOT DO THIS after July 28
const { anyOf, oneOf, allOf, ...rest } = schema;
return rest;
}
# Stripping composition keywords to satisfy older validators
❌ Remove this before July 28
def strip_composition_keywords(schema):
for key in ["anyOf", "oneOf", "allOf"]:
schema.pop(key, None)
return schema
// Flattening union schemas because "anyOf breaks things"
// ❌ This was a workaround — after July 28, anyOf is correct
function flattenUnionSchema(schema: JSONSchema): JSONSchema {
if (schema.anyOf) {
// merge branches into single object — WRONG after RC
return mergeSchemas(schema.anyOf);
}
return schema;
}
Audit checklist before July 28:
- Search your codebase for
anyOf,oneOf,allOfin schema-manipulation code - Look for any code that iterates tool schema properties and removes unknown/unsupported keys
- Check proxy servers that forward MCP
tools/listresponses — do they modify schemas in transit? - If you upgraded Zod to generate schemas (TypeScript SDK v1.26.0+ changed
z.union()output fromanyOftooneOf) — test that your client handles both shapes
The tech-agent will run a full normalization code audit on July 15 (T20260628-SIGMA-001). If you want to catch issues before then, run the AI JSONMedic validator against your tool schemas now and check the output against the July 28 change list.
Before/After: Complete Schema Transformation Examples
Before: Schema that breaks on 3+ providers
{
"name": "query_data",
"inputSchema": {
"anyOf": [
{
"type": "object",
"properties": {
"sql": { "type": "string" },
"params": { "type": "array" }
},
"required": ["sql"]
},
{
"type": "object",
"properties": {
"filter": {
"$dynamicRef": "#filter"
}
}
}
],
"$defs": {
"filter": {
"$dynamicAnchor": "filter",
"type": "object"
}
}
}
}
Breaks on: Azure (anyOf root), Gemini (array missing items), Directus ($dynamicRef), Kimi-Code ($defs + $ref chain)
After: Schema that works everywhere today
{
"name": "query_data",
"description": "Query data using SQL or filter syntax. Use query_mode to select.",
"inputSchema": {
"type": "object",
"properties": {
"query_mode": {
"type": "string",
"enum": ["sql", "filter"],
"description": "Use 'sql' for raw queries, 'filter' for structured filters"
},
"sql": {
"type": "string",
"description": "SQL query string (required when query_mode is 'sql')"
},
"params": {
"type": "array",
"items": {},
"description": "SQL parameters (optional, used with sql mode)"
},
"field": {
"type": "string",
"description": "Filter field name (required when query_mode is 'filter')"
},
"operator": {
"type": "string",
"enum": ["eq", "ne", "gt", "lt", "in", "contains"],
"description": "Filter operator (required when query_mode is 'filter')"
},
"value": {
"description": "Filter value (any type)"
}
},
"required": ["query_mode"],
"additionalProperties": false
}
}
After July 28 RC, you can restore the anyOf form — which is semantically cleaner and enforces the mutual exclusion properly.
Use aijsonmedic to Catch Schema Issues Before Deployment
Hitting an HTTP 400 in production because of a schema issue is avoidable. Validate before you deploy:
Quick check — paste your inputSchema into /validate:The AI JSONMedic JSON validator runs your schema through JSON Schema 2020-12 validation and flags:
- Missing
itemson array types (Gemini rejection pattern) $dynamicRefusage (Directus rejection pattern)- Root-level
anyOf/oneOfwithout atype: "object"wrapper (Azure rejection pattern) - Malformed
$refpaths pointing to missing$defsentries
FAQ
Why is my MCP tool schema being rejected with HTTP 400?
Most HTTP 400 rejections on MCP tools/list calls are caused by oneOf, anyOf, or allOf at root level of inputSchema — or by missing items on array types inside anyOf. Different providers enforce different rules pre-RC. Azure AI Foundry rejects anyOf at enterprise scale. Google Gemini CLI rejects anyOf when array items are missing. MoonshotAI Kimi-Code returns HTTP 400 on any non-compliant JSON Schema shape. The fix depends on which provider is rejecting your schema — see the provider matrix above.
Will oneOf and anyOf become valid in MCP tool schemas after July 28?
Yes. The MCP RC landing July 28, 2026 promotes inputSchema and outputSchema to full JSON Schema 2020-12. oneOf, anyOf, allOf, $defs, $ref, and all composition keywords become fully valid at root level. Code that strips or normalizes these keywords will break valid schemas after July 28. If you have middleware that removes anyOf or oneOf today, audit it before the RC lands.
What is the AJV validation error in claude-code-action SDK 0.2.15?
Claude Code Action SDK 0.2.15 added stricter AJV schema validation that rejects tool schemas not conforming to JSON Schema 2020-12. CI/CD pipelines that were working on 0.2.14 start throwing AJV validation errors after the upgrade. The immediate workaround is to pin to 0.2.14. The proper fix is to ensure your tool schemas pass JSON Schema 2020-12 validation — particularly: all arrays must declare items, no missing required fields, and composition keywords (oneOf/anyOf/allOf) are structured correctly.
Why does Google Gemini CLI reject my anyOf schema when it has missing array items?
Gemini CLI PR #6632 added a strict JSON schema validation rule that enforces JSON Schema 2020-12 compliance for MCP tool inputSchemas. One specific rule it enforces: any array type inside an anyOf branch must declare an items property. If you have type: "array" without items: { ... }, Gemini rejects the schema. Fix: add items to every array definition, even if it's items: {} as a permissive catch-all. The same fix resolves the Backstage rejection (backstage/backstage#34038) because Backstage uses Gemini as the underlying model.
Does stripping oneOf/anyOf from tool schemas break things after July 28?
Yes. Before July 28, stripping oneOf/anyOf is a common workaround to satisfy providers that reject composition keywords. After July 28 RC, the MCP spec requires full JSON Schema 2020-12 support — meaning oneOf/anyOf/allOf are valid and expected to be preserved. Any middleware, proxy, or SDK wrapper that strips these keywords will silently corrupt valid schemas. Audit your normalization code now and add a date-gated disable flag to stop stripping after July 28.
What is the $dynamicRef error in Directus and VS Code MCP tools?
Directus issue #25906 reports VS Code MCP tool schemas generating invalid schemas containing $dynamicRef — a JSON Schema 2020-12 feature that is not supported by the MCP pre-RC schema validator. The schema appears valid by 2020-12 rules but gets rejected because the MCP runtime is still validating against an older draft. Fix: replace $dynamicRef with a concrete $ref pointing to an explicitly defined $defs entry, or inline the schema instead of using dynamic references.
How do I validate my MCP tool schema before deploying?
Paste your inputSchema JSON into the AI JSONMedic JSON validator — it checks for missing items on arrays, malformed anyOf/oneOf branches, invalid $ref paths, and other patterns that cause provider rejections. For structural JSON issues (malformed JSON before schema validation), run it through /json-repair first. Alternatively, run the Python validation script in the "Error Pattern 2" section of this guide against all your tool definitions before pushing to production.
Summary
The MCP pre-RC enforcement wave is real, and it's hitting developers right now across five major platforms. Here's the concise fix map:
| Error | Provider | Fix |
|---|---|---|
anyOf at root level rejected | Azure AI Foundry | Flatten into single object with enum discriminator |
array type requires items | Gemini CLI, Backstage | Add items: {} to all array fields in anyOf branches |
| HTTP 400 on tools/list | Kimi-Code | Remove $defs/$ref chains; inline all definitions |
$dynamicRef not supported | Directus, VS Code | Replace with concrete $ref or inline the schema |
| AJV validation error in CI | claude-code-action 0.2.15 | Pin to 0.2.14 or fix schemas to pass 2020-12 meta-schema |
The underlying trajectory is good: July 28 RC makes oneOf/anyOf/allOf fully valid and preserved. The risk right now is the transition period — and the code you write today as a workaround that becomes a bug in 20 days.
Validate your schemas before you deploy using /validate. Check for structural JSON issues first with /json-repair. And if you're planning to strip composition keywords as a fix, build in the July 28 date gate before you ship.
Related: MCP outputSchema Validation Errors: Fix Guide · MCP Python SDK v2 Beta Migration · Anthropic Tool Schema Validation Errors · JSON Schema Validation
Still dealing with broken JSON?
Paste it in and get it fixed in under 1 second — free, no signup, no install. Works with ChatGPT, Claude, n8n, and any AI output.
Fix My JSON Free →Related Articles