Skip to content

SEP-2640: Skills Extension#2640

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

SEP-2640: Skills Extension#2640
pja-ant wants to merge 28 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 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

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) <[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]>
@wachterjohannes

Copy link
Copy Markdown

Feedback on resources/directory/read from implementing the server side (reference implementation in modelcontextprotocol/php-sdk#372, plus the symfony/ai Mate host). I know the WG is mid-review and heads-down on the June release, so these are just three concrete things that tripped me up while wiring this method up — no rush.

1. The error code mis-cites base resources/read

In Directory Listing → Semantics:

If the URI does not exist, or exists but is not a directory resource, the server MUST return error -32602 (Invalid params) — the same code resources/read uses for unknown resources.

Two problems:

  1. The parenthetical is incorrect about the base protocol. resources/read returns -32002 (Resource not found) for an unknown URI, not -32602. So this method actually deviates from resources/read while claiming to match it. If the intent is symmetry, the not-found case should be -32002.
  2. It overloads one code for two genuinely different conditions — URI absent vs. URI exists but is not a directory — with no way for a client to distinguish them. A client that just walked a tree and hit a stale/renamed path wants to tell "gone" from "not a directory" without parsing the message string.

Suggestion: return -32002 for a nonexistent URI (matching resources/read), and reserve -32602 for a genuinely malformed request (e.g. missing uri). If "exists but not a directory" warrants its own signal, state it explicitly rather than folding it into Invalid params.

2. The directory-root URI is specified twice, inconsistently

This determines the exact string a client passes to resources/directory/read:

  • Resource Mapping: "the skill's root directory is the URI obtained by stripping the trailing SKILL.md" — stripping SKILL.md from skill://acme/billing/refunds/SKILL.md yields skill://acme/billing/refunds/ with a trailing slash.
  • Directory Listing → Directory resources: "Directory URIs are written without a trailing slash" — giving skill://acme/billing/refunds without it.

So a client that derives the root per Resource Mapping and then calls resources/directory/read on it passes a URI the directory section calls non-canonical — and URI equality / cache keys differ by one byte. Please pick one canonical form and make both sections agree; I'd lean no-trailing-slash to match the inode/directory resources, and reword Resource Mapping to say the root is the parent-directory URI of SKILL.md rather than a literal string strip.

3. directoryRead does not apply to archive-only skills — worth stating

Per §Archives, a host that fetched a skill via an archive MUST serve directory navigation from its local unpacked tree, so it MUST NOT call resources/directory/read even when the server advertises directoryRead; an archive-only skill exposes no server-side directory resources at all. The method is described purely in terms of "the skill namespaces it serves as individual files," which is technically correct but easy to miss — I briefly expected it to work uniformly across delivery modes. One sentence ("archive-only skills expose no directory resources; navigation happens against the host's local unpack") would prevent the double-take, and makes clear an archive-serving server gets directory navigation for free without implementing the method at all.

@wachterjohannes

Copy link
Copy Markdown

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 resources/directory/read above. These are about the discovery index and integrity model.

1. Frontmatter "verbatim JSON" + field-by-field re-verification isn't interoperably implementable as written

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

  • Frontmatter: "the skill's SKILL.md YAML frontmatter rendered verbatim as a JSON object" and "MUST be identical in content to the frontmatter of the SKILL.md it describes."
  • Integrity: the host "MUST parse its YAML frontmatter and compare it field-by-field … Any discrepancy MUST be treated as a verification failure … the skill MUST NOT be loaded."

The index frontmatter is JSON; SKILL.md carries YAML. Comparing them means parsing both to a value model, and YAML→value is not canonical. Concrete cases where a conforming server and host diverge on valid frontmatter and then hard-reject the skill:

  • YAML 1.1 vs 1.2 booleans: active: yes / on / off → boolean in 1.1, string in 1.2; octal-looking 010 (8 vs 10).
  • Scalar typing: version: 2.10 is the float 2.1; version: "2.1.0" is a string — the SEP's own example ships "version": "2.1.0".
  • Null forms: empty value, ~, null, Null all mean null; a naive server may emit "".
  • Block scalars / chomping: description: | trailing-newline handling differs between emitters.
  • Key order / duplicate keys: JSON objects are unordered, yet "verbatim" implies order is preserved; on duplicate keys JSON keeps the last, YAML libraries vary or error.
  • Tags / anchors (!!str, &/*) widen the differential further.

The security teeth: because equality is undefined, a server can ship index frontmatter the host parses (and shows the user) one way while SKILL.md parses another — passing a naive field-by-field check yet not being "identical in content." The intent is right; the mechanism turns exotic-but-legal YAML into both an interop failure and a smuggling vector.

Two ways to tighten (either works):

  1. Define the comparison on parsed values, not text: both sides parse under the YAML 1.2 core schema into the JSON data model and compare by deep structural equality with object key order insignificant; mandate duplicate-key rejection (the cheapest smuggling vector). Then frontmatter is "the JSON encoding of the parsed frontmatter," not a byte-preserving copy.
  2. Or narrow the hard-fail to the fields that gate approval/registry — name and description (both spec'd as strings) — and treat the rest as advisory/preview. The SKILL.md digest already pins the authoritative bytes, so a full-tree MUST-reject over arbitrary author YAML buys little beyond fragility.

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 files

The 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 resources/read") is airtight for archive-delivered skills, but it quietly exposes an asymmetry for direct entries.

For a url+digest entry, digest covers only SKILL.md (§Integrity: "for a skill entry's digest, the artifact is the SKILL.md file"). There is no integrity coverage for supporting files — references/GUIDE.md, scripts/extract.py are each fetched by a live resources/read with nothing in the index to verify them against.

That interacts badly with Content-bound approval: a user approves a skill bound to the SKILL.md digest, but the bytes that actually drive behavior — the script the model is told to run — are unpinned and can differ on every read, including from a transport intermediary, without invalidating the approval. §Archives already admits this ("the archive digest is the only integrity check covering supporting files"), which is exactly why a direct-entry skill's guarantee is weaker than a host author would assume from "Hosts MUST verify retrieved content against the digest."

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 archives[] form when content-bound approval is in force.

3. Non-skill:// schemes make the index a de-facto MUST, which contradicts "enumeration is optional"

Resource Mapping says a server MAY use a native scheme: "A server MAY instead serve skills under another scheme native to its domain (e.g., github://…), provided each skill is listed in the skill://index.json resource. […] outside the index, hosts recognize skills by the skill:// scheme prefix."

That "provided" clause is a hard requirement in disguise: for a non-skill:// server, an unindexed skill is unrecognizable — nothing marks it as a skill. So for these servers the index is effectively a MUST.

But Enumeration keeps it at SHOULD and explicitly permits declining: "A server … MAY decline to expose skill://index.json, or MAY expose a partial index."

These collide: a github:// server that declines the index (or ships a partial one) has skills no host can discover or recognize, yet the spec says both are allowed. Suggest making the conditional MUST explicit — e.g. "A server using a non-skill:// scheme MUST list every such skill in skill://index.json" — and carving it out of the optional-enumeration language so the two sections stop contradicting each other.

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
pja-ant added 3 commits July 8, 2026 12:41
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
Comment thread seps/2640-skills-extension.md Outdated

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.

@m-paternostro m-paternostro Jul 10, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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/skills extension, the skills enumerated by skill://index.json or, if that's not available, the resources indicated by the server via different mechanisms like "server instructions to list all skill:// resources.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Thank you! This clarifies that I was conflating enumeration with explicit references. Also, I asked the question before the change to useskill/list

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

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

@m-paternostro m-paternostro Jul 10, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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?

@sep-automation-bot

Copy link
Copy Markdown

Maintainer Activity Check

Hi @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:

  • Current status of your review/work
  • Any blockers or concerns
  • Expected timeline for next steps

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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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?

@aditya-scio aditya-scio Jul 15, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Addressed in the next commit

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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
@S-Reagan

Copy link
Copy Markdown

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: {digest, verdict, timestamp, verifier, scanner/policy version}, signed. Content-addressing gets you integrity and content-privacy for free, it reuses the digest 2640 already defines, and it can ride the same JWS/Sigstore stack as the SEPs above instead of a new scheme.

Three things worth getting right for skills specifically:

  • The digest has to cover the whole skill, not just SKILL.md. The referenced files carry the same injection risk, so the verdict needs to bind to everything that got scanned.
  • Many verdicts per digest, across verifiers and over time. Timestamp and scanner version matter, and multiple attestations on one digest is the point, not a conflict.
  • Verdicts stay out of the card and out of inline _meta: signed, content-addressed, looked up, not embedded. A freeform governance _meta namespace doesn't really buy much without a shared schema and a signature behind it anyway.

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

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.