docs: sync SEP copy with v1 and add AGENTS.md (#117)
* docs: sync repo SEP copy with canonical v1 text The repo-local copy of SEP-2640 had drifted well behind the canonical text on the sep/skills-extension branch, which was a source of confusion: it still specified the well-known skill://index.json resource and predated skills/list, skills/get, resources/directory/read, nested skills, and per-file digests. Replace the body with a verbatim copy of the canonical SEP as of d7490ec (2026-07-15) and swap the stale "not actively maintained" notice for a disclaimer that states what this file now is: a synced snapshot, re-synced by overwriting rather than hand-editing, with review on the upstream PR and anything beyond v1 raised as a PR in this repository. Also drop "working draft" from the README and related-work references to this file, since it is no longer where the text is drafted. * docs: frame the SEP copy as the v1 baseline for decision-log proposals The previous note told readers not to propose changes here at all, which is the wrong instruction: the WG does want to propose beyond v1, just in this repo rather than on the upstream SEP. Reframe the file as the v1 baseline and route proposals through the existing decision log rather than a parallel mechanism. decisions.md already carries the ADR-lite format, the Proposed/Accepted/Superseded status idiom, and the precedent for consolidating a version's scope into one record (the 2026-07-16 v1 entry), so a change beyond v1 is simply the next dated entry. The note also defines the marker that ties an amended section back to its record, so v1 text stays distinguishable from accepted deltas as the document accumulates them. Align the README and related-work references to the same framing. * docs: mark the 2026-04-16 URI decision superseded in part Every other decision the v1 scope-down changed carries a forward pointer in its status line (2026-04-19, 2026-06-02, 2026-06-05, 2026-06-09). The 2026-04-16 URI entry was left as plain Accepted while three of its bullets no longer hold, so it reads as current guidance for index.json enumeration and the no-nesting rule. Give it the same treatment, naming which bullets changed and which parts of the URI convention carry forward, so the entry stays useful as a record of the structure it settled. * docs: add AGENTS.md and make the SEP PR link prominent Readers were landing on the repo-local SEP copy and taking it for the spec, which is how the stale version misled people in the first place. Say plainly, in the places they land, that the v1 spec text lives on modelcontextprotocol#2640: - README: a source-of-truth line at the top of the link block, above the charter. - The baseline copy: lead the header note with the PR rather than mentioning it mid-paragraph. - AGENTS.md: state the rule for agents working in this repo, along with how to propose beyond v1 via the decision log and the repo's markdown conventions. CLAUDE.md is a one-line pointer to AGENTS.md rather than a symlink, so it resolves on Windows checkouts too, where symlinks need Developer Mode and would otherwise land as a stub. * docs: use the @AGENTS.md import in CLAUDE.md A markdown link is not an import — Claude Code would have loaded the pointer line and never expanded AGENTS.md into context. The documented syntax is @path, which the docs specifically recommend over a symlink on Windows, where symlinks need Administrator privileges or Developer Mode. The maintainer note is a block-level HTML comment, which Claude Code strips before injecting the file, so it costs no context while staying visible to anyone opening the file. https://code.claude.com/docs/en/memory#agents-md * docs: tighten AGENTS.md to the source-of-truth rule Cut the project overview and conventions sections and lead with what an agent most needs to get right: the v1 spec text is on the PR, and the copy here is only as current as its pinned commit. CONTRIBUTING.md already covers participation and process.
This commit is contained in:
parent
64db7eaf72
commit
c00ca72d94
6 changed files with 471 additions and 107 deletions
17
AGENTS.md
Normal file
17
AGENTS.md
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
## What to reference as a source
|
||||
|
||||
**IMPORTANT**: The v1 SEP source of truth is the PR, not this repository. The specification text for the Skills Extension lives on **[SEP-2640](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2640)** (`modelcontextprotocol/modelcontextprotocol`, branch [`sep/skills-extension`](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/sep/skills-extension/seps/2640-skills-extension.md)). That PR is authoritative.
|
||||
|
||||
[`docs/sep-draft-skills-extension.md`](docs/sep-draft-skills-extension.md) is a verbatim copy of the v1 baseline, synced from that branch at a commit pinned in the file's header. It exists so Working Group discussion, meeting notes, and decision records can quote and link stable text.
|
||||
|
||||
- **Do not answer questions about what the spec says from this repository alone.** The copy is only as current as its last sync — check the pinned commit in the file header against the canonical branch before relying on it.
|
||||
|
||||
## Proposing changes beyond v1
|
||||
|
||||
Changes beyond v1 are proposed here rather than added to the upstream SEP. The vehicle is a dated entry in [`docs/decisions.md`](docs/decisions.md) with `**Status:** Proposed`, in the ADR-lite format used throughout that file (Status / Context / Decision / Rationale / References) — the PR carrying that entry is the proposal.
|
||||
|
||||
Once a proposal is accepted, the resulting spec text is applied to the baseline copy and that section is marked with a pointer back to its record. A section without such a marker is v1 as it stands upstream.
|
||||
|
||||
When a decision supersedes or amends an earlier one, add a forward pointer to the earlier entry's `**Status:**` line rather than rewriting the entry. The log is an auditable trace of the group's reasoning over time, not a snapshot of current state.
|
||||
|
||||
See [CONTRIBUTING.md](CONTRIBUTING.md) for participation, meetings, decision-making authority, and full decision-log guidance.
|
||||
10
CLAUDE.md
Normal file
10
CLAUDE.md
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
@AGENTS.md
|
||||
|
||||
<!--
|
||||
AGENTS.md is the single source of truth for agent instructions in this repository — edit that file, not this one.
|
||||
|
||||
The line above is a Claude Code import: it expands AGENTS.md into context at session start. A symlink would also work, but it requires Administrator privileges or Developer Mode on Windows, so the import is used instead.
|
||||
https://code.claude.com/docs/en/memory#agents-md
|
||||
|
||||
Claude-specific instructions, if any are ever needed, go below the import.
|
||||
-->
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
> ⚠️ **Experimental** — This repository is an incubation space for the Skills Over MCP Working Group. Contents are exploratory and do not represent official MCP specifications or recommendations.
|
||||
|
||||
**📄 SEP-2640 (Skills Extension):** [modelcontextprotocol#2640](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2640) — **the source of truth for the v1 spec text.** Review and comments belong on the PR. The copy in this repo is a synced baseline for discussion; see [`docs/sep-draft-skills-extension.md`](docs/sep-draft-skills-extension.md).
|
||||
**Charter:** [modelcontextprotocol.io/community/skills-over-mcp/charter](https://modelcontextprotocol.io/community/skills-over-mcp/charter) — mission, scope, membership, active work items, and success criteria.
|
||||
**Project board:** [Skills Over MCP WG](https://github.com/orgs/modelcontextprotocol/projects/38/views/1)
|
||||
**Meeting notes:** [Skills Over MCP WG discussions](https://github.com/modelcontextprotocol/modelcontextprotocol/discussions/categories/meeting-notes-skills-over-mcp-wg)
|
||||
|
|
@ -35,7 +36,7 @@ See [problem-statement.md](docs/problem-statement.md) for full details.
|
|||
| [Why Skills Over MCP?](docs/why-and-when.md) | Value proposition and decision guide |
|
||||
| [Use Cases](docs/use-cases.md) | Key use cases driving this work |
|
||||
| [Approaches](docs/approaches.md) | Approaches being explored (not mutually exclusive) |
|
||||
| [SEP-2640: Skills Extension](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2640) | Proposal to serve skills over MCP via the Resources primitive. Original working draft: [`docs/sep-draft-skills-extension.md`](docs/sep-draft-skills-extension.md). Comments and review happen on the PR. |
|
||||
| [SEP-2640: Skills Extension](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2640) | Proposal to serve skills over MCP via the Resources primitive. The v1 baseline text is kept at [`docs/sep-draft-skills-extension.md`](docs/sep-draft-skills-extension.md), synced from the canonical SEP. Comments and review of v1 happen on the PR; changes beyond v1 are proposed as entries in the [decision log](docs/decisions.md). |
|
||||
| [Open Questions](docs/open-questions.md) | Unresolved questions with community input (see also [issues](https://github.com/modelcontextprotocol/experimental-ext-skills/issues) and [meeting notes](https://github.com/modelcontextprotocol/modelcontextprotocol/discussions/categories/meeting-notes-skills-over-mcp-wg)) |
|
||||
| [Experimental Findings](docs/experimental-findings.md) | Results from implementations and testing |
|
||||
| [Related Work](docs/related-work.md) | SEPs, implementations, and external resources |
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ For background on the ADR format, see [adr.github.io](https://adr.github.io/).
|
|||
|
||||
### 2026-04-16: URI scheme conventions for skills as resources
|
||||
|
||||
**Status:** Accepted
|
||||
**Status:** Superseded in part — three of the bullets below were changed in SEP-2640 v1: enumeration moved from a `skill://index.json` resource to a `skills/list` method, the no-nesting constraint was reversed (nested skills are permitted, gated on fresh activation consent), and `skill://` was made non-privileged rather than the marker of what counts as a skill. See the 2026-07-16 v1 scope entry below, items 2, 6, and 8. The URI structure itself carries forward unchanged: explicit `SKILL.md`, final path segment equal to the skill `name`, optional organizational prefix, authority segment without special semantics.
|
||||
|
||||
**Context:** Several independent MCP implementations (FastMCP 3.0, NimbleBrain, skilljack-mcp, skills-over-mcp, etc.) had converged on using Resources to represent skills using either `skill://` or domain-specific URI schemes, but diverged on the rest of the URI structure. This included variations around whether to use an authority segment, whether `SKILL.md` is explicit in the URI, how to address sub-resources, and how the URI path relates to the skill's frontmatter `name`. A survey of these patterns was published in [`skill-uri-scheme.md`](skill-uri-scheme.md) and informed the draft [Skills Extension SEP (#69)](https://github.com/modelcontextprotocol/experimental-ext-skills/pull/69). The path↔name relationship went through two drafts before settling: the first required a single path segment equal to the `name`, which broke for servers needing hierarchy (e.g., `acme/billing/refunds` vs. `acme/support/refunds`); a second draft fully decoupled path from `name`, which was too loose — a URI like `skill://a/b/c/SKILL.md` revealed nothing about what the skill was called without a frontmatter round trip.
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
| Proposal | Venue | Description |
|
||||
| :--- | :--- | :--- |
|
||||
| [SEP-2640 (Skills Extension)](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2640) | MCP Spec | This WG's proposed extension: skills served over MCP using the Resources primitive and `skill://` URI scheme ([working draft](sep-draft-skills-extension.md)) |
|
||||
| [SEP-2640 (Skills Extension)](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2640) | MCP Spec | This WG's proposed extension: skills served over MCP using the Resources primitive and `skill://` URI scheme ([v1 baseline text](sep-draft-skills-extension.md)) |
|
||||
| [PR #2527](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2527) | MCP Spec | Recommend clients expose resource read to models — prerequisite for the resources-based skills approach |
|
||||
| [skills.json format proposal](https://github.com/modelcontextprotocol/registry/discussions/895) | MCP Registry | Skills metadata in registry schema |
|
||||
| ~~[SEP-2093](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2093)~~ | MCP Spec | ~~Resource Contents Metadata and Capabilities: scoped `resources/list`, per-resource capabilities, `resources/metadata` endpoint~~ — **rejected** ([labeled upstream](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2093)) |
|
||||
|
|
|
|||
|
|
@ -1,34 +1,48 @@
|
|||
# SEP-2640: Skills Extension
|
||||
|
||||
- **Status**: Draft (submitted)
|
||||
- **Status**: Draft
|
||||
- **Type**: Extensions Track
|
||||
- **Created**: 2026-03-17
|
||||
- **Author(s)**: Skills Over MCP Working Group
|
||||
- **Sponsor**: _(seeking)_
|
||||
- **Created**: 2026-04-23
|
||||
- **Author(s)**: Peter Alexander (@pja-ant), Ola Hungerford (@olaservo), Sambhav Kothari (@sambhav), on behalf of the Skills Over MCP Working Group
|
||||
- **Sponsor**: @pja-ant
|
||||
- **Extension Identifier**: `io.modelcontextprotocol/skills`
|
||||
- **PR**: [#2640](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2640)
|
||||
- **PR**: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2640
|
||||
|
||||
> ℹ️ **This repo-local copy is not actively maintained and may be out of date.** The canonical version of this SEP lives on the main MCP repository as [SEP-2640](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2640) — refer to it for the current text, and direct review and comments there. Additional discussion welcome via [GitHub Issues](https://github.com/modelcontextprotocol/experimental-ext-skills/issues) or [Discord #skills-over-mcp-wg](https://discord.com/channels/1358869848138059966/1464745826629976084).
|
||||
> # 📄 Source of truth: [modelcontextprotocol#2640](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2640)
|
||||
>
|
||||
> **The v1 spec text lives on that PR. Review and comments belong there, not on this copy.**
|
||||
>
|
||||
> This file is the v1 baseline: a verbatim copy of the canonical SEP at commit [`d7490ec`](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/d7490ecd1a250f7bc8c3ebb0d65450dfec274bad/seps/2640-skills-extension.md) (2026-07-15) on the [`sep/skills-extension`](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/sep/skills-extension/seps/2640-skills-extension.md) branch, kept here so Working Group discussion and decision records can quote and link stable text. It is only as current as that pinned commit. For what v1 comprises and why, see the 2026-07-16 v1 scope entry in the [decision log](decisions.md).
|
||||
>
|
||||
> **To propose a change beyond v1**, add a dated entry to [`decisions.md`](decisions.md) with **Status: Proposed**, in the ADR-lite format used throughout that file — the PR carrying that entry is the proposal. Once a proposal is accepted, the resulting spec text is applied to this document and its section marked with a pointer to the record:
|
||||
>
|
||||
> ```markdown
|
||||
> > **Beyond v1** — [2026-08-04: Batch resource read](decisions.md) · Proposed
|
||||
> ```
|
||||
>
|
||||
> A section without such a marker is v1 as it stands upstream. Re-sync by overwriting the unmarked text from the canonical file and updating the commit reference above.
|
||||
>
|
||||
> Discussion: [GitHub Issues](https://github.com/modelcontextprotocol/experimental-ext-skills/issues) · [Discord #skills-over-mcp-wg](https://discord.com/channels/1358869848138059966/1464745826629976084)
|
||||
|
||||
## 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.
|
||||
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 `skills/list` method enumerates the skills a server serves — servers whose skill catalogs are large, generated, or otherwise unenumerable MAY return an empty or partial listing — and a `skills/get` method returns any single skill's entry by URI. 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.
|
||||
|
||||
Because the extension adds no new protocol methods or capabilities, hosts that already treat MCP resources as a virtual filesystem can consume MCP-served skills identically to local filesystem skills. The specification is accompanied by implementation guidelines for host-provided resource-reading tools and SDK-level convenience wrappers.
|
||||
The extension defines three protocol methods. Every server declaring the extension implements `skills/list`, which enumerates the skills a server serves, and `skills/get`, which returns the entry for a single skill by URI — including skills absent from the listing. The optional `resources/directory/read` lists the direct children of a directory resource, giving agents scoped navigation of a skill's supporting files. Everything else rides on existing protocol surface, so hosts that already treat MCP resources as a virtual filesystem can consume MCP-served skills identically to local filesystem skills. The specification is accompanied by implementation guidelines for host-provided resource-reading tools and SDK-level convenience wrappers.
|
||||
|
||||
## Motivation
|
||||
|
||||
Native skills support in host applications demonstrates strong demand for rich, progressively disclosed workflow instructions. MCP does not currently offer a conventional way to ship this content alongside the tools it describes, which leads to:
|
||||
|
||||
- **Fragmented distribution.** A server and the skill that teaches an agent to use it are versioned, discovered, and installed separately. Users installing a server from a registry have no signal that a companion skill exists. ([problem-statement.md](problem-statement.md))
|
||||
- **Instruction size limits.** Server instructions load once at initialization and are practically bounded in size. Complex workflows — such as the 875-line [mcpGraph skill](https://github.com/TeamSparkAI/mcpGraph/blob/main/skills/mcpgraphtoolkit/SKILL.md) — do not fit this model. ([experimental-findings.md](experimental-findings.md#mcpgraph-skills-in-mcp-server-repo))
|
||||
- **Inconsistent ad-hoc solutions.** Absent a convention, four independent implementations have each invented their own `skill://` URI structure, with diverging semantics for authority, path, and sub-resource addressing.
|
||||
- **Fragmented distribution.** A server and the skill that teaches an agent to use it are versioned, discovered, and installed separately. Users installing a server from a registry have no signal that a companion skill exists. ([problem statement](https://github.com/modelcontextprotocol/experimental-ext-skills/blob/main/docs/problem-statement.md))
|
||||
- **Instruction size limits.** Server instructions load once at initialization and are practically bounded in size. Complex workflows — such as the 875-line [mcpGraph skill](https://github.com/TeamSparkAI/mcpGraph/blob/main/skills/mcpgraphtoolkit/SKILL.md) — do not fit this model. ([experimental findings](https://github.com/modelcontextprotocol/experimental-ext-skills/blob/main/docs/experimental-findings.md#mcpgraph-skills-in-mcp-server-repo))
|
||||
- **Inconsistent ad-hoc solutions.** Absent a convention, several independent implementations have each invented their own `skill://` URI structure, with diverging semantics for authority, path, and sub-resource addressing.
|
||||
|
||||
## Specification
|
||||
|
||||
### Dependencies
|
||||
|
||||
This extension has no dependencies beyond the base MCP Resources primitive.
|
||||
This extension has no dependencies beyond the base MCP Resources primitive. In protocol versions 2026-07-28 and later, `skills/list` results additionally carry the base protocol's list-caching attributes ([SEP-2549]).
|
||||
|
||||
### Skill Format
|
||||
|
||||
|
|
@ -49,10 +63,6 @@ Each file within a skill directory is exposed as an MCP resource. Servers SHOULD
|
|||
skill://<skill-path>/<file-path>
|
||||
```
|
||||
|
||||
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 below — `<skill-path>` ending in the skill name, `SKILL.md` explicit in the URI, no nesting — apply regardless of scheme.
|
||||
|
||||
where:
|
||||
|
||||
- `<skill-path>` is a `/`-separated path of one or more segments locating the skill directory within the server's skill namespace. It MAY be a single segment (`git-workflow`) or nested to arbitrary depth (`acme/billing/refunds`).
|
||||
|
|
@ -64,20 +74,24 @@ The final segment of `<skill-path>` MUST equal the skill's `name` as declared in
|
|||
|
||||
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.
|
||||
- A `SKILL.md` MAY appear in a descendant directory of a skill — skills can nest. See [Nested skills](#nested-skills).
|
||||
- The final `<skill-path>` segment, being the skill `name`, MUST satisfy the Agent Skills specification's naming rules. Prefix segments SHOULD be valid URI path segments per [RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986); no further constraints are imposed on them.
|
||||
|
||||
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 serve skills under another scheme native to its domain (e.g., `github://owner/repo/skills/refunds/SKILL.md`). No scheme is privileged: the structural constraints above — `<skill-path>` ending in the skill name, `SKILL.md` explicit in the URI — apply regardless of scheme, and [`skills/list`](#enumeration-via-skillslist) enumerates a server's skills whatever scheme they use.
|
||||
|
||||
Skill identity does not depend on the scheme. A host learns that a resource is a skill in one of two ways: from a [`skills/list`](#enumeration-via-skillslist) entry, the authoritative record of the skills a server publishes; or from an explicit reference — the server's `instructions` field, another skill, or the user — which [`skills/get`](#retrieval-via-skillsget) confirms, the server answering for a skill it serves and erroring otherwise. This holds for every scheme, `skill://` included. A host MUST NOT conclude that a resource is a skill merely because its URI carries a particular scheme.
|
||||
|
||||
#### Examples
|
||||
|
||||
| Skill path | File | Resource URI |
|
||||
|---|---|---|
|
||||
| `git-workflow` | `SKILL.md` | `skill://git-workflow/SKILL.md` |
|
||||
| `pdf-processing` | `references/FORMS.md` | `skill://pdf-processing/references/FORMS.md` |
|
||||
| `pdf-processing` | `scripts/extract.py` | `skill://pdf-processing/scripts/extract.py` |
|
||||
| `acme/billing/refunds` | `SKILL.md` | `skill://acme/billing/refunds/SKILL.md` |
|
||||
| `acme/billing/refunds` | `templates/email.md` | `skill://acme/billing/refunds/templates/email.md` |
|
||||
| Skill path | File | Resource URI |
|
||||
| ---------------------- | --------------------- | ------------------------------------------------ |
|
||||
| `git-workflow` | `SKILL.md` | `skill://git-workflow/SKILL.md` |
|
||||
| `pdf-processing` | `references/FORMS.md` | `skill://pdf-processing/references/FORMS.md` |
|
||||
| `pdf-processing` | `scripts/extract.py` | `skill://pdf-processing/scripts/extract.py` |
|
||||
| `acme/billing/refunds` | `SKILL.md` | `skill://acme/billing/refunds/SKILL.md` |
|
||||
| `acme/billing/refunds` | `examples/email.md` | `skill://acme/billing/refunds/examples/email.md` |
|
||||
|
||||
#### Resource Metadata
|
||||
|
||||
|
|
@ -89,61 +103,220 @@ For each `skill://<skill-path>/SKILL.md` resource:
|
|||
|
||||
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.
|
||||
|
||||
#### Nested skills
|
||||
|
||||
A skill directory MAY contain further skills in descendant directories. A nested skill is subject to the same rules as any other skill — its directory name is its `name`, and the enclosing skill's path becomes part of its organizational prefix — with the following semantics:
|
||||
|
||||
- **Nested content is supporting content.** From the enclosing skill's perspective, a nested skill's directory and files are ordinary supporting files, and reading them is ordinary reading. A nested `SKILL.md` read this way is ordinary markdown: hosts MUST NOT act on its frontmatter.
|
||||
- **Activation requires fresh consent.** Approval is per skill: approving a skill approves that skill alone and says nothing about skills nested within it. Activating a nested skill — loading it as a skill in its own right, whether through the host's skill-loading machinery or by giving effect to its frontmatter — requires fresh, explicit user consent; approval of the enclosing skill does not substitute for it. Once activated, a nested skill is an ordinary skill: its frontmatter takes effect under the same rules as any other MCP-served skill, including the approval gate on `allowed-tools`.
|
||||
- **Publication is flat.** A nested skill is published like any other: through its own [`skills/list`](#enumeration-via-skillslist) entry, or by explicit reference. The listing remains flat — an entry for a nested skill is an ordinary entry whose `uri` happens to share a path prefix with the enclosing skill's, and nothing in the listing marks nesting.
|
||||
|
||||
### Discovery
|
||||
|
||||
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: Model-Driven Resource Loading](#hosts-model-driven-resource-loading)). This is the baseline: if a model has the URI — from server instructions, from another skill, from the user — it can read the skill.
|
||||
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 listing, and hosts MUST support loading a skill given only its URI (see [Hosts: End-to-End Integration](#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.
|
||||
|
||||
On top of that baseline, two discovery mechanisms are defined. A server MAY support either or both.
|
||||
On top of that baseline, three mechanisms are defined. Two are discovery: enumeration via `skills/list`, which every server declaring this extension implements, and an optional pointer from server instructions. The third is retrieval: however a host arrives at a skill's URI, `skills/get` returns that skill's entry — its metadata and digests — including for skills no listing mentions.
|
||||
|
||||
#### Enumeration via `skill://index.json`
|
||||
#### Enumeration via `skills/list`
|
||||
|
||||
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. The index format follows the [Agent Skills well-known URI discovery index](https://agentskills.io/well-known-uri#index-format), with two differences: the `url` field contains a full MCP resource URI (any scheme the server serves), and the `digest` field is omitted (integrity is the transport's concern over an authenticated MCP connection). This binding also defines one additional `type` value, `"mcp-resource-template"`, for entries that describe a parameterized skill namespace.
|
||||
A server declaring the `io.modelcontextprotocol/skills` extension MUST implement the `skills/list` method, which returns the skills it serves. The result MAY be empty.
|
||||
|
||||
The request carries an optional pagination cursor:
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://schemas.agentskills.io/discovery/0.2.0/schema.json",
|
||||
"skills": [
|
||||
{
|
||||
"name": "git-workflow",
|
||||
"type": "skill-md",
|
||||
"description": "Follow this team's Git conventions for branching and commits",
|
||||
"url": "skill://git-workflow/SKILL.md"
|
||||
},
|
||||
{
|
||||
"name": "refunds",
|
||||
"type": "skill-md",
|
||||
"description": "Process customer refund requests per company policy",
|
||||
"url": "skill://acme/billing/refunds/SKILL.md"
|
||||
},
|
||||
{
|
||||
"type": "mcp-resource-template",
|
||||
"description": "Per-product documentation skill",
|
||||
"url": "skill://docs/{product}/SKILL.md"
|
||||
}
|
||||
]
|
||||
"jsonrpc": "2.0",
|
||||
"id": 4,
|
||||
"method": "skills/list",
|
||||
"params": {}
|
||||
}
|
||||
```
|
||||
|
||||
Index fields:
|
||||
The result carries the skill entries:
|
||||
|
||||
| Field | Required | Description |
|
||||
|---|---|---|
|
||||
| `$schema` | Yes | Schema version URI. Clients SHOULD match against known URIs before processing. |
|
||||
| `skills` | Yes | Array of skill entries. |
|
||||
| `skills[].type` | Yes | MUST be `"skill-md"` or `"mcp-resource-template"`. Archive distribution does not apply; supporting files are individually addressable as resources. |
|
||||
| `skills[].description` | Yes | For `"skill-md"`, the skill's `description` matching its `SKILL.md` frontmatter. For `"mcp-resource-template"`, a description of the addressable skill space. |
|
||||
| `skills[].url` | Yes | For `"skill-md"`, the full resource URI of the skill's `SKILL.md`. For `"mcp-resource-template"`, an [RFC 6570](https://datatracker.ietf.org/doc/html/rfc6570) URI template that resolves to `SKILL.md` resource URIs. |
|
||||
| `skills[].name` | Conditional | Required for `"skill-md"`; matches the `SKILL.md` frontmatter `name` and the final path segment. Omitted for `"mcp-resource-template"`. |
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 4,
|
||||
"result": {
|
||||
"skills": [
|
||||
{
|
||||
"uri": "skill://git-workflow/SKILL.md",
|
||||
"frontmatter": {
|
||||
"name": "git-workflow",
|
||||
"description": "Follow this team's Git conventions for branching and commits"
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"uri": "skill://git-workflow/SKILL.md",
|
||||
"digest": "sha256:a1b2c3d4..."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"uri": "skill://acme/billing/refunds/SKILL.md",
|
||||
"frontmatter": {
|
||||
"name": "refunds",
|
||||
"description": "Process customer refund requests per company policy",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"uri": "skill://acme/billing/refunds/SKILL.md",
|
||||
"digest": "sha256:b2c3d4e5..."
|
||||
},
|
||||
{
|
||||
"uri": "skill://acme/billing/refunds/examples/email.md",
|
||||
"digest": "sha256:c3d4e5f6..."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"uri": "skill://pdf-processing/SKILL.md",
|
||||
"frontmatter": {
|
||||
"name": "pdf-processing",
|
||||
"description": "Extract, fill, and assemble PDF documents",
|
||||
"metadata": { "version": "2.1.0" }
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"uri": "skill://pdf-processing/SKILL.md",
|
||||
"digest": "sha256:d5e6f7a8..."
|
||||
},
|
||||
{
|
||||
"uri": "skill://pdf-processing/references/FORMS.md",
|
||||
"digest": "sha256:e6f7a8b9..."
|
||||
},
|
||||
{
|
||||
"uri": "skill://pdf-processing/scripts/extract.py",
|
||||
"digest": "sha256:f7a8b9c0..."
|
||||
},
|
||||
{
|
||||
"uri": "skill://pdf-processing/templates/invoice.md",
|
||||
"digest": "sha256:a8b9c0d1..."
|
||||
},
|
||||
{
|
||||
"uri": "skill://pdf-processing/templates/purchase-order.md",
|
||||
"digest": "sha256:b9c0d1e2..."
|
||||
},
|
||||
{
|
||||
"uri": "skill://pdf-processing/templates/regional/eu-invoice.md",
|
||||
"digest": "sha256:c0d1e2f3..."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Clients SHOULD ignore unrecognized fields and SHOULD skip entries with an unrecognized `type`.
|
||||
Result fields:
|
||||
|
||||
**Template entries** (`type: "mcp-resource-template"`) describe a parameterized skill namespace without materializing every entry. A server SHOULD register the same `url` value as an MCP [resource template](https://modelcontextprotocol.io/specification/2025-11-25/server/resources#resource-templates) so hosts can wire template variables to the [completion API](https://modelcontextprotocol.io/specification/2025-11-25/server/utilities/completion). Hosts SHOULD surface template entries in their UI as interactive discovery points: the user fills in variables via completion, selects a skill, and the host passes the resolved URI into the conversation. This scales to servers with unbounded skill catalogs.
|
||||
| Field | Required | Description |
|
||||
| ----------------------------- | -------- | ------------------------------------------------------------------------------------------------------------ |
|
||||
| `skills` | Yes | Array of skill entries. |
|
||||
| `skills[].frontmatter` | Yes | Verbatim copy of the skill's `SKILL.md` YAML frontmatter, rendered as JSON. See [Frontmatter](#frontmatter). |
|
||||
| `skills[].uri` | Yes | Resource URI of the skill's `SKILL.md`. See [Skill URIs](#skill-uris). |
|
||||
| `skills[].resources` | Yes¹ | Complete enumeration of the skill's files with their digests. See [Resources](#resources). |
|
||||
| `skills[].resources[].uri` | Yes | Resource URI of the file. |
|
||||
| `skills[].resources[].digest` | Yes | SHA-256 digest of the file. See [Integrity](#integrity-and-verification). |
|
||||
|
||||
The `skill://index.json` resource is served via `resources/read` like any other resource, with `mimeType` of `application/json`. A server MAY also surface it in `resources/list` so clients can detect its presence, but clients MAY attempt to read it directly without prior discovery.
|
||||
¹ MAY be omitted only for dynamically generated skills whose content cannot be pre-digested. Hosts MAY decline to load skills without `resources`.
|
||||
|
||||
A server whose skill catalog is large, generated on demand, or otherwise unenumerable MAY decline to expose `skill://index.json`, or MAY expose a partial index. Hosts MUST NOT treat an absent or empty index as proof that a server has no skills.
|
||||
Pagination mirrors the base protocol's list methods: the request accepts an optional `cursor`, and when the result includes `nextCursor` the client passes it back to retrieve the next page. An entry is atomic — a skill's `resources` set is never split across pages.
|
||||
|
||||
The URI `skill://index.json` is reserved and does not conflict with any valid `<skill-path>`: skill names may contain only lowercase letters, digits, and hyphens, so `index.json` cannot be a skill name.
|
||||
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.
|
||||
|
||||
##### Names
|
||||
|
||||
A skill's `name` is a label, not an identifier — a skill is identified by its `uri`. Within a server's listing, names SHOULD be unique, but they are not guaranteed to be: two skills at different paths may share a final segment (`acme/billing/refunds` and `acme/support/refunds` are both named `refunds`), and a nested skill may share its name with a top-level one. Hosts MUST NOT assume name uniqueness. When two entries in one listing collide on `name`, hosts MUST disambiguate them — for example by their distinguishing path segments — rather than silently discarding or preferring one. When skills from different origins collide on `name`, hosts MUST resolve the name within a per-origin namespace, identifying servers by a host-assigned label; an MCP-served skill MUST NOT silently shadow, or be silently substituted for, a same-named skill from any other origin — another server's, or the host's own filesystem skills. See [Security Implications](#security-implications).
|
||||
|
||||
##### Frontmatter
|
||||
|
||||
`frontmatter` is the skill's `SKILL.md` YAML frontmatter rendered verbatim as a JSON object — every field the author wrote, not a curated subset. Because the Agent Skills specification requires `name` and `description`, those fields are always present; everything else (`license`, `metadata`, fields added by future revisions of the Agent Skills specification) passes through unchanged. A host can therefore build its skill registry — names, descriptions, and whatever other metadata it understands — from the listing alone, without fetching each `SKILL.md`.
|
||||
|
||||
The `frontmatter` object MUST be identical in content to the frontmatter of the `SKILL.md` it describes. The final `<skill-path>` segment of the entry's `uri` MUST equal `frontmatter.name`, per [Resource Mapping](#resource-mapping).
|
||||
|
||||
Within the frontmatter `metadata` object, keys prefixed with `io.modelcontextprotocol/` are reserved for metadata defined by MCP extensions. This extension currently defines no such keys. Implementations SHOULD ignore keys under this prefix that they do not recognize.
|
||||
|
||||
##### Skill URIs
|
||||
|
||||
`uri` is the full resource URI of the skill's `SKILL.md`, readable via `resources/read`. Supporting files are individually addressable as sibling resources under the same skill path, per [Resource Mapping](#resource-mapping). A skill is always retrieved as individually addressable resources; this extension defines no packed or bundled retrieval form. See [Appendix: Deferred Features](#appendix-deferred-features).
|
||||
|
||||
##### Resources
|
||||
|
||||
`resources` enumerates the skill's files — `SKILL.md` and every supporting file — as `{uri, digest}` pairs. It is the unit of content that a host verifies and that a user's approval binds to:
|
||||
|
||||
- When present, `resources` MUST be complete: it lists every file of the skill, each exactly once, including an entry matching the skill's top-level `uri` — that entry carries the digest of `SKILL.md` itself.
|
||||
- Each `uri` MUST be the skill's `SKILL.md` or a file within the skill's directory.
|
||||
- Completeness extends to nested skills: from the enclosing skill's perspective their files are supporting files ([Nested skills](#nested-skills)), so the enclosing skill's `resources` lists them too, and the same file may appear in both the enclosing and the nested skill's entries. A change to a nested skill is therefore a change to the enclosing skill's set.
|
||||
- `resources` MAY be omitted only when a skill's content is generated dynamically, such that stable digests cannot be published. A skill without `resources` offers no content integrity and cannot be content-bound ([Security Implications](#security-implications)). Hosts MAY decline to load such skills, and server authors SHOULD expect that some hosts will.
|
||||
|
||||
##### 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. Each entry in a skill's `resources` carries the digest of the file at its `uri`.
|
||||
|
||||
When a host retrieves a file listed in a skill's `resources`, it MUST verify the content against that entry's digest. A mismatch means the content is not what the listing promised — corrupted, tampered with, or simply stale because the skill was updated after the listing was fetched. Whatever the cause, hosts MUST NOT use the unverified content; to recover from staleness, call [`skills/get`](#retrieval-via-skillsget) for that skill — or `skills/list` to refresh the catalog — and proceed from the current `resources` set, which, being different, revokes any content-bound approval ([Security Implications](#security-implications)). Because `resources` is complete, an unlisted file is a change to the skill: while acting on a skill for which the host holds an entry, a host MUST resolve reads of the skill's files only to URIs listed in that entry's `resources`, and MUST treat a read of an unlisted file within the skill as a verification failure equivalent to a digest mismatch. Digests also enable efficient caching: compare digests against locally cached values to determine whether a file has changed without re-reading it, or fetch, verify, and cache the entire set at approval time and serve every subsequent read from the verified copy.
|
||||
|
||||
Digests are unsigned and supplied by the same server that supplies the content. A match proves the two are consistent, not that either is trustworthy. Any intermediary on the path, such as a gateway, can rewrite both the listing and the content together. Hosts MUST NOT treat a digest match as a security boundary.
|
||||
|
||||
After fetching a `SKILL.md` for which the host holds an entry, from either `skills/list` or `skills/get` — digest-verified when the entry carries `resources`, and unverifiable when it does not — hosts MUST parse its YAML frontmatter and compare it field-by-field against the entry's `frontmatter`. Any discrepancy MUST be treated as a verification failure equivalent to a digest mismatch, and the skill MUST NOT be loaded. This enforces the [Frontmatter](#frontmatter) identity requirement on the host side, so that what a user approves from the listing is what the model actually receives.
|
||||
|
||||
#### Retrieval via `skills/get`
|
||||
|
||||
A server declaring the `io.modelcontextprotocol/skills` extension MUST also implement the `skills/get` method, which returns the entry for a single skill named by its URI:
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 5,
|
||||
"method": "skills/get",
|
||||
"params": {
|
||||
"uri": "skill://pdf-processing/SKILL.md"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 5,
|
||||
"result": {
|
||||
"skill": {
|
||||
"uri": "skill://pdf-processing/SKILL.md",
|
||||
"frontmatter": {
|
||||
"name": "pdf-processing",
|
||||
"description": "Extract, fill, and assemble PDF documents",
|
||||
"metadata": { "version": "2.1.0" }
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"uri": "skill://pdf-processing/SKILL.md",
|
||||
"digest": "sha256:d5e6f7a8..."
|
||||
},
|
||||
{
|
||||
"uri": "skill://pdf-processing/references/FORMS.md",
|
||||
"digest": "sha256:e6f7a8b9..."
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`params.uri` MUST be the URI of a skill's `SKILL.md`. The `skill` object is a skill entry, identical in shape and meaning to an entry of `skills/list` — the same `uri`, `frontmatter`, and `resources` fields, under the same rules.
|
||||
|
||||
Semantics:
|
||||
|
||||
- If the URI does not identify a skill the server serves, the server MUST return error `-32602` (Invalid params) — the same code `resources/read` uses for unknown resources.
|
||||
- A server MUST answer for every skill it serves, whether or not that skill appears in its `skills/list` result. A skill absent from a partial listing is still retrievable by URI.
|
||||
- The result is a point-in-time snapshot, exactly as a listing entry is. Re-calling the method is how a host refreshes one skill's digests without re-enumerating the catalog.
|
||||
- A skill whose content is generated dynamically omits `resources`, per [Resources](#resources), whether it is reached through `skills/list` or `skills/get`.
|
||||
- The result carries no pagination cursor and no list-caching attributes: a single entry is not a list, and the entry is a snapshot of the skill as the server holds it at that moment.
|
||||
|
||||
The method complements the baseline: a URI alone is enough to read a skill, and `skills/get` turns that same URI into the skill's metadata and digests, so a skill that never appeared in a listing can still be verified and content-bound ([Security Implications](#security-implications)).
|
||||
|
||||
#### Pointer from Server Instructions
|
||||
|
||||
|
|
@ -157,27 +330,119 @@ Per [SEP-2133] extension negotiation, servers declare support for this extension
|
|||
{
|
||||
"capabilities": {
|
||||
"extensions": {
|
||||
"io.modelcontextprotocol/skills": {}
|
||||
"io.modelcontextprotocol/skills": {
|
||||
"directoryRead": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
No extension-specific settings are currently defined; an empty object indicates support.
|
||||
One extension-specific setting is defined:
|
||||
|
||||
| Setting | Type | Default | Meaning |
|
||||
| --------------- | ------- | ------- | ----------------------------------------------------------------------- |
|
||||
| `directoryRead` | boolean | `false` | The server implements [`resources/directory/read`](#directory-listing). |
|
||||
|
||||
An empty object indicates support for the extension with no optional features. Declaring the extension itself commits the server to [`skills/list`](#enumeration-via-skillslist) and [`skills/get`](#retrieval-via-skillsget); clients MUST NOT call `resources/directory/read` against a server that has not declared `directoryRead: true`.
|
||||
|
||||
### Reading
|
||||
|
||||
Skill files are read via the standard `resources/read` method. No skill-specific read semantics are defined.
|
||||
|
||||
Internal references within a skill (e.g., `SKILL.md` linking to `references/GUIDE.md`) are relative paths, as in the filesystem form of the Agent Skills specification. A client resolves a relative reference against the skill's root — `references/GUIDE.md` in `skill://acme/billing/refunds/SKILL.md` resolves to `skill://acme/billing/refunds/references/GUIDE.md` — exactly as a filesystem path would resolve. The skill's root is the directory containing `SKILL.md`, not the `skill://` scheme root.
|
||||
Internal references within a skill (e.g., `SKILL.md` linking to `references/GUIDE.md`) are relative paths, as in the filesystem form of the Agent Skills specification. A client resolves a relative reference against the skill's root — `references/GUIDE.md` in `skill://acme/billing/refunds/SKILL.md` resolves to `skill://acme/billing/refunds/references/GUIDE.md` — exactly as a filesystem path would resolve. The skill's root is the directory containing `SKILL.md`, not the scheme root. When skills nest, each `SKILL.md`'s references resolve against its own directory: a relative reference in a nested skill's `SKILL.md` resolves against the nested skill's root, regardless of how the file was reached.
|
||||
|
||||
### 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 May the Listing Be Empty or Partial?](#why-may-the-listing-be-empty-or-partial)) — 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).
|
||||
|
||||
#### 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.
|
||||
|
||||
#### `resources/directory/read`
|
||||
|
||||
The request carries the directory's URI and an optional pagination cursor. The result carries the resource metadata of the directory's direct children — the same `Resource` objects that `resources/list` returns, with the same `nextCursor` pagination contract.
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 7,
|
||||
"method": "resources/directory/read",
|
||||
"params": {
|
||||
"uri": "skill://pdf-processing/templates"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 7,
|
||||
"result": {
|
||||
"resources": [
|
||||
{
|
||||
"uri": "skill://pdf-processing/templates/invoice.md",
|
||||
"name": "invoice.md",
|
||||
"mimeType": "text/markdown"
|
||||
},
|
||||
{
|
||||
"uri": "skill://pdf-processing/templates/purchase-order.md",
|
||||
"name": "purchase-order.md",
|
||||
"mimeType": "text/markdown"
|
||||
},
|
||||
{
|
||||
"uri": "skill://pdf-processing/templates/regional",
|
||||
"name": "regional",
|
||||
"mimeType": "inode/directory"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Semantics:
|
||||
|
||||
- The method applies only to directory resources. 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.
|
||||
- The result contains every direct child of the directory: files with their ordinary resource metadata, subdirectories listed as directory resources (`mimeType: "inode/directory"`). The listing is not recursive; clients descend by calling the method again on a child directory.
|
||||
- An empty directory yields an empty `resources` array.
|
||||
- Pagination mirrors `resources/list`: when the result includes `nextCursor`, the client passes it back as `cursor` to retrieve the next page.
|
||||
|
||||
A server that declares `directoryRead` MUST support the method for every directory within the skill namespaces it serves as individual files. The method itself is not skill-specific: a server MAY support it on any directory resource it serves, under any scheme.
|
||||
|
||||
## Implementation Guidelines
|
||||
|
||||
The following are recommendations for interoperable implementations. They are not part of the normative specification.
|
||||
|
||||
### Hosts: Model-Driven Resource Loading
|
||||
### Hosts: End-to-End Integration
|
||||
|
||||
Hosts SHOULD expose a tool to the model that reads MCP resources by server and URI, enabling the model to load skill content on demand:
|
||||
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 `skills/list` results from each connected MCP server that declares the `io.modelcontextprotocol/skills` extension. Each registry entry records the skill's `name` and `description` (from the 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.
|
||||
|
||||
**Context.** The host surfaces the `name` and `description` of each enabled registry entry in the model's context — the same list the model already sees for filesystem skills, now with MCP-served entries mixed in. The host's UI presents the same merged list for user inspection and per-skill enable/disable, with provenance shown so users can see which server a skill came from.
|
||||
|
||||
**Loading.** The host exposes a single skill-loading tool to the model, keyed by skill name:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "read_skill",
|
||||
"description": "Load a skill's SKILL.md into context.",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string", "description": "The skill name" }
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
When the model calls `read_skill`, the host looks up the name in its registry and routes on origin: a filesystem skill is read from disk; an MCP skill is fetched via `resources/read` against the originating server. The mechanics are the same either way. When a name is collision-qualified ([Names](#names)), the qualified form is what appears in the model's context and what the model passes as `name`. Hosts that already expose a name-keyed skill-loading tool for filesystem skills extend it rather than introducing a parallel one.
|
||||
|
||||
**Supporting files.** Once a `SKILL.md` is in context, the model may encounter relative references to supporting files (`references/GUIDE.md`, `scripts/extract.py`). For filesystem skills the model reads these with the host's ordinary file-read tool; for MCP skills there is no local file. The host therefore also exposes a general-purpose resource-reading tool:
|
||||
|
||||
```json
|
||||
{
|
||||
|
|
@ -186,27 +451,24 @@ Hosts SHOULD expose a tool to the model that reads MCP resources by server and U
|
|||
"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" }
|
||||
"server": {
|
||||
"type": "string",
|
||||
"description": "Name of the connected MCP server"
|
||||
},
|
||||
"uri": { "type": "string", "description": "The resource URI" }
|
||||
},
|
||||
"required": ["server", "uri"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The signature shown is illustrative. Including the server name is one disambiguation strategy for identical `skill://` URIs served by different connected servers; hosts MAY instead prefix URIs on conflict, scope by session, or use any other scheme appropriate to their architecture. The tool is general-purpose — it reads any MCP resource — and benefits resource use cases beyond skills.
|
||||
The host arranges for the model to know, when it loads an MCP-served `SKILL.md`, which server it came from and what its base URI is — for example by stating both in the `read_skill` tool result — so the model can resolve `references/GUIDE.md` to `skill://<skill-path>/references/GUIDE.md` and issue `read_resource` against the right server. A host may instead fold this into its file-read tool by mounting each server's `skill://` namespace into a virtual path and translating reads under that path into `resources/read` calls, in which case no separate `read_resource` tool is needed and the model treats every supporting file as a local path. Either way the resolution rule is the same: relative references resolve against the skill's root directory, exactly as on a filesystem. When the skill's entry carries `resources`, the host verifies each such read against it, per [Integrity and verification](#integrity-and-verification).
|
||||
|
||||
Hosts SHOULD load the frontmatter (`name`, `description`) of available and enabled skills into the model's context so the model can judge relevance and construct a `read_resource` call when a skill applies. Hosts SHOULD surface available skills in their UI for user inspection and per-skill enable/disable, analogous to how tools are typically exposed.
|
||||
**Directory navigation.** Skill instructions may point the model at a directory rather than a file ("choose the right template from `templates/`"). When the originating server declares `directoryRead`, the host SHOULD surface this capability to the model: a `read_resource` call whose target is a directory resource can be routed to `resources/directory/read` and return the child listing, and the virtual-mount approach maps it onto the host's existing directory-listing tool — an `ls` of a mounted path becomes a `resources/directory/read` call.
|
||||
|
||||
A typical flow: the host reads `skill://index.json` from each connected server and surfaces the `name` and `description` of each entry in the model's context. The model calls `read_resource` with a concrete URI — one returned by enumeration, one handed to it by the user (who may have resolved it from a template entry in the host UI), or one obtained out-of-band — when a skill is relevant to the task.
|
||||
**Unenumerated skills.** Because a listing may be empty or partial, a host should also accept skill URIs it has never seen listed — handed to the model by the user, by server instructions, or by another skill. Calling `skills/get` on such a URI yields the same entry a listing would have carried, so an unlisted skill enters the registry, gets verified, and is approved on the same terms as a listed one; a server that does not serve the URI as a skill answers with an error. A host may additionally let `read_skill` accept a full URI for this case.
|
||||
|
||||
Because enumeration is optional, a `read_resource` call for a `skill://` URI that the host has never seen listed is normal and expected. The host forwards it to the named server; the server either serves the resource or returns a not-found error.
|
||||
|
||||
### Hosts: Unified Treatment of Filesystem and MCP Skills
|
||||
|
||||
Hosts that support both filesystem-based skills (loaded from local directories) and MCP-served skills SHOULD treat them identically, as though the set of connected servers' `skill://` resources were mounted into a virtual filesystem alongside local skill directories.
|
||||
|
||||
Concretely: the same discovery surface, the same loading tool, and the same relative-path resolution. A model that has learned to follow `references/GUIDE.md` from a local `SKILL.md` should find that MCP-served skills behave the same way. Divergence between the two paths is a source of model confusion and implementation complexity.
|
||||
The `read_resource` signature above includes `server` because two connected servers may both serve `skill://refunds/SKILL.md`. That is one disambiguation strategy; a host may instead rewrite URIs with a per-server prefix, scope by session, or anything else appropriate to its architecture. The tool is general-purpose — it reads any MCP resource — and is useful beyond skills.
|
||||
|
||||
### SDKs: Convenience Wrappers
|
||||
|
||||
|
|
@ -224,14 +486,18 @@ def refunds():
|
|||
return Path("./skills/refunds")
|
||||
```
|
||||
|
||||
The SDK handles: reading `SKILL.md` frontmatter to populate resource metadata, registering a `skill://<skill-path>/{+path}` resource template, serving file content on `resources/read`, and (where the server's skill set is bounded) generating the `skill://index.json` resource.
|
||||
The SDK handles: reading `SKILL.md` frontmatter to populate resource metadata, serving file content on `resources/read`, and answering `skills/get` — and, where the server's skill set is bounded, `skills/list` — computing entry digests from the registered files.
|
||||
|
||||
**Client-side** — enumerate and fetch skills:
|
||||
|
||||
```python
|
||||
skills = await client.list_skills() # reads skill://index.json, may be empty or absent
|
||||
skills = await client.list_skills() # calls skills/list, paginating; may be empty
|
||||
entry = await client.get_skill(
|
||||
"skill://acme/billing/refunds/SKILL.md") # calls skills/get, listed or not
|
||||
content = await client.read_skill_uri(
|
||||
"skill://acme/billing/refunds/SKILL.md") # wraps resources/read, works regardless of enumeration
|
||||
entries = await client.read_directory(
|
||||
"skill://pdf-processing/templates") # wraps resources/directory/read
|
||||
```
|
||||
|
||||
These wrappers are thin — each is a single underlying protocol call with a fixed URI pattern — but they give server authors an ergonomic way to declare skills and give client authors a discoverable entry point.
|
||||
|
|
@ -240,13 +506,13 @@ These wrappers are thin — each is a single underlying protocol call with a fix
|
|||
|
||||
### Why Resources Instead of a New Primitive?
|
||||
|
||||
The Interest Group's [decision log](decisions.md#2026-02-26-prioritize-skills-as-resources-with-client-helper-tools) records this as settled. Skills are files; Resources exist to expose files. Reusing Resources inherits URI addressability, `resources/read`, `resources/subscribe`, templates, and the existing client tooling for free. A new primitive would duplicate most of this and add ecosystem complexity — a concern raised in [community discussion](https://github.com/modelcontextprotocol/experimental-ext-skills/issues/14).
|
||||
The Working Group's [decision log](https://github.com/modelcontextprotocol/experimental-ext-skills/blob/main/docs/decisions.md#2026-02-26-prioritize-skills-as-resources-with-client-helper-tools) records this as settled. Skills are files; Resources exist to expose files. Reusing Resources inherits URI addressability, `resources/read`, `resources/subscribe`, and the existing client tooling for free. A new primitive would duplicate most of this and add ecosystem complexity. Using resources to describe files is also aligned with [Composability over specificity](https://modelcontextprotocol.io/community/design-principles#composability-over-specificity) and other MCP [design principles](https://modelcontextprotocol.io/community/design-principles).
|
||||
|
||||
[SEP-2076] proposes the new-primitive alternative. That approach offers cleaner capability negotiation and dedicated list-changed notifications, but at the cost of flattening skills to name-addressed blobs — losing the directory model that the Agent Skills specification defines and that supporting files depend on.
|
||||
[SEP-2076] originally proposed the new-primitive alternative. That approach offers cleaner capability negotiation and dedicated list-changed notifications, but at the cost of flattening skills to name-addressed blobs — losing the directory model that the Agent Skills specification defines and that supporting files depend on.
|
||||
|
||||
### Why `skill://<path>/<file>` With an Explicit `SKILL.md`?
|
||||
|
||||
Four independent implementations converged on `skill://` as the scheme without coordination — a strong signal. They diverged on structure. This SEP adopts the explicit-file form because:
|
||||
Several independent implementations converged on `skill://` as the scheme without coordination — a strong signal. They diverged on structure. This SEP adopts the explicit-file form because:
|
||||
|
||||
- It directly mirrors the Agent Skills specification's directory model. A skill _is_ a directory; its URI space should look like one.
|
||||
- `SKILL.md` being explicit means supporting files are siblings at the same level, with no special casing for "the skill URI" versus "a file in the skill."
|
||||
|
|
@ -260,54 +526,124 @@ Earlier drafts required `<skill-path>` to be a single segment equal to the front
|
|||
|
||||
A subsequent draft went further and fully decoupled the path from the name. That was too loose: a URI like `skill://a/b/c/SKILL.md` tells you nothing about what the skill is called until you fetch and parse frontmatter. Clients listing skills, hosts displaying them in a picker, and models reasoning over URIs all want the name visible without a round trip.
|
||||
|
||||
Constraining the final segment to match the frontmatter `name` gets both properties. The prefix carries the server's organizational structure; the final segment carries the skill's identity; and the two together form a locator from which the name can be read directly.
|
||||
Constraining the final segment to match the frontmatter `name` gets both properties. The prefix carries the server's organizational structure; the final segment carries the skill's name; and the two together form a locator from which the name can be read directly.
|
||||
|
||||
### Why Is Enumeration Optional?
|
||||
### Why May the Listing Be Empty or Partial?
|
||||
|
||||
Requiring every server to expose a complete `skill://index.json` fails for at least three server shapes: a documentation server that synthesizes a skill per API endpoint (thousands), a skill gateway fronting an external index (unbounded), and a server that generates skills from templates parameterized at read time (unenumerable by construction). For these, the list is either too large to be useful in the model's context or does not meaningfully exist.
|
||||
Requiring every server to implement a complete `skills/list` fails for at least three server shapes: a documentation server that synthesizes a skill per API endpoint (thousands), a skill gateway fronting an external index (unbounded), and a server that generates skills dynamically at read time (unenumerable by construction). For these, the list is either too large to be useful in the model's context or does not meaningfully exist.
|
||||
|
||||
The baseline is therefore direct readability — a skill URI is always a valid argument to `resources/read`. The index (concrete entries and templates) is layered on top for servers where it makes sense. A host that assumes enumeration is exhaustive will miss skills on servers where it is not, hence the requirement that hosts MUST NOT treat empty enumeration as proof of absence.
|
||||
The baseline is therefore direct readability — a skill URI is always a valid argument to `resources/read`. The method is universal but exhaustiveness is not: a server that cannot enumerate returns what it can, or nothing. A host that assumes enumeration is exhaustive will miss skills on servers where it is not, hence the requirement that hosts MUST NOT treat empty enumeration as proof of absence.
|
||||
|
||||
### Why `skills/get` Alongside `skills/list`?
|
||||
|
||||
Enumeration alone leaves two gaps, and both are integrity gaps rather than conveniences.
|
||||
|
||||
The first is the unlisted skill. Listings may be empty or partial by design, and the baseline hands hosts skill URIs that never appeared in one — from server instructions, another skill, or the user. Before `skills/get`, such a skill could be read but not verified: its digests existed nowhere, so it could not be content-bound — and a catalog too large to enumerate, or fronted by a gateway, serves static content whose digests were perfectly publishable, just unreachable. `skills/get` makes an entry reachable from a URI alone, so verification no longer depends on how the host happened to find the skill. Skills generated at read time remain unverifiable by construction, listed or not: they omit `resources` either way.
|
||||
|
||||
The second is the cost of picking up a change. A host does not need to poll for one: the approved `resources` set is what its reads are verified against, so a skill whose content has moved announces itself as a verification failure. What the host then needs is the skill's current entry — and before `skills/get`, the only way to get it was to re-enumerate the whole catalog, thousands of entries to learn the new digests of one skill. The same applies to a skill the user asks to update. Fetching one entry keeps the cost proportional to what changed.
|
||||
|
||||
Both are single-entry reads of a shape the listing already defines, so `skills/get` adds a method but no new schema: the `skill` object is a listing entry.
|
||||
|
||||
### Why a Method Instead of a Well-Known Index Resource?
|
||||
|
||||
An earlier revision served enumeration as a reserved resource at `skill://index.json`, read like any other resource. Replacing it with `skills/list` — same entry schema, different carrier — buys four things. Pagination: a resource is one monolithic document, while a method pages large catalogs with the same `cursor`/`nextCursor` contract as every base list method. Caching: list methods inherit the base protocol's caching attributes ([SEP-2549]) uniformly, instead of this extension inventing a parallel freshness signal for one resource. Discoverability: support is implied by the extension declaration itself, rather than probed by reading a URI and interpreting the error. And uniformity: clients reuse the machinery they already have for `tools/list` and `resources/list`, and the `skill://` namespace no longer needs a reserved-URI carve-out for a document that was never a skill.
|
||||
|
||||
### Why Delegate the Format to agentskills.io?
|
||||
|
||||
The Agent Skills specification already defines YAML frontmatter fields, naming rules, directory conventions, and the progressive-disclosure model. It has its own governance, contributing process, and multi-vendor participation. Redefining any of this in an MCP SEP would create a second source of truth and a drift risk. This SEP is a transport binding; the payload format is someone else's concern.
|
||||
|
||||
### Why an Index Resource Rather Than `resources/list`?
|
||||
### Why a Directory Read Method?
|
||||
|
||||
An earlier draft enumerated skills via a scoped `resources/list(uri="skill://")` call. Moving to a well-known index resource aligns discovery with the Agent Skills [well-known URI index](https://agentskills.io/well-known-uri) — the same JSON shape, the same schema URI, the same client-side parsing. A host that already consumes `.well-known/agent-skills/index.json` over HTTP can consume `skill://index.json` over MCP with the same code. It also drops the dependency on scoped `resources/list` (which the base spec does not guarantee), leaving this extension with zero protocol dependencies beyond `resources/read`.
|
||||
The virtual-filesystem model this SEP builds on had a read operation (`resources/read`) but no readdir. That gap is harmless while skill instructions name files explicitly, but skills routinely defer the choice to the agent — "use the template in `templates/` that matches the document type." On a filesystem the agent lists the directory; over MCP the only enumeration was `resources/list`, which is global: it returns the server's entire resource space, cannot be scoped to a subtree, and is precisely what large or generative servers — the ones whose partial listings this SEP accommodates — decline to implement. A server that cannot enumerate its catalog can still trivially enumerate one directory it is already serving.
|
||||
|
||||
`resources/directory/read` is the readdir analog: scoped, paginated, and composable — listings mark subdirectories with `inode/directory`, so an agent descends a skill's tree exactly as it would walk a directory locally.
|
||||
|
||||
Two alternatives were considered. Extending `resources/list` with a scope parameter would change a core method's semantics from inside an extension, and a URI-prefix filter misstates the model anyway — prefixes are string matching, not structure. Embedding a file manifest in `SKILL.md` or the listing was rejected as the navigation mechanism: it would freeze the listing at authoring or publication time and bloat context for skills with many files, while a method keeps listings live and on demand. The `resources` enumeration ([Resources](#resources)) is not that manifest — it is an integrity commitment consumed by the host at verification and approval time, with no need to occupy model context, and the dynamically generated skills that need live listings most are exactly the ones that omit it.
|
||||
|
||||
Although introduced by this extension, the method is deliberately general — any directory resource qualifies, under any scheme — making it a candidate for promotion into the core Resources primitive if usage warrants.
|
||||
|
||||
### Why Verbatim Frontmatter in the Listing?
|
||||
|
||||
The listing could instead carry a curated subset of skill metadata — `name` and `description` as dedicated top-level fields. That forces a choice every time the Agent Skills frontmatter grows a field: amend this SEP to mirror it, or leave listing consumers to fetch and parse every `SKILL.md` for it. Copying the frontmatter verbatim removes the choice. The listing carries exactly what the skill author wrote; the fields hosts need for a registry (`name`, `description`) are guaranteed present because the Agent Skills specification requires them; and new frontmatter fields flow through with no change to this extension. A host builds its complete skill registry from the listing alone.
|
||||
|
||||
### Why Per-File Digests Instead of a Single Skill Digest?
|
||||
|
||||
An earlier revision carried one digest per entry, covering only `SKILL.md`. That left every supporting file unbound: a server could obtain approval for a benign skill, then rotate `references/GUIDE.md` — instructional content the model follows as readily as the skill body — while the approved digest stayed valid. Persisted approval covered the one file a user is least likely to be attacked through.
|
||||
|
||||
Enumerating every file closes rotation and addition in one move: the set is complete, so a new file is as detectable as a changed one. It also gives hosts a pinning primitive — fetch, verify, and cache the whole set at approval time — recovering the atomic-snapshot property that archive distribution ([Appendix: Deferred Features](#appendix-deferred-features)) offered, without the unpacking surface. The cost falls on dynamically generated skills, which cannot publish stable digests; they omit `resources` and accept that hosts may decline them.
|
||||
|
||||
## Backward Compatibility
|
||||
|
||||
This extension introduces no new protocol methods, message types, or schema changes. A server that does not implement this extension simply exposes no `skill://` resources; existing clients are unaffected. A client that does not implement this extension sees `skill://` resources as ordinary resources, which they are.
|
||||
This extension introduces three protocol methods. `skills/list` and `skills/get` are implemented by every server declaring the extension, so a client only issues those calls after seeing the declaration, and a client that predates the extension never issues them. `resources/directory/read` is additionally gated behind the `directoryRead` capability setting — a server that does not declare it never receives the call. The extension introduces no other methods, message types, or schema changes. A server that does not implement this extension simply exposes no `skill://` resources; existing clients are unaffected. A client that does not implement this extension sees `skill://` resources as ordinary resources, which they are.
|
||||
|
||||
Existing implementations using other `skill://` URI structures (NimbleBrain's `skill://server/skill`, skilljack's implicit-`SKILL.md` `skill://name`) will need to adjust their URI paths to conform. These are small, mechanical changes.
|
||||
|
||||
## Reference Implementation
|
||||
|
||||
Will be provided prior to reaching Final status.
|
||||
Existing implementations using other `skill://` URI structures will need to adjust to conform — see the Working Group's [related-work survey](https://github.com/modelcontextprotocol/experimental-ext-skills/blob/main/docs/related-work.md) for a catalog. Notably, FastMCP's widely-used [SkillsProvider](https://gofastmcp.com/servers/providers/skills) diverges on URI structure, discovery (per-skill `_manifest` vs. central index), and metadata mapping; coordinating that migration is a near-term Working Group priority. These are mechanical changes, not semantic ones.
|
||||
|
||||
## Security Implications
|
||||
|
||||
Skill content is instructional text delivered to a model, which makes it a prompt-injection surface. The Interest Group's position, recorded in [open-questions.md §10](open-questions.md#10-how-should-skills-handle-security-and-trust-boundaries), is:
|
||||
Skill content is instructional text delivered to a model, which makes it a prompt-injection surface. The Working Group's position, recorded in [open-questions.md §10](https://github.com/modelcontextprotocol/experimental-ext-skills/blob/main/docs/open-questions.md#10-how-should-skills-handle-security-and-trust-boundaries), is:
|
||||
|
||||
- **Skill content is untrusted input.** Hosts MUST treat MCP-served skill content as untrusted model input, subject to the same prompt-injection defenses applied to any server-provided text. A server being connected does not make its skill content authoritative.
|
||||
- **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.
|
||||
- **Origin MUST be visible to the model.** Hosts MUST tag MCP-served skill content with its originating server identity at the point it enters model context and MUST NOT present an MCP-served skill to the model as indistinguishable from a local filesystem skill. The model, not the host, decides whether to follow a skill's instructions. Withholding origin from it makes the untrusted-input requirement above unenforceable at the layer that acts on it.
|
||||
- **Skills introduce host-side surfaces that tools do not.** Unlike a remote tool call, an MCP-served skill can place server-authored bytes on the host filesystem and direct the model to execute them with host-side tools. Hosts MUST treat MCP-served skills as a higher-risk surface than remote tool invocation.
|
||||
- **No implicit local execution.** Hosts MUST NOT allow MCP-served skill content to cause host-side code execution without explicit per-skill user approval. This covers (a) declarative fields the host parses (hooks, frontmatter scripts) and (b) instructions in the skill body that direct the model to invoke any host code-execution tool, whether to run a script bundled in the skill or to run an arbitrary command the skill specifies. Hosts MUST ignore or approval-gate (a), and MUST apply the same approval gate to code-execution tool calls issued while the model is acting on an MCP-served skill.
|
||||
- **Origin-scoped resource reads.** A model-callable resource-read surface (such as the `read_resource` pattern in [Hosts: End-to-End Integration](#hosts-end-to-end-integration)) is a cross-server confused-deputy vector when driven by untrusted skill content. Hosts MUST bind such reads to the skill's originating server: a skill served by server A MUST NOT cause a `resources/read` against server B. Hosts MUST identify servers by a host-assigned label, not the server's self-reported `serverInfo.name`. Any cross-origin read MUST be gated behind explicit per-call user approval naming both servers.
|
||||
- **Name collisions are an impersonation surface.** Skill names are not unique across origins, and a malicious server can publish a skill under the name of a popular one — another server's, or the user's own local skill — counting on the host resolving its way. Hosts MUST resolve skill names within a per-origin namespace, identifying servers by a host-assigned label, not the self-reported `serverInfo.name`; MUST NOT let an MCP-served skill silently shadow, replace, or intercept invocations of a same-named skill from any other origin, including the host's filesystem skills; and SHOULD surface collisions to the user. A name binds to whatever bytes its origin currently serves — it carries no authorship or endorsement. Intermediaries MAY attach provenance or verification annotations via `_meta` under their own reverse-domain prefix — not the `io.modelcontextprotocol.skills/` prefix reserved for this extension ([Resource Metadata](#resource-metadata)); this extension assigns such annotations no semantics.
|
||||
- **No implicit permission grants.** Hosts MUST NOT honor frontmatter fields that widen the model's tool or filesystem permissions when the skill arrives over MCP. In particular, the Agent Skills `allowed-tools` field, which a filesystem-sourced skill uses to declare the tools available while it runs, MUST be ignored for MCP-origin skills unless the user has explicitly approved that grant for that skill. A remote server populating `allowed-tools` is requesting elevated access on the host, not declaring a property of its own environment. Approval of a skill never extends to the frontmatter of any other `SKILL.md` within its file space: a nested skill's `allowed-tools` has no effect unless that nested skill is itself activated under its own approval ([Nested skills](#nested-skills)).
|
||||
- **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.
|
||||
- **Nested skill consent.** Approval is per skill: approving a skill does not approve skills nested within it. Activating a nested `SKILL.md` requires fresh, explicit user consent, per [Nested skills](#nested-skills). Silently promoting a file of an approved skill to an active skill would let a server ride new instructions and permission requests in on a prior approval.
|
||||
- **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.
|
||||
- **Not a third-party marketplace.** This extension is for servers to ship skills that describe their own tools, not for distributing arbitrary third-party content through a connected server.
|
||||
- **Digests are not a security boundary.** Listing digests are unsigned and come from the same server as the content. They confirm consistency between the listing and what was fetched, as described in [Integrity and verification](#integrity-and-verification), but they cannot establish trust in the content, defend against the server itself, or detect an intermediary that rewrites both together.
|
||||
- **Content-bound approval.** When a host persists any per-skill user approval, it MUST be bound to the entry's `resources` set — every `uri` and `digest` — observed at the moment of approval. If a subsequent entry for that skill — from `skills/list` or `skills/get` — advertises a different set, whether a file was rotated, added, or removed, the host MUST treat the prior approval as revoked and re-prompt before loading or executing. A host need not poll for changes. While it holds the approved entry, content that has moved fails verification when read; and if it does fetch a fresh entry, the rule above revokes the approval. Neither path lets moved content through under the old approval. A skill published without `resources` cannot be content-bound: hosts MAY decline to load it, and MUST NOT treat a persisted approval as covering whatever content the server currently serves. Digest verification ([Integrity and verification](#integrity-and-verification)) defends the approval after it is granted - it cannot establish that the content was trustworthy when the user approved it, because the server authors both the listing and the body.
|
||||
- **Cache isolation and durable origin.** Hosts that cache MCP-served skill content on disk MUST do so in a location excluded from every filesystem-skill discovery path, and MUST treat content loaded from that location as having arrived over MCP for all purposes of the no-implicit-local-execution rule above, including after host restart and after the originating server is disconnected. Cached bytes do not graduate to filesystem-skill trust by residing locally. Hosts SHOULD remove a server's cached skill content when the user removes that server.
|
||||
|
||||
The instructor-only scope of this extension ([decisions.md, 2026-02-14](decisions.md#2026-02-14-skills-served-over-mcp-use-the-instructor-format)) deliberately excludes the helper model. A filesystem skill might reasonably carry scripts the user has audited; an MCP skill arrives from a remote party and MUST be handled as text that influences model behavior, not as code that executes on the host.
|
||||
## Reference Implementation
|
||||
|
||||
Per [SEP-2133], an Extensions Track SEP requires at least one reference implementation in an official SDK prior to review.
|
||||
|
||||
**SDK implementation:**
|
||||
|
||||
- TypeScript SDK convenience wrappers (`@server.skill()`, `client.listSkills()`, `client.readSkillUri()`): [experimental-ext-skills#71](https://github.com/modelcontextprotocol/experimental-ext-skills/pull/71)
|
||||
|
||||
**Prototype host implementations** (reading `skill://` resources, surfacing skills alongside filesystem skills):
|
||||
|
||||
- gemini-cli: [olaservo/gemini-cli#1](https://github.com/olaservo/gemini-cli/pull/1)
|
||||
- fast-agent: [olaservo/fast-agent#1](https://github.com/olaservo/fast-agent/pull/1)
|
||||
- goose: [olaservo/goose#1](https://github.com/olaservo/goose/pull/1)
|
||||
- codex: [olaservo/codex#1](https://github.com/olaservo/codex/pull/1)
|
||||
- Claude Code: prototyped internally at Anthropic; not yet public
|
||||
|
||||
**Prototype server implementation:**
|
||||
|
||||
- GitHub MCP Server: [github/github-mcp-server#2360](https://github.com/github/github-mcp-server/pull/2360)
|
||||
|
||||
## Appendix: Deferred Features
|
||||
|
||||
Features recorded here appeared in earlier revisions of this SEP and were removed before review concluded. They are not part of this extension. Each is kept on record with the objections that removed it, so that any future proposal to reintroduce one starts from those objections rather than rediscovering them.
|
||||
|
||||
### Archive Distribution
|
||||
|
||||
An earlier revision let a skill entry advertise pre-packed archives of the entire skill directory — gzip-compressed tar and ZIP — as an alternative retrieval form alongside the skill's `uri`. A host could fetch a multi-file skill in a single `resources/read` rather than one per file, and an archive could carry UNIX file metadata (executable bits, symlinks) that individually served resources cannot represent.
|
||||
|
||||
The Core Maintainers removed archives during review, for two reasons:
|
||||
|
||||
- **Unpacking is an attack surface disproportionate to the benefit.** Safely extracting an archive supplied by a remote server means defending against decompression bombs, path traversal, links resolving outside the skill directory, case- and Unicode-normalization collisions that silently overwrite `SKILL.md`, setuid and setgid bits, and non-regular file entries such as device nodes. Every host would have to implement that checklist correctly, and a host that got any item wrong would be exploitable by any server it connects to. Serving a skill as individually addressable resources has no comparable surface.
|
||||
- **Two ways to serve one skill is a compatibility hazard.** Archives were a second encoding of content the protocol could already express. Hosts would have to support both forms to be certain of reading any skill, and a skill offered only as an archive would be unreadable to a host that implemented individual-file reads alone. A single retrieval form keeps the compatibility floor flat: any conforming host can read any conforming skill.
|
||||
|
||||
The cost of removal is the one archives were introduced to address — a skill with many supporting files takes one round trip per file, and executable bits and symlinks have no representation. Should archives be reconsidered, the questions to settle first are how to bound host-side unpacking risk, perhaps by restricting the format to a profile admitting no symlinks, no non-regular entries, and a declared uncompressed size; and how to keep an archive strictly an optimization, never the sole way to retrieve a skill, so that the compatibility floor stays flat. An archive form would now also be required to unpack to exactly the file set enumerated in the entry's `resources` ([Resources](#resources)).
|
||||
|
||||
## References
|
||||
|
||||
- [Agent Skills specification](https://agentskills.io/specification)
|
||||
- [Agent Skills well-known URI discovery](https://agentskills.io/well-known-uri)
|
||||
- [SEP-2133]: Extensions
|
||||
- [SEP-2549]: TTL for list results
|
||||
- [SEP-2076]: Agent Skills as first-class primitive (alternative approach)
|
||||
- [Decision Log](decisions.md) — Interest Group decisions and rationale
|
||||
- [Experimental Findings](experimental-findings.md) — results from implementations
|
||||
- [Skills Over MCP Working Group charter](https://modelcontextprotocol.io/community/skills-over-mcp/charter)
|
||||
- [Decision Log](https://github.com/modelcontextprotocol/experimental-ext-skills/blob/main/docs/decisions.md) — Working Group decisions and rationale
|
||||
- [Experimental Findings](https://github.com/modelcontextprotocol/experimental-ext-skills/blob/main/docs/experimental-findings.md) — results from implementations (WIP)
|
||||
- [Related Work](https://github.com/modelcontextprotocol/experimental-ext-skills/blob/main/docs/related-work.md) — survey of existing skill-serving implementations
|
||||
- [Skill `_meta` Keys](https://github.com/modelcontextprotocol/experimental-ext-skills/blob/main/docs/skill-meta-keys.md) — `_meta` key conventions for skill resources
|
||||
- [RFC 3986: URIs](https://datatracker.ietf.org/doc/html/rfc3986)
|
||||
|
||||
[SEP-2076]: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2076
|
||||
[SEP-2133]: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2133
|
||||
[SEP-2549]: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2549
|
||||
|
|
|
|||
Loading…
Reference in a new issue