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

# SEP-2106: 工具 `inputSchema` 与 `outputSchema` 符合 JSON Schema 2020-12

> 工具 `inputSchema` 与 `outputSchema` 符合 JSON Schema 2020-12

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

  <Badge color="gray" shape="pill">
    标准轨道
  </Badge>
</div>

| Field         | Value                                                                                                                                   |
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| **SEP**       | 2106                                                                                                                                    |
| **Title**     | 工具 `inputSchema` 与 `outputSchema` 符合 JSON Schema 2020-12                                                                                |
| **Status**    | 最终                                                                                                                                      |
| **Type**      | 标准轨道                                                                                                                                    |
| **Created**   | 2026-01-06                                                                                                                              |
| **Author(s)** | John McBride ([@jpmcb](https://github.com/jpmcb)) — 原始提案；Ola Hungerford ([@olaservo](https://github.com/olaservo)) — 当前维护人，SEP-1850 转换后 |
| **Sponsor**   | Ola Hungerford ([@olaservo](https://github.com/olaservo))                                                                               |
| **PR**        | [#2106](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2106)                                                         |

***

## 摘要

本 SEP 提议放宽对 `inputSchema`、`outputSchema` 和 `structuredContent` 的限制，以更好地支持 JSON Schema 2020-12。具体来说：

* **`inputSchema`**：继续要求 `type: "object"`（因为工具参数是对象），但允许任何额外的 JSON Schema 属性，以支持强大的验证组合（`anyOf`、`oneOf`、`allOf` 等）
* **`outputSchema`**：由于 MCP 服务器可能返回任何有效 JSON，因此全面支持 JSON Schema 2020-12
* **`structuredContent`**：接受由 `outputSchema` 验证的任何 JSON 值

该提案使 MCP 服务器能够利用 JSON Schema 2020-12 的表达能力，同时保持与现有实现的向后兼容性。

## 动机

当前的 MCP 规范以几种方式限制了工具 schema，与完整的 JSON Schema 支持相冲突：

1. **inputSchema 限制**：当前只允许 `type`、`properties` 和 `required` 字段。这使得无法使用 `anyOf`、`oneOf` 和 `allOf` 等组合关键字来实现复杂的对象验证模式。

2. **outputSchema 限制**：同样限制为仅能使用 `type: "object"`、`properties` 和 `required`，尽管规范声称支持 “JSON Schema”。

3. **structuredContent 限制**：定义为 `{ [key: string]: unknown }`（一个以字符串为键的对象），这阻止了返回数组——而数组是常见的 API 响应模式。

### 现实世界的影响

设想一个返回每小时预报的天气 API 工具：

```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" }
]
```

目前，这种自然的数组响应是**不可能的**，因为 `structuredContent` 必须是对象。开发者被迫将数组包装进不必要的容器对象中：

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

这种人为限制：

* 为响应增加了不必要的嵌套
* 与常见的 REST API 模式相冲突
* 阻止了对数组响应进行直接的 schema 验证

### Schema 组合用例

当前对 `inputSchema` 的限制阻止了合法的 schema 模式。借助此 SEP，工具可以在 `type: "object"` 之外使用组合关键字：

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

这种模式允许工具接受基于 ID 或基于名称的查询——这是一种常见的 API 设计，而目前由于 schema 只允许 `type`、`properties` 和 `required` 字段，因此无法支持。

## 规范

### 1. 放宽 inputSchema

**当前定义：**

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

**建议定义：**

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

`inputSchema` 字段保留 `type: "object"` 的要求（因为工具参数始终是对象），但现在接受任何额外的 JSON Schema 属性。这使得以下内容成为可能：

* 组合关键字：`anyOf`、`oneOf`、`allOf`、`not`
* 条件 schema：`if`/`then`/`else`
* 引用 schema：`$ref`、`$defs`
* 任何其他有效的 JSON Schema 2020-12 关键字

### 2. 放宽 outputSchema

**当前定义：**

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

**建议定义：**

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

`outputSchema` 字段接受任何有效的 JSON Schema 2020-12 对象，从而支持验证数组、原始值或复杂组合的 schema。与 `inputSchema` 不同，这里没有 `type: "object"` 的要求，因为工具输出可以是任何有效 JSON。

### 3. 放宽 structuredContent

**当前定义：**

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

**建议定义：**

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

`structuredContent` 字段接受任何符合工具 `outputSchema` 的有效 JSON 值。这包括：

* 对象：`{ "key": "value" }`
* 数组：`[1, 2, 3]` 或 `[{ "id": "abc" }, { "id": "xyz" }]`
* 原始值：`"string"`、`42`、`true`、`null`

### 4. 文档更新

更新 `docs/specification/draft/server/tools.mdx`：

* 删除 `structuredContent` “作为 JSON 对象返回”的表述
* 说明 `structuredContent` 可以是任何符合 `outputSchema` 的 JSON 值
* 添加展示数组响应的示例

### 5. 示例

#### 返回对象数组的工具：

```json theme={null}
{
  "name": "list_users",
  "description": "列出系统中的所有用户",
  "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"]
    }
  }
}
```

响应：

```json theme={null}
{
  "content": [
    {
      "type": "text",
      "text": "找到 2 个用户：Alice（u1，alice@example.com）和 Bob（u2，bob@example.com）。"
    }
  ],
  "structuredContent": [
    { "id": "u1", "name": "Alice", "email": "alice@example.com" },
    { "id": "u2", "name": "Bob", "email": "bob@example.com" }
  ]
}
```

#### 使用组合 schema 的工具：

```json theme={null}
{
  "name": "find_resource",
  "description": "按 ID 或名称查找资源",
  "inputSchema": {
    "type": "object",
    "oneOf": [
      {
        "properties": { "id": { "type": "string", "format": "uuid" } },
        "required": ["id"]
      },
      {
        "properties": { "name": { "type": "string", "minLength": 1 } },
        "required": ["name"]
      }
    ]
  }
}
```

## 原因说明

### 为什么不只允许数组？

虽然我们可以简单地扩展 `structuredContent` 以允许数组，但这将是一个不完整的解决方案。根本原因在于 schema 类型被人为限制为 `type: "object"`。通过允许任何有效的 JSON Schema，我们可以：

1. 发挥 JSON Schema 2020-12 的全部能力
2. 与规范中声称支持 JSON Schema 保持一致
3. 提供一种一致、原则性的方案，而不是零散修补

### 为什么不要求包装对象？

曾考虑要求将数组包装在对象中（例如 `{ "items": [...] }`），但最终被拒绝，因为：

1. 它为响应增加了不必要的复杂性
2. 它与常见的 API 设计模式相冲突
3. 它阻止了对实际响应结构进行直接的 schema 验证
4. JSON Schema 本就能优雅地处理数组验证

### 现实中的 API 模式

许多生产环境 API 会直接返回数组：

* **GitHub Events API**：返回事件对象数组
* **AccuWeather Search API**：返回地点匹配数组
* **REST 集合端点**：标准的 `GET /users` 返回 `[{...}, {...}]`

强制使用包装对象会给将现有 API 与 MCP 集成的开发者带来摩擦。通用的 JSON Schema 验证库应该无需 MCP 特定定制即可工作。

### 与 JSON Schema 2020-12 的一致性

JSON Schema 2020-12 为 schema 组合和验证提供了强大的特性。通过移除人为限制，MCP 与行业标准保持一致（OpenAPI 3.1 使用 JSON Schema 2020-12），并使开发者能够利用现有的 JSON Schema 知识和工具链。

### SDK 生态证据

当前限制带来的摩擦并非理论上的。FastMCP 作为最流行的 MCP Python SDK 之一，已经实现了大量变通方案：

1. **明确的错误信息** 会承认这一限制：

   ```python theme={null}
   raise ValueError(
       f"输出 schema 必须表示对象类型，这是 MCP 规范限制所致。"
   )
   ```

2. **自动包装基础设施** 增加了复杂性：
   * `_WrappedResult` dataclass 用于包装非对象返回值
   * 自定义的 `x-fastmcp-wrap-result` 扩展允许客户端侧解包
   * SDK 和客户端都需要匹配的包裹/解包逻辑

3. **实际 bug** 已经由这些变通方案引发：
   * Issue #2455：没有 `type: object` 的 `$ref` schemas 导致服务器上的所有工具失效
   * Issue #2421：意外的 `{"result": ...}` 包装让用户感到困惑

这表明当前限制确实给生态系统带来了实质性摩擦，而 SEP-2106 将消除这些摩擦。

### OpenAPI 先例

OpenAPI 规范经历过类似的演进。OpenAPI 3.0 使用的是 JSON Schema 的“扩展子集”，带有自定义限制（例如要求使用 `nullable: true`，而不是允许将 `"null"` 作为类型）。

OpenAPI 3.1 做出了战略性决定，全面对齐 JSON Schema 2020-12，接受破坏性变更以消除这些摩擦。结果是：更好的工具兼容性，以及更少的生态混淆。

| OpenAPI 的问题         | MCP 的对应问题                    |
| ------------------- | ---------------------------- |
| `type` 必须是字符串，而不是数组 | `inputSchema` 只允许特定字段        |
| 无法使用标准的 null 处理     | schema 中不能使用 `oneOf`/`anyOf` |
| 自定义 `nullable` 关键字  | 仅限对象的 `structuredContent`    |
| 导致工具链混乱             | 导致 SDK 变通方案                  |

MCP 可以从 OpenAPI 的经验中学习，而不是在未来几年里重复同样的演进过程。

## 向后兼容性

此更改在**线格式上向后兼容**，但根据版本不匹配的方向不同，存在一些细微差别。

### 兼容性矩阵

|                 | 新客户端（SEP 后）        | 旧客户端（SEP 前）                                                               |
| --------------- | ------------------ | ------------------------------------------------------------------------- |
| **新服务器（SEP 后）** | 完全兼容。              | 仅当服务器返回对象类型的 `structuredContent` 时兼容。`structuredContent` 中的数组/原始值可能会导致失败。 |
| **旧服务器（SEP 前）** | 完全兼容。现有的仅对象模式仍然有效。 | 不变。                                                                       |

这种不对称性在于：利用数组或原始值 `structuredContent`（或 `inputSchema` 中的组合关键字）的新服务器，不能假设旧客户端会接受该响应。按照先前线格式编写的旧客户端可能会拒绝不是 JSON 对象的 `structuredContent`，或者无法验证包含 `type`/`properties`/`required` 之外关键字的 `inputSchema`。

为了与旧客户端保持互操作性，**使用数组或原始值 `structuredContent` 的服务器还必须输出一个包含序列化 JSON 的 `TextContent` 块**（这也正是工具规范中已经建议的做法）。不理解非对象 `structuredContent` 的客户端可以回退到文本内容。

### TypeScript / SDK 迁移

将 `structuredContent` 字段类型从 `{ [key: string]: unknown }` 扩展为 `unknown` 对于类型化消费者来说是一个**源码破坏性变更**，即使线格式本身没有变化。如下代码：

```typescript theme={null}
const result = await client.callTool({ name: "get_weather", arguments: { ... } });
const temp = result.structuredContent?.temperature;        // 之前可以编译通过（类型：unknown）
const city = result.structuredContent?.["city"] as string; // 之前可以编译通过
```

在变更后将不再通过类型检查，因为 TypeScript 禁止在没有缩小类型保护的情况下对 `unknown` 进行属性访问：

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

这种破坏是有意为之——只要某个工具返回的不是对象，之前的类型就是谎言——但 SDK 维护者**应当**：

* 在 SDK 发布说明中记录此次迁移。
* 在合适且便于使用的情况下，提供类型化辅助工具（例如基于某个工具 `outputSchema` 的泛型），以便消费者无需手写缩小类型保护。

### 迁移路径

* **服务器**：无需迁移即可继续按原方式工作。若要使用数组或原始值 `structuredContent`，还应同时输出序列化的 `TextContent` 作为回退。
* **客户端**：旧客户端在仅对象的服务器上仍可正常工作。要使用新的灵活性，请接受 `structuredContent` 中的任意 JSON 值，并在存在 `outputSchema` 时据此进行验证。
* **SDK**：更新生成的类型以反映新模式（`structuredContent` 使用 `unknown`，`inputSchema`/`outputSchema` 为开放式），并在发布说明中指出这一源码破坏性类型变更。

## 安全影响

JSON Schema 验证已经负责处理类型检查、值约束以及必填字段验证，且实现**必须**继续根据声明的 schema 验证所有输入和输出。允许完整的 JSON Schema 2020-12 词汇表会暴露出两个需要明确说明的方面。

### `$ref` 解引用（SSRF 与 Fetch-DoS）

JSON Schema 2020-12 允许 `$ref` 指向绝对 URI，而不仅仅是同一文档内的 JSON Pointer。一个天真的实现如果对遇到的每个 `$ref` 都通过发起 HTTP 请求来解析，就会给攻击者提供服务端请求伪造 / 抓取放大量的原语：恶意的工具定义可以迫使宿主抓取任意 URL，包括内部元数据端点或用于耗尽资源的大型负载。

为缓解这一点：

* 实现**不得**自动解引用那些解析到网络 URI 的 `$ref` 值（即任何不是同文档 JSON Pointer 的内容，例如 `#/$defs/Foo` 或内部 `$anchor`）。
* 这里的“自动”是指“作为正常验证或 schema 处理的一部分，而无需显式的操作员动作”。实现**可以**提供一个可选开启的模式，用于抓取非本地 `$ref`，但该模式**必须**默认关闭，并且**应当**强制执行主机白名单（或至少拒绝回环、链路本地和私有网络地址），应用超时和大小限制，并记录被解引用的 URI。
* 若由于未解析的外部 `$ref` 导致验证失败，**应当**拒绝该 schema，而不是悄悄将其视为宽松模式。

### 组合关键字的资源使用

组合关键字（`anyOf`、`oneOf`、`allOf`、`if`/`then`/`else`）和 `$defs` 能够表达丰富的 schema，但病态组合在验证时可能代价高昂。实现**应当**施加合理的边界——例如最大 schema 深度、子 schema 总数上限，或每次验证的时间预算——以防止恶意工具定义将验证器变成 CPU DoS 向量。

## 参考实现

### TypeScript SDK

展示放宽类型限制的参考实现：

* **分支**: [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`
* **关键变更**：
  * `inputSchema`：保留 `type: "object"`，但允许任何额外的 JSON Schema 属性（例如 `oneOf`/`anyOf` 之类的组合）
  * `outputSchema`：任意有效的 JSON Schema 对象（数组、原始值、对象、组合）
  * `structuredContent`：任意 JSON 值（对象、数组或原始值）
  * 更新了 McpServer 高级 API，以支持数组和原始值 `outputSchema`

### Everything Server 演示工具

在 `everything` 服务器中新增了三个演示工具，展示 SEP-2106 的能力：

* **分支**: [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`
* **工具**：
  * `get-weather-forecast`：直接在 `structuredContent` 中返回每小时预报的**原始数组**
    * 与 SEP-2106 的 Motivation 部分中的精确示例一致
    * `outputSchema`: `z.array(HourlyForecastSchema)` - 根层级为数组类型
    * `structuredContent`: `[{hour, temp, conditions}, ...]` - 直接数组
  * `find-by-id-or-name`：演示灵活的输入模式（接受 `id` 或 `name`）
  * `get-count`：直接在 `structuredContent` 中返回**原始数字**（不包裹在对象中）
    * `outputSchema`: `z.number()` - 根层级为原始类型
    * `structuredContent`: `42` - 直接原始值

### 相关链接

* 原始 PR: [https://github.com/modelcontextprotocol/modelcontextprotocol/pull/881](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/881)
* 相关 issue: [https://github.com/modelcontextprotocol/modelcontextprotocol/issues/834](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/834)
* `outputSchema` 类型限制不一致: [https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1906](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1906)
* TypeScript SDK schema 类型: [https://github.com/modelcontextprotocol/typescript-sdk/issues/1149](https://github.com/modelcontextprotocol/typescript-sdk/issues/1149)
* SEP-2200（澄清工具结果内容与模型可见性）: [https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2200](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2200)

### 实现指南

SDK 实现需要：

1. 更新 `inputSchema` 类型，保留 `type: "object"`，但允许任何额外的 JSON Schema 属性
2. 更新 `outputSchema` 类型，允许任何有效的 JSON Schema（移除 `type: "object"` 约束）
3. 更新 `structuredContent` 类型，使其接受任何有效的 JSON 值
4. 相应更新 JSON Schema 定义

## 致谢

本提案建立在 GitHub issue #834 的讨论基础之上，并吸收了 MCP 社区的反馈。
