SEP-2640:Skills 扩展

SEP-2640:Skills 扩展

社区译文;来源: MCP 1155ca46

本 SEP 已达到 Final 状态,并作为已接受设计的历史记录保留。 最终确定后对协议所做的更改不会反映在此处。权威要求请参阅当前 规范及其变更日志。
字段值
SEP2640
标题Skills Extension
状态Final
类型Extensions Track
创建日期2026-04-23
作者Peter Alexander (@pja-ant)、Ola Hungerford (@olaservo)、Sambhav Kothari (@sambhav)、Aditya Kumar (@aditya-scio),代表 Skills Over MCP 工作组
发起人@pja-ant
PR#2640

摘要

本 SEP 定义了一项约定:使用现有 Resources 原语通过 MCP 提供 Agent Skills。一个 Skill 是一组文件构成的目录(至少包含 SKILL.md),为 Agent 提供结构化工作流指令。本扩展规定,Skill 目录中的每个文件都公开为 MCP 资源,通常使用 skill:// URI 方案。Skills 通过 URI 寻址并可直接读取;skills/list 方法列举服务器提供的 Skills(Skill 目录规模很大、由系统生成或因其他原因无法完整列举的服务器可以(MAY)返回空列表或部分列表),skills/get 方法则按 URI 返回任意单个 Skill 的条目。Skill 格式本身(目录结构、YAML frontmatter、命名规则,以及控制宿主如何分阶段将内容载入上下文的渐进式披露模型)完全交由 Agent Skills 规范定义;本 SEP 只定义传输绑定。

本扩展定义三个协议方法。每个声明该扩展的服务器都实现 skills/list 和 skills/get:前者列举服务器提供的 Skills,后者按 URI 返回单个 Skill 的条目,包括列表中没有出现的 Skills。可选的 resources/directory/read 列出目录资源的直接子项,使 Agent 能在限定范围内浏览 Skill 的支持文件。其他行为都建立在现有协议表面之上,因此,已经把 MCP 资源视为虚拟文件系统的宿主可以像使用本地文件系统 Skills 一样使用 MCP 提供的 Skills。本规范还附有宿主资源读取工具和 SDK 便捷封装的实现指南。

动机

宿主应用对 Skills 的原生支持表明,市场非常需要内容丰富、渐进式披露的工作流指令。MCP 当前没有约定俗成的方式,将这些内容与其所描述的工具一起交付,因此产生了以下问题:

  • 分散式分发。 服务器与教 Agent 如何使用该服务器的 Skill 分别进行版本管理、发现和安装。用户从注册表安装服务器时,无法知道是否存在配套 Skill。(问题说明)
  • 指令大小限制。 服务器指令通过 server/discover 结果的 instructions 字段传递,实际容量有限。复杂工作流(例如 875 行的 mcpGraph skill)无法适配这种模型。(实验结果)
  • 不一致的临时方案。 在没有统一约定的情况下,多个独立实现各自发明了自己的 skill:// URI 结构,对 authority、路径和子资源寻址采用了不同语义。

规范

依赖

除 MCP 基础 Resources 原语外,本扩展没有其他依赖。在 2026-07-28 及之后的协议版本中,skills/list 结果还带有基础协议的列表缓存属性(SEP-2549)。

Skill 格式

通过 MCP 提供的 Skill 必须(MUST)符合 Agent Skills 规范。具体而言:

  • Skill 是一个目录。其 Skill 名称 是 SKILL.md frontmatter 中 name 字段的值。
  • 每个 Skill 的根目录都必须(MUST)包含一个 SKILL.md 文件。
  • SKILL.md 必须(MUST)以 YAML frontmatter 开头,其中至少包含 Agent Skills 规范定义的 name 和 description 字段。
  • Skill 可以(MAY)包含其他文件和子目录(参考资料、脚本、示例、资源文件)。

本扩展不重新定义、约束或扩展 Skill 格式。Agent Skills 规范的未来修订会自动适用。如果 Agent Skills 规范发生不向后兼容的变更,客户端必须(MUST)遵守 Agent Skills 规范提供的任何向后兼容机制,并且应该(SHOULD)继续支持不兼容变更前的 Agent Skills 规范。

资源映射

Skill 目录中的每个文件都公开为 MCP 资源。服务器应该(SHOULD)使用 skill:// URI 方案,其资源 URI 形式为:

skill://<skill-path>/<file-path>

其中:

  • <skill-path> 是一个或多个由 / 分隔的路径片段,用于在服务器的 Skill 命名空间中定位 Skill 目录。它可以(MAY)是单个片段(git-workflow),也可以嵌套到任意深度(acme/billing/refunds)。
  • <file-path> 是文件相对于 Skill 根目录的路径,使用 / 作为分隔符。

因此,Skill 必需的 SKILL.md 资源始终可通过 skill://<skill-path>/SKILL.md 寻址,而 Skill 根目录为 skill://<skill-path>(移除 /SKILL.md 后缀且不带尾部斜杠),与目录列举一致。

<skill-path> 的最后一个片段必须(MUST)等于 Skill 的 SKILL.md frontmatter 中声明的 name。这与 Agent Skills 规范要求 name 匹配父目录名称一致。前面的片段(如果存在)是服务器选择的组织前缀。服务器可以(MAY)按领域、团队、版本或任何其他维度分层组织 Skills。在 skill://acme/billing/refunds/SKILL.md 中,前缀为 acme/billing,Skill 的 name 为 refunds;在 skill://git-workflow/SKILL.md 中没有前缀,name 为 git-workflow。这意味着无需读取 frontmatter,仅从 URI 就能还原 Skill 名称。

其他约束如下:

  • SKILL.md 可以(MAY)出现在 Skill 的后代目录中,因此 Skills 可以嵌套。参见嵌套 Skills。
  • <skill-path> 的最后一个片段就是 Skill name,必须(MUST)满足 Agent Skills 规范的命名规则。第一个 <skill-path> 片段占用 authority 组件,并且应该(SHOULD)是 RFC 3986 定义的有效 reg-name;其他前缀片段应该(SHOULD)是有效 URI 路径片段;除此之外不施加其他约束。

根据 RFC 3986,<skill-path> 的第一个片段占用 authority 组件。在本约定中,它没有特殊语义,客户端不得(MUST NOT)尝试对其执行 DNS 或网络解析。

服务器可以(MAY)使用其领域原生的其他方案提供 Skills(例如 github://owner/repo/skills/refunds/SKILL.md)。任何方案都不享有特殊地位:无论采用何种方案,上述结构约束(<skill-path> 以 Skill 名称结尾、URI 中显式包含 SKILL.md)都适用,skills/list 也会列举服务器采用任意方案提供的 Skills。

Skill 身份不取决于 URI 方案。宿主通过两种方式之一得知资源是 Skill:来自 skills/list 条目,这是服务器所发布 Skills 的权威记录;或者来自显式引用(服务器的 instructions 字段、另一个 Skill 或用户),再由 skills/get 确认,服务器对其提供的 Skill 作出响应,否则返回错误。这对包括 skill:// 在内的每种方案都成立。宿主不得(MUST NOT)仅因资源 URI 使用某个特定方案,就断定该资源是 Skill。

示例

Skill 路径文件资源 URI
git-workflowSKILL.mdskill://git-workflow/SKILL.md
pdf-processingreferences/FORMS.mdskill://pdf-processing/references/FORMS.md
pdf-processingscripts/extract.pyskill://pdf-processing/scripts/extract.py
acme/billing/refundsSKILL.mdskill://acme/billing/refunds/SKILL.md
acme/billing/refundsexamples/email.mdskill://acme/billing/refunds/examples/email.md

资源元数据

对于每个 skill://<skill-path>/SKILL.md 资源:

  • mimeType 应该(SHOULD)为 text/markdown。
  • name 应该(SHOULD)取自 SKILL.md YAML frontmatter 的 name 字段。根据上述路径约束,它将等于 <skill-path> 的最后一个片段。
  • description 应该(SHOULD)取自 SKILL.md YAML frontmatter 的 description 字段。

服务器可以(MAY)通过资源的 _meta 对象公开其他 frontmatter 字段。Skill 资源使用 _meta 键时,实现应该(SHOULD)使用反向域名前缀 io.modelcontextprotocol.skills/。Skill 中的其他文件根据其内容使用适当的 mimeType。

嵌套 Skills

Skill 目录可以(MAY)在后代目录中包含其他 Skills。嵌套 Skill 受制于与其他 Skill 相同的规则(其目录名就是 name,外层 Skill 的路径成为其组织前缀的一部分),并具有以下语义:

  • 嵌套内容是支持内容。 从外层 Skill 的角度看,嵌套 Skill 的目录和文件是普通支持文件,对它们的读取也是普通读取。以这种方式读取的嵌套 SKILL.md 是普通 Markdown:宿主不得(MUST NOT)执行其 frontmatter。
  • 激活需要新的同意。 审批按 Skill 进行:批准一个 Skill 只批准该 Skill,不代表批准其中嵌套的 Skills。激活嵌套 Skill(将其作为独立 Skill 载入,无论通过宿主的 Skill 加载机制还是让其 frontmatter 生效)都需要用户重新给予明确同意;对外层 Skill 的批准不能替代该同意。激活后,嵌套 Skill 就是普通 Skill:其 frontmatter 按照与其他 MCP 提供的 Skill 相同的规则生效,包括 allowed-tools 的审批门禁。
  • 发布是扁平的。 嵌套 Skill 与其他 Skill 一样发布:通过它自己的 skills/list 条目或显式引用。列表保持扁平:嵌套 Skill 的条目是普通条目,其 uri 恰好与外层 Skill 共享路径前缀,列表中没有任何标记表示嵌套关系。

发现

服务器不必使其 Skills 可列举。无论 Skill 是否出现在任何列表中,其 URI 都可以通过 resources/read 直接读取,并且宿主必须(MUST)支持仅凭 URI 加载 Skill(参见宿主:端到端集成)。这是基线:如果模型拥有 URI,无论它来自服务器指令、另一个 Skill 还是用户,模型都能读取该 Skill。

在此基线之上定义了三种机制。其中两种用于发现:声明本扩展的每个服务器都实现通过 skills/list 进行列举;服务器指令还可以提供可选指针。第三种用于获取:无论宿主如何得到 Skill URI,skills/get 都会返回该 Skill 的条目(其元数据和摘要),包括任何列表都未提及的 Skills。

通过 skills/list 列举

声明 io.modelcontextprotocol/skills 扩展的服务器必须(MUST)实现 skills/list 方法,该方法返回服务器所提供的 Skills。结果可以(MAY)为空。

请求带有可选的分页游标:

{
  "jsonrpc": "2.0",
  "id": 4,
  "method": "skills/list",
  "params": {}
}

结果包含 Skill 条目:

{
  "jsonrpc": "2.0",
  "id": 4,
  "result": {
    "resultType": "complete",
    "skills": [
      {
        "uri": "skill://git-workflow/SKILL.md",
        "frontmatter": {
          "name": "git-workflow",
          "description": "Follow this team's Git conventions for branching and commits"
        },
        "resources": [
          {
            "uri": "skill://git-workflow/SKILL.md",
            "digest": "sha256:a1b2c3d4...",
            "size": 2314
          }
        ]
      },
      {
        "uri": "skill://acme/billing/refunds/SKILL.md",
        "frontmatter": {
          "name": "refunds",
          "description": "Process customer refund requests per company policy",
          "license": "Apache-2.0"
        },
        "resources": [
          {
            "uri": "skill://acme/billing/refunds/SKILL.md",
            "digest": "sha256:b2c3d4e5...",
            "size": 3871
          },
          {
            "uri": "skill://acme/billing/refunds/examples/email.md",
            "digest": "sha256:c3d4e5f6...",
            "size": 962
          }
        ]
      },
      {
        "uri": "skill://pdf-processing/SKILL.md",
        "frontmatter": {
          "name": "pdf-processing",
          "description": "Extract, fill, and assemble PDF documents",
          "metadata": { "version": "2.1.0" }
        },
        "resources": [
          {
            "uri": "skill://pdf-processing/SKILL.md",
            "digest": "sha256:d5e6f7a8...",
            "size": 5120
          },
          {
            "uri": "skill://pdf-processing/references/FORMS.md",
            "digest": "sha256:e6f7a8b9...",
            "size": 18433
          },
          {
            "uri": "skill://pdf-processing/scripts/extract.py",
            "digest": "sha256:f7a8b9c0...",
            "size": 4096
          },
          {
            "uri": "skill://pdf-processing/templates/invoice.md",
            "digest": "sha256:a8b9c0d1...",
            "size": 1210
          },
          {
            "uri": "skill://pdf-processing/templates/purchase-order.md",
            "digest": "sha256:b9c0d1e2...",
            "size": 1388
          },
          {
            "uri": "skill://pdf-processing/templates/regional/eu-invoice.md",
            "digest": "sha256:c0d1e2f3...",
            "size": 1472
          }
        ]
      }
    ]
  }
}

结果字段:

字段必需说明
skills是Skill 条目数组。
skills[].frontmatter是Skill 的 SKILL.md YAML frontmatter 原样副本,以 JSON 呈现。参见 Frontmatter。
skills[].uri是Skill 的 SKILL.md 资源 URI。参见 Skill URI。
skills[].resources是Skill 的文件:列举文件及其摘要和大小的数组,或字符串 "dynamic"。参见资源。
skills[].resources[].uri是文件的资源 URI。
skills[].resources[].digest是文件的 SHA-256 摘要。参见完整性与验证。
skills[].resources[].size是文件原始内容的字节长度。参见限制。

内容动态生成的 Skill 使用 "resources": "dynamic" 代替数组。完全没有 resources 的条目无效。

分页方式与基础协议的列表方法一致:请求接受可选 cursor;结果包含 nextCursor 时,客户端将其传回以获取下一页。条目不可拆分:一个 Skill 的 resources 集合绝不会跨页拆分。

在 2026-07-28 及之后的协议版本中,结果还带有基础协议为 tools/list 和 resources/list 定义的列表缓存属性 ttlMs 和 cacheScope(SEP-2549),语义相同:它们是列表的新鲜度提示和缓存范围标记,而不是完整性属性。无论缓存的列表有多新,内容都由完整性与验证约束。

Skill 目录规模很大、按需生成或因其他原因无法列举的服务器可以(MAY)返回空列表或部分列表。宿主不得(MUST NOT)把空列表或部分列表视为服务器没有 Skills 的证明。该方法会为服务器采用任意 URI 方案提供的 Skills 返回条目。列举在不同方案之间保持一致。

名称

Skill 的 name 是标签,不是标识符。在一台服务器内,Skill 由其 uri 标识;跨服务器时,则由服务器身份与 uri 组成的二元组标识(Skill URI)。同一服务器列表中的名称应该(SHOULD)唯一,但不保证唯一:不同路径上的两个 Skills 可能共享最后一个片段(acme/billing/refunds 和 acme/support/refunds 的名称都是 refunds),嵌套 Skill 也可能与顶层 Skill 同名。宿主不得(MUST NOT)假定名称唯一。同一列表中的两个条目发生 name 冲突时,宿主必须(MUST)区分它们,例如使用有区别的路径片段,而不能静默丢弃或优先选择其中一个。来自不同来源的 Skills 发生 name 冲突时,宿主必须(MUST)在各来源独立的命名空间中解析名称,并使用宿主分配的标签识别服务器;MCP 提供的 Skill 不得(MUST NOT)静默遮蔽来自任何其他来源的同名 Skill,也不得被静默替换为后者,无论该来源是另一台服务器还是宿主自己的文件系统 Skills。参见安全影响。

Frontmatter

frontmatter 是 Skill 的 SKILL.md YAML frontmatter 原样呈现的 JSON 对象。它包含作者写入的每个字段,而不是经过筛选的子集。由于 Agent Skills 规范要求 name 和 description,这两个字段始终存在;其他所有内容(license、metadata、Agent Skills 规范未来修订新增的字段)均原样传递。因此,宿主仅凭列表即可建立 Skill 注册表(名称、描述及它能理解的任何其他元数据),无需获取每个 SKILL.md。

frontmatter 对象的内容必须(MUST)与其描述的 SKILL.md 的 frontmatter 完全相同。根据资源映射,条目 uri 的最后一个 <skill-path> 片段必须(MUST)等于 frontmatter.name。

在 frontmatter 的 metadata 对象中,以 io.modelcontextprotocol/ 开头的键保留给 MCP 扩展定义的元数据。本扩展目前未定义此类键。实现应该(SHOULD)忽略该前缀下无法识别的键。

Skill URI

uri 是 Skill 的 SKILL.md 完整资源 URI,可通过 resources/read 读取。根据资源映射,支持文件可分别通过同一 Skill 路径下的同级资源寻址。Skill 始终作为可分别寻址的资源获取;本扩展没有定义打包或捆绑式获取方式。参见附录:延期功能。

Skill URI 的作用域限定于提供它的服务器。两台已连接服务器完全可能同时提供 skill://refunds/SKILL.md,而它们是两个互不相关的 Skills。因此,MCP 提供的 Skill 身份是宿主对来源服务器的标识与 Skill uri 组成的二元组。凡是记录或寻址 Skill 的地方,包括注册表、持久化审批、缓存,以及模型访问 Skill 的任何工具或路径,宿主都必须(MUST)保留这两部分,并且不得(MUST NOT)只用 uri 作为其中任何一项的键。具体来说,宿主将 Skill 内容实体化到的任何路径,无论是缓存目录还是虚拟挂载点,都必须(MUST)同时编码服务器身份和 uri,使不同服务器的同 URI Skills 落到不同路径,并能从路径还原来源服务器;这也使宿主能够履行安全影响中的持久来源要求。

资源

每个 Skill 条目都**必须(REQUIRED)**包含 resources,其形式只能是以下两种之一:列举 Skill 文件(SKILL.md 和每个支持文件)的数组,每项为 {uri, digest, size} 三元组;或者字符串 "dynamic"。该数组是宿主验证内容和用户审批绑定的单位:

  • 如果提供 resources 数组,它必须(MUST)完整:列出 Skill 的每个文件且每个文件恰好出现一次,包括与 Skill 顶层 uri 匹配的条目。该条目包含 SKILL.md 本身的摘要和大小。
  • 每个 uri 必须(MUST)是 Skill 的 SKILL.md,或 Skill 目录内的文件。
  • 每个条目都必须(MUST)包含 size:文件原始内容的字节长度(与 digest 覆盖相同的字节)。size 让宿主无需获取任何内容就能为 Skill 分配预算:它可以仅根据条目执行限制,判断文件是否值得获取,并在计算摘要前检测截断或填充的读取结果。读取内容的字节长度与条目的 size 不同时,无论宿主之后是否继续计算摘要,都属于与摘要不匹配等价的验证失败(完整性与验证)。
  • 完整性要求涵盖嵌套 Skills:从外层 Skill 的角度看,它们的文件是支持文件(嵌套 Skills),因此外层 Skill 的 resources 也要列出这些文件,同一个文件可能同时出现在外层和嵌套 Skill 的条目中。嵌套 Skill 的变更因此也是外层 Skill 集合的变更。
  • 当 Skill 内容动态生成、无法发布稳定摘要时,服务器必须(MUST)设置 "resources": "dynamic",而不能使用数组。该标记是显式的,使宿主能够区分有意不可验证的 Skill 与格式错误的条目:完全没有 resources,或其值既不是数组也不是 "dynamic" 的条目无效,宿主不得(MUST NOT)加载。resources 为 "dynamic" 的 Skill 不提供内容完整性,也无法绑定内容(安全影响)。宿主可以(MAY)拒绝加载此类 Skills,服务器作者也应该(SHOULD)预期部分宿主会这样做。

完整性与验证

摘要是制品原始字节的 SHA-256 哈希,格式为 sha256:{hex},其中 {hex} 是 64 个小写十六进制字符。Skill 的 resources 中每个条目都带有其 uri 所指文件的摘要。

宿主获取 Skill resources 中列出的文件时,必须(MUST)用对应条目的摘要验证内容。摘要不匹配表示内容与列表承诺的不一致。内容可能已损坏、被篡改,也可能只是因为列表获取后 Skill 已更新而过时。无论原因如何,宿主都不得(MUST NOT)使用未经验证的内容;若要从过时状态恢复,应为该 Skill 调用 skills/get(或调用 skills/list 刷新目录),再根据当前 resources 集合继续。由于集合已经不同,任何内容绑定审批都将撤销(安全影响)。宿主从把 Skill 的 SKILL.md 载入模型上下文起,就处于 使用该 Skill 行动 的状态,最早要到该 SKILL.md 离开上下文时才能结束;宿主可以(MAY)延长这一窗口,但不能缩短。宿主在整个窗口内持有加载该 Skill 时所依据的条目。由于 resources 是完整的,未列出的文件就是对 Skill 的变更:在使用 Skill 行动期间,宿主必须(MUST)只把 Skill 文件读取解析为该条目 resources 中列出的 URI,并且必须(MUST)将读取 Skill 内未列出文件视为与摘要不匹配等价的验证失败。宿主不得(MUST NOT)在需要前获取 Skill 文件,无论是在连接、列举还是审批时。加载 Skill 时才获取 SKILL.md,读取支持文件时才获取该文件。服务器可能发布许多 Skills,每个 Skill 又有许多文件;若每个连接的宿主都获取全部内容,服务器负载将与目录规模而非实际使用量成正比。宿主应该(SHOULD)改为缓存实际获取的内容,摘要使缓存验证成本很低:摘要与当前条目一致的缓存文件无需再次获取;不一致的必须重新获取。缓存副本的可信程度,取决于宿主能否确认其字节自验证后未发生变化;磁盘缓存要求见安全影响。延迟获取与内容绑定审批兼容,因为审批绑定的是条目的 resources 集合而非已获取字节;审批很久后才获取的文件,会在读取时根据该集合验证。

摘要未签名,并由提供内容的同一服务器提供。匹配只能证明两者一致,不能证明任何一方可信。路径上的任何中间方(例如网关)都能同时重写列表和内容。宿主不得(MUST NOT)把摘要匹配视为安全边界。

宿主持有来自 skills/list 或 skills/get 的条目并获取相应 SKILL.md 后(当条目 resources 是数组时已通过摘要验证,是 "dynamic" 时则不可验证),必须(MUST)解析其 YAML frontmatter,并逐字段与条目的 frontmatter 比较。任何差异都必须(MUST)视为与摘要不匹配等价的验证失败,并且不得(MUST NOT)加载该 Skill。这在宿主侧强制执行了 Frontmatter 身份要求,确保用户根据列表批准的内容正是模型实际收到的内容。

限制

本扩展固定了两项每 Skill 限制,使服务器知道每个合规宿主都会接受什么,宿主也知道自己必须准备处理什么:

限制值统计范围
每个 Skill 的资源数512 个条目Skill 的 resources 条目,包括 SKILL.md
每个 Skill 文件总量16 MiB(16,777,216 字节)Skill 的 resources 中所有 size 之和

宿主必须(MUST)支持达到并包括这些上限的 Skills,也可以(MAY)支持更大的 Skills。服务器不应该(SHOULD NOT)提供超出任一限制的 Skill;任何合规宿主都不保证能够加载这样的 Skill。由于 resources 是完整的,宿主只需计算条目数量并累加 size,无需获取任何文件即可根据条目检查两项限制;基于此拒绝 Skill 的宿主应该(SHOULD)向用户说明原因,而不是在后续读取时静默失败。

对于 resources 为 "dynamic" 的 Skill,条目没有可统计的内容。选择加载此类 Skill 的宿主将总量限制应用到实际获取的内容,并且可以(MAY)在达到限制时停止加载 Skill。

这些限制约束宿主面对单个 Skill 时的暴露面。它们没有规定服务器可以提供多少 Skills,或宿主必须接受多少 Skills;列表可以任意大,这也是宿主只按需获取文件的原因之一(完整性与验证)。

通过 skills/get 获取

声明 io.modelcontextprotocol/skills 扩展的服务器还必须(MUST)实现 skills/get 方法,该方法返回 URI 指定的单个 Skill 条目:

{
  "jsonrpc": "2.0",
  "id": 5,
  "method": "skills/get",
  "params": {
    "uri": "skill://pdf-processing/SKILL.md"
  }
}
{
  "jsonrpc": "2.0",
  "id": 5,
  "result": {
    "resultType": "complete",
    "skill": {
      "uri": "skill://pdf-processing/SKILL.md",
      "frontmatter": {
        "name": "pdf-processing",
        "description": "Extract, fill, and assemble PDF documents",
        "metadata": { "version": "2.1.0" }
      },
      "resources": [
        {
          "uri": "skill://pdf-processing/SKILL.md",
          "digest": "sha256:d5e6f7a8...",
          "size": 5120
        },
        {
          "uri": "skill://pdf-processing/references/FORMS.md",
          "digest": "sha256:e6f7a8b9...",
          "size": 18433
        },
        {
          "uri": "skill://pdf-processing/scripts/extract.py",
          "digest": "sha256:f7a8b9c0...",
          "size": 4096
        },
        {
          "uri": "skill://pdf-processing/templates/invoice.md",
          "digest": "sha256:a8b9c0d1...",
          "size": 1210
        },
        {
          "uri": "skill://pdf-processing/templates/purchase-order.md",
          "digest": "sha256:b9c0d1e2...",
          "size": 1388
        },
        {
          "uri": "skill://pdf-processing/templates/regional/eu-invoice.md",
          "digest": "sha256:c0d1e2f3...",
          "size": 1472
        }
      ]
    }
  }
}

params.uri 必须(MUST)是 Skill 的 SKILL.md URI。skill 对象是 Skill 条目,其形状和含义与 skills/list 的条目相同,包含受同样规则约束的 uri、frontmatter 和 resources 字段。

语义如下:

  • 如果 URI 未标识服务器提供的 Skill,服务器必须(MUST)返回错误 -32602(Invalid params),与 resources/read 对未知资源使用的代码相同。
  • 服务器必须(MUST)响应其提供的每个 Skill,无论该 Skill 是否出现在 skills/list 结果中。部分列表中缺失的 Skill 仍可按 URI 获取。
  • 结果是一个时点快照,与列表条目完全一样。再次调用该方法,可以刷新单个 Skill 的摘要,而无需重新列举目录。
  • 内容动态生成的 Skill 无论通过 skills/list 还是 skills/get 到达,都根据资源使用 "resources": "dynamic"。
  • 结果不带分页游标:单个条目不是列表。条目是服务器在该时刻所持 Skill 的快照;结果是否还应像 resources/read 结果一样带有基础协议缓存属性(ttlMs 和 cacheScope,见 SEP-2549),仍是开放问题。

该方法补充了基线:仅有 URI 就足以读取 Skill,而 skills/get 将同一 URI 转换为 Skill 的元数据和摘要,因此从未出现在列表中的 Skill 仍可验证并绑定内容(安全影响)。

来自服务器指令的指针

服务器可以(MAY)在其 instructions 字段中把 Agent 引向特定 Skill URI。这不需要宿主提供发现机制;URI 只是出现在模型上下文中,并可通过 resources/read 读取。

能力声明

根据 SEP-2133 的扩展协商机制,服务器在其 capabilities 的 extensions 字段中声明支持本扩展:

{
  "capabilities": {
    "extensions": {
      "io.modelcontextprotocol/skills": {
        "directoryRead": true
      }
    }
  }
}

定义了一个扩展专用设置:

设置类型默认值含义
directoryReadbooleanfalse服务器实现 resources/directory/read。

空对象表示支持扩展,但不支持任何可选功能。声明扩展本身即承诺服务器实现 skills/list 和 skills/get;客户端不得(MUST NOT)对没有声明 directoryRead: true 的服务器调用 resources/directory/read。声明本扩展的服务器还必须(MUST)声明 resources 能力。

读取

Skill 文件通过标准 resources/read 方法读取。本扩展没有定义 Skill 专用读取语义。

具体来说,通过 resources/read 读取 SKILL.md 本身不会激活 Skill。resources/read 是传输机制:无论请求方是谁(通用资源读取工具、资源浏览器或正在检查服务器的用户),它都只返回字节。只有宿主自己的 Skill 加载路径才会激活 Skill,该路径会根据 Skill 条目验证内容(完整性与验证)、应用所需的用户审批(安全影响),并开启宿主使用该 Skill 行动的窗口。通过其他任何路径到达的 SKILL.md resources/read 都不得(MUST NOT)被宿主视为加载:它不授予批准、不打开窗口,也不赋予 Skill 支持文件任何特殊地位。以这种方式取得的内容是普通资源内容,宿主将其返回给模型时,应该(SHOULD)像处理其他资源读取一样处理,而不是将其作为已加载 Skill。希望此类读取能加载 Skill 的宿主,应改为让它经过 Skill 加载路径。

Skill 内部引用(例如 SKILL.md 链接到 references/GUIDE.md)是相对路径,与 Agent Skills 规范的文件系统形式相同。客户端相对于 Skill 根目录解析相对引用,因此 skill://acme/billing/refunds/SKILL.md 中的 references/GUIDE.md 会解析为 skill://acme/billing/refunds/references/GUIDE.md,与文件系统路径的解析方式完全相同。Skill 根目录是包含 SKILL.md 的目录,而不是方案根目录。Skills 嵌套时,每个 SKILL.md 的引用都相对于它自己的目录解析:无论通过何种方式到达嵌套 Skill 的 SKILL.md,其中的相对引用都相对于嵌套 Skill 根目录解析。

目录列举

Skill 指令经常引用目录而非文件,例如“从 templates/ 中选择适当模板”“运行 scripts/ 中匹配的脚本”。为执行这些指令,Agent 必须知道目录包含什么。resources/list 无法回答这种限定范围的问题:它列举服务器的整个资源空间,而本 SEP 最希望支持的服务器,即目录规模很大、由系统生成或无法列举的服务器(参见为何列表可以为空或不完整?),可能根本无法提供有意义的全局列表。

因此,本扩展定义了一个新方法 resources/directory/read,并由能力声明中的 directoryRead 设置控制。

目录资源

目录资源 是 mimeType 为 inode/directory 的资源。在以独立文件形式提供的 Skill 命名空间中,每一级目录都是目录资源:Skill 根目录(skill://pdf-processing)和每个子目录(skill://pdf-processing/templates)。目录 URI 不带尾部斜杠。目录资源无需出现在 resources/list 中;无论是否列出,都可以寻址。

resources/directory/read

请求带有目录 URI 和可选分页游标。结果包含该目录直接子项的资源元数据,即 resources/list 返回的同一种 Resource 对象,并遵循相同的 nextCursor 分页合同。

{
  "jsonrpc": "2.0",
  "id": 7,
  "method": "resources/directory/read",
  "params": {
    "uri": "skill://pdf-processing/templates"
  }
}
{
  "jsonrpc": "2.0",
  "id": 7,
  "result": {
    "resultType": "complete",
    "resources": [
      {
        "uri": "skill://pdf-processing/templates/invoice.md",
        "name": "invoice.md",
        "mimeType": "text/markdown"
      },
      {
        "uri": "skill://pdf-processing/templates/purchase-order.md",
        "name": "purchase-order.md",
        "mimeType": "text/markdown"
      },
      {
        "uri": "skill://pdf-processing/templates/regional",
        "name": "regional",
        "mimeType": "inode/directory"
      }
    ]
  }
}

语义如下:

  • 该方法只适用于目录资源。如果 URI 不存在,或存在但不是目录资源,服务器必须(MUST)返回错误 -32602(Invalid params),与 resources/read 对未知资源使用的代码相同。
  • 结果包含目录的每个直接子项:文件带有普通资源元数据,子目录作为目录资源列出(mimeType: "inode/directory")。列表不递归;客户端通过对某个子目录再次调用该方法继续向下遍历。
  • 空目录产生空的 resources 数组。
  • 分页方式与 resources/list 一致:结果包含 nextCursor 时,客户端将其作为 cursor 传回以获取下一页。

声明 directoryRead 的服务器必须(MUST)为其以独立文件形式提供的 Skill 命名空间内每个目录支持该方法。该方法本身不是 Skill 专用方法:服务器可以(MAY)在其提供的任何方案下,对任何目录资源支持此方法。

目录读取与持有的条目

对于条目带有 resources 的 Skill,宿主已经持有该 Skill 文件的完整清单(资源);目录读取不会告诉宿主任何该条目尚未说明的 Skill 内容。目录读取的价值在别处:用于 resources 为 "dynamic" 的动态生成 Skills;用于根本不是 Skills 的资源树;以及在不先刷新条目的情况下,取得服务器当前的目录视图。宿主根据带清单的 Skill 行动并想知道 templates/ 包含什么时,可以(MAY)只根据该条目作答。

两种视图可能不一致。如果服务器在宿主取得条目后向 Skill 添加文件,目录读取可能会列出该文件,但宿主持有的清单中没有它。这是完整性与验证已约束的过时快照情形,恢复路径也如其中规定:宿主根据持有的条目使用该 Skill 行动时,不得(MUST NOT)读取新列出的子项(未列出的文件是验证失败,与摘要不匹配完全相同),也不得(MUST NOT)把它作为 Skill 文件呈现给模型。要访问该文件,宿主应使用 skills/get 刷新条目,此时 resources 集合已变化,任何持久化的内容绑定审批都会撤销,必须重新获取(安全影响)。只有根据刷新后的条目,才能读取新文件。宿主应该(SHOULD)预期此流程,并将其呈现为 Skill 已变化、需要重新批准,而不是读取错误。反过来,清单中存在但目录读取中不存在的子项,是服务器已不再提供的文件;读取它会失败,并适用相同的刷新流程。

本扩展没有定义共享版本或缓存令牌,使宿主能够判断目录结果和条目是否描述服务器的同一快照。清单是宿主在当前审批下可以读取什么的权威依据;目录读取是实时观察,可能领先或落后于它。宿主不得(MUST NOT)把目录结果视为清单的扩展。

对于 resources 为 "dynamic" 的动态生成 Skill,上述规则不会改变其地位:它不提供内容完整性,也无法绑定内容,宿主可以(MAY)拒绝加载(资源)。目录读取是发现这类 Skill 文件的方式,但无法提供条目所缺少的完整性。

实现指南

以下是实现互操作性的建议,不属于规范性要求。

宿主:端到端集成

本节概述宿主把 MCP 提供的 Skills 接入现有 Skills 实现的一种方式。内容仅供说明,不作强制规定。宿主可以采用适合自身架构的工具、命名和路由结构。目标是让 MCP 提供的 Skill 使用与文件系统 Skill 相同的加载和读取机制,同时按照安全影响保留来源标记。

注册表。 启动时以及连接发生变化时,宿主从它支持的所有来源组装一个内部统一 Skill 注册表:文件系统 Skill 目录,以及每台已连接、声明 io.modelcontextprotocol/skills 扩展的 MCP 服务器返回的 skills/list 结果。每个注册表条目记录 Skill 的 name 和 description(来自条目的 frontmatter)及其来源:对文件系统 Skill 是本地目录,对 MCP Skill 是服务器身份和 SKILL.md 资源 URI。组装注册表时只读取列表:此阶段宿主不得(MUST NOT)获取 SKILL.md 或任何支持文件(完整性与验证)。条目的 frontmatter 包含注册表需要的一切。注册表以 Skill 身份,即来源与 SKILL.md URI 的组合为键(Skill URI),绝不能以名称为键。由于名称在同一来源内和不同来源间都会冲突(名称),注册表会在显示时限定冲突名称,而不是删除任一条目;因为名称不是键,冲突绝不会改变 Skill 的加载方式。

上下文。 宿主在模型上下文中公开每个已启用注册表条目的 name 和 description,即模型已经能看到的文件系统 Skills 列表,如今混入 MCP 提供的条目;同时公开条目的身份,即宿主为来源服务器分配的标签和 SKILL.md URI。名称和描述告诉模型 Skill 的用途;身份则是模型加载 Skill 时传入的内容。宿主 UI 向用户提供同一个合并列表,用于检查和逐 Skill 启用或停用,并显示来源,使用户能看出 Skill 来自哪台服务器。

加载。 宿主向模型公开一个统一的 Skill 加载工具,以 Skill 身份,即来源服务器和 SKILL.md URI 为键:

{
  "name": "read_skill",
  "description": "Load a skill's SKILL.md into context.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "server": {
        "type": "string",
        "description": "Name of the connected MCP server"
      },
      "uri": { "type": "string", "description": "The skill's SKILL.md URI" }
    },
    "required": ["server", "uri"]
  }
}

在此示例中,read_skill 是宿主的 Skill 加载路径,也是激活 Skill 的唯一途径(读取);针对同一 SKILL.md URI 的 read_resource 调用会返回其内容,但不会加载 Skill。模型调用 read_skill 时,宿主在注册表中查找该二元组并根据来源路由:从磁盘读取文件系统 Skill;在调用时而非提前通过 resources/read 从来源服务器获取 MCP Skill,除非宿主缓存中已有经过验证的副本。两种方式的机制相同。工具按身份而非名称作为键,使 URI 可以直接流转:用户、服务器指令或另一个 Skill 的 SKILL.md 交给模型的 Skill URI,正是 read_skill 所接收的内容,无需把它映射回宿主为解决名称冲突而限定的显示名称(名称)。已经为文件系统 Skills 公开名称键加载工具的宿主,可以扩展该工具以接受 server 和 uri,而不是引入另一个并行工具;对于文件系统 Skill,它可以为本地来源保留一个 server 值,并把 SKILL.md 路径作为 uri 传入。

支持文件。 SKILL.md 进入上下文后,模型可能遇到支持文件的相对引用(references/GUIDE.md、scripts/extract.py)。对于文件系统 Skills,模型使用宿主的普通文件读取工具读取这些文件;对于 MCP Skills,本地并不存在文件。因此,宿主还要公开一个通用资源读取工具:

{
  "name": "read_resource",
  "description": "Read an MCP resource from a connected server.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "server": {
        "type": "string",
        "description": "Name of the connected MCP server"
      },
      "uri": { "type": "string", "description": "The resource URI" }
    },
    "required": ["server", "uri"]
  }
}

宿主应安排模型在加载 MCP 提供的 SKILL.md 时知道其来源服务器及基础 URI,例如在 read_skill 工具结果中说明两者,使模型能够把 references/GUIDE.md 解析为 skill://<skill-path>/references/GUIDE.md,并针对正确服务器发出 read_resource。宿主也可以把该机制折叠到自己的文件读取工具中:把每台服务器的 skill:// 命名空间挂载到虚拟路径,每台服务器使用独立挂载根目录,使路径编码服务器身份(Skill URI),再将该路径下的读取转换为 resources/read 调用。这样无需单独提供 read_resource 工具,模型会把每个支持文件视为本地路径。虚拟挂载在访问时解析读取;不得(MUST NOT)通过提前获取 Skill 文件来填充挂载点。无论采用哪种方式,解析规则都相同:相对引用像在文件系统上一样相对于 Skill 根目录解析。当 Skill 条目带有 resources 数组时,宿主根据该数组验证每次读取,详见完整性与验证。

目录导航。 Skill 指令可能让模型访问目录而非文件(“从 templates/ 中选择正确模板”)。来源服务器声明 directoryRead 时,宿主应该(SHOULD)向模型公开此能力:目标为目录资源的 read_resource 调用可以路由到 resources/directory/read 并返回子项列表;虚拟挂载方案则把挂载路径上的 ls 转换为 resources/directory/read 调用。

未列举的 Skills。 由于列表可能为空或不完整,宿主还应接受从未在列表中见过、由用户、服务器指令或另一个 Skill 交给模型的 Skill URI。对这样的 URI 调用 skills/get 会产生与列表相同的条目,因此未列出的 Skill 会进入注册表,并按照与已列出 Skill 相同的条件完成验证和审批;不把该 URI 作为 Skill 提供的服务器会返回错误。无需特殊工具表面:read_skill 已经接受服务器和 URI,因此,read_skill 调用中出现注册表从未见过的二元组时,就触发 skills/get。

上述两个工具签名都包含 server,因为两台已连接服务器可能都提供 skill://refunds/SKILL.md(Skill URI)。这是区分歧义的一种策略;宿主也可以为 URI 重写每服务器前缀、按会话设定作用域,或采用任何适合其架构的其他方式。该工具是通用工具,可以读取任何 MCP 资源,其用途不局限于 Skills。

SDK:便捷封装

SDK 维护者应该(SHOULD)提供以 Skill 专用术语封装底层资源操作的便利接口。例如:

服务器端:从给定路径的目录声明 Skill:

@server.skill("git-workflow")                 # → skill://git-workflow/SKILL.md
def git_workflow():
    return Path("./skills/git-workflow")

@server.skill("acme/billing/refunds")         # → skill://acme/billing/refunds/SKILL.md
def refunds():
    return Path("./skills/refunds")

SDK 负责:读取 SKILL.md frontmatter 以填充资源元数据;通过 resources/read 提供文件内容;响应 skills/get(以及在服务器 Skill 集合有界时响应 skills/list);根据已注册文件计算条目摘要和大小;注册的 Skill 超出限制时发出警告。

客户端:列举和获取 Skills:

skills = await client.list_skills()               # calls skills/list, paginating; may be empty
entry = await client.get_skill(
    "skill://acme/billing/refunds/SKILL.md")      # calls skills/get, listed or not
content = await client.read_skill_uri(
    "skill://acme/billing/refunds/SKILL.md")      # wraps resources/read, works regardless of enumeration
entries = await client.read_directory(
    "skill://pdf-processing/templates")           # wraps resources/directory/read

这些封装很薄,每个封装都只是具有固定 URI 模式的单次底层协议调用;但它们为服务器作者提供符合人体工学的 Skill 声明方式,也为客户端作者提供容易发现的入口。

设计理由

本 SEP 的设计理由(为何把 Skills 映射到 Resources 而非新原语、URI 结构、列表语义、skills/get、选择方法而非索引资源、把格式委托给 agentskills.io、目录读取、原样 frontmatter 和逐文件摘要)作为独立文档维护在工作组仓库中:rationale.md。

其中有一点值得在此重申,因为它决定了两个方法如何关联。skills/list 条目被有意设计为 Skill 的完整清单,包括原样 frontmatter 和带摘要的完整 resources 集合,而不是需要后续调用才能补全的摘要。因此,宿主分页读取列表后,只需这一轮遍历就拥有建立注册表、呈现 Skill 供用户审批、将审批绑定到内容,并验证之后读取的每个文件所需的一切;无需为每个 Skill 再进行一次往返,这对连接许多服务器或连接包含许多 Skills 的服务器的宿主尤其重要。skills/get 用于列表无法覆盖的情形:通常在摘要不匹配后,无需重新列举目录即可刷新单个 Skill 的条目;以及为部分列表遗漏的 Skill 获取条目。它绝不是宿主补全已列出条目所必须执行的步骤。

向后兼容性

本扩展引入三个协议方法。每个声明本扩展的服务器都实现 skills/list 和 skills/get,因此客户端只会在看到声明后发出这些调用,而早于本扩展的客户端永远不会发出这些调用。resources/directory/read 还由 directoryRead 能力设置控制,因此未声明该设置的服务器永远不会收到此调用。本扩展不引入其他方法、消息类型或 schema 变更。未实现本扩展的服务器不会公开任何 skill:// 资源;现有客户端不受影响。未实现本扩展的客户端会把 skill:// 资源视为普通资源,而它们本来就是普通资源。

采用其他 skill:// URI 结构的现有实现需要调整以符合本扩展。目录见工作组的相关工作调查。特别是 FastMCP 广泛使用的 SkillsProvider,其 URI 结构、发现机制(逐 Skill _manifest 而非中央索引)和元数据映射均有所不同;协调该迁移是工作组近期优先事项。这些都是机械性变更,不是语义变更。

安全影响

Skill 内容是传递给模型的指令文本,因此构成提示注入表面(背景见 open-questions.md §10)。本扩展提出以下要求:

  • Skill 内容是不可信输入。 宿主必须(MUST)把 MCP 提供的 Skill 内容视为不可信模型输入,对其应用与任何服务器提供文本相同的提示注入防护。服务器已连接并不使其 Skill 内容成为权威内容。
  • 模型必须(MUST)能看到来源。 宿主在 MCP 提供的 Skill 内容进入模型上下文时,必须(MUST)用其来源服务器身份标记内容,并且不得(MUST NOT)把 MCP 提供的 Skill 呈现为与本地文件系统 Skill 无法区分的内容。由模型而非宿主决定是否遵循 Skill 指令。向模型隐瞒来源,会使上述不可信输入要求在实际执行指令的层面无法落实。
  • Skills 引入工具不具备的宿主侧表面。 与远程工具调用不同,MCP 提供的 Skill 可以把服务器创作的字节放到宿主文件系统,并指示模型使用宿主侧工具执行它们。宿主必须(MUST)把 MCP 提供的 Skills 视为风险高于远程工具调用的表面。
  • 禁止隐式本地执行。 未经用户对每个 Skill 明确批准,宿主不得(MUST NOT)允许 MCP 提供的 Skill 内容导致宿主侧代码执行。这包括:(a)宿主解析的声明式字段(hook、frontmatter 脚本);(b)Skill 正文中指示模型调用任意宿主代码执行工具的指令,无论是运行 Skill 捆绑的脚本,还是运行 Skill 指定的任意命令。宿主必须(MUST)忽略(a)或对其施加审批门禁,并且必须(MUST)对模型使用 MCP 提供的 Skill 行动时发出的代码执行工具调用应用相同审批门禁。
  • 按来源限定资源读取。 模型可调用的资源读取表面(例如宿主:端到端集成中的 read_resource 模式)受不可信 Skill 内容驱动时,会形成跨服务器 confused deputy 风险。宿主必须(MUST)把这类读取绑定到 Skill 的来源服务器:服务器 A 提供的 Skill 不得(MUST NOT)导致对服务器 B 执行 resources/read。宿主必须(MUST)使用宿主分配的标签识别服务器,而不是服务器自报的 serverInfo.name。任何跨来源读取都必须(MUST)经过逐调用的明确用户审批,并指明两台服务器。
  • 名称冲突会形成冒充表面。 Skill 名称在不同来源间不唯一,恶意服务器可以用常见 Skill 的名称发布 Skill,无论冒充的是另一台服务器的 Skill 还是用户自己的本地 Skill,并期待宿主按恶意方的意图完成解析。宿主必须(MUST)在各来源独立的命名空间中解析 Skill 名称,使用宿主分配的标签而非自报的 serverInfo.name 识别服务器;不得(MUST NOT)让 MCP 提供的 Skill 静默遮蔽、替换或截获任何其他来源的同名 Skill,包括宿主的文件系统 Skills;并且应该(SHOULD)向用户显示冲突。名称绑定其来源当前提供的字节,不代表作者身份或背书。中间方可以(MAY)通过 _meta 附加来源或验证注解,键使用其自己的反向域名前缀,而不能使用为本扩展保留的 io.modelcontextprotocol.skills/ 前缀(资源元数据);本扩展不为此类注解赋予语义。
  • 禁止隐式授予权限。 Skill 经 MCP 到达时,宿主不得(MUST NOT)采用任何会扩大模型工具或文件系统权限的 frontmatter 字段。特别是,文件系统来源的 Skill 使用 Agent Skills 的 allowed-tools 字段声明运行时可用工具;对于 MCP 来源 Skills,除非用户已经为该 Skill 明确批准此项授权,否则宿主必须(MUST)忽略该字段。远程服务器填充 allowed-tools 是在请求宿主上的更高权限,而不是在声明其自身环境的属性。对一个 Skill 的批准绝不延伸到其文件空间内任何其他 SKILL.md 的 frontmatter:除非嵌套 Skill 按自己的审批完成激活,否则其 allowed-tools 不产生任何效果(嵌套 Skills)。
  • Skills 是数据,不是指令。 宿主不得(MUST NOT)把 Skill 资源视为权威性高于其他上下文的内容。显式用户策略决定是否加载 Skill。
  • 嵌套 Skill 同意。 审批按 Skill 进行:批准一个 Skill 不代表批准其中嵌套的 Skills。根据嵌套 Skills,激活嵌套 SKILL.md 需要新的明确用户同意。静默把已批准 Skill 的文件提升为活动 Skill,会让服务器借助先前审批植入新指令和权限请求。
  • 来源与检查。 宿主在呈现 Skill 时应该(SHOULD)指出它来自哪台服务器,应该(SHOULD)允许用户在内容载入模型上下文前检查 Skill 内容,并且可以(MAY)要求逐 Skill 或逐服务器批准后才加载。
  • 摘要不是安全边界。 列表摘要未签名,并与内容来自同一服务器。它们确认列表与获取的内容一致(见完整性与验证),但不能确立内容可信、抵御服务器本身,或检测同时重写两者的中间方。
  • 内容绑定审批。 宿主持久保存任何逐 Skill 用户审批时,必须(MUST)将审批绑定到批准时观察到的条目 resources 集合(每个 uri 和 digest)。如果该 Skill 之后通过 skills/list 或 skills/get 得到的条目声明不同集合,无论是文件被轮换、新增还是移除,宿主都必须(MUST)把先前审批视为已撤销,并在加载或执行前重新提示。宿主无需轮询变更。当宿主正在使用 Skill 行动时(完整性与验证),内容发生移动会在读取时验证失败;如果宿主确实获取新条目,上述规则会撤销审批。两条路径都不会让已移动内容在旧审批下通过。resources 为 "dynamic" 的 Skill 无法绑定内容:宿主可以(MAY)拒绝加载,并且不得(MUST NOT)把持久化审批视为覆盖服务器当前提供的任意内容。摘要验证(完整性与验证)在审批授予后保护该审批。但它无法证明用户批准时内容是可信的,因为列表和正文都由服务器编写。
  • 缓存、缓存完整性、缓存隔离与持久来源。 宿主应该(SHOULD)在本地缓存已验证的 Skill 内容,并在读取文件时按需填充,而不是批量填充(完整性与验证)。缓存是曾经验证过的内容的第二份副本;验证不会自动延续到之后可能发生变化的字节。因此,把 Skill 内容缓存在磁盘上的宿主,对从缓存提供的每个文件都必须(MUST)执行以下两种方案之一:把缓存保存在只有宿主能够写入的位置(模型、模型运行的脚本或工具、机器上的其他用户都不能写入),且绝不原地修改缓存文件;或者每次访问时从缓存字节重新计算文件的 SHA-256 摘要,与条目摘要比较,并将不匹配视为与新读取不匹配完全相同。比较已保存的摘要标签或修改时间并不构成验证。把 MCP 提供的 Skill 内容缓存在磁盘上的宿主,还必须(MUST)把缓存放在所有文件系统 Skill 发现路径之外,并且无论宿主是否重启、来源服务器是否断开,都必须(MUST)始终把从该位置加载的内容视为通过 MCP 到达,以应用上述禁止隐式本地执行规则。缓存字节不会因为存储在本地就升级为文件系统 Skill 的信任等级。用户移除服务器时,宿主应该(SHOULD)删除该服务器缓存的 Skill 内容。

参考实现

根据 SEP-2133,Extensions Track SEP 在评审前至少需要一个官方 SDK 参考实现。

SDK 实现:

一致性测试:

  • 带 SEP 可追溯文件(sep-2640.yaml)的客户端和服务器场景:conformance#330

宿主原型实现(读取 skill:// 资源,在文件系统 Skills 旁公开 Skills):

服务器原型实现:

附录:延期功能

此处记录的功能曾出现在本 SEP 的早期修订中,但在评审结束前被移除。它们不属于本扩展。这里保留每项功能及移除它的异议,使未来任何重新引入的提案都能从这些异议出发,而不是重新发现它们。

归档分发

早期修订允许 Skill 条目在其 uri 之外声明预先打包的整个 Skill 目录归档(gzip 压缩 tar 和 ZIP),作为另一种获取形式。宿主可以通过单次 resources/read 获取多文件 Skill,而不是逐个文件获取;归档还可以携带独立资源无法表示的 UNIX 文件元数据(可执行位、符号链接)。

Core Maintainers 在评审期间移除了归档,原因有二:

  • 解包的攻击面与收益不成比例。 安全提取远程服务器提供的归档意味着必须防范解压炸弹、路径遍历、解析到 Skill 目录之外的链接、静默覆盖 SKILL.md 的大小写和 Unicode 规范化冲突、setuid 和 setgid 位,以及设备节点等非常规文件条目。每个宿主都必须正确实现整套检查清单,任何一项出错都会使宿主可被其连接的任何服务器利用。以可分别寻址的资源提供 Skill 不存在可比的攻击面。
  • 同一 Skill 的两种提供方式会造成兼容性风险。 归档是协议已经能够表达的内容的第二种编码。宿主必须同时支持两种形式,才能确定能读取任何 Skill;仅以归档提供的 Skill 无法被只实现独立文件读取的宿主读取。单一获取形式让兼容性基线保持平坦:任何合规宿主都能读取任何合规 Skill。

移除归档的代价正是其最初要解决的问题:包含许多支持文件的 Skill 每个文件都需要一次往返,可执行位和符号链接也无法表示。由于宿主仅在需要时获取文件(完整性与验证),该成本随会话实际使用的文件数量增长,而不是随 Skill 大小增长。如果将来重新考虑归档,首先需要解决的问题是如何约束宿主侧解包风险,例如把格式限制为不允许符号链接、非常规条目,并声明解压后大小的 profile;以及如何确保归档严格作为优化,绝不能成为获取 Skill 的唯一方式,从而让兼容性基线保持平坦。届时还必须要求归档解包后的文件集合与条目 resources 中列出的集合完全一致(资源)。

参考资料