Skip to main content
  • 状态(Status): Final
  • 类型(Type): Standards Track
  • 创建(Created): 2026-01-06
  • 作者(Author(s)): John McBride (@jpmcb) —— 原始提案;Ola Hungerford (@olaservo) —— 当前引导者,SEP-1850 转换后
  • 担保人(Sponsor): Ola Hungerford (@olaservo)
  • PR: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2106
作者身份说明: 原始提案由 John McBride (@jpmcb) 在 PR #881 中撰写,早于 SEP-1850 基于 PR 的工作流。本文件将该提案转换为当前 SEP 格式,并由 Ola Hungerford (@olaservo) 引导,她还根据评审反馈修订了向后兼容性、安全影响和 SDK 迁移章节。原始文字和设计意图仍属 John;转换以来的实质性变更在本 PR 的提交历史中跟踪。

摘要

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

动机

当前的 MCP 规范以与完整 JSON Schema 支持相冲突的方式限制了工具 schema:
  1. inputSchema 限制:当前只允许 typepropertiesrequired 字段。这妨碍了使用 anyOfoneOfallOf 等组合关键字来实现复杂的对象校验模式。
  2. outputSchema 限制:同样被限制为带 propertiesrequiredtype: "object",尽管规范声称支持”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. 这些变通办法已导致真实缺陷
    • Issue #2455:没有 type: object$ref schema 破坏了服务器上的所有工具
    • 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 的服务器还必须(MUST)发出一个包含序列化 JSON 的 TextContent(如工具规范中已建议的)。不理解非对象 structuredContent 的客户端可以回退到文本内容。

TypeScript / SDK 迁移

structuredContent 字段类型从 { [key: string]: unknown } 拓宽为 unknown,对于类型化的消费者而言是一个源码破坏性变更,即便线路格式不变。诸如以下代码:
在变更后将不再通过类型检查,因为 TypeScript 禁止在没有收窄守卫的情况下对 unknown 进行属性访问:
这一破坏是有意的——每当工具返回非对象时,先前的类型就是个谎言——但 SDK 维护者应当(SHOULD)
  • 在 SDK 发布说明中记录该迁移。
  • 在符合工效学之处,提供类型化辅助工具(例如对工具 outputSchema 的泛型),使消费者无需手写收窄守卫。

迁移路径

  • 服务器:无需迁移即可像以前一样工作。要使用数组或基本类型 structuredContent,还需发出一个序列化的 TextContent 回退。
  • 客户端:旧客户端继续针对仅对象服务器工作。要消费新的灵活性,应在 structuredContent 中接受任何 JSON 值,并在存在 outputSchema 时对照校验。
  • SDK:更新生成的类型以反映新 schema(structuredContentunknown,开放式的 inputSchema/outputSchema),并在发布说明中指出源码破坏性的类型变更。

安全影响

JSON Schema 校验已经处理类型检查、值约束和必填字段校验,实现**必须(MUST)**继续对照声明的 schema 校验所有输入和输出。允许完整的 JSON Schema 2020-12 词汇表带来两个值得明确指导的方面。

$ref 解引用(SSRF 与 Fetch-DoS)

JSON Schema 2020-12 允许 $ref 指向绝对 URI,而不仅是同一文档中的 JSON 指针。天真地对遇到的每个 $ref 发起 HTTP 请求来解析的实现,给了攻击者一个服务器端请求伪造/获取放大的原语:恶意的工具定义可以致使宿主获取任意 URL,包括内部元数据端点或旨在耗尽资源的大型载荷。 为缓解这一点:
  • 实现**不得(MUST NOT)**自动解引用解析为网络 URI 的 $ref 值(即任何不是同文档 JSON 指针,如 #/$defs/Foo 或内部 $anchor 的东西)。
  • 此处”自动”意为”作为正常校验或 schema 处理的一部分,无需显式的操作员动作”。实现**可以(MAY)提供一个获取非本地 $ref 的选择加入模式,但它必须默认禁用,并应当(SHOULD)**强制执行主机允许列表(或至少拒绝环回、链路本地和私有网络地址)、应用超时和大小限制,并记录解引用的 URI。
  • 因未解析的外部 $ref 而校验失败的 schema **应当(SHOULD)**被拒绝,而非默默地被当作宽松处理。

组合关键字的资源使用

组合关键字(anyOfoneOfallOfif/then/else)和 $defs 支持有表达力的 schema,但病态组合校验起来可能代价高昂。实现**应当(SHOULD)**应用合理的界限——例如最大 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 动机章节中的确切示例匹配
      • outputSchemaz.array(HourlyForecastSchema) —— 根为数组类型
      • structuredContent[{hour, temp, conditions}, ...] —— 直接数组
    • find-by-id-or-name:演示灵活的输入模式(接受 idname
    • get-count:直接在 structuredContent 中返回原始数字(不封装在对象中)
      • outputSchemaz.number() —— 根为基本类型
      • structuredContent42 —— 直接基本类型

相关链接

实现指南

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

致谢

本提案建立在 GitHub issue #834 的讨论之上,并纳入了来自 MCP 社区的反馈。