SEP-2640: Skills Extension#2640
Conversation
1f35055 to
7b35855
Compare
2216f34 to
8d51b62
Compare
|
|
||
| Per RFC 3986, the first segment of `<skill-path>` occupies the authority component. This carries no special semantics under this convention and clients MUST NOT attempt DNS or network resolution of it. | ||
|
|
||
| A server MAY instead serve skills under another scheme native to its domain (e.g., `github://owner/repo/skills/refunds/SKILL.md`), provided each skill is listed in the [`skill://index.json`](#enumeration-via-skillindexjson) resource. The index is the authoritative record of which resources are skills; outside the index, hosts recognize skills by the `skill://` scheme prefix. The structural constraints above — `<skill-path>` ending in the skill name, `SKILL.md` explicit in the URI, no nesting — apply regardless of scheme. |
There was a problem hiding this comment.
So the skill path for the github:// example is owner/repo/skills/refunds, right?
| ```json | ||
| { | ||
| "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, e.g. skill://git-workflow/SKILL.md" | ||
| } | ||
| }, | ||
| "required": ["server", "uri"] | ||
| } | ||
| } | ||
| ``` |
There was a problem hiding this comment.
I know this is just an example - but I don't think the model typically knows about MCP servers in most client implementations (or do they in your investigations into existing read_resource impls?).
For tools, the host keeps track of which tools exposed to the model come from MCP servers; it doesn't disclose to the model which MCP servers are available or how tools map to individual MCP servers.
Similarly, I would expect hosts to potentially have something like a single read_skill progressive disclosure tool that can read skills locally on disk and from MCP servers. Especially wrt the section below "Unified Treatement of Filesystem and MCP Skills". It's not clear to me how this signature would support unified treatment, since local file-based skills wouldn't have a server input value.
There was a problem hiding this comment.
@olaservo did an exploration here: #2527 (comment)
I did some digging in code and:
- claude code, codex, and fast-agent all have a single
read_resourcetool with a server param. - gemini-cli and goose just take uri alone and seem to probe servers one-by-one until one hits... which seems pretty janky and IMO should be fixed regardless of skills.
but yes, it's non-normative and implementations are free to do what they like here.
Similarly, I would expect hosts to potentially have something like a single read_skill progressive disclosure tool that can read skills locally on disk and from MCP servers. Especially wrt the section below "Unified Treatement of Filesystem and MCP Skills". It's not clear to me how this signature would support unified treatment, since local file-based skills wouldn't have a server input value.
What I expect (but again, non-normative):
- Client enumerate skills for all servers by reading
skill://index.json - They create a map of skill-name to skill metadata. The skill metadata says whether it is a local file or MCP-backed skill + which server it came from (or anything else).
- They have a single
read_skilltool that keys on skill-name and when invoked uses the metadata to decide whether to do a local file read or MCPresources/read(note: the model doesn't need to useread_resourcefor this -- justread_skilland it is backed by aresource/readfor MCP skills). read_resourceis mostly needed for reading related files as part of the skill, e.g. if the skill referencesskill://code-review/checklist.jsonthen the model will need to read that using aread_resourcetool and make sure it is disambiguated.
There was a problem hiding this comment.
That matches how I'm thinking about too, I recommend adding it to the SEP
|
|
||
| ## Abstract | ||
|
|
||
| This SEP defines a convention for serving [Agent Skills](https://agentskills.io/) over MCP using the existing Resources primitive. A _skill_ is a directory of files (minimally a `SKILL.md`) that provides structured workflow instructions to an agent. This extension specifies that each file in a skill directory is exposed as an MCP resource, conventionally under the `skill://` URI scheme. Skills are addressed by URI and may be read directly; a well-known `skill://index.json` resource enumerates concrete skills and parameterized skill templates, but is not required — accommodating servers whose skill catalogs are large, generated, or otherwise unenumerable. The skill format itself — directory structure, YAML frontmatter, naming rules, and the [progressive disclosure](https://agentskills.io/specification#progressive-disclosure) model that governs how hosts stage content into context — is delegated entirely to the [Agent Skills specification](https://agentskills.io/specification); this SEP defines only the transport binding. |
There was a problem hiding this comment.
Good callout on boundaries here 👍🏼
Probably a good idea to wait until this SEP lands, but was curious if there is an RFC on how we could leverage this in Agent Skills specs, or if there has already been a conversation. Sorry if my question had already been addressed. I'm still catching up on the topic.
There was a problem hiding this comment.
I'm in touch with the Agent Skills folks and they are happy with this proposal since it keeps the Agent Skills format and just focuses on delivery. Once it lands can maybe chat with them about linking to it from the agentskills.io site as a reference.
* Align resource layer with SEP-2640 (Skills Extension)
Replace legacy `skill://{name}` and `skill://{name}/` URIs with the SEP-2640
shape: `skill://<skill-path>/SKILL.md` for skill markdown, individual files
addressable as siblings, and a new `skill://index.json` discovery resource.
Declares the `io.modelcontextprotocol/skills` extension in initialize
capabilities, and adds `getSkillPath` / `buildSkillResourceUri` /
`parseSkillResourceUri` / `buildSkillIndex` helpers in skill-discovery.ts.
This is a breaking change to the URI scheme; pre-1.0, draft SEP, no shims.
SEP: modelcontextprotocol/modelcontextprotocol#2640
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
* Update CLAUDE.md for SEP-2640 resource layer
Document the new URI shapes, the `extensions["io.modelcontextprotocol/skills"]`
capability, and the four SEP helpers in skill-discovery.ts. Note the explicit
`skill://index.json` update notification fired from `refreshSkills()`.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
|
Feedback on 1. The error code mis-cites base
|
|
A few more things from implementing the server + host side (reference impl in modelcontextprotocol/php-sdk#372, host in symfony/ai Mate) — companion to my note on 1. Frontmatter "verbatim JSON" + field-by-field re-verification isn't interoperably implementable as writtenHaving implemented both a server that emits this index and the host-side loader, I think the frontmatter identity + re-verification requirement is underspecified enough that (a) two good-faith implementations will reject each other's valid skills, and (b) a parser differential lets a server smuggle content past the very check meant to enforce "what the user approved is what the model receives." The rules in tension:
The index
The security teeth: because equality is undefined, a server can ship index frontmatter the host parses (and shows the user) one way while Two ways to tighten (either works):
As written, I don't think two independent SDKs can be relied on to agree, and the check doesn't fully deliver the security guarantee it claims. 2. Direct entries have no integrity coverage for supporting filesThe archive-vs-live-read rule in §Archives ("MUST thereafter serve every read of that skill's files from the locally unpacked archive … MUST NOT satisfy those reads by issuing live For a That interacts badly with Content-bound approval: a user approves a skill bound to the This is precisely why our server builds a deterministic archive so a single archive digest covers the whole tree. Suggest the SEP say so directly: either state that for direct entries supporting-file integrity is trust-on-read (server-level, not transport-verified), or recommend hosts prefer an 3. Non-
|
Add the missing conjunction in the host-side surfaces bullet, and restore the blank line before the skill://index.json paragraph so it no longer renders joined to the preceding archive-read paragraph. Also regenerate docs/seps/2640-skills-extension.mdx, which had gone stale against the source markdown. No-Verification-Needed: doc-only change with no runtime surface
Archives allowed a skill entry to advertise pre-packed .tar.gz/.zip forms of the whole skill directory as an alternative retrieval form. Remove them, per Core Maintainer review: - Unpacking a server-supplied archive is a large host-side attack surface (decompression bombs, path traversal, link escapes, normalization collisions, setuid bits, special-file entries), and every host would have to defend it correctly. - It gave servers two ways to express the same skill, so hosts had to support both forms to interoperate, and an archive-only skill was unreadable to a host that implemented individual-file reads alone. With archives gone, `url` and `digest` become unconditionally required on an index entry, so the "url, archives, or both" footnote is dropped. Rename the "Direct entries" subsection to "Skill URLs", since it no longer contrasts with an archive form. Record the removal and its reasoning in a new "Appendix: Deferred Features" so a future proposal starts from the objections. No-Verification-Needed: doc-only change with no runtime surface
Skills may be served under any URI scheme. Three things in Resource Mapping contradicted that: - Serving skills under a non-skill:// scheme was conditional on listing every skill in the index, a requirement skill:// servers did not have. Drop the condition; the index is equally optional under all schemes. - Hosts were told to "recognize skills by the skill:// scheme prefix" outside the index, keying skill identity off the scheme. Replace it with the rule the rest of the document already assumes: a host learns a resource is a skill from an index entry or from an explicit reference, for every scheme alike. - The relative-reference rule spoke of "the skill:// scheme root". The index stays at the well-known skill://index.json for every scheme, since it is a discovery resource rather than a skill, and one fixed location keeps discovery uniform. The no-nesting constraint is untouched and still applies regardless of scheme; relaxing it is a separate change. No-Verification-Needed: doc-only change with no runtime surface
Replace the ban on a SKILL.md appearing in a descendant directory of a skill with defined nesting semantics, per review discussion: - Nested content is supporting content: read through the enclosing skill, a nested SKILL.md is ordinary markdown and hosts MUST NOT act on its frontmatter. - Activation requires fresh consent: approval is per skill, and activating a nested skill - through the host's skill-loading machinery or by giving effect to its frontmatter - requires explicit user consent; approval of the enclosing skill does not substitute. - Publication is flat: a nested skill is published through its own index entry or explicit reference, and nothing in the index marks nesting. Clarifications that follow: relative references in any SKILL.md resolve against its own directory regardless of how the file was reached, and the digest-verification requirement is scoped to content retrieved via its index entry. Security Implications gains a nested-skill-consent bullet, and the no-implicit-permission-grants bullet states that a nested skill's allowed-tools has no effect unless that skill is activated under its own approval. No-Verification-Needed: doc-only change with no runtime surface
PR review surfaced that duplicate skill names are legal but unaddressed: two skills at different paths can share a name, nesting makes that likelier, existing harnesses resolve same-name collisions five different ways, and published-skill scans show name-squatting as a live impersonation pattern. Add a Names subsection to the index section: a skill's name is a label, not an identifier - a skill is identified by its url. Names SHOULD be unique within an index; hosts MUST NOT assume they are. Intra-index collisions are disambiguated rather than silently dropped, and names resolve within per-origin namespaces, so an MCP-served skill can never silently shadow a same-named skill from another server or from the host's own filesystem. A matching Security Implications bullet records the impersonation risk, requires host-assigned server labels rather than the self-reported serverInfo.name, and notes that intermediaries may attach provenance annotations via _meta under their own prefix, with no semantics assigned by this extension. Align the host integration guidelines: the illustrative registry keys by origin plus name and qualifies collisions, and the section no longer describes MCP-served skills as indistinguishable from filesystem skills, which contradicted the origin-visibility requirement in Security Implications. No-Verification-Needed: doc-only change with no runtime surface
|
|
||
| This section sketches one way a host might wire MCP-served skills into an existing skills implementation. It is illustrative, not prescriptive — hosts are free to structure tools, naming, and routing however suits their architecture. The goal is that an MCP-served skill flows through the same loading and reading mechanics as a filesystem skill — while remaining origin-tagged, per [Security Implications](#security-implications). | ||
|
|
||
| **Registry.** At startup and on connection change, the host assembles a single internal skill registry from every origin it supports: filesystem skill directories, and `skill://index.json` from each connected MCP server that declares the `io.modelcontextprotocol/skills` extension. Each registry entry records the skill's `name` and `description` (from the index entry's `frontmatter`) and its origin — for a filesystem skill, the local directory; for an MCP skill, the server identity and the `SKILL.md` resource URI. Because names collide within and across origins ([Names](#names)), the registry keys entries by origin and name together, qualifying colliding names for display and invocation rather than dropping either entry. |
There was a problem hiding this comment.
Should this paragraph describe the expected behavior for when the server provides a conventions for resources (like the skill:// resources with server instructions) but not skill://index.json?
I'd expect something like
and, from each connected MCP server that declares the
io.modelcontextprotocol/skillsextension, the skills enumerated byskill://index.jsonor, if that's not available, the resources indicated by the server via different mechanisms like "server instructions to list allskill://resources.
There was a problem hiding this comment.
There's no normative behavior for skills referenced by just server instructions or any other non-index means, i.e. specifically the host should NOT be parsing instructions to find skill:// resources. If a server wants to enumerate skills it should use the index.
There was a problem hiding this comment.
Thank you! This clarifies that I was conflating enumeration with explicit references. Also, I asked the question before the change to useskill/list
|
|
||
| ##### Integrity and verification | ||
|
|
||
| Digests are SHA-256 hashes of an artifact's raw bytes, formatted as `sha256:{hex}` where `{hex}` is 64 lowercase hexadecimal characters. A skill entry's `digest` covers its `SKILL.md` file. The index carries no digest for a skill's supporting files; they are ordinary resources, outside the integrity mechanism this extension defines. |
There was a problem hiding this comment.
The idea of using a digest to validate the integrity of a skill is interesting. That said, I question whether it should be a required property.
My understanding is that when a server does not provide a skill://index.json, the host discovers skills by using a mechanism like enumerating the resources returned by resources/list and selecting those with the skill scheme - the mechanism would be communicated via server instructions, another skill, ...
Since that discovery path does not provide a digest, I'm curious about the rationale for making the digest mandatory only for skill://index.json?
Maintainer Activity CheckHi @pja-ant! You're assigned to this SEP but there hasn't been any activity from you in 14 days. Please provide an update on:
If you're no longer able to sponsor this SEP, please let us know so we can find another maintainer. This is an automated message from the SEP lifecycle bot. |
A digest covering only SKILL.md left every supporting file unbound: a
server could obtain approval for a benign skill, then rotate reference
content the model follows just as readily, while the approved digest
stayed valid.
Each index entry now carries a resources array enumerating the skill's
files as {uri, digest} pairs, SKILL.md included; the entry-level digest
field is gone, and the entry's url field is renamed uri to match. The
set is complete - an unlisted file within the skill is a verification
failure, so rotation and addition are equally detectable - and
completeness extends to nested skills' files, which are the enclosing
skill's supporting content. Content-bound approval binds to the whole
set; any change revokes it. A digest mismatch may also just mean the
skill was updated after the index was read: recovery is to re-read the
index, with re-approval falling out of the set change.
resources MAY be omitted only for dynamically generated skills that
cannot publish stable digests; such skills have no content integrity,
cannot be content-bound, and hosts MAY decline to load them. The
frontmatter identity check now applies to every indexed fetch, not
only digest-verified ones, so dynamic entries cannot dodge it.
Update the directory-read rationale (the manifest it rejected was a
navigation listing; this is an integrity commitment), add a rationale
section for per-file digests, and note in the appendix that any future
archive form must unpack to exactly the enumerated set.
No-Verification-Needed: doc-only change with no runtime surface
Serve enumeration through a protocol method rather than a well-known resource, per review feedback. The entry schema is unchanged; the carrier changes: - skills/list is mandatory for every server declaring the io.modelcontextprotocol/skills extension. The result MAY be empty or partial - unenumerable catalogs return what they can - and hosts MUST NOT treat an empty or partial listing as proof a server has no skills. No capability setting gates the method; declaring the extension is the commitment. resources/directory/read keeps its directoryRead gate. - Pagination follows the base list methods (cursor/nextCursor), with entries atomic across pages so a skill's resources set is never split. - In protocol versions 2026-07-28 and later, results carry the base protocol's list-caching attributes, ttlMs and cacheScope (SEP-2549), as a freshness hint distinct from integrity verification. The reserved skill://index.json URI and its carve-out are gone, and the version-marker paragraph with it - extension versioning is inherent to MCP extensions now that the schema rides a method rather than a convention on a resource. Rename the enumeration rationale to "Why May the Listing Be Empty or Partial?" and add "Why a Method Instead of a Well-Known Index Resource?". No-Verification-Needed: doc-only change with no runtime surface
|
|
||
| In protocol versions 2026-07-28 and later, the result also carries the base protocol's list-caching attributes — `ttlMs` and `cacheScope`, as defined for `tools/list` and `resources/list` ([SEP-2549]) — with the same semantics: a freshness hint for the listing and a cache-scope marker, not an integrity property. [Integrity and verification](#integrity-and-verification) governs content regardless of how fresh a cached listing is. | ||
|
|
||
| A server whose skill catalog is large, generated on demand, or otherwise unenumerable MAY return an empty or partial listing. Hosts MUST NOT treat an empty or partial listing as proof that a server has no skills. The method serves entries for a server's skills whatever URI scheme they use — enumeration is uniform across schemes. |
There was a problem hiding this comment.
Given a server may return partial listing; I think we should also introduce a skills/get so that we can get the resource's manifest (so that the digest can be verified) for a given skill. Otherwise there's no way to do fetch it.
wdyt?
There was a problem hiding this comment.
Ty for the change @pja-ant - feel free to resolve the comment. I can't.
skills/list leaves two gaps. A listing may be empty or partial, so a skill reached by URI alone - from server instructions, another skill, or the user - had its digests published nowhere and could be read but never verified or content-bound, even where the server could perfectly well publish them. And picking up a changed skill meant re-enumerating the whole catalog to learn the new digests of one entry. Add skills/get, taking a skill's SKILL.md URI and returning that skill's entry: the same uri, frontmatter and resources fields as a skills/list entry, under the same rules, so the method adds no schema. It is mandatory for every server declaring the extension, answers for every skill the server serves whether or not the listing mentions it, and returns -32602 for a URI it does not serve as a skill. A single entry is not a list, so the result carries no cursor and no list caching attributes. Hosts are not asked to poll. The approved resources set is the anchor: while a host holds it, moved content fails verification when read, and a host that does fetch a fresh entry hits the existing revocation rule. skills/get is what makes recovery cheap once a mismatch has already announced the change. No-Verification-Needed: doc-only change with no runtime surface
|
Opinion from the gateway perspective, and building on this cluster of attestation work in flight already: tool-call attestation (#2787), attested tool-server admission (#2777), and signed execution records (#2828). Those are mostly about publisher provenance and tool-call attestation. The gap I keep running into for skills is third-party scan verdicts: an independent verifier saying "this exact skill scanned clean," which is a different claim from who built or served it. The interoperable shape here is a signed scan attestation in a transparency log, keyed by the skill digest: Three things worth getting right for skills specifically:
So as 2640 scopes down, the skills-specific ask is small: keep the digest a clean, stable signing target that covers the full skill, and leave verification and scan governance to coordinate with the 2787 / 2777 and registry/Sigstore folks so it reuses their format instead of growing a parallel one. For what it's worth, we run a gateway that scans and stamps provenance on tools today, so I'm happy to be a reference implementation or test case for the skills verification path. (Related: I left a dynamic-server note on the Server Card thread, this is the verification side of the same story.) |
Extensions Track SEP defining the
skill://resource convention for serving Agent Skills over MCP. Developed by the Skills Over MCP Working Group; transports the design from experimental-ext-skills#69 with archive distribution per experimental-ext-skills#83.Extension identifier:
io.modelcontextprotocol/skills.Reference implementations: TypeScript SDK wrappers, host prototypes in gemini-cli/fast-agent/goose/codex/Claude Code, and the GitHub MCP Server.