SEP-2640:Skills 扩展
社区译文;来源: MCP 1155ca46
| 字段 | 值 |
|---|---|
| SEP | 2640 |
| 标题 | 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.mdfrontmatter 中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>的最后一个片段就是 Skillname,必须(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-workflow | SKILL.md | skill://git-workflow/SKILL.md |
pdf-processing | references/FORMS.md | skill://pdf-processing/references/FORMS.md |
pdf-processing | scripts/extract.py | skill://pdf-processing/scripts/extract.py |
acme/billing/refunds | SKILL.md | skill://acme/billing/refunds/SKILL.md |
acme/billing/refunds | examples/email.md | skill://acme/billing/refunds/examples/email.md |
资源元数据
对于每个 skill://<skill-path>/SKILL.md 资源:
mimeType应该(SHOULD)为text/markdown。name应该(SHOULD)取自SKILL.mdYAML frontmatter 的name字段。根据上述路径约束,它将等于<skill-path>的最后一个片段。description应该(SHOULD)取自SKILL.mdYAML 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
}
}
}
}定义了一个扩展专用设置:
| 设置 | 类型 | 默认值 | 含义 |
|---|---|---|---|
directoryRead | boolean | false | 服务器实现 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 实现:
- Python SDK:python-sdk#3485
- C# SDK:csharp-sdk#1856
- Go SDK:go-sdk#1238
一致性测试:
- 带 SEP 可追溯文件(
sep-2640.yaml)的客户端和服务器场景:conformance#330
宿主原型实现(读取 skill:// 资源,在文件系统 Skills 旁公开 Skills):
- gemini-cli:olaservo/gemini-cli#1
- fast-agent:evalstate/fast-agent#815
- codex:olaservo/codex#1
- Claude Code:已在 Anthropic 内部完成原型;尚未公开
服务器原型实现:
- GitHub MCP Server:github/github-mcp-server#3046
附录:延期功能
此处记录的功能曾出现在本 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 中列出的集合完全一致(资源)。
参考资料
- Agent Skills specification
- SEP-2133:Extensions
- SEP-2549:列表结果的 TTL
- SEP-2076:将 Agent Skills 作为一等原语(替代方案)
- Skills Over MCP Working Group charter
- Decision Log:工作组决策与理由
- Experimental Findings:实现结果(进行中)
- Related Work:现有 Skill 提供实现调查
- Skill
_metaKeys:Skill 资源的_meta键约定 - RFC 3986: URIs