> ## Documentation Index
> Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt
> Use this file to discover all available pages before exploring further.

# SEP-2106: Tools `inputSchema` & `outputSchema` Conform to JSON Schema 2020-12

> Tools `inputSchema` & `outputSchema` Conform to JSON Schema 2020-12

<div className="flex items-center gap-2 mb-4">
  <Badge color="green" shape="pill">
    Final
  </Badge>

  <Badge color="gray" shape="pill">
    Standards Track
  </Badge>
</div>

| Field         | Value                                                                                                                                                                         |
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **SEP**       | 2106                                                                                                                                                                          |
| **Title**     | Tools `inputSchema` & `outputSchema` Conform to JSON Schema 2020-12                                                                                                           |
| **Status**    | Final                                                                                                                                                                         |
| **Type**      | Standards Track                                                                                                                                                               |
| **Created**   | 2026-01-06                                                                                                                                                                    |
| **Author(s)** | John McBride ([@jpmcb](https://github.com/jpmcb)) — original proposal; Ola Hungerford ([@olaservo](https://github.com/olaservo)) — current shepherd, post-SEP-1850 conversion |
| **Sponsor**   | Ola Hungerford ([@olaservo](https://github.com/olaservo))                                                                                                                     |
| **PR**        | [#2106](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2106)                                                                                               |

***

## Abstract

This SEP proposes loosening the restrictions on `inputSchema`, `outputSchema`, and `structuredContent` to better support JSON Schema 2020-12. Specifically:

* **`inputSchema`**: Keeps `type: "object"` required (since tool arguments are objects), but allows any additional JSON Schema properties to support powerful validation compositions (`anyOf`, `oneOf`, `allOf`, etc.)
* **`outputSchema`**: Fully supports JSON Schema 2020-12 since MCP servers may return any valid JSON
* **`structuredContent`**: Accepts any JSON value validated by `outputSchema`

This proposal enables MCP servers to leverage the expressiveness of JSON Schema 2020-12 while maintaining backward compatibility with existing implementations.

## Motivation

The current MCP specification restricts tool schemas in ways that conflict with full JSON Schema support:

1. **inputSchema restriction**: Currently only allows `type`, `properties`, and `required` fields. This prevents use of composition keywords like `anyOf`, `oneOf`, and `allOf` for sophisticated object validation patterns.

2. **outputSchema restriction**: Also restricted to `type: "object"` with only `properties` and `required`, despite the specification claiming to support "JSON Schema."

3. **structuredContent restriction**: Defined as `{ [key: string]: unknown }` (an object with string keys), which prevents returning arrays—a common API response pattern.

### Real-World Impact

Consider a weather API tool that returns hourly forecasts:

```json theme={null}
[
  { "hour": "09:00", "temp": 68, "conditions": "sunny" },
  { "hour": "10:00", "temp": 72, "conditions": "partly cloudy" },
  { "hour": "11:00", "temp": 75, "conditions": "cloudy" }
]
```

Currently, this natural array response is **impossible** because `structuredContent` must be an object. Developers are forced to wrap arrays in unnecessary container objects:

```json theme={null}
{
  "forecasts": [
    { "hour": "09:00", "temp": 68, "conditions": "sunny" },
    ...
  ]
}
```

This artificial constraint:

* Adds unnecessary nesting to responses
* Conflicts with common REST API patterns
* Prevents direct schema validation of array responses

### Schema Composition Use Cases

The current `inputSchema` restriction prevents legitimate schema patterns. With this SEP, tools can use composition keywords alongside `type: "object"`:

```json theme={null}
{
  "type": "object",
  "oneOf": [
    { "properties": { "id": { "type": "string" } }, "required": ["id"] },
    { "properties": { "name": { "type": "string" } }, "required": ["name"] }
  ]
}
```

This pattern allows a tool to accept either an ID-based or name-based lookup—a common API design that is currently unsupported because the schema only allows `type`, `properties`, and `required` fields.

## Specification

### 1. Loosen inputSchema

**Current definition:**

```typescript theme={null}
inputSchema: {
  type: "object";
  properties?: { [key: string]: object };
  required?: string[];
};
```

**Proposed definition:**

```typescript theme={null}
inputSchema: {
  $schema?: string;
  type: "object";
  [key: string]: unknown;
};
```

The `inputSchema` field retains the `type: "object"` requirement (since tool arguments are always objects), but now accepts any additional JSON Schema properties. This enables:

* Composition keywords: `anyOf`, `oneOf`, `allOf`, `not`
* Conditional schemas: `if`/`then`/`else`
* Reference schemas: `$ref`, `$defs`
* Any other valid JSON Schema 2020-12 keywords

### 2. Loosen outputSchema

**Current definition:**

```typescript theme={null}
outputSchema?: {
  type: "object";
  properties?: { [key: string]: object };
  required?: string[];
};
```

**Proposed definition:**

```typescript theme={null}
outputSchema?: {
  $schema?: string;
  [key: string]: unknown;
};
```

The `outputSchema` field accepts any valid JSON Schema 2020-12 object, enabling schemas that validate arrays, primitives, or complex compositions. Unlike `inputSchema`, there is no `type: "object"` requirement since tool outputs can be any valid JSON.

### 3. Loosen structuredContent

**Current definition:**

```typescript theme={null}
structuredContent?: { [key: string]: unknown };
```

**Proposed definition:**

```typescript theme={null}
structuredContent?: unknown;
```

The `structuredContent` field accepts any valid JSON value that conforms to the tool's `outputSchema`. This includes:

* Objects: `{ "key": "value" }`
* Arrays: `[1, 2, 3]` or `[{ "id": "abc" }, { "id": "xyz" }]`
* Primitives: `"string"`, `42`, `true`, `null`

### 4. Documentation Updates

Update `docs/specification/draft/server/tools.mdx`:

* Remove statement that `structuredContent` is "returned as a JSON object"
* Clarify that `structuredContent` can be any JSON value conforming to `outputSchema`
* Add examples demonstrating array responses

### 5. Examples

#### Tool returning an array of objects:

```json theme={null}
{
  "name": "list_users",
  "description": "List all users in the system",
  "inputSchema": {
    "type": "object",
    "properties": {
      "limit": { "type": "integer", "minimum": 1, "maximum": 100 }
    }
  },
  "outputSchema": {
    "type": "array",
    "items": {
      "type": "object",
      "properties": {
        "id": { "type": "string" },
        "name": { "type": "string" },
        "email": { "type": "string", "format": "email" }
      },
      "required": ["id", "name"]
    }
  }
}
```

Response:

```json theme={null}
{
  "content": [
    {
      "type": "text",
      "text": "Found 2 users: Alice (u1, alice@example.com) and Bob (u2, bob@example.com)."
    }
  ],
  "structuredContent": [
    { "id": "u1", "name": "Alice", "email": "alice@example.com" },
    { "id": "u2", "name": "Bob", "email": "bob@example.com" }
  ]
}
```

#### Tool with composition schema:

```json theme={null}
{
  "name": "find_resource",
  "description": "Find a resource by ID or name",
  "inputSchema": {
    "type": "object",
    "oneOf": [
      {
        "properties": { "id": { "type": "string", "format": "uuid" } },
        "required": ["id"]
      },
      {
        "properties": { "name": { "type": "string", "minLength": 1 } },
        "required": ["name"]
      }
    ]
  }
}
```

## Rationale

### Why not just allow arrays?

While we could simply extend `structuredContent` to allow arrays, this would be an incomplete solution. The root cause is that the schema types are artificially restricted to `type: "object"`. By allowing any valid JSON Schema, we:

1. Enable the full power of JSON Schema 2020-12
2. Align with the specification's claim of JSON Schema support
3. Provide a consistent, principled approach rather than piecemeal fixes

### Why not require a wrapper object?

Requiring arrays to be wrapped in objects (e.g., `{ "items": [...] }`) was considered but rejected because:

1. It adds unnecessary complexity to responses
2. It conflicts with common API design patterns
3. It prevents direct schema validation of the actual response structure
4. JSON Schema already handles array validation elegantly

### Real-World API Patterns

Many production APIs return arrays directly:

* **GitHub Events API**: Returns arrays of event objects
* **AccuWeather Search API**: Returns arrays of location matches
* **REST collection endpoints**: Standard `GET /users` returns `[{...}, {...}]`

Forcing wrapper objects creates friction for developers integrating existing APIs with MCP. Generic JSON Schema validation libraries should work without MCP-specific customization.

### Alignment with JSON Schema 2020-12

JSON Schema 2020-12 provides powerful features for schema composition and validation. By removing artificial restrictions, MCP aligns with industry standards (OpenAPI 3.1 uses JSON Schema 2020-12) and enables developers to leverage existing JSON Schema knowledge and tooling.

### SDK Ecosystem Evidence

The friction caused by current restrictions is not theoretical. FastMCP, one of the most popular Python SDKs for MCP, has implemented extensive workarounds:

1. **Explicit error messages** acknowledge the limitation:

   ```python theme={null}
   raise ValueError(
       f"Output schemas must represent object types due to MCP spec limitations."
   )
   ```

2. **Auto-wrapping infrastructure** adds complexity:
   * A `_WrappedResult` dataclass wraps non-object returns
   * A custom `x-fastmcp-wrap-result` extension enables client-side unwrapping
   * Both SDK and client need matching wrap/unwrap logic

3. **Real bugs** have resulted from these workarounds:
   * Issue #2455: `$ref` schemas without `type: object` broke ALL tools on the server
   * Issue #2421: Unexpected `{"result": ...}` wrapping confused users

This demonstrates that the current restrictions create genuine ecosystem friction that SEP-2106 would eliminate.

### OpenAPI Precedent

The OpenAPI specification went through a similar evolution. OpenAPI 3.0 used an "extended subset" of JSON Schema with custom restrictions (like requiring `nullable: true` instead of allowing `"null"` as a type).

OpenAPI 3.1 made the strategic decision to fully align with JSON Schema 2020-12, accepting breaking changes to eliminate the friction. The result: better tooling compatibility and less ecosystem confusion.

| OpenAPI's Problem                   | MCP's Parallel                            |
| ----------------------------------- | ----------------------------------------- |
| `type` must be string, not array    | `inputSchema` only allows specific fields |
| Couldn't use standard null handling | Can't use `oneOf`/`anyOf` in schemas      |
| Custom `nullable` keyword           | Object-only `structuredContent`           |
| Caused tooling confusion            | Causes SDK workarounds                    |

MCP can learn from OpenAPI's experience rather than repeating the same evolution over several years.

## Backward Compatibility

This change is **wire-format backward compatible** but has nuances depending on the direction of the version mismatch.

### Compatibility Matrix

|                           | New client (post-SEP)                                        | Old client (pre-SEP)                                                                                                              |
| ------------------------- | ------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------- |
| **New server (post-SEP)** | Fully compatible.                                            | Compatible **only when the server returns object-typed `structuredContent`**. Arrays/primitives in `structuredContent` may break. |
| **Old server (pre-SEP)**  | Fully compatible. Existing object-only schemas remain valid. | Unchanged.                                                                                                                        |

The asymmetry: a new server that takes advantage of array or primitive `structuredContent` (or composition keywords in `inputSchema`) cannot assume an old client will accept the response. Old clients written against the previous wire format may reject `structuredContent` that is not a JSON object, or fail to validate `inputSchema` containing keywords beyond `type`/`properties`/`required`.

To remain interoperable with older clients, **servers using array or primitive `structuredContent` MUST also emit a `TextContent` block containing the serialized JSON** (as already recommended in the tools specification). Clients that do not understand non-object `structuredContent` can fall back to the text content.

### TypeScript / SDK Migration

Widening the `structuredContent` field type from `{ [key: string]: unknown }` to `unknown` is a **source-breaking change for typed consumers**, even though the wire format is unchanged. Code such as:

```typescript theme={null}
const result = await client.callTool({ name: "get_weather", arguments: { ... } });
const temp = result.structuredContent?.temperature;        // previously compiled (type: unknown)
const city = result.structuredContent?.["city"] as string; // previously compiled
```

will no longer type-check after the change, because TypeScript forbids property access on `unknown` without a narrowing guard:

```typescript theme={null}
const sc = result.structuredContent;
if (sc && typeof sc === "object" && !Array.isArray(sc)) {
  const temp = (sc as Record<string, unknown>).temperature;
}
```

This break is intentional — the previous type was a lie whenever a tool returned a non-object — but SDK maintainers SHOULD:

* Document the migration in SDK release notes.
* Where ergonomic, provide typed helpers (e.g. generics over a tool's `outputSchema`) so consumers do not need to write narrowing guards by hand.

### Migration Path

* **Servers**: No migration is required to keep working as before. To use array or primitive `structuredContent`, also emit a serialized `TextContent` fallback.
* **Clients**: Old clients continue to work against object-only servers. To consume the new flexibility, accept any JSON value in `structuredContent` and validate against `outputSchema` if present.
* **SDKs**: Update generated types to mirror the new schema (`unknown` for `structuredContent`, open-ended `inputSchema`/`outputSchema`) and call out the source-breaking type change in release notes.

## Security Implications

JSON Schema validation already handles type checking, value constraints, and required field validation, and implementations MUST continue to validate all inputs and outputs against declared schemas. Allowing the full JSON Schema 2020-12 vocabulary surfaces two areas that warrant explicit guidance.

### `$ref` Dereferencing (SSRF and Fetch-DoS)

JSON Schema 2020-12 permits `$ref` to point at an absolute URI, not just a JSON Pointer into the same document. A naive implementation that resolves every `$ref` it encounters by issuing an HTTP request gives an attacker a server-side request forgery / fetch amplification primitive: a malicious tool definition can cause the host to fetch arbitrary URLs, including internal metadata endpoints or large payloads designed to exhaust resources.

To mitigate this:

* Implementations **MUST NOT** automatically dereference `$ref` values that resolve to a network URI (i.e. anything that is not a same-document JSON Pointer such as `#/$defs/Foo` or an internal `$anchor`).
* "Automatically" here means "as part of normal validation or schema processing, without explicit operator action." Implementations **MAY** offer an opt-in mode that fetches non-local `$ref`s, but it MUST be disabled by default and SHOULD enforce an allowlist of hosts (or at minimum reject loopback, link-local, and private network addresses), apply timeouts and size limits, and log dereferenced URIs.
* Schemas that fail to validate due to an unresolved external `$ref` SHOULD be rejected rather than silently treated as permissive.

### Composition-Keyword Resource Use

Composition keywords (`anyOf`, `oneOf`, `allOf`, `if`/`then`/`else`) and `$defs` enable expressive schemas, but pathological combinations can be expensive to validate. Implementations SHOULD apply reasonable bounds — for example, a maximum schema depth, a cap on the total number of subschemas, or a per-validation time budget — to prevent a malicious tool definition from acting as a CPU DoS vector against the validator.

## Reference Implementation

### TypeScript SDK

A reference implementation demonstrating the loosened type restrictions:

* **Branch**: [olaservo/typescript-sdk@sep-834-v1x](https://github.com/olaservo/typescript-sdk/tree/sep-834-v1x)
* **npm**: `@olaservo/mcp-sdk@1.25.2-sep834.4`
* **Key changes**:
  * `inputSchema`: Retains `type: "object"` but allows any additional JSON Schema properties (compositions like `oneOf`/`anyOf`)
  * `outputSchema`: Any valid JSON Schema object (arrays, primitives, objects, compositions)
  * `structuredContent`: Any JSON value (objects, arrays, or primitives)
  * McpServer high-level API updated to support array and primitive outputSchema

### Everything Server Demo Tools

Three demo tools added to the `everything` server demonstrating SEP-2106 capabilities:

* **Branch**: [olaservo/servers@sep-834-json-schema-2020-12](https://github.com/olaservo/servers/tree/sep-834-json-schema-2020-12/src/everything)
* **npm**: `@olaservo/mcp-server-everything-sep834@1.1.0-sep834.1`
* **Tools**:
  * `get-weather-forecast`: Returns **raw array** of hourly forecasts directly in `structuredContent`
    * Matches the exact example from SEP-2106's Motivation section
    * `outputSchema`: `z.array(HourlyForecastSchema)` - array type at root
    * `structuredContent`: `[{hour, temp, conditions}, ...]` - direct array
  * `find-by-id-or-name`: Demonstrates flexible input patterns (accepts `id` OR `name`)
  * `get-count`: Returns **raw number** directly in `structuredContent` (not wrapped in object)
    * `outputSchema`: `z.number()` - primitive type at root
    * `structuredContent`: `42` - direct primitive

### Related Links

* Original PR: [https://github.com/modelcontextprotocol/modelcontextprotocol/pull/881](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/881)
* Related issue: [https://github.com/modelcontextprotocol/modelcontextprotocol/issues/834](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/834)
* `outputSchema` type restriction inconsistency: [https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1906](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1906)
* TypeScript SDK schema types: [https://github.com/modelcontextprotocol/typescript-sdk/issues/1149](https://github.com/modelcontextprotocol/typescript-sdk/issues/1149)
* SEP-2200 (Clarify Tool Result Content and Model Visibility): [https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2200](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2200)

### Implementation Guidance

SDK implementations will need to:

1. Update `inputSchema` types to retain `type: "object"` but allow any additional JSON Schema properties
2. Update `outputSchema` types to allow any valid JSON Schema (remove `type: "object"` constraint)
3. Update `structuredContent` types to accept any valid JSON value
4. Update JSON Schema definitions accordingly

## Acknowledgments

This proposal builds on discussions in GitHub issue #834 and incorporates feedback from the MCP community.
