SEP-1330:信息征询枚举 Schema 改进与标准合规

SEP-1330:信息征询枚举 Schema 改进与标准合规

社区译文;来源: MCP 1255d50e

本 SEP 已达到 Final 状态,并作为已接受设计的历史记录保留。 定稿后对协议所做的更改不会反映在此处。权威要求请参阅当前规范 及其变更日志。
字段值
SEP1330
标题信息征询枚举 Schema 改进与标准合规
状态Final
类型标准跟踪
创建日期2025-08-11
作者chughtapan
发起人无
PR#1330

摘要

本 SEP 建议改进 MCP 中的枚举 schema 定义:弃用非标准的 enumNames 属性,改用符合 JSON Schema 标准的模式;除单选 schema 外,还增加对多选枚举 schema 的支持。新 schema 已依据 JSON 规范完成验证。

Schema 变更: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1148 TypeScript SDK 变更:https://github.com/modelcontextprotocol/typescript-sdk/pull/1077 Python SDK 变更:https://github.com/modelcontextprotocol/python-sdk/pull/1246 客户端实现: https://github.com/evalstate/fast-agent/pull/324/files 可运行演示: https://asciinema.org/a/anBvJdqEmTjw0JkKYOooQa5Ta

动机

现有枚举 schema 使用非标准方式为枚举值添加标题。它还将信息征询中枚举的使用(以及未来任何应采用 EnumSchema 的其他 schema 对象)限制为单选模式。要求用户选择多个条目是一种常见模式。在用户界面中,这相当于复选框与单选按钮之间的区别。

基于这些原因,我们建议对 EnumSchema 进行以下非破坏性的小幅改进,以改善用户和开发者体验。

  • 保留现有的 EnumSchema,并将其标记为“Legacy”
    • 它使用非标准方式为枚举值添加标题
    • 将其标记为 Legacy,但目前仍予以支持
    • 按照 @dsp-ant 的建议,在建立适当的弃用策略后,我们会将其标记为已弃用
  • 区分无标题枚举和带标题枚举
    • 如果枚举值本身已足够清晰,则不必为每个值另行指定标题
    • 如果枚举值不适合直接显示,则可为每个值指定标题
  • 区分单选枚举和多选枚举
    • 如果只能选择一个值,可以使用单选 schema
    • 如果可以选择多个值,可以使用多选 schema
  • 在 ElicitResponse 中将数组加入 additionalProperty 类型
    • 允许将多个选中的枚举值返回给服务器

规范

1. 将使用非标准 enumNames 属性的当前 EnumSchema 标记为“Legacy”

当前 MCP 规范使用非标准的 enumNames 属性为枚举值提供显示名称。我们建议将 enumNames 属性标记为 legacy,并建议使用下文定义的、符合标准的枚举类型 TitledSingleSelectEnum。

// Continue to support the current EnumSchema as Legacy

/**
 * Legacy: Use TitledSingleSelectEnumSchema instead.
 * This interface will be removed in a future version.
 */
export interface LegacyEnumSchema {
  type: "string";
  title?: string;
  description?: string;
  enum: string[];
  enumNames?: string[]; // Titles for enum values (non-standard, legacy)
}

2. 定义单选枚举(带标题和无标题两种)

枚举可能需要标题,也可能不需要。枚举值本身可能具备可读性并适合直接显示。在这种情况下,使用 JSON Schema 关键字 enum 的无标题实现更简单。若要添加标题,则需要用由 const 和 title 构成的对象数组替换 enum 数组。

// Single select enum without titles
export type UntitledSingleSelectEnumSchema = {
  type: "string";
  title?: string;
  description?: string;
  enum: string[]; // Plain enum without titles
};

// Single select enum with titles
export type TitledSingleSelectEnumSchema = {
  type: "string";
  title?: string;
  description?: string;
  oneOf: Array<{
    const: string; // Enum value
    title: string; // Display name for enum value
  }>;
};

// Combined single selection enumeration
export type SingleSelectEnumSchema =
  UntitledSingleSelectEnumSchema | TitledSingleSelectEnumSchema;

3. 引入多选枚举(带标题和无标题两种)

信息征询不支持数组和对象等任意 JSON 类型,以便客户端能够轻松显示选择选项;但多选枚举可以轻松实现。

// Multiple select enums without titles
export type UntitledMultiSelectEnumSchema = {
  type: "array";
  title?: string;
  description?: string;
  minItems?: number; // Minimum number of items to choose
  maxItems?: number; // Maximum number of items to choose
  items: {
    type: "string";
    enum: string[]; // Plain enum without titles
  };
};

// Multiple select enums with titles
export type TitledMultiSelectEnumSchema = {
  type: "array";
  title?: string;
  description?: string;
  minItems?: number; // Minimum number of items to choose
  maxItems?: number; // Maximum number of items to choose
  items: {
    oneOf: Array<{
      const: string; // Enum value
      title: string; // Display name for enum value
    }>;
  };
};

// Combined Multiple select enumeration
export type MultiSelectEnumSchema =
  UntitledMultiSelectEnumSchema | TitledMultiSelectEnumSchema;

4. 将所有类型合并为 EnumSchema

最终的 EnumSchema 将 legacy、多选和单选 schema 汇总为一个类型,其定义如下:

// Combined legacy, multiple, and single select enumeration
export type EnumSchema =
  SingleSelectEnumSchema | MultiSelectEnumSchema | LegacyEnumSchema;

5. 扩展 ElicitResult

当前的信息征询结果 schema 只允许返回基本类型。我们扩展该 schema,使其能够为 MultiSelectEnums 返回字符串数组:

export interface ElicitResult extends Result {
  action: "accept" | "decline" | "cancel";
  content?: { [key: string]: string | number | boolean | string[] }; // string[] is new
}

实例 Schema 示例

无标题单选(无变化)

{
  "type": "string",
  "title": "Color Selection",
  "description": "Choose your favorite color",
  "enum": ["Red", "Green", "Blue"],
  "default": "Green"
}

带标题的 Legacy 单选

{
  "type": "string",
  "title": "Color Selection",
  "description": "Choose your favorite color",
  "enum": ["#FF0000", "#00FF00", "#0000FF"],
  "enumNames": ["Red", "Green", "Blue"],
  "default": "#00FF00"
}

带标题单选

{
  "type": "string",
  "title": "Color Selection",
  "description": "Choose your favorite color",
  "oneOf": [
    { "const": "#FF0000", "title": "Red" },
    { "const": "#00FF00", "title": "Green" },
    { "const": "#0000FF", "title": "Blue" }
  ],
  "default": "#00FF00"
}

无标题多选

{
  "type": "array",
  "title": "Color Selection",
  "description": "Choose your favorite colors",
  "minItems": 1,
  "maxItems": 3,
  "items": {
    "type": "string",
    "enum": ["Red", "Green", "Blue"]
  },
  "default": ["Green"]
}

带标题多选

{
  "type": "array",
  "title": "Color Selection",
  "description": "Choose your favorite colors",
  "minItems": 1,
  "maxItems": 3,
  "items": {
    "anyOf": [
      { "const": "#FF0000", "title": "Red" },
      { "const": "#00FF00", "title": "Green" },
      { "const": "#0000FF", "title": "Blue" }
    ]
  },
  "default": ["Green"]
}

理由

  1. 标准合规:与正式 JSON Schema 规范保持一致。标准模式可与现有 JSON Schema 验证器配合使用
  2. 灵活性:单选和多选枚举都支持纯枚举值及带显示名称的枚举值
  3. 客户端实现: 表明实现一组复选框相较于实现单个复选框,额外开销很小:https://github.com/evalstate/fast-agent/pull/324/files

向后兼容性

LegacyEnumSchema 类型在迁移期间保持向后兼容。使用 enumNames 的现有实现将继续工作,直至实施协议范围的弃用策略并移除此 schema。

参考实现

Schema 变更: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1148 TypeScript SDK 变更:https://github.com/modelcontextprotocol/typescript-sdk/pull/1077 Python SDK 变更:https://github.com/modelcontextprotocol/python-sdk/pull/1246 客户端实现: https://github.com/evalstate/fast-agent/pull/324/files 可运行演示: https://asciinema.org/a/anBvJdqEmTjw0JkKYOooQa5Ta

安全考量

未发现安全影响。此变更只涉及 schema 结构和标准合规。

附录

验证

使用 https://www.jsonschemavalidator.net/ 中保存的验证,我们验证:

  • 本文中的所有实例 schema 示例是否符合下一节建议的 JSON meta-schema EnumSchema
  • 有效值和无效值是否符合本文中的实例 schema 示例

Legacy 单选

单选

多选

JSON meta-schema

以下是我们建议在规范的 schema.json 中替换当前 EnumSchema 的方案。

{
  "$schema": "https://json-schema.org/draft-07/schema",
  "definitions": {
    // New Definitions Follow
    "UntitledSingleSelectEnumSchema": {
      "type": "object",
      "properties": {
        "type": { "const": "string" },
        "title": { "type": "string" },
        "description": { "type": "string" },
        "enum": {
          "type": "array",
          "items": { "type": "string" },
          "minItems": 1
        }
      },
      "required": ["type", "enum"],
      "additionalProperties": false
    },

    "UntitledMultiSelectEnumSchema": {
      "type": "object",
      "properties": {
        "type": { "const": "array" },
        "title": { "type": "string" },
        "description": { "type": "string" },
        "minItems": {
          "type": "number",
          "minimum": 0
        },
        "maxItems": {
          "type": "number",
          "minimum": 0
        },
        "items": {
          "type": "object",
          "properties": {
            "type": { "const": "string" },
            "enum": {
              "type": "array",
              "items": { "type": "string" },
              "minItems": 1
            }
          },
          "required": ["type", "enum"],
          "additionalProperties": false
        }
      },
      "required": ["type", "items"],
      "additionalProperties": false
    },

    "TitledSingleSelectEnumSchema": {
      "type": "object",
      "required": ["type", "anyOf"],
      "properties": {
        "type": { "const": "string" },
        "title": { "type": "string" },
        "description": { "type": "string" },
        "anyOf": {
          "type": "array",
          "items": {
            "type": "object",
            "required": ["const", "title"],
            "properties": {
              "const": { "type": "string" },
              "title": { "type": "string" }
            },
            "additionalProperties": false
          }
        }
      },
      "additionalProperties": false
    },

    "TitledMultiSelectEnumSchema": {
      "type": "object",
      "required": ["type", "anyOf"],
      "properties": {
        "type": { "const": "array" },
        "title": { "type": "string" },
        "description": { "type": "string" },
        "anyOf": {
          "type": "array",
          "items": {
            "type": "object",
            "required": ["const", "title"],
            "properties": {
              "const": { "type": "string" },
              "title": { "type": "string" }
            },
            "additionalProperties": false
          }
        }
      },
      "additionalProperties": false
    },

    "LegacyEnumSchema": {
      "properties": {
        "type": {
          "type": "string",
          "const": "string"
        },
        "title": { "type": "string" },
        "description": { "type": "string" },
        "enum": {
          "type": "array",
          "items": { "type": "string" }
        },
        "enumNames": {
          "type": "array",
          "items": { "type": "string" }
        }
      },
      "required": ["enum", "type"],
      "type": "object"
    },

    "EnumSchema": {
      "oneOf": [
        { "$ref": "#/definitions/UntitledSingleSelectEnumSchema" },
        { "$ref": "#/definitions/UntitledMultiSelectEnumSchema" },
        { "$ref": "#/definitions/TitledSingleSelectEnumSchema" },
        { "$ref": "#/definitions/TitledMultiSelectEnumSchema" },
        { "$ref": "#/definitions/LegacyEnumSchema" }
      ]
    }
  }
}