DEV Community

Cover image for MCP SEP-2106: Full JSON Schema 2020-12 in Tool I/O
pueding
pueding

Posted on • Originally published at learnaivisually.com

MCP SEP-2106: Full JSON Schema 2020-12 in Tool I/O

What: MCP SEP-2106 — merged into the protocol on May 18, 2026 — lets an MCP tool describe its inputs and outputs with the full JSON Schema 2020-12 keyword set in inputSchema and outputSchema, and widens structuredContent from object-only to any JSON value.

Why: Composition (oneOf / anyOf / allOf), conditionals (if / then / else), and references ($ref / $defs) let a tool author push contract rules out of free-form description prose and into the schema, where runtimes and SDKs can validate them before the call ever reaches the tool.

vs prior: The previous MCP spec accepted only a narrow JSON Schema subset (object root with a basic type / properties / required vocabulary); composition, conditionals, refs, and non-object output shapes were not part of the wire vocabulary and had to live in tool description prose.

Think of it as

It's like a job application form with conditional sections, alternatives, and refs.

           THE TOOL'S inputSchema (a form)
                        │
        ┌───────────────┴────────────────┐
 ┌──────▼───────┐                 ┌───────▼──────┐
 │ BEFORE 2106  │                 │ AFTER 2106   │
 │ plain fields │                 │ fields PLUS  │
 │ + prose note │                 │ oneOf / if / │
 │ at the bottom│                 │ then / $ref  │
 └──────┬───────┘                 └───────┬──────┘
        │                                 │
  rules live in                    rules live in
  English prose                    the schema
        │                                 │
        ▼                                 ▼
 ✗ runtime CANNOT                 ✓ runtime REJECTS
   check them                       bad calls early
Enter fullscreen mode Exit fullscreen mode
  • MCP tool inputSchema = the application form a tool requires from the agent
  • basic keywords (type / properties / required) = plain text fields and required checkboxes
  • oneOf / anyOf / allOf = pick exactly one / any combination / all of these alternatives
  • if / then / else = if you marked 'married', also fill spouse details
  • $ref / $defs = see the 'Company Address' subform on page 4

Quick glossary

MCP — The Model Context Protocol — a JSON-RPC wire protocol that lets LLM clients (Claude, ChatGPT, IDEs) discover and call tools served by external processes. See the MCP step in the Tool Use module.

SEP — A Specification Enhancement Proposal — the MCP equivalent of a Python PEP or a TC39 proposal. Each SEP is a numbered RFC merged into the spec only after review.

inputSchema / outputSchema — The two JSON Schema documents an MCP server attaches to a tool definition — one for the arguments the agent must send, one for the structured value the tool returns. The runtime validates traffic against them before either side sees a malformed payload.

structuredContent — The field inside a tool result that carries a typed value alongside the human-readable content blocks. Pre-SEP-2106 the TypeScript type was { [key: string]: unknown } — objects only; after SEP-2106 it is plain unknown, so arrays and primitives are wire-legal too.

JSON Schema 2020-12 — The 2020-12 draft of the JSON Schema spec — the most recent stable version. Adds composition (oneOf / anyOf / allOf / not), conditionals (if / then / else), references ($ref / $defs), and tighter $dynamicRef semantics over the older draft-07 vocabulary MCP previously implied.

oneOf / anyOf / allOf — JSON Schema composition keywords. oneOf = match exactly one of N subschemas; anyOf = match at least one; allOf = match every subschema (intersection).

if / then / else — JSON Schema conditional keywords. If a value matches the if subschema, it must also match then; otherwise it must match else. Lets a single schema express "if roundTrip is true, return_date is required."

$ref / $defs — JSON Schema reference keywords. $defs declares reusable named subschemas; $ref points at one of them by JSON Pointer. Lets a long schema avoid copy-pasting the same address or money sub-shape three times.

The news. On May 18, 2026, SEP-2106 merged into the MCP specification. The change widens the schema vocabulary that tools may use to describe their input and output: inputSchema now allows the full JSON Schema 2020-12 keyword set inside its required type: "object" root, outputSchema drops the object-root constraint entirely and accepts any 2020-12 schema, and structuredContent is retyped from object-only to plain unknown. Loosening on paper — but the SEP is explicit that compatibility is asymmetric: a newer server emitting a non-object structuredContent or a composition-rich schema may be rejected by an older client that hasn't been updated, so the SEP recommends servers also emit a serialized TextContent fallback for non-object results during the transition.

Picture a job application that, until last week, only let you fill in plain text fields and checkboxes — name, address, "married?" yes/no. If the form needed something conditional ("if married, also provide spouse name") or alternative ("attach exactly ONE of passport, driver's license, or state ID"), the only way to express it was a paragraph of free-text instructions at the bottom of the page. SEP-2106 hands the form designer a richer template language: now the conditional, the alternatives, and the cross-references to other subforms are spelled out on the form itself, in a way the form's automated validator can actually check before the application gets routed.

The technical reason mirrors the metaphor. Before SEP-2106, the MCP wire spec implied a narrow JSON Schema subset — basically the keywords a 2014-era schema validator would understand: type, properties, required, items, enum, additionalProperties. If a tool needed to express "either a one-way booking (no return date) or a round-trip booking (return date required)," the schema author had two bad options: split it into two separate tools (now the model has to pick), or leave it as one tool with a permissive schema and a paragraph of natural-language instructions in description. The first option inflates the agent's tool registry; the second relies on the model honoring prose constraints that the runtime can't enforce.

Three surfaces, three changes

SEP-2106 touches three places on the wire, with slightly different shapes of change.

Surface Before SEP-2106 After SEP-2106
inputSchema root must be type: "object" (SEP-2106 commit) must be type: "object" (unchanged)
inputSchema keywords inside the object root restricted vocabulary the spec named — type / properties / required (SDKs typically also accepted items, enum, additionalProperties) full JSON Schema 2020-12 — adds oneOf / anyOf / allOf / not, if / then / else, $ref / $defs, and the rest of the 2020-12 keyword set (SEP-2106 commit)
outputSchema basic, object-rooted (mirrored inputSchema) (SEP-2106 commit) fully flexible — any 2020-12 schema, including array roots, primitive roots, and composition (SEP-2106 commit)
structuredContent TypeScript type { [key: string]: unknown } — object only (SEP-2106 commit) unknown — array, primitive, union, object all wire-legal (SEP-2106 commit)

The root constraint on inputSchema is preserved because every tool call still ships a JSON-RPC arguments object — the call is arguments: { ... }, not arguments: 7. What changed is everything inside that object, plus the symmetric story for what a tool can return.

A worked example

Picture a book_flight tool. Before SEP-2106, its inputSchema could declare four fields — from, to, departure, optional return — using the restricted vocabulary the spec named (type, properties, required). To express "round-trip flights require return, one-way flights forbid it," the author had three options: split into two tools (book_one_way, book_round_trip), leave a permissive schema and write a paragraph of description prose, or both. After SEP-2106, the same tool fits in one schema using composition:

{
  "type": "object",
  "properties": {
    "from": {...}, "to": {...},
    "departure": {...}, "return": {...},
    "roundTrip": { "type": "boolean" }
  },
  "required": ["from", "to", "departure", "roundTrip"],
  "oneOf": [
    {
      "properties": { "roundTrip": { "const": true } },
      "required": ["return"]
    },
    {
      "properties": { "roundTrip": { "const": false } },
      "not": { "required": ["return"] }
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

The new schema reaches for oneOf, two branch subschemas with their own properties and required, a not, and two const guards — every one of those keywords lived in the JSON Schema 2020-12 standard already, but none were in the wire vocabulary MCP would accept before this SEP. The runtime can now reject a malformed call before it ever reaches the tool, instead of relying on the LLM to read and honor a paragraph of English in the description field.

Why this lands now

Two pressures converged. First, tool authors kept hitting the prose-vs-schema boundary: every nontrivial real-world tool grew a description paragraph explaining what its schema couldn't say, and that paragraph then needed to be re-explained to every model that called the tool. Second, the structured tool I/O step of the agent stack — where output validation lives — assumed an object-rooted structuredContent shape that forced tools returning a list (list_files) or a scalar (count_rows) to wrap their result in { "value": ... }. Both pressures land at the schema vocabulary, so SEP-2106 widens both at once.

The rollout story is more nuanced than "strictly loosening." Existing tools that already used only the previously-allowed keywords keep working unchanged, and the wire protocol stays backward-compatible at the schema vocabulary level — composition keywords like oneOf are legal JSON either way, so an older client that doesn't validate them will simply skip the extra checks (the schema still parses, just with weaker validation). The friction is asymmetric: a newer server emitting a non-object structuredContent or a primitive-rooted outputSchema may be rejected by an older client whose type checks still expect an object, which is why the SEP recommends servers also emit a serialized TextContent fallback for non-object results during the transition. SDK consumers also see one TypeScript source break — the narrower { [k]: unknown } type loses to plain unknown, and any code that depended on the narrower type needs to widen its own annotations to match.

Goes deeper in: AI Agents → Tool Use → Structured tool I/O and AI Agents → Tool Use → MCP

FAQ

What changed in MCP SEP-2106 in one sentence?

SEP-2106 lets MCP tool authors describe their inputs and outputs with the full JSON Schema 2020-12 keyword set — composition (oneOf / anyOf / allOf / not), conditionals (if / then / else), and references ($ref / $defs) — and widens structuredContent from an object-only TypeScript type to plain unknown, while keeping inputSchema's root type: "object" constraint unchanged.

Why does richer tool-schema vocabulary matter for agents?

The wire vocabulary is the only contract the runtime can validate before traffic reaches the tool. Anything that lives in the tool's free-form description prose has to be re-explained to every model that calls the tool, and the runtime can't reject a malformed call until the tool itself errors out. Pushing rules like "if roundTrip is true then return is required" into the schema means the SDK can reject the call before invocation and the model gets a structured error it can react to, instead of a tool-side stack trace.

Does SEP-2106 break existing MCP tools?

Existing tool definitions remain valid because the change only adds allowed keywords and widens types — nothing is removed. Compatibility is asymmetric, though: a newer server emitting a non-object structuredContent or a primitive-rooted outputSchema may be rejected by an older client whose type checks still expect an object. The SEP recommends servers also emit a serialized TextContent fallback for non-object results during the transition. There is also one source-level TypeScript break — consumers whose generic types narrowed structuredContent from unknown to { [key: string]: unknown } see a type error when they upgrade SDK versions, fixed by widening the consumer's type to match.


Originally posted on Learn AI Visually.

Top comments (0)