Skip to main content
最终版标准轨道
字段
SEP1330
标题询问枚举模式改进与标准合规性
状态最终版
类型标准轨道
创建日期2025-08-11
作者chughtapan
赞助人
PR#1330

摘要

本 SEP 提议改进 MCP 中的枚举模式定义,弃用非标准的 enumNames 属性,转而采用符合 JSON 模式的模式,并除了单选模式外,引入对多选枚举模式的额外支持。新模式已针对 JSON 规范进行了验证。 模式变更: 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

动机

现有的枚举模式使用了一种非标准的方法来为枚举值添加标题。它还限制了枚举在询问(Elicitation)(以及未来任何应采用 EnumSchema 的模式对象)中的使用,仅限于单选模型。要求用户选择多个条目是一种常见模式。在 UI 中,这相当于使用复选框或单选按钮的区别。 出于这些原因,我们提议对 EnumSchema 进行以下非破坏性的次要改进,以改善用户和开发者体验。
  • 将现有的 EnumSchema 保留为“遗留”
    • 它使用一种非标准的方法来为枚举值添加标题
    • 将其标记为遗留,但目前仍支持它。
    • 根据 @dsp-ant 的说法,当我们有了适当的弃用策略时,我们将标记它为已弃用
  • 引入无标题枚举和有标题枚举的区别。
    • 如果枚举值已足够,则无需为每个值指定单独的标题。
    • 如果枚举值不适合显示,则可以为每个值指定标题。
  • 引入单选和多选枚举的区别。
    • 如果只能选择一个值,可以使用单选模式
    • 如果可以选择多个值,可以使用多选模式
  • ElicitResponse 中,将数组添加为 additionalProperty 类型
    • 允许将多个枚举值的选择返回给服务器

规范

1. 将带有非标准 enumNames 属性的当前 EnumSchema 标记为“遗留”

当前的 MCP 规范使用非标准的 enumNames 属性来提供枚举值的显示名称。我们提议将 enumNames 属性标记为遗留,建议使用 TitledSingleSelectEnum,这是我们下面定义的一种符合标准的枚举类型。
// 继续支持当前的 EnumSchema 作为遗留版本

/**
 * 遗留:请改用 TitledSingleSelectEnumSchema。
 * 此接口将在未来版本中移除。
 */
export interface LegacyEnumSchema {
  type: "string";
  title?: string;
  description?: string;
  enum: string[];
  enumNames?: string[]; // 枚举值的标题(非标准,遗留)
}

2. 定义单选枚举(带有有标题和无标题变体)

枚举可能需要也可能不需要标题。枚举值可能是人类可读的并且适合显示。在这种情况下,使用 JSON 模式关键字 enum 的无标题实现更简单。添加标题需要使用 consttitleenum 数组替换为对象数组。
// 不带标题的单选枚举
export type UntitledSingleSelectEnumSchema = {
  type: "string";
  title?: string;
  description?: string;
  enum: string[]; // 不带标题的普通枚举
};

// 带标题的单选枚举
export type TitledSingleSelectEnumSchema = {
  type: "string";
  title?: string;
  description?: string;
  oneOf: Array<{
    const: string; // 枚举值
    title: string; // 枚举值的显示名称
  }>;
};

// 组合单选枚举
export type SingleSelectEnumSchema =
  | UntitledSingleSelectEnumSchema
  | TitledSingleSelectEnumSchema;

3. 引入多选枚举(带有有标题和无标题变体)

虽然询问不支持任意 JSON 类型(如数组和对象),以便客户端可以轻松显示选择项,但可以轻松实现多选枚举。
// 不带标题的多选枚举
export type UntitledMultiSelectEnumSchema = {
  type: "array";
  title?: string;
  description?: string;
  minItems?: number; // 要选择的最小项目数
  maxItems?: number; // 要选择的最大项目数
  items: {
    type: "string";
    enum: string[]; // 不带标题的普通枚举
  };
};

// 带标题的多选枚举
export type TitledMultiSelectEnumSchema = {
  type: "array";
  title?: string;
  description?: string;
  minItems?: number; // 要选择的最小项目数
  maxItems?: number; // 要选择的最大项目数
  items: {
    oneOf: Array<{
      const: string; // 枚举值
      title: string; // 枚举值的显示名称
    }>;
  };
};

// 组合多选枚举
export type MultiSelectEnumSchema =
  | UntitledMultiSelectEnumSchema
  | TitledMultiSelectEnumSchema;

4. 将所有变体组合为 EnumSchema

最终的 EnumSchema 将遗留、多选和单选模式汇总为一个,定义如下:
// 组合遗留、多选和单选枚举
export type EnumSchema =
  | SingleSelectEnumSchema
  | MultiSelectEnumSchema
  | LegacyEnumSchema;

5. 扩展 ElicitResult

当前的询问结果模式仅允许返回原始类型。我们扩展此内容以包含用于 MultiSelectEnums 的字符串数组:
export interface ElicitResult extends Result {
  action: "accept" | "decline" | "cancel";
  content?: { [key: string]: string | number | boolean | string[] }; // string[] 是新增的
}

实例模式示例

不带标题的单选(无变更)

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

带标题的遗留单选

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

带标题的单选

{
  "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 模式规范保持一致。标准模式可与现有的 JSON 模式验证器一起工作
  2. 灵活性:支持普通枚举和带有显示名称的枚举,适用于单选和多选枚举。
  3. 客户端实现: 表明实现一组复选框与单个复选框的额外开销是最小的:https://github.com/evalstate/fast-agent/pull/324/files

向后兼容性

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

参考实现

模式变更: 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

安全考虑

未发现安全隐患。此变更纯粹是关于模式结构和标准合规性。

附录

验证

使用存储在 https://www.jsonschemavalidator.net/ 的 JSON Schema Validator 中的验证,我们验证:
  • 本文档中的所有示例实例模式针对下一节中提出的 JSON 元模式 EnumSchema 进行验证。
  • 针对本文档中的示例实例模式验证有效和无效的值。

遗留单选

单选

多选

JSON 元模式

这是我们提出的用于替换规范中 schema.json 内当前 EnumSchema 的方案。
{
  "$schema": "https://json-schema.org/draft-07/schema",
  "definitions": {
    // 新定义如下
    "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" }
      ]
    }
  }
}