Skip to content

SEP-2640: Skills Extension#2640

Open
pja-ant wants to merge 20 commits into
mainfrom
sep/skills-extension
Open

SEP-2640: Skills Extension#2640
pja-ant wants to merge 20 commits into
mainfrom
sep/skills-extension

Conversation

@pja-ant

@pja-ant pja-ant commented Apr 23, 2026

Copy link
Copy Markdown
Contributor

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.

@pja-ant pja-ant requested a review from olaservo April 23, 2026 19:46
@pja-ant pja-ant force-pushed the sep/skills-extension branch from 1f35055 to 7b35855 Compare April 23, 2026 19:47
@pja-ant pja-ant changed the title SEP: Skills Extension SEP-2640: Skills Extension Apr 23, 2026
@pja-ant pja-ant added SEP draft SEP proposal with a sponsor. extension labels Apr 23, 2026
@pja-ant pja-ant marked this pull request as ready for review April 23, 2026 19:51
@pja-ant pja-ant requested review from a team as code owners April 23, 2026 19:51
@pja-ant pja-ant self-assigned this Apr 23, 2026
@olaservo olaservo moved this from Backlog to In review in Skills Over MCP Working Group Apr 24, 2026
Comment thread seps/2640-skills-extension.md Outdated
Comment thread seps/2640-skills-extension.md Outdated
Comment thread seps/2640-skills-extension.md Outdated
Comment thread seps/2640-skills-extension.md Outdated
Comment thread seps/2640-skills-extension.md Outdated
Comment thread seps/2640-skills-extension.md Outdated
Comment thread seps/2640-skills-extension.md Outdated
Comment thread seps/2640-skills-extension.md
Comment thread seps/2640-skills-extension.md Outdated
Comment thread seps/2640-skills-extension.md Outdated
Comment thread seps/2640-skills-extension.md

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the skill path for the github:// example is owner/repo/skills/refunds, right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes

Comment on lines +199 to +218
```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"]
}
}
```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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_resource tool 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_skill tool that keys on skill-name and when invoked uses the metadata to decide whether to do a local file read or MCP resources/read (note: the model doesn't need to use read_resource for this -- just read_skill and it is backed by a resource/read for MCP skills).
  • read_resource is mostly needed for reading related files as part of the skill, e.g. if the skill references skill://code-review/checklist.json then the model will need to read that using a read_resource tool and make sure it is disambiguated.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That matches how I'm thinking about too, I recommend adding it to the SEP

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

Comment thread docs/seps/2640-skills-extension.mdx Outdated

## 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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread seps/2640-skills-extension.md Outdated
olaservo added a commit to olaservo/skilljack-mcp that referenced this pull request May 3, 2026
* 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) <noreply@anthropic.com>

* 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) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment thread seps/2640-skills-extension.md
Collapse the Author(s) field onto one line so the generated SEP page
includes the full author list and working-group attribution.

Claude-Session: https://claude.ai/code/session_01MbcPVz67EwJdcNgeXgn7iG
@mintlify

mintlify Bot commented Jun 18, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
mcp-staging 🟢 Ready View Preview Jun 18, 2026, 12:19 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@mintlify

mintlify Bot commented Jun 18, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
mcp 🟢 Ready View Preview Jun 18, 2026, 12:33 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

…tter

Keys under this prefix in the frontmatter metadata object are reserved
for MCP-defined semantics. No keys are defined yet; unrecognized keys
under the prefix should be ignored.

Claude-Session: https://claude.ai/code/session_01MbcPVz67EwJdcNgeXgn7iG

Further constraints:

- A `SKILL.md` MUST NOT appear in any descendant directory of a skill. The skill directory is the boundary; skills do not nest inside other skills.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this constraint. I think it's quite valid to have skills nested into skills. Do we have a very strong reason to impose such constraint and reduce forward compatibility?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can relax this. I think there was a good reason earlier on but I don't recall it and maybe the reason just went away after some iterations. Not sure if you remember any reason for this @olaservo? Seems actually quite useful to nest sub-skills for the pathological cases.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Agent Skills spec currently does not specify how skill should be packaged or related to each other. Many clients only support skills at the top level, with a handful supporting nesting (although sometimes only a level or two).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a skill is nested inside the folder; does that change how index.json looks? Is the recommendation that the index.json only highlight the top level skills?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pja-ant I think that nesting used to be more ambiguous when the URI shape was doing more of the work. The index and the directory-read method came after we added this constraint. Now that we have those, skill identity can get asserted by the index rather than inferred from the path, so I think we can relax it.

Like Kurtis mentioned, the spec doesn't disallow it and client nesting support varies. Should we frame this as servers MAY nest and not something hosts must support? A host that only handles top-level skills can still treat every entry as flat like they would in a plain filesystem.

@addy007-icy I think a nested SKILL.md is its own skill root with its own index entry, so index.json stays a flat list and nested skills just show up as additional entries whose URLs share a path prefix with the parent. That way a host walking the parent treats a child SKILL.md as a boundary rather than subsuming the entire subtree. That could keep relative-path resolution clearer and enable the top-level-skill-as-entry-point pattern that we have mentioned as a valid way to handle a giant/hierarchical set of skills.


For each `skill://<skill-path>/SKILL.md` resource:

- `mimeType` SHOULD be `text/markdown`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We had a brief discussion in ai-catalog about this. It's not fully clear that any form of Markdown allows for frontmatter. I think this is fine, but it might be strictly speaking wrong.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think strictly speaking you are right, but the agent skills spec is SKILL.md (markdown suffix) with YAML frontmatter. I think the ship has sailed...

- `name` SHOULD be set from the `name` field of the `SKILL.md` YAML frontmatter. By the path constraint above, this will equal the final segment of `<skill-path>`.
- `description` SHOULD be set from the `description` field of the `SKILL.md` YAML frontmatter.

Servers MAY expose additional frontmatter fields via the resource's `_meta` object. When `_meta` keys are used for skill resources, implementations SHOULD use the `io.modelcontextprotocol.skills/` reverse-domain prefix. Other files in the skill use the `mimeType` appropriate to their content.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we do want to be more specific here. I assume that gateway authors would really love some definitions here to ensure they can rely on governance. E.g. we can say you must map the frontmatter to _meta, hence allowing additional governance at intermediate steps.

I am sure you all thought about this, so I am curious what am I missing here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's not really a lot in the skills spec frontmatter that relates to governance. If people do add their own governance metadata then of course they can add it and this line gives a carve out for where it should go, but forcing implementations to mirror it just introduces potential for things to get out of sync (servers that are fronting dynamic skills would need to expand and re-parse the frontmatter on any upstream change) and it otherwise just adds a lot of overhead for unclear value.

Comment thread seps/2640-skills-extension.md Outdated
Comment on lines +183 to +198
##### Archives

`archives` lists pre-packed forms of the entire skill directory. Each archive is a single resource whose content is the complete skill directory in an archive format; reading it via `resources/read` retrieves the skill — `SKILL.md` and all supporting files — in one round trip. When multiple archives are listed, they are alternative encodings of identical content; a host picks one whose format it supports, determined from `mimeType`.

`mimeType` MUST identify an archive format. Hosts SHOULD support at least gzip-compressed tar (`application/gzip`, conventionally `.tar.gz`) and ZIP (`application/zip`); servers MAY offer additional formats but SHOULD include at least one of these two so that any conforming host can retrieve the skill.

Archive layout:

- `SKILL.md` MUST be at the archive root, not nested inside a wrapper directory.
- Entry paths MUST use `/` as the separator and MUST be relative — no path-traversal sequences (`..`) and no absolute paths.

Archive contents represent the skill directory directly. When an entry carries both `url` and `archives`, each archive MUST unpack to the same files that are individually addressable under the skill path — a host may fetch the skill either way and observes the same content. When an entry is archive-only, the skill's files are not individually addressable on the server; the host unpacks the archive locally and addresses its contents under `skill://<name>/<file-path>`, where `<name>` is `frontmatter.name`. In both cases the archive `url` itself carries no path semantics — it is a download location, nothing more.

Compared to individual-file distribution, an archive delivers a multi-file skill atomically and can carry UNIX file metadata (executable bits, symlinks) that individual resources cannot represent — robustly for `.tar.gz`, variably for `.zip`.

Archives are an unpacking attack surface. Hosts unpacking an archive MUST reject archives containing path-traversal sequences or absolute paths, reject symlinks or hard links that resolve outside the skill directory, and enforce a limit on total unpacked size to prevent decompression bombs.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the motivation for including archives? This feels like something that clients will have a hard time reasoning about, since there is no additional IT admin control if the archives don't contain zip-bombs, malicious materials, etc, compared to a marketplace.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Archives are highly requested here since otherwise a skill that potentially contains 100s or 1000s of files would require that the client pulls them one-by-one using resources/read, putting a lot of unnecessary strain on both the server and client. They are also a standard format for distributing skills.

It's true that zip bombs are a threat, but you have to deal with that anyway: even if the skill wasn't archived for delivery, it could still have a reference file skill://my-skill/references/data.zip that the client has to extract. Clients just need to deal with this.


#### Directory resources

A _directory resource_ is a resource whose `mimeType` is `inode/directory`. In a skill namespace served as individual files, every directory level is a directory resource: the skill root (`skill://pdf-processing`) and each subdirectory (`skill://pdf-processing/templates`). Directory URIs are written without a trailing slash. Directory resources need not appear in `resources/list`; they are addressable whether listed or not.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does the URI need to be without a trailiing slash? that feels like an arbitrary limitation?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The alternative of allowing either IMO would just lead to incompatibility: some servers may only accept one or the other / some clients may normalize one way or the other -- leads to incompatibility. Having one correct approach and avoids the need for normalization (e.g. you blocklist access to skill://dangerous/dir but the client requests skill://dangerous/dir/ and if you forget to normalize then it falls over).

Comment on lines +100 to +102
#### Enumeration via `skill://index.json`

A server SHOULD expose a resource at the well-known URI `skill://index.json` whose content is a JSON index of the skills it serves.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm. This makes sense. One thing I Worry about is that Gateway authors cannot easily introspect this on the protocol level or enhance attributes via _meta, e.g. signing them or verifying that a skill has been checked/verified in a form, or add other provenance or policy metadata. I wish we could find a way to put this into _meta on a directory/read, or a new skills/list. I am a bit torn, I do think modelling it purely on resources is a good idea in principle for backwards compatibility but it comes with some real drawbacks.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean you can put _meta on a resources/read of skill://index.json and the index contains the full frontmatter which is another place to put that kind of metadata that is specific to skills. The resources/read on the individual skills can also contains _meta metadata.


##### 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. For a skill entry's `digest`, the artifact is the `SKILL.md` file; for an archive's `digest`, the artifact is the archive file.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why use hexadecimal and not BASE64? Most modern SRI formats use base64. We might want to do that? We might even just use SRI so people can even choose if they want sha256,sha384 or sha512. It's the de-factor default for modern package management / integrity verification and serializes more compact.

https://developer.mozilla.org/en-US/docs/Web/Security/Defenses/Subresource_Integrity

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honest answer is that we were following https://github.com/cloudflare/agent-skills-discovery-rfc#integrity-and-verification initially.

Other answer: hex appears to be the industry standard here. base64 seems to be a primarily an npm-ecosystem thing, but outside of that hex dominates: OCI, cargo, pip, homebrew, apt - all use hex.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is correct, the hex digest is the prevelant norm for this.


Digests are SHA-256 hashes of an artifact's raw bytes, formatted as `sha256:{hex}` where `{hex}` is 64 lowercase hexadecimal characters. For a skill entry's `digest`, the artifact is the `SKILL.md` file; for an archive's `digest`, the artifact is the archive file.

Hosts MUST verify retrieved content against the `digest` in the index. A mismatch indicates the content is corrupted or tampered with — hosts MUST NOT use unverified content. Digests also enable efficient caching: compare a skill's `digest` against a locally cached value to determine whether the artifact has changed without re-reading it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing to consider is to enforce size being available for any served file for skill. I think we missed this in the original spec, but now that you enforce verification, you enforce that the client reads the full content and hence it needs to understand the max bounds it should read in advance or discard if it's too big.

So maybe we say, if you have a skill you MUST use size

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. Can add it.

Comment on lines +246 to +250
### Directory Listing

A skill's instructions frequently reference a directory rather than a file: "pick the appropriate template from `templates/`", "run the matching script in `scripts/`". To act on this, the agent must learn what the directory contains. `resources/list` cannot answer that scoped question: it enumerates the server's entire resource space, not a subtree, and the servers this SEP most wants to accommodate — large, generated, or unenumerable catalogs (see [Why Is Enumeration Optional?](#why-is-enumeration-optional)) — may not implement meaningful global listing at all.

This extension therefore defines one new method, `resources/directory/read`, gated behind the `directoryRead` setting of the [capability declaration](#capability-declaration).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should give this lots of scrutiny. I assume that this will be the basis for a rework of resource handling to look more like a filesystem API.


On top of that baseline, two discovery mechanisms are defined. A server MAY support either or both.

#### Enumeration via `skill://index.json`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing is that this file might become very large right? It also means it might be served without size so a potential unconstraint read, and lastly, since it's json it can't be read sequentially or partially. @sambhav here who mentioned that he might have some pathological use-cases. also @clareliguori because AWS also tends to have those. Any concerns?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We discussed a bit in the working group. @jonathanhefner (IIRC) made a good point that even with 10000 skills, we're talking megabytes (uncompressed) at most. It's not a huge amount.

There are alternatives as well for servers that have far too many skills, e.g. just adding a find_skill(query) tool that returns a ResourceLink. Alternatively you can have skill groups where e.g. you just add a skill://writing/SKILL.md to your index and when you read the skill it just says "go and read skill://writing/genres to find other more specific instructions" and the model can explore with the directory read verb. Of course that wouldn't include all 10000 of your skills in the system prompt, but if you have >10000 skills then you don't want to do that anyway.

This all feels like it plays into the broader progressive discovery picture and trying to solve these pathological cases here feels like trying to boil the ocean.

@sambhav sambhav Jun 19, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with it not being paginated, even with 100k entries it will still be reasonable to fetch. Also the SEP specifies that not every skill needs to be in the index, which is perfect as it allows for progressive discovery if needed.

One option if we are really worried is making it jsonl, but this is fine for now.

- `SKILL.md` MUST begin with YAML frontmatter containing at minimum the `name` and `description` fields as defined by the Agent Skills specification.
- A skill MAY contain additional files and subdirectories (references, scripts, examples, assets).

This extension does not redefine, constrain, or extend the skill format. Future revisions of the Agent Skills specification apply automatically.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am curious how we potentially adapt (or adjust) if there are upstream breaking changes (do we expect any with Skills)? I suspect that for the most part this is a non-issue since this spec formalizes the distribution mechanism, but it might be worth spelling out.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can add more text here. Generally, I would not expect breaking changes from skills without some form of backwards compatibility otherwise everyone's skills would just stop working (which would include MCP-delivered ones).

For some clarity, could add some language like:

In the event that the Agent Skills specification changes in a backwards incompatible way, clients MUST honor any backwards compatibility mechanisms provided by the Agent Skills specification and SHOULD continue to support the Agent Skills specification as it existed prior to any incompatible change.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could also just advertise the supported agentskills spec range in the extension's capabilites.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately agent skills doesn't have any versioning.

- `SKILL.md` MUST be at the archive root, not nested inside a wrapper directory.
- Entry paths MUST use `/` as the separator and MUST be relative — no path-traversal sequences (`..`) and no absolute paths.

Archive contents represent the skill directory directly. When an entry carries both `url` and `archives`, each archive MUST unpack to the same files that are individually addressable under the skill path — a host may fetch the skill either way and observes the same content. When an entry is archive-only, the skill's files are not individually addressable on the server; the host unpacks the archive locally and addresses its contents under `skill://<name>/<file-path>`, where `<name>` is `frontmatter.name`. In both cases the archive `url` itself carries no path semantics — it is a download location, nothing more.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels a bit counter to the previous guarantees of skill readability:

A server is not required to make its skills enumerable. A skill's URI is directly readable via resources/read whether or not it appears in any index, and hosts MUST support loading a skill given only its URI (see Hosts: End-to-End Integration). This is the baseline: if a model has the URI — from server instructions, from another skill, from the user — it can read the skill.

Wouldn't this effectively contradict the MUST you just mentioned about addressability of a skill for an entire distribution class?

This also might introduce weirdness because the client now needs to know that the skill came from the archive vs. server and route to the unpacked version. Should there be a carve-out for how clients can address a local extracted cache vs. server-served skills?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I follow. For archives, the skill will still be available via the archive, e.g. a skill://code-review.zip is still readable via read_resource even if that skill is not in the index.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could clarify with:

A skill's URI — either its SKILL.md resource or an archive resource containing it — is directly readable via resources/read whether or not it appears in any index…

Comment thread seps/2640-skills-extension.md Outdated
- `SKILL.md` MUST be at the archive root, not nested inside a wrapper directory.
- Entry paths MUST use `/` as the separator and MUST be relative — no path-traversal sequences (`..`) and no absolute paths.

Archive contents represent the skill directory directly. When an entry carries both `url` and `archives`, each archive MUST unpack to the same files that are individually addressable under the skill path — a host may fetch the skill either way and observes the same content. When an entry is archive-only, the skill's files are not individually addressable on the server; the host unpacks the archive locally and addresses its contents under `skill://<name>/<file-path>`, where `<name>` is `frontmatter.name`. In both cases the archive `url` itself carries no path semantics — it is a download location, nothing more.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another point here:

"the host unpacks the archive locally and addresses its contents under skill://<name>/<file-path>, where <name> is frontmatter.name"

frontmatter.name is refunds. So acme/billing/refunds unpacks to skill://refunds/.... And acme/support/refunds also unpacks to skill://refunds/.... The two skills the prefix existed to separate now address the same location on the host. Second one fetched overwrites the first, or the host has two skills at one URI, depending on implementation.

Do you need to introduce a skillPath?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

urgh, yeah this section is incorrect. The archive should unpack to just raw reference paths, not resource URIs (since you aren't going to load these as resources anyway).

Should be something like:

…the host unpacks the archive locally; relative references within the skill resolve against the unpacked root, per Internal References.


##### 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. For a skill entry's `digest`, the artifact is the `SKILL.md` file; for an archive's `digest`, the artifact is the archive file.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am a bit uneasy about the digest logic, TBH. We're saying that the artifact is SKILL.md but then there is also the ability to load supporting files, like scripts. If the skill is "Use server.py to do FooBar" and we only create a digest for the Markdown file, doesn't it mean that now any of the supporting code can be... whatever server wants it to be?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The digest is primarily for caching purposes to avoid refetching the skill.

For verification, I think at some level you have to accept that you can't lock down absolutely everything. If we (somehow) expanded the digest to cover all the relevant resources, but then the skill says something like "call tool get_script, which returns a script file and run that" - we can't guarantee that the tool result is consistent across every usage.

Comment thread seps/2640-skills-extension.md Outdated
- **Skills do not introduce a new trust tier.** A user who connects a server has already extended their trust boundary to it; a malicious server can do as much harm via tools as via a skill document. Serving skills over MCP adds no risk beyond what skills already carry in any transport — but the defensive posture above applies regardless.
- **No implicit local execution.** Hosts MUST NOT honor mechanisms in skill content that would cause local code execution without explicit user opt-in. This includes, non-exhaustively: hook declarations, pre/post-invocation scripts, shell commands embedded in frontmatter, or any field that a filesystem-sourced skill might use to register executable behavior on the host. Hosts MUST either ignore such fields entirely when the skill arrives over MCP, or gate them behind an explicit per-skill user approval that states what will execute and where. Silently executing server-provided code because it appeared in a skill directory is a remote code execution vector.
- **Skills are data, not directives.** Hosts MUST NOT treat skill resources as higher-authority than other context. Explicit user policy governs whether a skill is loaded at all.
- **Provenance and inspection.** Hosts SHOULD indicate which server a skill originates from when presenting it, SHOULD let users inspect a skill's content before it is loaded into model context, and MAY gate loading behind per-skill or per-server user approval.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These need to be MUSTs. From a security standpoint, hosts should never let skills transit from server to host without transparent origin and consent, because that now enables local execution.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally agree for standard chat-bot like clients, but I think it depends on context. e.g. for skills that have already been vetted in some way or pre-signed off on (particularly thinking about fully autonomous use cases) then clients would need a way to use those skills without a human in the loop.

For example: a customer support agent may use a skill to guide their conversation with customers. It would be a show-stopper if that skill needed a human sign off before the agent could do its job.

Comment thread seps/2640-skills-extension.md Outdated
Comment thread seps/2640-skills-extension.md
Comment thread seps/2640-skills-extension.md
@sambhav

sambhav commented Jun 20, 2026

Copy link
Copy Markdown
Member

I spent some time looking at how the major harnesses implement skill discovery, because it affects both:

  1. @dsp-ant's question about whether nesting should be allowed here: L67
  2. @localden's question about what happens when two same-name skills collide on unpack: L194

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

Harness Nesting depth Same-name collision Warns?
Claude Code depth 1 only project shadows user no
Gemini CLI depth 1 (['SKILL.md','*/SKILL.md']) workspace > user yes
Codex recursive ≤6 dedup by path; both kept, then skipped at selection no
Goose recursive, unbounded dedup by name; first-found wins no
OpenCode recursive, unbounded (**/SKILL.md) dedup by name; last-found wins yes

References:

Why this matters

An 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:

  • Codex keeps both by path, but later skips ambiguous name-based selection.
  • Goose deduplicates by name and keeps the first match.
  • OpenCode deduplicates by name and keeps the last match.

So if two nested skills share the same name, the result depends on which client loads them.

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 name: refunds land at the same local path, regardless of where they appeared on the server.

Codex has a related runtime ambiguity: select_skills_from_mentions drops any name that matches more than one skill, so /refunds resolves to nothing if there are multiple refunds skills. Its test appears to treat that as intended behaviour. Related issues: #9930, #25324, #23987.

A skillPath field could distinguish the two files in the URI, but it would not solve the common case where a user or model invokes a skill by name.

An invariant that might cut through this

The framing I keep coming back to is name uniqueness within a server. Concretely, something like:

A server SHOULD NOT publish two skills with the same name in the same index.json.

The server owns its index.json, so this could be a publish-time validation check on the author side, similar to how a package registry rejects duplicate package names in the same namespace.

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/skillPath direction you were thinking about?

What this could imply for nesting

If 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:

  • directory depth carries no protocol meaning;
  • clients don't infer hierarchy from paths;
  • names stay unique within a server's published index.

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 collisions

Across 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 hierarchy

I 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:

  • billing/refunds may depend on a shared auth skill;
  • support/refunds may also depend on that same auth skill;
  • both may belong to different product areas, workflows, or bundles;
  • a single skill may be reused by several higher-level skills.

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:

  • the distribution namespace stays simple and unambiguous;
  • clients resolve skills by unique name;
  • authors can still group, relate, compose, or annotate skills;
  • relationship models can evolve without changing the discovery mechanism;
  • the same skill can participate in multiple workflows or bundles without moving on the filesystem.

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:

  1. Treat name uniqueness within a server's published index.json as the core guarantee, ideally validated at publish time.
  2. Allow nested files, but define them as server-side layout only.
  3. Keep the distributed index flat, with no protocol semantics attached to directory depth.
  4. Be explicit that folder structure isn't the composition/hierarchy model.
  5. Use the composition use case as motivation for explicit relationship metadata in the agentskills spec, rather than solving it here.
  6. Note that across-server name collisions are resolved by client-side server/origin namespacing.

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.

@AgentGymLeader

This comment was marked as spam.

@eeee2345

Copy link
Copy Markdown

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:

  1. Resolution order needs to be specified, not implementation-defined. If each harness (gemini-cli / goose / codex / Claude Code / etc.) breaks same-name ties differently, an attacker only needs the one harness that resolves toward them. The cross-server → origin/server namespacing direction in the thread is right; it just needs to be normative.

  2. Skill metadata should leave room for a provenance / detection annotation that the resolver can consult — without the spec binding to any particular scanner or trust authority. A thin, optional hook (e.g. a signature/attestation or a detection-result field a resolver MAY honor) lets an open detection layer attach without putting trust policy into the transport spec. (Disclosure: I maintain one such open ruleset, so I'm biased toward keeping that seam open — but the seam matters regardless of who fills it.)

Happy to share the collision cases from the dataset if they'd help stress-test the resolution rules.

Co-authored-by: Den Delimarsky <53200638+localden@users.noreply.github.com>

@localden localden left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two additions clarifying that digests are not a security boundary.

Comment thread seps/2640-skills-extension.md Outdated
Comment thread seps/2640-skills-extension.md
pja-ant and others added 2 commits June 25, 2026 14:39
Co-authored-by: Den Delimarsky <53200638+localden@users.noreply.github.com>
Co-authored-by: Den Delimarsky <53200638+localden@users.noreply.github.com>
@eeee2345

This comment has been minimized.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

draft SEP proposal with a sponsor. extension SEP

Projects

Status: No status
Status: In review

Development

Successfully merging this pull request may close these issues.