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]>
|
I spent some time looking at how the major harnesses implement skill discovery, because it affects both:
I think these are the same issue from two directions: is a skill name unique within the namespace a client resolves against? What current implementations do
References:
Why this mattersAn author cannot currently rely on nesting as portable behaviour. Claude Code and Gemini only discover skills one level deep, so a nested skill may simply disappear in two of the five harnesses, with no clear error explaining why. The three harnesses that do recurse then handle same-name collisions differently:
So if two nested skills share the same This is not only theoretical. OpenCode's remote-index loader resolves each skill cache location as: path.resolve(sourceRoot, skill.name)That means two remote skills whose frontmatter both say Codex has a related runtime ambiguity: A An invariant that might cut through thisThe framing I keep coming back to is name uniqueness within a server. Concretely, something like:
The server owns its The appeal for me is that it gives clients a clean namespace to consume and removes the need for each client to invent its own collision-resolution behaviour at runtime, which is what Codex, Goose, and OpenCode are each doing differently today. But I'd genuinely like to hear where this breaks down — @localden, does this interact well or badly with the unpack/ What this could imply for nestingIf we take that invariant, I don't think the SEP would need to ban nested files, which I know was the part @dsp-ant was uncomfortable with. Nesting could stay permitted and be treated purely as server-side file layout rather than protocol-level hierarchy. That avoids the forward-compatibility cost @dsp-ant raised: a server can organise files however it wants, while the distribution layer still exposes a flat, unambiguous index. In that model:
I think this gets us most of what the nesting request was after without the discovery inconsistency, but I may be missing a case where layout genuinely needs to be load-bearing. Across-server collisionsAcross servers, names can still collide, and that seems fine and unavoidable under any design. The client can namespace by origin/server, the same way clients already disambiguate tool names when multiple servers are connected, so this reuses existing client-side machinery rather than adding a new burden to the SEP. On skill composition and hierarchyI do think the nesting discussion is pointing at a real need: authors want a way to express relationships between skills. I just don't think directory hierarchy is the right abstraction for it, because the composition model is usually more of a graph than a tree. For example:
A directory layout can't express that cleanly without either duplicating skills, inventing path semantics, or forcing one "primary" parent where there may not be one. That's why, on our side, we've been keeping the distributed list of skills flat and using metadata for relationships rather than encoding them in folder structure. The properties that has been buying us:
So I wouldn't read composition as irrelevant to this SEP. I'd read it as evidence that hierarchy probably wants to be explicit metadata rather than implicit directory structure. I also don't think this SEP needs to define that relationship model itself, since it feels more like a skill-semantics question for the agentskills spec, where there are already related proposals: The distribution case feels like a strong argument for picking that work up, though. We have the same need internally today and currently express these links as metadata key/value pairs in frontmatter rather than through layout. Where I'd lean (open to other framings)Pulling it together, the direction I'd lean toward for this SEP:
That's where I've landed, but I'm very open to being wrong on any of these, especially whether uniqueness belongs in this SEP or upstream. Curious what @dsp-ant and @localden think. |
This comment was marked as spam.
This comment was marked as spam.
|
On the same-name collision / cross-server namespacing point — this isn't only a discovery/UX question, it's a live impersonation surface. We scanned ~96,000 published agent skills and confirmed 751 as malicious. A recurring pattern was name collision: a malicious skill published under (or near) the name of a popular legitimate one, relying on the consumer resolving the wrong artifact at unpack time. Once "two skills with the same name" can occur across sources, "which one unpacks" becomes a security decision, not just a precedence rule. Two implications for SEP-2640:
Happy to share the collision cases from the dataset if they'd help stress-test the resolution rules. |
Co-authored-by: Den Delimarsky <[email protected]>
localden
left a comment
There was a problem hiding this comment.
Two additions clarifying that digests are not a security boundary.
Co-authored-by: Den Delimarsky <[email protected]>
Co-authored-by: Den Delimarsky <[email protected]>
This comment has been minimized.
This comment has been minimized.
|
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
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.