Skip to main content
最终标准轨道

摘要

本 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 工具:
目前,这种自然的数组响应是不可能的,因为 structuredContent 必须是对象。开发者被迫将数组包装进不必要的容器对象中:
这种人为限制:
  • 为响应增加了不必要的嵌套
  • 与常见的 REST API 模式相冲突
  • 阻止了对数组响应进行直接的 schema 验证

Schema 组合用例

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

规范

1. 放宽 inputSchema

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

2. 放宽 outputSchema

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

3. 放宽 structuredContent

当前定义:
建议定义:
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. 示例

返回对象数组的工具:

响应:

使用组合 schema 的工具:

原因说明

为什么不只允许数组?

虽然我们可以简单地扩展 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. 明确的错误信息 会承认这一限制:
  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,接受破坏性变更以消除这些摩擦。结果是:更好的工具兼容性,以及更少的生态混淆。 MCP 可以从 OpenAPI 的经验中学习,而不是在未来几年里重复同样的演进过程。

向后兼容性

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

兼容性矩阵

这种不对称性在于:利用数组或原始值 structuredContent(或 inputSchema 中的组合关键字)的新服务器,不能假设旧客户端会接受该响应。按照先前线格式编写的旧客户端可能会拒绝不是 JSON 对象的 structuredContent,或者无法验证包含 type/properties/required 之外关键字的 inputSchema 为了与旧客户端保持互操作性,使用数组或原始值 structuredContent 的服务器还必须输出一个包含序列化 JSON 的 TextContent(这也正是工具规范中已经建议的做法)。不理解非对象 structuredContent 的客户端可以回退到文本内容。

TypeScript / SDK 迁移

structuredContent 字段类型从 { [key: string]: unknown } 扩展为 unknown 对于类型化消费者来说是一个源码破坏性变更,即使线格式本身没有变化。如下代码:
在变更后将不再通过类型检查,因为 TypeScript 禁止在没有缩小类型保护的情况下对 unknown 进行属性访问:
这种破坏是有意为之——只要某个工具返回的不是对象,之前的类型就是谎言——但 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/[email protected]
  • 关键变更
    • 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/[email protected]
  • 工具
    • get-weather-forecast:直接在 structuredContent 中返回每小时预报的原始数组
      • 与 SEP-2106 的动机部分中的精确示例一致
      • 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 社区的反馈。