Skip to main content

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.

草案标准轨道
FieldValue
SEP2106
Title工具 inputSchemaoutputSchema 符合 JSON Schema 2020-12
Status草案
Type标准轨道
Created2026-01-06
Author(s)John McBride (@jpmcb) — 原始提案;Ola Hungerford (@olaservo) — 当前维护人,SEP-1850 转换后
SponsorOla Hungerford (@olaservo)
PR#2106

摘要

本 SEP 提议放宽对 inputSchemaoutputSchemastructuredContent 的限制,以更好地支持 JSON Schema 2020-12。具体来说:
  • inputSchema:继续要求 type: "object"(因为工具参数是对象),但允许任何额外的 JSON Schema 属性,以支持强大的验证组合(anyOfoneOfallOf 等)
  • outputSchema:由于 MCP 服务器可能返回任何有效 JSON,因此全面支持 JSON Schema 2020-12
  • structuredContent:接受由 outputSchema 验证的任何 JSON 值
该提案使 MCP 服务器能够利用 JSON Schema 2020-12 的表达能力,同时保持与现有实现的向后兼容性。

动机

当前的 MCP 规范以几种方式限制了工具 schema,与完整的 JSON Schema 支持相冲突:
  1. inputSchema 限制:当前只允许 typepropertiesrequired 字段。这使得无法使用 anyOfoneOfallOf 等组合关键字来实现复杂的对象验证模式。
  2. outputSchema 限制:同样限制为仅能使用 type: "object"propertiesrequired,尽管规范声称支持 “JSON Schema”。
  3. structuredContent 限制:定义为 { [key: string]: unknown }(一个以字符串为键的对象),这阻止了返回数组——而数组是常见的 API 响应模式。

现实世界的影响

设想一个返回每小时预报的天气 API 工具:
[
  { "hour": "09:00", "temp": 68, "conditions": "sunny" },
  { "hour": "10:00", "temp": 72, "conditions": "partly cloudy" },
  { "hour": "11:00", "temp": 75, "conditions": "cloudy" }
]
目前,这种自然的数组响应是不可能的,因为 structuredContent 必须是对象。开发者被迫将数组包装进不必要的容器对象中:
{
  "forecasts": [
    { "hour": "09:00", "temp": 68, "conditions": "sunny" },
    ...
  ]
}
这种人为限制:
  • 为响应增加了不必要的嵌套
  • 与常见的 REST API 模式相冲突
  • 阻止了对数组响应进行直接的 schema 验证

Schema 组合用例

当前对 inputSchema 的限制阻止了合法的 schema 模式。借助此 SEP,工具可以在 type: "object" 之外使用组合关键字:
{
  "type": "object",
  "oneOf": [
    { "properties": { "id": { "type": "string" } }, "required": ["id"] },
    { "properties": { "name": { "type": "string" } }, "required": ["name"] }
  ]
}
这种模式允许工具接受基于 ID 或基于名称的查询——这是一种常见的 API 设计,而目前由于 schema 只允许 typepropertiesrequired 字段,因此无法支持。

规范

1. 放宽 inputSchema

当前定义:
inputSchema: {
  type: "object";
  properties?: { [key: string]: object };
  required?: string[];
};
建议定义:
inputSchema: {
  $schema?: string;
  type: "object";
  [key: string]: unknown;
};
inputSchema 字段保留 type: "object" 的要求(因为工具参数始终是对象),但现在接受任何额外的 JSON Schema 属性。这使得以下内容成为可能:
  • 组合关键字:anyOfoneOfallOfnot
  • 条件 schema:if/then/else
  • 引用 schema:$ref$defs
  • 任何其他有效的 JSON Schema 2020-12 关键字

2. 放宽 outputSchema

当前定义:
outputSchema?: {
  type: "object";
  properties?: { [key: string]: object };
  required?: string[];
};
建议定义:
outputSchema?: {
  $schema?: string;
  [key: string]: unknown;
};
outputSchema 字段接受任何有效的 JSON Schema 2020-12 对象,从而支持验证数组、原始值或复杂组合的 schema。与 inputSchema 不同,这里没有 type: "object" 的要求,因为工具输出可以是任何有效 JSON。

3. 放宽 structuredContent

当前定义:
structuredContent?: { [key: string]: unknown };
建议定义:
structuredContent?: unknown;
structuredContent 字段接受任何符合工具 outputSchema 的有效 JSON 值。这包括:
  • 对象:{ "key": "value" }
  • 数组:[1, 2, 3][{ "id": "abc" }, { "id": "xyz" }]
  • 原始值:"string"42truenull

4. 文档更新

更新 docs/specification/draft/server/tools.mdx
  • 删除 structuredContent “作为 JSON 对象返回”的表述
  • 说明 structuredContent 可以是任何符合 outputSchema 的 JSON 值
  • 添加展示数组响应的示例

5. 示例

返回对象数组的工具:

{
  "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"]
    }
  }
}
响应:
{
  "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 的工具:

{
  "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. 明确的错误信息 会承认这一限制:
    raise ValueError(
        f"Output schemas must represent object types due to MCP spec limitations."
    )
    
  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 对于类型化消费者来说是一个源码破坏性变更,即使线格式本身没有变化。如下代码:
const result = await client.callTool({ name: "get_weather", arguments: { ... } });
const temp = result.structuredContent?.temperature;        // 之前可以编译通过(类型:unknown)
const city = result.structuredContent?.["city"] as string; // 之前可以编译通过
在变更后将不再通过类型检查,因为 TypeScript 禁止在没有缩小类型保护的情况下对 unknown 进行属性访问:
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 使用 unknowninputSchema/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,而不是悄悄将其视为宽松模式。

组合关键字的资源使用

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

参考实现

TypeScript SDK

展示放宽类型限制的参考实现:
  • 分支: olaservo/typescript-sdk@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
  • 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:演示灵活的输入模式(接受 idname
    • get-count:直接在 structuredContent 中返回原始数字(不包裹在对象中)
      • outputSchema: z.number() - 根层级为原始类型
      • structuredContent: 42 - 直接原始值

相关链接

实现指南

SDK 实现需要:
  1. 更新 inputSchema 类型,保留 type: "object",但允许任何额外的 JSON Schema 属性
  2. 更新 outputSchema 类型,允许任何有效的 JSON Schema(移除 type: "object" 约束)
  3. 更新 structuredContent 类型,使其接受任何有效的 JSON 值
  4. 相应更新 JSON Schema 定义

致谢

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