From 877dd92deb655d4592e9e2d875b2d433965b0fb7 Mon Sep 17 00:00:00 2001 From: Peter Alexander Date: Fri, 4 Sep 2026 11:21:34 +0000 Subject: [PATCH 1/4] Add specification/stable/skills.mdx for SEP-2640 Spec-format rendering of the Skills extension (io.modelcontextprotocol/skills), following the layout used by ext-tasks' specification/draft/tasks.md. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01Sure6Sknngow87Q6TkzTfL --- specification/stable/skills.mdx | 784 ++++++++++++++++++++++++++++++++ 1 file changed, 784 insertions(+) create mode 100644 specification/stable/skills.mdx diff --git a/specification/stable/skills.mdx b/specification/stable/skills.mdx new file mode 100644 index 0000000..3dce363 --- /dev/null +++ b/specification/stable/skills.mdx @@ -0,0 +1,784 @@ +--- +title: Skills +--- + +# Skills + + +**This document is part of the Skills extension (`io.modelcontextprotocol/skills`).** + +This extension defines how a server serves [Agent Skills](https://agentskills.io/) over MCP using the existing Resources primitive. Each file in a skill directory is exposed as a resource, conventionally under the `skill://` URI scheme, and is read with the standard `resources/read` method. The extension introduces three methods: `skills/list`, which enumerates the skills a server serves; `skills/get`, which returns the entry for a single skill by URI; and the optional `resources/directory/read`, which lists the direct children of a directory resource. It also defines a `Skill` entry shape that carries a skill's verbatim frontmatter and a complete, digest-bearing manifest of its files. + +The skill format itself — directory structure, YAML frontmatter, naming rules, and the progressive-disclosure model — is delegated entirely to the [Agent Skills specification](https://agentskills.io/specification). This extension defines only the transport binding. + + +The Model Context Protocol (MCP) Skills extension allows servers to publish **skills** alongside the tools, resources, and prompts they already serve. A skill is a directory of files, minimally a `SKILL.md`, that provides structured workflow instructions to an agent. Skills are addressed by the URI of their `SKILL.md` and may be read directly whether or not the server enumerates them. + +Skills are useful for shipping the instructions for using a server together with the server itself, for workflows too large to fit in server instructions, and for orchestration that spans several tools or servers. + +## Extension Identifier + +This extension is identified as: `io.modelcontextprotocol/skills`. + +## Capability Negotiation + +Servers declare support for this extension in the `extensions` field of their capabilities: + +```jsonc +// Server capabilities +{ + "capabilities": { + // Other capabilities... + "extensions": { + "io.modelcontextprotocol/skills": { + "directoryRead": true, + }, + }, + }, +} +``` + +One extension-specific setting is defined: + +| Setting | Type | Default | Meaning | +| --------------- | ------- | ------- | ------------------------------------------------------------------------------ | +| `directoryRead` | boolean | `false` | The server implements [`resources/directory/read`](#reading-directories). | + +An empty object indicates support for the extension with no optional features. + +A server that declares this extension **MUST** implement [`skills/list`](#listing-skills) and [`skills/get`](#getting-a-skill). A server that additionally declares `directoryRead: true` **MUST** implement [`resources/directory/read`](#reading-directories). Clients **MUST NOT** call `resources/directory/read` against a server that has not declared `directoryRead: true`. + +Clients issue `skills/list` and `skills/get` only after observing the server's declaration. A client that does not implement this extension sees `skill://` resources as ordinary resources, which they are. + +This extension depends only on the base protocol's Resources primitive. Servers declaring it **MUST** also declare the `resources` capability and serve skill files through `resources/read`. + +## Skill Format + +A skill served over MCP **MUST** conform to the [Agent Skills specification](https://agentskills.io/specification). In particular: + +- A skill is a directory. Its _skill name_ is the value of the `name` field in its `SKILL.md` frontmatter. +- Every skill **MUST** contain a `SKILL.md` file at its root. +- `SKILL.md` **MUST** begin with YAML frontmatter containing at minimum the `name` and `description` fields as defined by the Agent Skills specification. +- A skill **MAY** contain additional files and subdirectories (references, scripts, examples, assets). + +This extension does not redefine, constrain, or extend the skill format. Future revisions of the Agent Skills specification apply automatically. + +## Resource Mapping + +Each file within a skill directory is exposed as an MCP resource. Servers **SHOULD** use the `skill://` URI scheme, under which the resource URI has the form: + +``` +skill:/// +``` + +where: + +- `` 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`). +- `` is the file's path relative to the skill directory root, using `/` as the separator. + +The resource for the skill's required `SKILL.md` is therefore always addressable as `skill:///SKILL.md`. The skill's root directory is `skill://` — the `/SKILL.md` suffix removed, no trailing slash — matching [Directory Resources](#directory-resources). + +The final segment of `` **MUST** equal the skill's `name` as declared in its `SKILL.md` frontmatter. This mirrors the Agent Skills specification's requirement that `name` [match the parent directory name](https://agentskills.io/specification#name-field). Preceding segments, if any, are a server-chosen organizational prefix; servers **MAY** organize skills hierarchically by domain, team, version, or any other axis. In `skill://acme/billing/refunds/SKILL.md` the prefix is `acme/billing` and the skill's `name` is `refunds`; in `skill://git-workflow/SKILL.md` there is no prefix and the `name` is `git-workflow`. The skill name is therefore always recoverable from the URI alone, without reading frontmatter. + +Further constraints: + +- A `SKILL.md` **MAY** appear in a descendant directory of a skill — skills can nest. See [Nested Skills](#nested-skills). +- The final `` segment, being the skill `name`, **MUST** satisfy the Agent Skills specification's naming rules. The first `` segment occupies the authority component and **SHOULD** be a valid `reg-name` per [RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986); any other prefix segments **SHOULD** be valid URI path segments; no further constraints are imposed on them. +- Per RFC 3986, the first segment of `` occupies the authority component. This carries no special semantics under this extension and clients **MUST NOT** attempt DNS or network resolution of it. + +A server **MAY** serve skills under another scheme native to its domain (for example, `github://owner/repo/skills/refunds/SKILL.md`). No scheme is privileged: the structural constraints above — `` ending in the skill name, `SKILL.md` explicit in the URI — apply regardless of scheme, and [`skills/list`](#listing-skills) 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`](#listing-skills) entry, the authoritative record of the skills a server publishes; or from an explicit reference — the server's `instructions`, another skill, or the user — which [`skills/get`](#getting-a-skill) 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` | `examples/email.md` | `skill://acme/billing/refunds/examples/email.md` | + +### Resource Metadata + +For each `skill:///SKILL.md` resource: + +- `mimeType` **SHOULD** be `text/markdown`. +- `name` **SHOULD** be set from the `name` field of the `SKILL.md` YAML frontmatter. By the path constraint above, this equals the final segment of ``. +- `description` **SHOULD** be set from the `description` field of the `SKILL.md` YAML frontmatter. + +Servers **MAY** expose additional frontmatter fields via the resource's `_meta` object. When `_meta` keys are used for skill resources, implementations **SHOULD** use the `io.modelcontextprotocol.skills/` reverse-domain prefix. Other files in the skill use the `mimeType` appropriate to their content. + +### 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` (see [Security Considerations](#security-considerations)). +- **Publication is flat.** A nested skill is published like any other: through its own [`skills/list`](#listing-skills) 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. + +## Skill Entries + +A `Skill` entry is the unit of metadata this extension exchanges. It is returned by both `skills/list` and `skills/get`, with identical shape and meaning in each. + +```typescript +/** + * A file belonging to a skill, with the digest and size of its content. + */ +interface SkillResource { + /** Resource URI of the file. */ + uri: string; + + /** + * SHA-256 digest of the file's raw bytes, formatted as `sha256:{hex}` + * where {hex} is 64 lowercase hexadecimal characters. + */ + digest: string; + + /** + * Length in bytes of the file's raw content — the same bytes `digest` covers. + */ + size: number; +} + +/** + * The entry for a single skill. + */ +interface Skill { + /** Resource URI of the skill's SKILL.md, readable via resources/read. */ + uri: string; + + /** + * The skill's SKILL.md YAML frontmatter, rendered verbatim as a JSON object. + * `name` and `description` are always present; every other field the author + * wrote passes through unchanged. + */ + frontmatter: { + name: string; + description: string; + [key: string]: unknown; + }; + + /** + * The skill's files: a complete enumeration of SKILL.md and every supporting + * file, or the string "dynamic" when the skill's content is generated such + * that stable digests cannot be published. + */ + resources: SkillResource[] | "dynamic"; +} +``` + +### 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. + +A skill URI is scoped to the server that serves it. Nothing prevents two connected servers from both serving `skill://refunds/SKILL.md`, and those are two unrelated skills. The identity of an MCP-served skill is therefore the pair of the host's identity for the originating server and the skill's `uri`. Hosts **MUST** preserve both halves wherever a skill is recorded or addressed — the registry, persisted approvals, the cache, and any tool or path through which the model reaches the skill — and **MUST NOT** key any of these on the `uri` alone. In particular, any path at which a host materializes skill content, whether a cache directory or a virtual mount, **MUST** encode the server identity as well as the `uri`, so that same-URI skills from different servers land at distinct paths and the originating server is recoverable from the path. + +### 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 from entries 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 `` 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. + +### Names + +A skill's `name` is a label, not an identifier — a skill is identified by its `uri` within a server, and by the pair of server identity and `uri` across servers ([Skill URIs](#skill-uris)). 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 Considerations](#security-considerations). + +### Resources + +`resources` is **REQUIRED** on every skill entry and takes one of two forms: an array enumerating the skill's files — `SKILL.md` and every supporting file — as `SkillResource` objects, or the string `"dynamic"`. The array is the unit of content that a host verifies and that a user's approval binds to. + +- When `resources` is an array, it **MUST** be complete: it lists every file of the skill, each exactly once, including an entry whose `uri` equals the skill's top-level `uri` — that entry carries the digest and size of `SKILL.md` itself. +- Each `uri` **MUST** be the skill's `SKILL.md` or a file within the skill's directory. +- Each entry **MUST** carry `size`: the length in bytes of the file's raw content — the same bytes the `digest` covers. A read whose byte length differs from the entry's `size` is a verification failure equivalent to a digest mismatch ([Integrity and Verification](#integrity-and-verification)), whether or not the host goes on to compute the digest. +- 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. +- When a skill's content is generated dynamically, such that stable digests cannot be published, the server **MUST** set `resources` to the string `"dynamic"` instead of an array. An entry with no `resources` at all, or with any value other than an array or `"dynamic"`, is invalid, and hosts **MUST NOT** load it. A skill whose `resources` is `"dynamic"` offers no content integrity and cannot be content-bound ([Security Considerations](#security-considerations)). Hosts **MAY** decline to load such skills, and server authors **SHOULD** expect that some hosts will. + +### Limits + +This extension fixes two per-skill limits so that servers know what every conforming host will accept and hosts know what they must be prepared to handle: + +| Limit | Value | Counted over | +| ------------------------- | ------------------------- | ----------------------------------------------------------- | +| Resources per skill | 512 entries | The entries of the skill's `resources`, `SKILL.md` included | +| Total file size per skill | 16 MiB (16,777,216 bytes) | The sum of `size` over the skill's `resources` | + +Hosts **MUST** support skills up to and including these limits, and **MAY** support larger ones. Servers **SHOULD NOT** serve a skill that exceeds either limit; a skill that does is not guaranteed to be loadable by any conforming host. Because `resources` is complete, both limits are checkable from the entry alone before the host retrieves a single file, and a host that declines a skill on this basis **SHOULD** tell the user why rather than fail silently on a later read. + +For a skill whose `resources` is `"dynamic"`, the entry offers nothing to count. A host that chooses to load such a skill applies the total-size limit to what it actually retrieves and **MAY** stop loading the skill once that limit is reached. + +These limits bound a host's exposure to a single skill. They say nothing about how many skills a server may serve or a host must accept; a listing may be arbitrarily large. + +## Listing Skills + +A server declaring this extension **MUST** implement the `skills/list` method, which returns entries for the skills it serves. This operation supports [pagination](https://modelcontextprotocol.io/specification/draft/basic/utilities/pagination). + +### Request + +```typescript +interface ListSkillsRequest extends PaginatedRequest { + method: "skills/list"; +} +``` + +```json +{ + "jsonrpc": "2.0", + "id": 4, + "method": "skills/list", + "params": {} +} +``` + +### Response + +```typescript +interface ListSkillsResult extends PaginatedResult { + skills: Skill[]; +} +``` + +```json +{ + "jsonrpc": "2.0", + "id": 4, + "result": { + "resultType": "complete", + "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...", + "size": 2314 + } + ] + }, + { + "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...", + "size": 3871 + }, + { + "uri": "skill://acme/billing/refunds/examples/email.md", + "digest": "sha256:c3d4e5f6...", + "size": 962 + } + ] + }, + { + "uri": "skill://reports/daily/SKILL.md", + "frontmatter": { + "name": "daily", + "description": "Assemble today's operational report from live data" + }, + "resources": "dynamic" + } + ] + } +} +``` + +The `resultType` field **MUST** be set to `"complete"` on `ListSkillsResult`, as it is the standard result shape for the `skills/list` request. + +The result **MAY** be empty. 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, and **MUST** support loading a skill given only its URI ([Getting a Skill](#getting-a-skill)). + +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. + +`ListSkillsResult` also carries the base protocol's list-caching attributes, `ttlMs` and `cacheScope`, as defined for `tools/list` and `resources/list`, 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. + +The method serves entries for a server's skills whatever URI scheme they use; enumeration is uniform across schemes. + +A `skills/list` entry is a complete manifest of the skill, not a summary to be filled in by a follow-up call. A host that pages through the listing has, in that one pass, everything it needs to build its registry, present a skill for approval, bind the approval to content, and verify every file it later reads. `skills/get` is never a step a host must take to complete a listed entry. + +## Getting a Skill + +A server declaring this extension **MUST** implement the `skills/get` method, which returns the entry for a single skill named by the URI of its `SKILL.md`. + +### Request + +```typescript +interface GetSkillRequest extends Request { + method: "skills/get"; + params: { + /** URI of the skill's SKILL.md. */ + uri: string; + }; +} +``` + +```json +{ + "jsonrpc": "2.0", + "id": 5, + "method": "skills/get", + "params": { + "uri": "skill://pdf-processing/SKILL.md" + } +} +``` + +### Response + +```typescript +interface GetSkillResult extends Result { + skill: Skill; +} +``` + +```json +{ + "jsonrpc": "2.0", + "id": 5, + "result": { + "resultType": "complete", + "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...", + "size": 5120 + }, + { + "uri": "skill://pdf-processing/references/FORMS.md", + "digest": "sha256:e6f7a8b9...", + "size": 18433 + }, + { + "uri": "skill://pdf-processing/scripts/extract.py", + "digest": "sha256:f7a8b9c0...", + "size": 4096 + }, + { + "uri": "skill://pdf-processing/templates/invoice.md", + "digest": "sha256:a8b9c0d1...", + "size": 1210 + }, + { + "uri": "skill://pdf-processing/templates/purchase-order.md", + "digest": "sha256:b9c0d1e2...", + "size": 1388 + }, + { + "uri": "skill://pdf-processing/templates/regional/eu-invoice.md", + "digest": "sha256:c0d1e2f3...", + "size": 1472 + } + ] + } + } +} +``` + +The `resultType` field **MUST** be set to `"complete"` on `GetSkillResult`, as it is the standard result shape for the `skills/get` request. + +`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`, 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 carries `"resources": "dynamic"`, per [Resources](#resources), whether it is reached through `skills/list` or `skills/get`. +- The result carries no pagination cursor: a single entry is not a list. + +`skills/get` complements direct reading: a URI alone is enough to read a skill via `resources/read`, 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 Considerations](#security-considerations)). + +### Pointer from Server Instructions + +A server **MAY** direct the agent to specific skill URIs from its `instructions`. This requires no discovery machinery on the host; the URI is simply present in the model's context, confirmable via `skills/get` and readable via `resources/read`. + +## Reading Skill Content + +Skill files are read via the standard `resources/read` method. No skill-specific read semantics are defined. + +Reading a `SKILL.md` via `resources/read` does not by itself activate the skill. `resources/read` is transport: it returns bytes, whoever asked for them — a generic resource-reading tool, a resource browser, a user inspecting the server. A skill is activated only by the host's own skill-loading path — the one that verifies the content against the skill's entry ([Integrity and Verification](#integrity-and-verification)), applies any required user approval ([Security Considerations](#security-considerations)), and opens the window in which the host is acting on the skill. Hosts **MUST NOT** treat a `resources/read` of a `SKILL.md` that arrives by any other route as a load: it grants no approval, opens no window, and confers no standing on the skill's supporting files. Content obtained that way is ordinary resource content, and a host that returns it to the model **SHOULD** do so as it would any other resource read, not as a loaded skill. A host that wishes such a read to load the skill routes it through the skill-loading path instead. + +### Relative References + +Internal references within a skill (for example, `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. + +## Integrity and Verification + +Digests are SHA-256 hashes of a file's raw bytes, formatted as `sha256:{hex}` where `{hex}` is 64 lowercase hexadecimal characters. Each `SkillResource` in a skill's `resources` carries the digest of the file at its `uri`. + +**Verification on read.** When a host retrieves a file listed in a skill's `resources`, it **MUST** verify the content against that entry's `digest` and `size`. A mismatch means the content is not what the entry promised — corrupted, tampered with, or stale because the skill was updated after the entry was fetched. Whatever the cause, hosts **MUST NOT** use the unverified content. To recover from staleness, the host calls [`skills/get`](#getting-a-skill) for that skill — or `skills/list` to refresh the catalog — and proceeds from the current `resources` set, which, being different, revokes any content-bound approval ([Security Considerations](#security-considerations)). + +**The acting window and the held entry.** A host is _acting on_ a skill from the moment it loads the skill's `SKILL.md` into the model's context until, at the earliest, that `SKILL.md` leaves context; hosts **MAY** hold the window open longer, never shorter. For the whole of that window the host holds the entry from which it loaded the skill. Because `resources` is complete, an unlisted file is a change to the skill: while acting on a skill, 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. + +**Frontmatter verification.** After fetching a `SKILL.md` for which the host holds an entry — digest-verified when the entry's `resources` is an array, and unverifiable when it is `"dynamic"` — 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 entry is what the model actually receives. + +**Lazy retrieval.** Hosts **MUST NOT** retrieve a skill's files ahead of need — not on connection, not on listing, and not at approval. A `SKILL.md` is fetched when the skill is loaded, and a supporting file when it is read. Hosts **SHOULD** cache what they do retrieve; a cached file whose digest matches the current entry can be served without fetching it again, and one whose digest does not match must be fetched again. The requirements on a disk cache are in [Security Considerations](#security-considerations). Lazy retrieval is compatible with content-bound approval, which binds to the entry's `resources` set rather than to retrieved bytes; a file fetched long after approval is verified against that set when it is read. + +**Digests are not a trust anchor.** 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 entry and the content together. Hosts **MUST NOT** treat a digest match as a security boundary. + +## Reading Directories + +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` enumerates the server's entire resource space rather than a subtree, and servers with large or generated catalogs may not implement meaningful global listing at all. This extension therefore defines the `resources/directory/read` method, gated behind the `directoryRead` capability setting. + +### 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. + +### Request + +```typescript +interface ReadResourceDirectoryRequest extends PaginatedRequest { + method: "resources/directory/read"; + params: { + /** URI of the directory resource to read. */ + uri: string; + cursor?: Cursor; + }; +} +``` + +```json +{ + "jsonrpc": "2.0", + "id": 7, + "method": "resources/directory/read", + "params": { + "uri": "skill://pdf-processing/templates" + } +} +``` + +### Response + +```typescript +interface ReadResourceDirectoryResult extends PaginatedResult { + /** Resource metadata of the directory's direct children. */ + resources: Resource[]; +} +``` + +```json +{ + "jsonrpc": "2.0", + "id": 7, + "result": { + "resultType": "complete", + "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" + } + ] + } +} +``` + +The `resultType` field **MUST** be set to `"complete"` on `ReadResourceDirectoryResult`, as it is the standard result shape for the `resources/directory/read` request. + +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 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. + +### Directory Reads and the Held Entry + +For a skill whose entry carries a `resources` array, the host already holds a complete manifest of the skill's files; a directory read tells it nothing about that skill's contents that the entry did not. When a host acting on such a skill wants to know what `templates/` contains, it **MAY** answer from the entry alone. Directory reading serves dynamically generated skills, whose `resources` is `"dynamic"`; resource trees that are not skills at all; and obtaining the server's current view of a directory without first refreshing the entry. + +The two views can disagree. If the server adds a file to a skill after the host obtained its entry, a directory read may list that file while the held manifest does not. This is the stale-snapshot case that [Integrity and Verification](#integrity-and-verification) governs: while acting on the skill under the held entry, the host **MUST NOT** read the newly listed child — an unlisted file is a verification failure — and **MUST NOT** surface it to the model as a file of the skill. To reach it, the host refreshes the entry with `skills/get`, at which point the `resources` set has changed and any persisted content-bound approval is revoked and must be obtained again. Hosts **SHOULD** present this sequence as a skill that has changed and needs re-approval, rather than as a read error. Conversely, a child present in the manifest but absent from a directory read is a file the server no longer serves; a read of it will fail, and the same refresh applies. + +This extension defines no shared version or cache token that would let a host determine whether a directory result and an entry describe the same snapshot of the server. The manifest is authoritative for what the host may read under its current approval; a directory read is a live observation that may run ahead of or behind it. Hosts **MUST NOT** treat the directory result as extending the manifest. + +For a dynamically generated skill, a directory read is how the skill's files are discovered at all, but it does not supply the integrity the entry lacks. + +## Example Message Flow + +Consider a host connected to a server it labels `docs-server`, which declares the extension with `directoryRead: true`. The host builds its skill registry by listing: + +```json +{ + "jsonrpc": "2.0", + "id": 2, + "method": "skills/list", + "params": {} +} +``` + +```json +{ + "jsonrpc": "2.0", + "id": 2, + "result": { + "resultType": "complete", + "skills": [ + { + "uri": "skill://pdf-processing/SKILL.md", + "frontmatter": { + "name": "pdf-processing", + "description": "Extract, fill, and assemble PDF documents" + }, + "resources": [ + { + "uri": "skill://pdf-processing/SKILL.md", + "digest": "sha256:d5e6f7a8...", + "size": 5120 + }, + { + "uri": "skill://pdf-processing/templates/invoice.md", + "digest": "sha256:a8b9c0d1...", + "size": 1210 + }, + { + "uri": "skill://pdf-processing/templates/purchase-order.md", + "digest": "sha256:b9c0d1e2...", + "size": 1388 + } + ] + } + ] + } +} +``` + +The host records the entry under the identity (`docs-server`, `skill://pdf-processing/SKILL.md`), surfaces the skill's name, description, and origin to the model and the user, and fetches nothing further. + +Later the model asks to load the skill. The host obtains user approval, binding it to the three `{uri, digest}` pairs above, then reads `SKILL.md` from `docs-server`: + +```json +{ + "jsonrpc": "2.0", + "id": 3, + "method": "resources/read", + "params": { + "uri": "skill://pdf-processing/SKILL.md" + } +} +``` + +```json +{ + "jsonrpc": "2.0", + "id": 3, + "result": { + "resultType": "complete", + "contents": [ + { + "uri": "skill://pdf-processing/SKILL.md", + "mimeType": "text/markdown", + "text": "---\nname: pdf-processing\ndescription: Extract, fill, and assemble PDF documents\n---\n\n# PDF processing\n\nChoose the matching template from `templates/` ..." + } + ] + } +} +``` + +The host checks that the content is 5120 bytes and hashes to `sha256:d5e6f7a8...`, parses the frontmatter and confirms it equals the entry's `frontmatter`, then places the content in the model's context tagged as originating from `docs-server`. The host is now acting on the skill and holds this entry. + +The instructions reference `templates/`. The model lists the directory; the host routes this to `docs-server`: + +```json +{ + "jsonrpc": "2.0", + "id": 4, + "method": "resources/directory/read", + "params": { + "uri": "skill://pdf-processing/templates" + } +} +``` + +```json +{ + "jsonrpc": "2.0", + "id": 4, + "result": { + "resultType": "complete", + "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/credit-note.md", + "name": "credit-note.md", + "mimeType": "text/markdown" + } + ] + } +} +``` + +`credit-note.md` is not in the held entry's `resources`: the server has added it since the listing. The host does not surface it as a file of the skill and would treat a read of it as a verification failure. The model reads `invoice.md`, which is listed; the host issues `resources/read`, verifies size and digest against the held entry, and returns the content. + +To make `credit-note.md` available, the host refreshes the entry: + +```json +{ + "jsonrpc": "2.0", + "id": 6, + "method": "skills/get", + "params": { + "uri": "skill://pdf-processing/SKILL.md" + } +} +``` + +```json +{ + "jsonrpc": "2.0", + "id": 6, + "result": { + "resultType": "complete", + "skill": { + "uri": "skill://pdf-processing/SKILL.md", + "frontmatter": { + "name": "pdf-processing", + "description": "Extract, fill, and assemble PDF documents" + }, + "resources": [ + { + "uri": "skill://pdf-processing/SKILL.md", + "digest": "sha256:d5e6f7a8...", + "size": 5120 + }, + { + "uri": "skill://pdf-processing/templates/invoice.md", + "digest": "sha256:a8b9c0d1...", + "size": 1210 + }, + { + "uri": "skill://pdf-processing/templates/purchase-order.md", + "digest": "sha256:b9c0d1e2...", + "size": 1388 + }, + { + "uri": "skill://pdf-processing/templates/credit-note.md", + "digest": "sha256:e2f3a4b5...", + "size": 1104 + } + ] + } + } +} +``` + +The `resources` set differs from the one the user approved, so the prior approval is revoked. The host presents the skill as changed, obtains fresh approval bound to the new set, and only then reads `credit-note.md` under the refreshed entry. + +## Error Handling + +Servers **MUST** return standard JSON-RPC errors for the following protocol error cases: + +- `skills/get` with a `uri` that does not identify a skill the server serves: `-32602` (Invalid params). +- `resources/directory/read` with a `uri` that does not exist or is not a directory resource: `-32602` (Invalid params). +- `resources/read` of a skill file the server does not serve: `-32602` (Invalid params), per the base Resources specification. +- Internal errors: `-32603` (Internal error). + +A server that has not declared `directoryRead: true` is not required to recognize `resources/directory/read` and responds as the base protocol specifies for an unknown method. + +Servers **SHOULD** provide informative error messages to describe the cause of errors. + +**Example: unknown skill** + +```json +{ + "jsonrpc": "2.0", + "id": 5, + "error": { + "code": -32602, + "message": "No skill is served at skill://acme/billing/chargebacks/SKILL.md" + } +} +``` + +**Example: not a directory** + +```json +{ + "jsonrpc": "2.0", + "id": 7, + "error": { + "code": -32602, + "message": "skill://pdf-processing/SKILL.md is not a directory resource" + } +} +``` + +Verification failures — a digest or size mismatch, a frontmatter discrepancy, or a read of an unlisted file while acting on a skill — are host-side conditions, not protocol errors. They are governed by [Integrity and Verification](#integrity-and-verification) and produce no message on the wire beyond the `skills/get` or `skills/list` call a host makes to refresh the entry. + +## Reservations + +- The `skills/` method prefix is reserved for this extension. +- The method name `resources/directory/read` is defined by this extension. +- The label `io.modelcontextprotocol/skills` is reserved for this extension. +- The `_meta` key prefix `io.modelcontextprotocol.skills/` is reserved for this extension on skill resources. +- Within `SKILL.md` frontmatter `metadata`, keys prefixed `io.modelcontextprotocol/` are reserved for metadata defined by MCP extensions. + +## Security Considerations + +Skill content is instructional text delivered to a model, which makes it a prompt-injection surface. Unlike a remote tool call, an MCP-served skill can place server-authored bytes on the host and direct the model to execute them with host-side tools. This extension imposes the following requirements. + +- **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. Hosts **MUST** treat MCP-served skills as a higher-risk surface than remote tool invocation. +- **Origin is 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. +- **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 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. Hosts **MUST** resolve skill names within a per-origin namespace, identifying servers by a host-assigned label; **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; 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 **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). +- **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. +- **Digests are not a security boundary.** Entry digests are unsigned and come from the same server as the content. They confirm consistency between the entry 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 is acting on the skill, content that has moved fails verification when read; and if it does fetch a fresh entry, the rule above revokes the approval. A skill whose `resources` is `"dynamic"` 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. +- **Cache integrity, cache isolation, and durable origin.** Hosts **SHOULD** cache verified skill content locally, populated on demand as files are read rather than in bulk. A cache is a second copy of content that was verified once; the verification does not carry over to bytes that may have changed since. Hosts that cache skill content on disk **MUST** therefore do one of the following for every file served from the cache: keep the cache where nothing but the host can write to it — not the model, not scripts or tools the model runs, not other users of the machine — and never modify a cached file in place; or recompute the file's SHA-256 digest from the cached bytes on each access and compare it against the entry's digest, treating a mismatch exactly as a mismatch on a fresh read. Comparing a stored digest label, or a modification time, is not verification. Hosts that cache MCP-served skill content on disk **MUST** also 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. + +## Implementation Considerations + +The following are recommendations for interoperable implementations. They are not part of the normative specification. The full host-integration sketch and SDK guidance are maintained with [SEP-2640](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2640). + +### Hosts + +- **One registry, keyed by identity.** A host assembles a single skill registry from every origin it supports — filesystem skill directories and `skills/list` results from each connected server declaring the extension — keyed by origin and `SKILL.md` URI together, never by name. Assembling the registry reads only entries; no `SKILL.md` or supporting file is fetched at this stage. +- **One loading path, taking server and URI.** The host's skill-loading tool accepts the originating server and the `SKILL.md` URI, so a URI handed to the model by the user, by server instructions, or by another skill is exactly what the tool takes. A pair the registry has not seen triggers `skills/get`; the resulting entry is verified and approved on the same terms as a listed one. This tool is the only route by which a skill is activated; a generic resource read of the same URI returns content but does not load the skill. +- **Supporting files by resource read or virtual mount.** Once a `SKILL.md` is in context, relative references resolve against the skill's root. A host either exposes a server-scoped resource-reading tool and tells the model the skill's server and base URI, or mounts each server's skill namespace at a per-server virtual path and translates file reads under it into `resources/read`. A virtual mount resolves reads on access and is never pre-populated. Either way each read is verified against the held entry. +- **Directories.** When the originating server declares `directoryRead`, a read or `ls` of a directory path routes to `resources/directory/read`. + +### SDKs + +SDKs **SHOULD** provide thin wrappers in skill-specific terms: on the server, registering a skill from a directory at a given skill path, populating resource metadata from frontmatter, computing entry digests and sizes, answering `skills/get` and — where the skill set is bounded — `skills/list`, and warning when a registered skill exceeds the [Limits](#limits); on the client, `listSkills`, `getSkill`, a `resources/read` wrapper for skill URIs, and `readDirectory`. From 0863e03b1bee37e5934d2e2b0bb6166e126d3585 Mon Sep 17 00:00:00 2001 From: Peter Alexander Date: Fri, 4 Sep 2026 11:41:22 +0000 Subject: [PATCH 2/4] skills.mdx: list normative requirements as bullets Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01Sure6Sknngow87Q6TkzTfL --- specification/stable/skills.mdx | 180 +++++++++++++++++++++++--------- 1 file changed, 130 insertions(+), 50 deletions(-) diff --git a/specification/stable/skills.mdx b/specification/stable/skills.mdx index 3dce363..9a2e281 100644 --- a/specification/stable/skills.mdx +++ b/specification/stable/skills.mdx @@ -7,7 +7,13 @@ title: Skills **This document is part of the Skills extension (`io.modelcontextprotocol/skills`).** -This extension defines how a server serves [Agent Skills](https://agentskills.io/) over MCP using the existing Resources primitive. Each file in a skill directory is exposed as a resource, conventionally under the `skill://` URI scheme, and is read with the standard `resources/read` method. The extension introduces three methods: `skills/list`, which enumerates the skills a server serves; `skills/get`, which returns the entry for a single skill by URI; and the optional `resources/directory/read`, which lists the direct children of a directory resource. It also defines a `Skill` entry shape that carries a skill's verbatim frontmatter and a complete, digest-bearing manifest of its files. +This extension defines how a server serves [Agent Skills](https://agentskills.io/) over MCP using the existing Resources primitive. Each file in a skill directory is exposed as a resource, conventionally under the `skill://` URI scheme, and is read with the standard `resources/read` method. The extension introduces three methods: + +- `skills/list`, which enumerates the skills a server serves; +- `skills/get`, which returns the entry for a single skill by URI; and +- `resources/directory/read` (optional), which lists the direct children of a directory resource. + +It also defines a `Skill` entry shape that carries a skill's verbatim frontmatter and a complete, digest-bearing manifest of its files. The skill format itself — directory structure, YAML frontmatter, naming rules, and the progressive-disclosure model — is delegated entirely to the [Agent Skills specification](https://agentskills.io/specification). This extension defines only the transport binding. @@ -46,12 +52,13 @@ One extension-specific setting is defined: An empty object indicates support for the extension with no optional features. -A server that declares this extension **MUST** implement [`skills/list`](#listing-skills) and [`skills/get`](#getting-a-skill). A server that additionally declares `directoryRead: true` **MUST** implement [`resources/directory/read`](#reading-directories). Clients **MUST NOT** call `resources/directory/read` against a server that has not declared `directoryRead: true`. +- A server that declares this extension **MUST** implement [`skills/list`](#listing-skills) and [`skills/get`](#getting-a-skill). +- A server that additionally declares `directoryRead: true` **MUST** implement [`resources/directory/read`](#reading-directories). +- Clients **MUST NOT** call `resources/directory/read` against a server that has not declared `directoryRead: true`. +- This extension depends only on the base protocol's Resources primitive. A server declaring it **MUST** also declare the `resources` capability and serve skill files through `resources/read`. Clients issue `skills/list` and `skills/get` only after observing the server's declaration. A client that does not implement this extension sees `skill://` resources as ordinary resources, which they are. -This extension depends only on the base protocol's Resources primitive. Servers declaring it **MUST** also declare the `resources` capability and serve skill files through `resources/read`. - ## Skill Format A skill served over MCP **MUST** conform to the [Agent Skills specification](https://agentskills.io/specification). In particular: @@ -78,17 +85,20 @@ where: The resource for the skill's required `SKILL.md` is therefore always addressable as `skill:///SKILL.md`. The skill's root directory is `skill://` — the `/SKILL.md` suffix removed, no trailing slash — matching [Directory Resources](#directory-resources). -The final segment of `` **MUST** equal the skill's `name` as declared in its `SKILL.md` frontmatter. This mirrors the Agent Skills specification's requirement that `name` [match the parent directory name](https://agentskills.io/specification#name-field). Preceding segments, if any, are a server-chosen organizational prefix; servers **MAY** organize skills hierarchically by domain, team, version, or any other axis. In `skill://acme/billing/refunds/SKILL.md` the prefix is `acme/billing` and the skill's `name` is `refunds`; in `skill://git-workflow/SKILL.md` there is no prefix and the `name` is `git-workflow`. The skill name is therefore always recoverable from the URI alone, without reading frontmatter. - -Further constraints: +Constraints on ``: +- The final segment of `` **MUST** equal the skill's `name` as declared in its `SKILL.md` frontmatter. This mirrors the Agent Skills specification's requirement that `name` [match the parent directory name](https://agentskills.io/specification#name-field), and means the skill name is always recoverable from the URI alone, without reading frontmatter. +- The final segment, being the skill `name`, **MUST** satisfy the Agent Skills specification's naming rules. +- Preceding segments, if any, are a server-chosen organizational prefix; servers **MAY** organize skills hierarchically by domain, team, version, or any other axis. In `skill://acme/billing/refunds/SKILL.md` the prefix is `acme/billing` and the skill's `name` is `refunds`; in `skill://git-workflow/SKILL.md` there is no prefix and the `name` is `git-workflow`. +- The first segment occupies the URI authority component and **SHOULD** be a valid `reg-name` per [RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986); any other prefix segments **SHOULD** be valid URI path segments; no further constraints are imposed on them. +- The authority component carries no special semantics under this extension, and clients **MUST NOT** attempt DNS or network resolution of it. - A `SKILL.md` **MAY** appear in a descendant directory of a skill — skills can nest. See [Nested Skills](#nested-skills). -- The final `` segment, being the skill `name`, **MUST** satisfy the Agent Skills specification's naming rules. The first `` segment occupies the authority component and **SHOULD** be a valid `reg-name` per [RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986); any other prefix segments **SHOULD** be valid URI path segments; no further constraints are imposed on them. -- Per RFC 3986, the first segment of `` occupies the authority component. This carries no special semantics under this extension and clients **MUST NOT** attempt DNS or network resolution of it. -A server **MAY** serve skills under another scheme native to its domain (for example, `github://owner/repo/skills/refunds/SKILL.md`). No scheme is privileged: the structural constraints above — `` ending in the skill name, `SKILL.md` explicit in the URI — apply regardless of scheme, and [`skills/list`](#listing-skills) enumerates a server's skills whatever scheme they use. +Schemes other than `skill://`: -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`](#listing-skills) entry, the authoritative record of the skills a server publishes; or from an explicit reference — the server's `instructions`, another skill, or the user — which [`skills/get`](#getting-a-skill) 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. +- A server **MAY** serve skills under another scheme native to its domain (for example, `github://owner/repo/skills/refunds/SKILL.md`). No scheme is privileged: the structural constraints above — `` ending in the skill name, `SKILL.md` explicit in the URI — apply regardless of scheme, and [`skills/list`](#listing-skills) 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`](#listing-skills) entry, the authoritative record of the skills a server publishes; or from an explicit reference — the server's `instructions`, another skill, or the user — which [`skills/get`](#getting-a-skill) 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 @@ -107,8 +117,9 @@ For each `skill:///SKILL.md` resource: - `mimeType` **SHOULD** be `text/markdown`. - `name` **SHOULD** be set from the `name` field of the `SKILL.md` YAML frontmatter. By the path constraint above, this equals the final segment of ``. - `description` **SHOULD** be set from the `description` field of the `SKILL.md` YAML frontmatter. +- Servers **MAY** expose additional frontmatter fields via the resource's `_meta` object. When `_meta` keys are used for skill resources, implementations **SHOULD** use the `io.modelcontextprotocol.skills/` reverse-domain prefix. -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. +Other files in the skill use the `mimeType` appropriate to their content. ### Nested Skills @@ -173,19 +184,28 @@ interface Skill { `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. -A skill URI is scoped to the server that serves it. Nothing prevents two connected servers from both serving `skill://refunds/SKILL.md`, and those are two unrelated skills. The identity of an MCP-served skill is therefore the pair of the host's identity for the originating server and the skill's `uri`. Hosts **MUST** preserve both halves wherever a skill is recorded or addressed — the registry, persisted approvals, the cache, and any tool or path through which the model reaches the skill — and **MUST NOT** key any of these on the `uri` alone. In particular, any path at which a host materializes skill content, whether a cache directory or a virtual mount, **MUST** encode the server identity as well as the `uri`, so that same-URI skills from different servers land at distinct paths and the originating server is recoverable from the path. +A skill URI is scoped to the server that serves it. Nothing prevents two connected servers from both serving `skill://refunds/SKILL.md`, and those are two unrelated skills. The identity of an MCP-served skill is therefore the pair of the host's identity for the originating server and the skill's `uri`. + +- Hosts **MUST** preserve both halves wherever a skill is recorded or addressed — the registry, persisted approvals, the cache, and any tool or path through which the model reaches the skill. +- Hosts **MUST NOT** key any of these on the `uri` alone. +- Any path at which a host materializes skill content, whether a cache directory or a virtual mount, **MUST** encode the server identity as well as the `uri`, so that same-URI skills from different servers land at distinct paths and the originating server is recoverable from the path. ### 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 from entries 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 `` 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. +- The `frontmatter` object **MUST** be identical in content to the frontmatter of the `SKILL.md` it describes. +- The final `` 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. ### Names -A skill's `name` is a label, not an identifier — a skill is identified by its `uri` within a server, and by the pair of server identity and `uri` across servers ([Skill URIs](#skill-uris)). 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 Considerations](#security-considerations). +A skill's `name` is a label, not an identifier — a skill is identified by its `uri` within a server, and by the pair of server identity and `uri` across servers ([Skill URIs](#skill-uris)). 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. + +- Within a server's listing, names **SHOULD** be unique, but they are not guaranteed to be. 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 Considerations](#security-considerations). ### Resources @@ -206,9 +226,10 @@ This extension fixes two per-skill limits so that servers know what every confor | Resources per skill | 512 entries | The entries of the skill's `resources`, `SKILL.md` included | | Total file size per skill | 16 MiB (16,777,216 bytes) | The sum of `size` over the skill's `resources` | -Hosts **MUST** support skills up to and including these limits, and **MAY** support larger ones. Servers **SHOULD NOT** serve a skill that exceeds either limit; a skill that does is not guaranteed to be loadable by any conforming host. Because `resources` is complete, both limits are checkable from the entry alone before the host retrieves a single file, and a host that declines a skill on this basis **SHOULD** tell the user why rather than fail silently on a later read. - -For a skill whose `resources` is `"dynamic"`, the entry offers nothing to count. A host that chooses to load such a skill applies the total-size limit to what it actually retrieves and **MAY** stop loading the skill once that limit is reached. +- Hosts **MUST** support skills up to and including these limits, and **MAY** support larger ones. +- Servers **SHOULD NOT** serve a skill that exceeds either limit; a skill that does is not guaranteed to be loadable by any conforming host. +- Because `resources` is complete, both limits are checkable from the entry alone before the host retrieves a single file. A host that declines a skill on this basis **SHOULD** tell the user why rather than fail silently on a later read. +- For a skill whose `resources` is `"dynamic"`, the entry offers nothing to count. A host that chooses to load such a skill applies the total-size limit to what it actually retrieves and **MAY** stop loading the skill once that limit is reached. These limits bound a host's exposure to a single skill. They say nothing about how many skills a server may serve or a host must accept; a listing may be arbitrarily large. @@ -295,15 +316,14 @@ interface ListSkillsResult extends PaginatedResult { } ``` -The `resultType` field **MUST** be set to `"complete"` on `ListSkillsResult`, as it is the standard result shape for the `skills/list` request. - -The result **MAY** be empty. 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, and **MUST** support loading a skill given only its URI ([Getting a Skill](#getting-a-skill)). - -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. - -`ListSkillsResult` also carries the base protocol's list-caching attributes, `ttlMs` and `cacheScope`, as defined for `tools/list` and `resources/list`, 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. +Semantics: -The method serves entries for a server's skills whatever URI scheme they use; enumeration is uniform across schemes. +- The `resultType` field **MUST** be set to `"complete"` on `ListSkillsResult`, as it is the standard result shape for the `skills/list` request. +- The result **MAY** be empty. 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, and **MUST** support loading a skill given only its URI ([Getting a Skill](#getting-a-skill)). +- 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. +- `ListSkillsResult` also carries the base protocol's list-caching attributes, `ttlMs` and `cacheScope`, as defined for `tools/list` and `resources/list`, 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. +- The method serves entries for a server's skills whatever URI scheme they use; enumeration is uniform across schemes. A `skills/list` entry is a complete manifest of the skill, not a summary to be filled in by a follow-up call. A host that pages through the listing has, in that one pass, everything it needs to build its registry, present a skill for approval, bind the approval to content, and verify every file it later reads. `skills/get` is never a step a host must take to complete a listed entry. @@ -392,12 +412,12 @@ interface GetSkillResult extends Result { } ``` -The `resultType` field **MUST** be set to `"complete"` on `GetSkillResult`, as it is the standard result shape for the `skills/get` request. - -`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`, under the same rules. +The `skill` object is a `Skill` entry, identical in shape and meaning to an entry of `skills/list`, under the same rules. Semantics: +- The `resultType` field **MUST** be set to `"complete"` on `GetSkillResult`, as it is the standard result shape for the `skills/get` request. +- `params.uri` **MUST** be the URI of a skill's `SKILL.md`. - 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. @@ -414,7 +434,11 @@ A server **MAY** direct the agent to specific skill URIs from its `instructions` Skill files are read via the standard `resources/read` method. No skill-specific read semantics are defined. -Reading a `SKILL.md` via `resources/read` does not by itself activate the skill. `resources/read` is transport: it returns bytes, whoever asked for them — a generic resource-reading tool, a resource browser, a user inspecting the server. A skill is activated only by the host's own skill-loading path — the one that verifies the content against the skill's entry ([Integrity and Verification](#integrity-and-verification)), applies any required user approval ([Security Considerations](#security-considerations)), and opens the window in which the host is acting on the skill. Hosts **MUST NOT** treat a `resources/read` of a `SKILL.md` that arrives by any other route as a load: it grants no approval, opens no window, and confers no standing on the skill's supporting files. Content obtained that way is ordinary resource content, and a host that returns it to the model **SHOULD** do so as it would any other resource read, not as a loaded skill. A host that wishes such a read to load the skill routes it through the skill-loading path instead. +Reading a `SKILL.md` via `resources/read` does not by itself activate the skill. `resources/read` is transport: it returns bytes, whoever asked for them — a generic resource-reading tool, a resource browser, a user inspecting the server. A skill is activated only by the host's own skill-loading path — the one that verifies the content against the skill's entry ([Integrity and Verification](#integrity-and-verification)), applies any required user approval ([Security Considerations](#security-considerations)), and opens the window in which the host is acting on the skill. + +- Hosts **MUST NOT** treat a `resources/read` of a `SKILL.md` that arrives by any other route as a load: it grants no approval, opens no window, and confers no standing on the skill's supporting files. +- Content obtained that way is ordinary resource content, and a host that returns it to the model **SHOULD** do so as it would any other resource read, not as a loaded skill. +- A host that wishes such a read to load the skill routes it through the skill-loading path instead. ### Relative References @@ -424,15 +448,39 @@ Internal references within a skill (for example, `SKILL.md` linking to `referenc Digests are SHA-256 hashes of a file's raw bytes, formatted as `sha256:{hex}` where `{hex}` is 64 lowercase hexadecimal characters. Each `SkillResource` in a skill's `resources` carries the digest of the file at its `uri`. -**Verification on read.** When a host retrieves a file listed in a skill's `resources`, it **MUST** verify the content against that entry's `digest` and `size`. A mismatch means the content is not what the entry promised — corrupted, tampered with, or stale because the skill was updated after the entry was fetched. Whatever the cause, hosts **MUST NOT** use the unverified content. To recover from staleness, the host calls [`skills/get`](#getting-a-skill) for that skill — or `skills/list` to refresh the catalog — and proceeds from the current `resources` set, which, being different, revokes any content-bound approval ([Security Considerations](#security-considerations)). +### Verification on Read + +- When a host retrieves a file listed in a skill's `resources`, it **MUST** verify the content against that entry's `digest` and `size`. +- A mismatch means the content is not what the entry promised — corrupted, tampered with, or stale because the skill was updated after the entry was fetched. Whatever the cause, hosts **MUST NOT** use the unverified content. +- To recover from staleness, the host calls [`skills/get`](#getting-a-skill) for that skill — or `skills/list` to refresh the catalog — and proceeds from the current `resources` set, which, being different, revokes any content-bound approval ([Security Considerations](#security-considerations)). + +### The Acting Window and the Held Entry + +A host is _acting on_ a skill from the moment it loads the skill's `SKILL.md` into the model's context until, at the earliest, that `SKILL.md` leaves context. For the whole of that window the host holds the entry from which it loaded the skill. + +- Hosts **MAY** hold the window open longer, never shorter. +- Because `resources` is complete, an unlisted file is a change to the skill. While acting on a skill, a host **MUST** resolve reads of the skill's files only to URIs listed in the held entry's `resources`. +- A host **MUST** treat a read of an unlisted file within the skill as a verification failure equivalent to a digest mismatch. -**The acting window and the held entry.** A host is _acting on_ a skill from the moment it loads the skill's `SKILL.md` into the model's context until, at the earliest, that `SKILL.md` leaves context; hosts **MAY** hold the window open longer, never shorter. For the whole of that window the host holds the entry from which it loaded the skill. Because `resources` is complete, an unlisted file is a change to the skill: while acting on a skill, 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. +### Frontmatter Verification -**Frontmatter verification.** After fetching a `SKILL.md` for which the host holds an entry — digest-verified when the entry's `resources` is an array, and unverifiable when it is `"dynamic"` — 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 entry is what the model actually receives. +- After fetching a `SKILL.md` for which the host holds an entry — digest-verified when the entry's `resources` is an array, and unverifiable when it is `"dynamic"` — 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. -**Lazy retrieval.** Hosts **MUST NOT** retrieve a skill's files ahead of need — not on connection, not on listing, and not at approval. A `SKILL.md` is fetched when the skill is loaded, and a supporting file when it is read. Hosts **SHOULD** cache what they do retrieve; a cached file whose digest matches the current entry can be served without fetching it again, and one whose digest does not match must be fetched again. The requirements on a disk cache are in [Security Considerations](#security-considerations). Lazy retrieval is compatible with content-bound approval, which binds to the entry's `resources` set rather than to retrieved bytes; a file fetched long after approval is verified against that set when it is read. +This enforces the [Frontmatter](#frontmatter) identity requirement on the host side, so that what a user approves from the entry is what the model actually receives. -**Digests are not a trust anchor.** 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 entry and the content together. Hosts **MUST NOT** treat a digest match as a security boundary. +### Lazy Retrieval + +- Hosts **MUST NOT** retrieve a skill's files ahead of need — not on connection, not on listing, and not at approval. A `SKILL.md` is fetched when the skill is loaded, and a supporting file when it is read. +- Hosts **SHOULD** cache what they do retrieve. A cached file whose digest matches the current entry can be served without fetching it again, and one whose digest does not match must be fetched again. The requirements on a disk cache are in [Security Considerations](#security-considerations). + +Lazy retrieval is compatible with content-bound approval, which binds to the entry's `resources` set rather than to retrieved bytes; a file fetched long after approval is verified against that set when it is read. + +### Digests Are Not a Trust Anchor + +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 entry and the content together. + +- Hosts **MUST NOT** treat a digest match as a security boundary. ## Reading Directories @@ -510,16 +558,20 @@ Semantics: - 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. +- 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. ### Directory Reads and the Held Entry For a skill whose entry carries a `resources` array, the host already holds a complete manifest of the skill's files; a directory read tells it nothing about that skill's contents that the entry did not. When a host acting on such a skill wants to know what `templates/` contains, it **MAY** answer from the entry alone. Directory reading serves dynamically generated skills, whose `resources` is `"dynamic"`; resource trees that are not skills at all; and obtaining the server's current view of a directory without first refreshing the entry. -The two views can disagree. If the server adds a file to a skill after the host obtained its entry, a directory read may list that file while the held manifest does not. This is the stale-snapshot case that [Integrity and Verification](#integrity-and-verification) governs: while acting on the skill under the held entry, the host **MUST NOT** read the newly listed child — an unlisted file is a verification failure — and **MUST NOT** surface it to the model as a file of the skill. To reach it, the host refreshes the entry with `skills/get`, at which point the `resources` set has changed and any persisted content-bound approval is revoked and must be obtained again. Hosts **SHOULD** present this sequence as a skill that has changed and needs re-approval, rather than as a read error. Conversely, a child present in the manifest but absent from a directory read is a file the server no longer serves; a read of it will fail, and the same refresh applies. +The two views can disagree. If the server adds a file to a skill after the host obtained its entry, a directory read may list that file while the held manifest does not. This is the stale-snapshot case that [Integrity and Verification](#integrity-and-verification) governs: -This extension defines no shared version or cache token that would let a host determine whether a directory result and an entry describe the same snapshot of the server. The manifest is authoritative for what the host may read under its current approval; a directory read is a live observation that may run ahead of or behind it. Hosts **MUST NOT** treat the directory result as extending the manifest. +- While acting on the skill under the held entry, the host **MUST NOT** read the newly listed child — an unlisted file is a verification failure — and **MUST NOT** surface it to the model as a file of the skill. +- To reach it, the host refreshes the entry with `skills/get`, at which point the `resources` set has changed and any persisted content-bound approval is revoked and must be obtained again. +- Hosts **SHOULD** present this sequence as a skill that has changed and needs re-approval, rather than as a read error. +- Conversely, a child present in the manifest but absent from a directory read is a file the server no longer serves; a read of it will fail, and the same refresh applies. +- Hosts **MUST NOT** treat the directory result as extending the manifest. This extension defines no shared version or cache token that would let a host determine whether a directory result and an entry describe the same snapshot of the server; the manifest is authoritative for what the host may read under its current approval, and a directory read is a live observation that may run ahead of or behind it. For a dynamically generated skill, a directory read is how the skill's files are discovered at all, but it does not supply the integrity the entry lacks. @@ -755,18 +807,46 @@ Verification failures — a digest or size mismatch, a frontmatter discrepancy, Skill content is instructional text delivered to a model, which makes it a prompt-injection surface. Unlike a remote tool call, an MCP-served skill can place server-authored bytes on the host and direct the model to execute them with host-side tools. This extension imposes the following requirements. -- **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. Hosts **MUST** treat MCP-served skills as a higher-risk surface than remote tool invocation. -- **Origin is 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. -- **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 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. Hosts **MUST** resolve skill names within a per-origin namespace, identifying servers by a host-assigned label; **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; 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 **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)). +- **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. + - Hosts **MUST** treat MCP-served skills as a higher-risk surface than remote tool invocation. +- **Origin is visible to the model.** + - Hosts **MUST** tag MCP-served skill content with its originating server identity at the point it enters model context. + - Hosts **MUST NOT** present an MCP-served skill to the model as indistinguishable from a local filesystem skill. +- **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). + - Hosts **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 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. A name binds to whatever bytes its origin currently serves — it carries no authorship or endorsement. + - Hosts **MUST** resolve skill names within a per-origin namespace, identifying servers by a host-assigned label. + - Hosts **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. + - Hosts **SHOULD** surface collisions to the user. + - 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; this extension assigns such annotations no semantics. +- **No implicit permission grants.** A remote server populating `allowed-tools` is requesting elevated access on the host, not declaring a property of its own environment. + - 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 **MUST** be ignored for MCP-origin skills unless the user has explicitly approved that grant for that skill. + - 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). -- **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. +- **Provenance and inspection.** + - Hosts **SHOULD** indicate which server a skill originates from when presenting it. + - Hosts **SHOULD** let users inspect a skill's content before it is loaded into model context. + - Hosts **MAY** gate loading behind per-skill or per-server user approval. - **Digests are not a security boundary.** Entry digests are unsigned and come from the same server as the content. They confirm consistency between the entry 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 is acting on the skill, content that has moved fails verification when read; and if it does fetch a fresh entry, the rule above revokes the approval. A skill whose `resources` is `"dynamic"` 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. -- **Cache integrity, cache isolation, and durable origin.** Hosts **SHOULD** cache verified skill content locally, populated on demand as files are read rather than in bulk. A cache is a second copy of content that was verified once; the verification does not carry over to bytes that may have changed since. Hosts that cache skill content on disk **MUST** therefore do one of the following for every file served from the cache: keep the cache where nothing but the host can write to it — not the model, not scripts or tools the model runs, not other users of the machine — and never modify a cached file in place; or recompute the file's SHA-256 digest from the cached bytes on each access and compare it against the entry's digest, treating a mismatch exactly as a mismatch on a fresh read. Comparing a stored digest label, or a modification time, is not verification. Hosts that cache MCP-served skill content on disk **MUST** also 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. +- **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 is acting on the skill, content that has moved fails verification when read; and if it does fetch a fresh entry, the rule above revokes the approval. + - A skill whose `resources` is `"dynamic"` 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. +- **Cache integrity, cache isolation, and durable origin.** A cache is a second copy of content that was verified once; the verification does not carry over to bytes that may have changed since. Cached bytes do not graduate to filesystem-skill trust by residing locally. + - Hosts **SHOULD** cache verified skill content locally, populated on demand as files are read rather than in bulk. + - Hosts that cache skill content on disk **MUST** do one of the following for every file served from the cache: keep the cache where nothing but the host can write to it — not the model, not scripts or tools the model runs, not other users of the machine — and never modify a cached file in place; or recompute the file's SHA-256 digest from the cached bytes on each access and compare it against the entry's digest, treating a mismatch exactly as a mismatch on a fresh read. Comparing a stored digest label, or a modification time, is not verification. + - Hosts that cache MCP-served skill content on disk **MUST** do so in a location excluded from every filesystem-skill discovery path. + - Hosts **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. + - Hosts **SHOULD** remove a server's cached skill content when the user removes that server. ## Implementation Considerations From 44fce9203b90f0eefa1a6482c5fa6544387d86b2 Mon Sep 17 00:00:00 2001 From: Peter Alexander Date: Fri, 4 Sep 2026 11:59:20 +0000 Subject: [PATCH 3/4] skills.mdx: plain punctuation, drop em-dashes and rhetorical phrasing Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01Sure6Sknngow87Q6TkzTfL --- specification/stable/skills.mdx | 163 ++++++++++++++++---------------- 1 file changed, 84 insertions(+), 79 deletions(-) diff --git a/specification/stable/skills.mdx b/specification/stable/skills.mdx index 9a2e281..361d60c 100644 --- a/specification/stable/skills.mdx +++ b/specification/stable/skills.mdx @@ -9,13 +9,13 @@ title: Skills This extension defines how a server serves [Agent Skills](https://agentskills.io/) over MCP using the existing Resources primitive. Each file in a skill directory is exposed as a resource, conventionally under the `skill://` URI scheme, and is read with the standard `resources/read` method. The extension introduces three methods: -- `skills/list`, which enumerates the skills a server serves; -- `skills/get`, which returns the entry for a single skill by URI; and +- `skills/list`, which enumerates the skills a server serves. +- `skills/get`, which returns the entry for a single skill by URI. - `resources/directory/read` (optional), which lists the direct children of a directory resource. -It also defines a `Skill` entry shape that carries a skill's verbatim frontmatter and a complete, digest-bearing manifest of its files. +It also defines a `Skill` entry shape that carries a skill's verbatim frontmatter and a complete manifest of its files with digests. -The skill format itself — directory structure, YAML frontmatter, naming rules, and the progressive-disclosure model — is delegated entirely to the [Agent Skills specification](https://agentskills.io/specification). This extension defines only the transport binding. +The skill format itself (directory structure, YAML frontmatter, naming rules, and the progressive-disclosure model) is delegated entirely to the [Agent Skills specification](https://agentskills.io/specification). This extension defines only the transport binding. The Model Context Protocol (MCP) Skills extension allows servers to publish **skills** alongside the tools, resources, and prompts they already serve. A skill is a directory of files, minimally a `SKILL.md`, that provides structured workflow instructions to an agent. Skills are addressed by the URI of their `SKILL.md` and may be read directly whether or not the server enumerates them. @@ -57,7 +57,7 @@ An empty object indicates support for the extension with no optional features. - Clients **MUST NOT** call `resources/directory/read` against a server that has not declared `directoryRead: true`. - This extension depends only on the base protocol's Resources primitive. A server declaring it **MUST** also declare the `resources` capability and serve skill files through `resources/read`. -Clients issue `skills/list` and `skills/get` only after observing the server's declaration. A client that does not implement this extension sees `skill://` resources as ordinary resources, which they are. +Clients issue `skills/list` and `skills/get` only after observing the server's declaration. To a client that does not implement this extension, `skill://` resources are ordinary resources. ## Skill Format @@ -83,21 +83,21 @@ where: - `` 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`). - `` is the file's path relative to the skill directory root, using `/` as the separator. -The resource for the skill's required `SKILL.md` is therefore always addressable as `skill:///SKILL.md`. The skill's root directory is `skill://` — the `/SKILL.md` suffix removed, no trailing slash — matching [Directory Resources](#directory-resources). +The resource for the skill's required `SKILL.md` is therefore always addressable as `skill:///SKILL.md`. The skill's root directory is `skill://`, that is, the `SKILL.md` URI with the `/SKILL.md` suffix removed and no trailing slash (see [Directory Resources](#directory-resources)). Constraints on ``: - The final segment of `` **MUST** equal the skill's `name` as declared in its `SKILL.md` frontmatter. This mirrors the Agent Skills specification's requirement that `name` [match the parent directory name](https://agentskills.io/specification#name-field), and means the skill name is always recoverable from the URI alone, without reading frontmatter. - The final segment, being the skill `name`, **MUST** satisfy the Agent Skills specification's naming rules. -- Preceding segments, if any, are a server-chosen organizational prefix; servers **MAY** organize skills hierarchically by domain, team, version, or any other axis. In `skill://acme/billing/refunds/SKILL.md` the prefix is `acme/billing` and the skill's `name` is `refunds`; in `skill://git-workflow/SKILL.md` there is no prefix and the `name` is `git-workflow`. -- The first segment occupies the URI authority component and **SHOULD** be a valid `reg-name` per [RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986); any other prefix segments **SHOULD** be valid URI path segments; no further constraints are imposed on them. +- Preceding segments, if any, are a server-chosen organizational prefix. Servers **MAY** organize skills hierarchically by domain, team, version, or any other axis. In `skill://acme/billing/refunds/SKILL.md` the prefix is `acme/billing` and the skill's `name` is `refunds`. In `skill://git-workflow/SKILL.md` there is no prefix and the `name` is `git-workflow`. +- The first segment occupies the URI authority component and **SHOULD** be a valid `reg-name` per [RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986). Any other prefix segments **SHOULD** be valid URI path segments. No further constraints are imposed on prefix segments. - The authority component carries no special semantics under this extension, and clients **MUST NOT** attempt DNS or network resolution of it. -- A `SKILL.md` **MAY** appear in a descendant directory of a skill — skills can nest. See [Nested Skills](#nested-skills). +- A `SKILL.md` **MAY** appear in a descendant directory of a skill (skills can nest). See [Nested Skills](#nested-skills). Schemes other than `skill://`: -- A server **MAY** serve skills under another scheme native to its domain (for example, `github://owner/repo/skills/refunds/SKILL.md`). No scheme is privileged: the structural constraints above — `` ending in the skill name, `SKILL.md` explicit in the URI — apply regardless of scheme, and [`skills/list`](#listing-skills) 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`](#listing-skills) entry, the authoritative record of the skills a server publishes; or from an explicit reference — the server's `instructions`, another skill, or the user — which [`skills/get`](#getting-a-skill) confirms, the server answering for a skill it serves and erroring otherwise. This holds for every scheme, `skill://` included. +- A server **MAY** serve skills under another scheme native to its domain (for example, `github://owner/repo/skills/refunds/SKILL.md`). No scheme is privileged. The structural constraints above (`` ending in the skill name, `SKILL.md` explicit in the URI) apply regardless of scheme, and [`skills/list`](#listing-skills) enumerates a server's skills regardless of scheme. +- 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`](#listing-skills) entry, which is the authoritative record of the skills a server publishes, or from an explicit reference (in the server's `instructions`, in another skill, or from the user) that the host confirms with [`skills/get`](#getting-a-skill). The server answers `skills/get` for a skill it serves and returns an error otherwise. This applies to `skill://` URIs as well as to any other scheme. - A host **MUST NOT** conclude that a resource is a skill merely because its URI carries a particular scheme. ### Examples @@ -123,15 +123,15 @@ 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: +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. The following additional semantics apply: -- **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` (see [Security Considerations](#security-considerations)). -- **Publication is flat.** A nested skill is published like any other: through its own [`skills/list`](#listing-skills) 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. +- **Nested content is supporting content.** From the enclosing skill's perspective, a nested skill's directory and files are ordinary supporting files. A nested `SKILL.md` read this way is ordinary markdown, and hosts **MUST NOT** act on its frontmatter. +- **Activation requires fresh consent.** Approval is per skill. Approving a skill approves that skill alone and not any skill nested within it. Activating a nested skill (loading it as a separate skill, whether through the host's skill-loading machinery or by giving effect to its frontmatter) requires fresh, explicit user consent, and approval of the enclosing skill does not substitute for it. Once activated, a nested skill is an ordinary skill and its frontmatter takes effect under the same rules as any other MCP-served skill, including the approval gate on `allowed-tools` (see [Security Considerations](#security-considerations)). +- **Publication is flat.** A nested skill is published like any other, through its own [`skills/list`](#listing-skills) entry or by explicit reference. The listing remains flat. An entry for a nested skill is an ordinary entry whose `uri` shares a path prefix with the enclosing skill's `uri`, and nothing in the listing marks nesting. ## Skill Entries -A `Skill` entry is the unit of metadata this extension exchanges. It is returned by both `skills/list` and `skills/get`, with identical shape and meaning in each. +A `Skill` entry describes one skill. It is returned by both `skills/list` and `skills/get`, with identical shape and meaning in each. ```typescript /** @@ -148,7 +148,7 @@ interface SkillResource { digest: string; /** - * Length in bytes of the file's raw content — the same bytes `digest` covers. + * Length in bytes of the file's raw content (the same bytes that `digest` covers). */ size: number; } @@ -182,17 +182,17 @@ interface Skill { ### 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. +`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. -A skill URI is scoped to the server that serves it. Nothing prevents two connected servers from both serving `skill://refunds/SKILL.md`, and those are two unrelated skills. The identity of an MCP-served skill is therefore the pair of the host's identity for the originating server and the skill's `uri`. +A skill URI is scoped to the server that serves it. Two connected servers can both serve `skill://refunds/SKILL.md`, and those are two unrelated skills. The identity of an MCP-served skill is therefore the pair of the host's identity for the originating server and the skill's `uri`. -- Hosts **MUST** preserve both halves wherever a skill is recorded or addressed — the registry, persisted approvals, the cache, and any tool or path through which the model reaches the skill. +- Hosts **MUST** preserve both halves wherever a skill is recorded or addressed, including the registry, persisted approvals, the cache, and any tool or path through which the model reaches the skill. - Hosts **MUST NOT** key any of these on the `uri` alone. - Any path at which a host materializes skill content, whether a cache directory or a virtual mount, **MUST** encode the server identity as well as the `uri`, so that same-URI skills from different servers land at distinct paths and the originating server is recoverable from the path. ### 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 from entries alone, without fetching each `SKILL.md`. +`frontmatter` is the skill's `SKILL.md` YAML frontmatter rendered verbatim as a JSON object. It contains 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 from entries 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 `` segment of the entry's `uri` **MUST** equal `frontmatter.name`, per [Resource Mapping](#resource-mapping). @@ -200,21 +200,21 @@ A skill URI is scoped to the server that serves it. Nothing prevents two connect ### Names -A skill's `name` is a label, not an identifier — a skill is identified by its `uri` within a server, and by the pair of server identity and `uri` across servers ([Skill URIs](#skill-uris)). 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. +A skill's `name` is a label, not an identifier. A skill is identified by its `uri` within a server, and by the pair of server identity and `uri` across servers ([Skill URIs](#skill-uris)). 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. - Within a server's listing, names **SHOULD** be unique, but they are not guaranteed to be. 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 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 Considerations](#security-considerations). +- An MCP-served skill **MUST NOT** silently shadow, or be silently substituted for, a same-named skill from any other origin, whether another server or the host's own filesystem skills. See [Security Considerations](#security-considerations). ### Resources -`resources` is **REQUIRED** on every skill entry and takes one of two forms: an array enumerating the skill's files — `SKILL.md` and every supporting file — as `SkillResource` objects, or the string `"dynamic"`. The array is the unit of content that a host verifies and that a user's approval binds to. +`resources` is **REQUIRED** on every skill entry and takes one of two forms: an array of `SkillResource` objects enumerating the skill's files (`SKILL.md` and every supporting file), or the string `"dynamic"`. The array is the unit of content that a host verifies and that a user's approval binds to. -- When `resources` is an array, it **MUST** be complete: it lists every file of the skill, each exactly once, including an entry whose `uri` equals the skill's top-level `uri` — that entry carries the digest and size of `SKILL.md` itself. +- When `resources` is an array, it **MUST** be complete. It lists every file of the skill, each exactly once, including an entry whose `uri` equals the skill's top-level `uri`. That entry carries the digest and size of `SKILL.md` itself. - Each `uri` **MUST** be the skill's `SKILL.md` or a file within the skill's directory. -- Each entry **MUST** carry `size`: the length in bytes of the file's raw content — the same bytes the `digest` covers. A read whose byte length differs from the entry's `size` is a verification failure equivalent to a digest mismatch ([Integrity and Verification](#integrity-and-verification)), whether or not the host goes on to compute the digest. -- 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. +- Each entry **MUST** carry `size`, the length in bytes of the file's raw content (the same bytes the `digest` covers). A read whose byte length differs from the entry's `size` is a verification failure equivalent to a digest mismatch ([Integrity and Verification](#integrity-and-verification)), whether or not the host goes on to compute the digest. +- 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. - When a skill's content is generated dynamically, such that stable digests cannot be published, the server **MUST** set `resources` to the string `"dynamic"` instead of an array. An entry with no `resources` at all, or with any value other than an array or `"dynamic"`, is invalid, and hosts **MUST NOT** load it. A skill whose `resources` is `"dynamic"` offers no content integrity and cannot be content-bound ([Security Considerations](#security-considerations)). Hosts **MAY** decline to load such skills, and server authors **SHOULD** expect that some hosts will. ### Limits @@ -227,11 +227,11 @@ This extension fixes two per-skill limits so that servers know what every confor | Total file size per skill | 16 MiB (16,777,216 bytes) | The sum of `size` over the skill's `resources` | - Hosts **MUST** support skills up to and including these limits, and **MAY** support larger ones. -- Servers **SHOULD NOT** serve a skill that exceeds either limit; a skill that does is not guaranteed to be loadable by any conforming host. -- Because `resources` is complete, both limits are checkable from the entry alone before the host retrieves a single file. A host that declines a skill on this basis **SHOULD** tell the user why rather than fail silently on a later read. +- Servers **SHOULD NOT** serve a skill that exceeds either limit. A skill that does is not guaranteed to be loadable by any conforming host. +- Because `resources` is complete, both limits are checkable from the entry alone before the host retrieves any file. A host that declines a skill on this basis **SHOULD** tell the user why rather than fail silently on a later read. - For a skill whose `resources` is `"dynamic"`, the entry offers nothing to count. A host that chooses to load such a skill applies the total-size limit to what it actually retrieves and **MAY** stop loading the skill once that limit is reached. -These limits bound a host's exposure to a single skill. They say nothing about how many skills a server may serve or a host must accept; a listing may be arbitrarily large. +These limits bound a host's exposure to a single skill. They do not limit how many skills a server may serve or a host must accept, and a listing may be arbitrarily large. ## Listing Skills @@ -321,11 +321,11 @@ Semantics: - The `resultType` field **MUST** be set to `"complete"` on `ListSkillsResult`, as it is the standard result shape for the `skills/list` request. - The result **MAY** be empty. 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, and **MUST** support loading a skill given only its URI ([Getting a Skill](#getting-a-skill)). -- 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. -- `ListSkillsResult` also carries the base protocol's list-caching attributes, `ttlMs` and `cacheScope`, as defined for `tools/list` and `resources/list`, 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. -- The method serves entries for a server's skills whatever URI scheme they use; enumeration is uniform across schemes. +- 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, and a skill's `resources` set is never split across pages. +- `ListSkillsResult` also carries the base protocol's list-caching attributes, `ttlMs` and `cacheScope`, as defined for `tools/list` and `resources/list`, with the same semantics. They are a freshness hint and a cache-scope marker for the listing, not an integrity property. [Integrity and Verification](#integrity-and-verification) governs content regardless of how fresh a cached listing is. +- The method serves entries for a server's skills regardless of URI scheme. -A `skills/list` entry is a complete manifest of the skill, not a summary to be filled in by a follow-up call. A host that pages through the listing has, in that one pass, everything it needs to build its registry, present a skill for approval, bind the approval to content, and verify every file it later reads. `skills/get` is never a step a host must take to complete a listed entry. +A `skills/list` entry is a complete manifest of the skill rather than a summary. After paging through the listing, a host has everything it needs to build its registry, present a skill for approval, bind the approval to content, and verify every file it later reads. A host does not need to call `skills/get` to complete a listed entry. ## Getting a Skill @@ -418,31 +418,31 @@ Semantics: - The `resultType` field **MUST** be set to `"complete"` on `GetSkillResult`, as it is the standard result shape for the `skills/get` request. - `params.uri` **MUST** be the URI of a skill's `SKILL.md`. -- 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. +- 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. +- The result is a point-in-time snapshot, as a listing entry is. A host re-calls the method to refresh one skill's digests without re-enumerating the catalog. - A skill whose content is generated dynamically carries `"resources": "dynamic"`, per [Resources](#resources), whether it is reached through `skills/list` or `skills/get`. -- The result carries no pagination cursor: a single entry is not a list. +- The result carries no pagination cursor. -`skills/get` complements direct reading: a URI alone is enough to read a skill via `resources/read`, 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 Considerations](#security-considerations)). +`skills/get` complements direct reading. A URI alone is enough to read a skill via `resources/read`, and `skills/get` returns the metadata and digests for that same URI, so a skill that never appeared in a listing can still be verified and content-bound ([Security Considerations](#security-considerations)). ### Pointer from Server Instructions -A server **MAY** direct the agent to specific skill URIs from its `instructions`. This requires no discovery machinery on the host; the URI is simply present in the model's context, confirmable via `skills/get` and readable via `resources/read`. +A server **MAY** direct the agent to specific skill URIs from its `instructions`. This requires no discovery machinery on the host. The URI is present in the model's context, and the host can confirm it with `skills/get` and read it with `resources/read`. ## Reading Skill Content Skill files are read via the standard `resources/read` method. No skill-specific read semantics are defined. -Reading a `SKILL.md` via `resources/read` does not by itself activate the skill. `resources/read` is transport: it returns bytes, whoever asked for them — a generic resource-reading tool, a resource browser, a user inspecting the server. A skill is activated only by the host's own skill-loading path — the one that verifies the content against the skill's entry ([Integrity and Verification](#integrity-and-verification)), applies any required user approval ([Security Considerations](#security-considerations)), and opens the window in which the host is acting on the skill. +Reading a `SKILL.md` via `resources/read` does not by itself activate the skill. `resources/read` is transport. It returns bytes to whatever asked for them, whether a generic resource-reading tool, a resource browser, or a user inspecting the server. A skill is activated only by the host's own skill-loading path, which verifies the content against the skill's entry ([Integrity and Verification](#integrity-and-verification)), applies any required user approval ([Security Considerations](#security-considerations)), and opens the window in which the host is acting on the skill. -- Hosts **MUST NOT** treat a `resources/read` of a `SKILL.md` that arrives by any other route as a load: it grants no approval, opens no window, and confers no standing on the skill's supporting files. +- Hosts **MUST NOT** treat a `resources/read` of a `SKILL.md` that arrives by any other route as a load. Such a read grants no approval, opens no acting window, and confers no standing on the skill's supporting files. - Content obtained that way is ordinary resource content, and a host that returns it to the model **SHOULD** do so as it would any other resource read, not as a loaded skill. -- A host that wishes such a read to load the skill routes it through the skill-loading path instead. +- A host that wants such a read to load the skill routes it through the skill-loading path instead. ### Relative References -Internal references within a skill (for example, `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. +Internal references within a skill (for example, `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, as a filesystem path would resolve. For example, `references/GUIDE.md` in `skill://acme/billing/refunds/SKILL.md` resolves to `skill://acme/billing/refunds/references/GUIDE.md`. 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, so a relative reference in a nested skill's `SKILL.md` resolves against the nested skill's root regardless of how the file was reached. ## Integrity and Verification @@ -451,30 +451,30 @@ Digests are SHA-256 hashes of a file's raw bytes, formatted as `sha256:{hex}` wh ### Verification on Read - When a host retrieves a file listed in a skill's `resources`, it **MUST** verify the content against that entry's `digest` and `size`. -- A mismatch means the content is not what the entry promised — corrupted, tampered with, or stale because the skill was updated after the entry was fetched. Whatever the cause, hosts **MUST NOT** use the unverified content. -- To recover from staleness, the host calls [`skills/get`](#getting-a-skill) for that skill — or `skills/list` to refresh the catalog — and proceeds from the current `resources` set, which, being different, revokes any content-bound approval ([Security Considerations](#security-considerations)). +- A mismatch means the content is not what the entry described. It may be corrupted, tampered with, or stale because the skill was updated after the entry was fetched. In all cases, hosts **MUST NOT** use the unverified content. +- To recover from staleness, the host calls [`skills/get`](#getting-a-skill) for that skill (or `skills/list` to refresh the catalog) and proceeds from the current `resources` set. Because that set differs, any content-bound approval is revoked ([Security Considerations](#security-considerations)). ### The Acting Window and the Held Entry -A host is _acting on_ a skill from the moment it loads the skill's `SKILL.md` into the model's context until, at the earliest, that `SKILL.md` leaves context. For the whole of that window the host holds the entry from which it loaded the skill. +A host is _acting on_ a skill from the moment it loads the skill's `SKILL.md` into the model's context until, at the earliest, that `SKILL.md` leaves context. Throughout that window the host holds the entry from which it loaded the skill. -- Hosts **MAY** hold the window open longer, never shorter. +- Hosts **MAY** hold the window open longer but not shorter. - Because `resources` is complete, an unlisted file is a change to the skill. While acting on a skill, a host **MUST** resolve reads of the skill's files only to URIs listed in the held entry's `resources`. - A host **MUST** treat a read of an unlisted file within the skill as a verification failure equivalent to a digest mismatch. ### Frontmatter Verification -- After fetching a `SKILL.md` for which the host holds an entry — digest-verified when the entry's `resources` is an array, and unverifiable when it is `"dynamic"` — hosts **MUST** parse its YAML frontmatter and compare it field-by-field against the entry's `frontmatter`. +- After fetching a `SKILL.md` for which the host holds an entry (digest-verified when the entry's `resources` is an array, unverifiable when it is `"dynamic"`), 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 entry is what the model actually receives. +This enforces the [Frontmatter](#frontmatter) identity requirement on the host side, so that what a user approves from the entry is what the model receives. ### Lazy Retrieval -- Hosts **MUST NOT** retrieve a skill's files ahead of need — not on connection, not on listing, and not at approval. A `SKILL.md` is fetched when the skill is loaded, and a supporting file when it is read. +- Hosts **MUST NOT** retrieve a skill's files ahead of need, whether on connection, on listing, or at approval. A `SKILL.md` is fetched when the skill is loaded, and a supporting file when it is read. - Hosts **SHOULD** cache what they do retrieve. A cached file whose digest matches the current entry can be served without fetching it again, and one whose digest does not match must be fetched again. The requirements on a disk cache are in [Security Considerations](#security-considerations). -Lazy retrieval is compatible with content-bound approval, which binds to the entry's `resources` set rather than to retrieved bytes; a file fetched long after approval is verified against that set when it is read. +Lazy retrieval is compatible with content-bound approval, which binds to the entry's `resources` set rather than to retrieved bytes. A file fetched long after approval is verified against that set when it is read. ### Digests Are Not a Trust Anchor @@ -484,11 +484,11 @@ Digests are unsigned and supplied by the same server that supplies the content. ## Reading Directories -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` enumerates the server's entire resource space rather than a subtree, and servers with large or generated catalogs may not implement meaningful global listing at all. This extension therefore defines the `resources/directory/read` method, gated behind the `directoryRead` capability setting. +A skill's instructions frequently reference a directory rather than a file, for example "pick the appropriate template from `templates/`" or "run the matching script in `scripts/`". To act on this, the agent must learn what the directory contains. `resources/list` enumerates the server's entire resource space rather than a subtree, and servers with large or generated catalogs may not implement meaningful global listing at all. This extension therefore defines the `resources/directory/read` method, gated behind the `directoryRead` capability setting. ### 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. +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`, and are addressable whether listed or not. ### Request @@ -555,25 +555,25 @@ The `resultType` field **MUST** be set to `"complete"` on `ReadResourceDirectory 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 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. +- The result contains every direct child of the directory. Files carry their ordinary `Resource` metadata and subdirectories are listed as directory resources (`mimeType: "inode/directory"`). The listing is not recursive, and 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. +- 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. +- The method itself is not skill-specific, and a server **MAY** support it on any directory resource it serves, under any scheme. ### Directory Reads and the Held Entry -For a skill whose entry carries a `resources` array, the host already holds a complete manifest of the skill's files; a directory read tells it nothing about that skill's contents that the entry did not. When a host acting on such a skill wants to know what `templates/` contains, it **MAY** answer from the entry alone. Directory reading serves dynamically generated skills, whose `resources` is `"dynamic"`; resource trees that are not skills at all; and obtaining the server's current view of a directory without first refreshing the entry. +For a skill whose entry carries a `resources` array, the host already holds a complete manifest of the skill's files, and a directory read adds nothing about that skill's contents. When a host acting on such a skill wants to know what `templates/` contains, it **MAY** answer from the entry alone. Directory reading is useful for dynamically generated skills (whose `resources` is `"dynamic"`), for resource trees that are not skills at all, and for obtaining the server's current view of a directory without first refreshing the entry. The two views can disagree. If the server adds a file to a skill after the host obtained its entry, a directory read may list that file while the held manifest does not. This is the stale-snapshot case that [Integrity and Verification](#integrity-and-verification) governs: -- While acting on the skill under the held entry, the host **MUST NOT** read the newly listed child — an unlisted file is a verification failure — and **MUST NOT** surface it to the model as a file of the skill. +- While acting on the skill under the held entry, the host **MUST NOT** read the newly listed child (an unlisted file is a verification failure) and **MUST NOT** surface it to the model as a file of the skill. - To reach it, the host refreshes the entry with `skills/get`, at which point the `resources` set has changed and any persisted content-bound approval is revoked and must be obtained again. - Hosts **SHOULD** present this sequence as a skill that has changed and needs re-approval, rather than as a read error. -- Conversely, a child present in the manifest but absent from a directory read is a file the server no longer serves; a read of it will fail, and the same refresh applies. -- Hosts **MUST NOT** treat the directory result as extending the manifest. This extension defines no shared version or cache token that would let a host determine whether a directory result and an entry describe the same snapshot of the server; the manifest is authoritative for what the host may read under its current approval, and a directory read is a live observation that may run ahead of or behind it. +- Conversely, a child present in the manifest but absent from a directory read is a file the server no longer serves. A read of it will fail, and the same refresh applies. +- Hosts **MUST NOT** treat the directory result as extending the manifest. This extension defines no shared version or cache token that would let a host determine whether a directory result and an entry describe the same snapshot of the server. The manifest is authoritative for what the host may read under its current approval. A directory read is a live observation that may be ahead of or behind it. -For a dynamically generated skill, a directory read is how the skill's files are discovered at all, but it does not supply the integrity the entry lacks. +For a dynamically generated skill, a directory read is the only way to discover the skill's files, but it does not provide the content integrity that the entry lacks. ## Example Message Flow @@ -658,7 +658,7 @@ Later the model asks to load the skill. The host obtains user approval, binding The host checks that the content is 5120 bytes and hashes to `sha256:d5e6f7a8...`, parses the frontmatter and confirms it equals the entry's `frontmatter`, then places the content in the model's context tagged as originating from `docs-server`. The host is now acting on the skill and holds this entry. -The instructions reference `templates/`. The model lists the directory; the host routes this to `docs-server`: +The instructions reference `templates/`. The model lists the directory, and the host routes this to `docs-server`: ```json { @@ -698,7 +698,7 @@ The instructions reference `templates/`. The model lists the directory; the host } ``` -`credit-note.md` is not in the held entry's `resources`: the server has added it since the listing. The host does not surface it as a file of the skill and would treat a read of it as a verification failure. The model reads `invoice.md`, which is listed; the host issues `resources/read`, verifies size and digest against the held entry, and returns the content. +`credit-note.md` is not in the held entry's `resources`, because the server added it after the listing. The host does not surface it as a file of the skill and would treat a read of it as a verification failure. The model reads `invoice.md`, which is listed. The host issues `resources/read`, verifies size and digest against the held entry, and returns the content. To make `credit-note.md` available, the host refreshes the entry: @@ -793,7 +793,7 @@ Servers **SHOULD** provide informative error messages to describe the cause of e } ``` -Verification failures — a digest or size mismatch, a frontmatter discrepancy, or a read of an unlisted file while acting on a skill — are host-side conditions, not protocol errors. They are governed by [Integrity and Verification](#integrity-and-verification) and produce no message on the wire beyond the `skills/get` or `skills/list` call a host makes to refresh the entry. +Verification failures (a digest or size mismatch, a frontmatter discrepancy, or a read of an unlisted file while acting on a skill) are host-side conditions rather than protocol errors. They are governed by [Integrity and Verification](#integrity-and-verification). The only resulting wire traffic is the `skills/get` or `skills/list` call a host makes to refresh the entry. ## Reservations @@ -817,33 +817,35 @@ Skill content is instructional text delivered to a model, which makes it a promp - Hosts **MUST** ignore or approval-gate (a). - Hosts **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 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** 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. A name binds to whatever bytes its origin currently serves — it carries no authorship or endorsement. +- **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. A name binds to whatever bytes its origin currently serves and carries no authorship or endorsement. - Hosts **MUST** resolve skill names within a per-origin namespace, identifying servers by a host-assigned label. - Hosts **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. - Hosts **SHOULD** surface collisions to the user. - - 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; this extension assigns such annotations no semantics. + - 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. This extension assigns such annotations no semantics. - **No implicit permission grants.** A remote server populating `allowed-tools` is requesting elevated access on the host, not declaring a property of its own environment. - 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 **MUST** be ignored for MCP-origin skills unless the user has explicitly approved that grant for that skill. - - 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)). + - 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). +- **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). - **Provenance and inspection.** - Hosts **SHOULD** indicate which server a skill originates from when presenting it. - Hosts **SHOULD** let users inspect a skill's content before it is loaded into model context. - Hosts **MAY** gate loading behind per-skill or per-server user approval. - **Digests are not a security boundary.** Entry digests are unsigned and come from the same server as the content. They confirm consistency between the entry 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. + - 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 is acting on the skill, content that has moved fails verification when read; and if it does fetch a fresh entry, the rule above revokes the approval. - - A skill whose `resources` is `"dynamic"` 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. -- **Cache integrity, cache isolation, and durable origin.** A cache is a second copy of content that was verified once; the verification does not carry over to bytes that may have changed since. Cached bytes do not graduate to filesystem-skill trust by residing locally. + - A host need not poll for changes. While it is acting on the skill, content that has changed fails verification when read, and if it fetches a fresh entry, the rule above revokes the approval. + - A skill whose `resources` is `"dynamic"` 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. +- **Cache integrity, cache isolation, and durable origin.** A cache is a second copy of content that was verified once, and the verification does not carry over to bytes that may have changed since. Cached bytes do not acquire filesystem-skill trust by being stored locally. - Hosts **SHOULD** cache verified skill content locally, populated on demand as files are read rather than in bulk. - - Hosts that cache skill content on disk **MUST** do one of the following for every file served from the cache: keep the cache where nothing but the host can write to it — not the model, not scripts or tools the model runs, not other users of the machine — and never modify a cached file in place; or recompute the file's SHA-256 digest from the cached bytes on each access and compare it against the entry's digest, treating a mismatch exactly as a mismatch on a fresh read. Comparing a stored digest label, or a modification time, is not verification. + - Hosts that cache skill content on disk **MUST** do one of the following for every file served from the cache. Comparing a stored digest label or a modification time is not verification. + - Keep the cache where only the host can write to it (not the model, scripts or tools the model runs, or other users of the machine) and never modify a cached file in place. + - Recompute the file's SHA-256 digest from the cached bytes on each access and compare it against the entry's digest, treating a mismatch as a mismatch on a fresh read. - Hosts that cache MCP-served skill content on disk **MUST** do so in a location excluded from every filesystem-skill discovery path. - Hosts **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. - Hosts **SHOULD** remove a server's cached skill content when the user removes that server. @@ -854,11 +856,14 @@ The following are recommendations for interoperable implementations. They are no ### Hosts -- **One registry, keyed by identity.** A host assembles a single skill registry from every origin it supports — filesystem skill directories and `skills/list` results from each connected server declaring the extension — keyed by origin and `SKILL.md` URI together, never by name. Assembling the registry reads only entries; no `SKILL.md` or supporting file is fetched at this stage. -- **One loading path, taking server and URI.** The host's skill-loading tool accepts the originating server and the `SKILL.md` URI, so a URI handed to the model by the user, by server instructions, or by another skill is exactly what the tool takes. A pair the registry has not seen triggers `skills/get`; the resulting entry is verified and approved on the same terms as a listed one. This tool is the only route by which a skill is activated; a generic resource read of the same URI returns content but does not load the skill. -- **Supporting files by resource read or virtual mount.** Once a `SKILL.md` is in context, relative references resolve against the skill's root. A host either exposes a server-scoped resource-reading tool and tells the model the skill's server and base URI, or mounts each server's skill namespace at a per-server virtual path and translates file reads under it into `resources/read`. A virtual mount resolves reads on access and is never pre-populated. Either way each read is verified against the held entry. +- **One registry, keyed by identity.** A host assembles a single skill registry from every origin it supports (filesystem skill directories, and `skills/list` results from each connected server declaring the extension), keyed by origin and `SKILL.md` URI together rather than by name. Assembling the registry reads only entries, and no `SKILL.md` or supporting file is fetched at this stage. +- **One loading path, taking server and URI.** The host's skill-loading tool accepts the originating server and the `SKILL.md` URI, so a URI handed to the model by the user, by server instructions, or by another skill can be passed to the tool directly. A pair the registry has not seen triggers `skills/get`, and the resulting entry is verified and approved on the same terms as a listed one. This tool is the only route by which a skill is activated. A generic resource read of the same URI returns content but does not load the skill. +- **Supporting files by resource read or virtual mount.** Once a `SKILL.md` is in context, relative references resolve against the skill's root. A host either exposes a server-scoped resource-reading tool and tells the model the skill's server and base URI, or mounts each server's skill namespace at a per-server virtual path and translates file reads under it into `resources/read`. A virtual mount resolves reads on access and is never pre-populated. In both cases each read is verified against the held entry. - **Directories.** When the originating server declares `directoryRead`, a read or `ls` of a directory path routes to `resources/directory/read`. ### SDKs -SDKs **SHOULD** provide thin wrappers in skill-specific terms: on the server, registering a skill from a directory at a given skill path, populating resource metadata from frontmatter, computing entry digests and sizes, answering `skills/get` and — where the skill set is bounded — `skills/list`, and warning when a registered skill exceeds the [Limits](#limits); on the client, `listSkills`, `getSkill`, a `resources/read` wrapper for skill URIs, and `readDirectory`. +SDKs **SHOULD** provide thin wrappers in skill-specific terms. + +- Server side: register a skill from a directory at a given skill path, populate resource metadata from frontmatter, compute entry digests and sizes, answer `skills/get` (and `skills/list` where the skill set is bounded), and warn when a registered skill exceeds the [Limits](#limits). +- Client side: `listSkills`, `getSkill`, a `resources/read` wrapper for skill URIs, and `readDirectory`. From 15a35237a32952aced14ceb210d2a51c64ad8215 Mon Sep 17 00:00:00 2001 From: Peter Alexander Date: Fri, 4 Sep 2026 12:12:06 +0000 Subject: [PATCH 4/4] skills.mdx: remove Implementation Considerations section Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01Sure6Sknngow87Q6TkzTfL --- specification/stable/skills.mdx | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/specification/stable/skills.mdx b/specification/stable/skills.mdx index 361d60c..e632647 100644 --- a/specification/stable/skills.mdx +++ b/specification/stable/skills.mdx @@ -849,21 +849,3 @@ Skill content is instructional text delivered to a model, which makes it a promp - Hosts that cache MCP-served skill content on disk **MUST** do so in a location excluded from every filesystem-skill discovery path. - Hosts **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. - Hosts **SHOULD** remove a server's cached skill content when the user removes that server. - -## Implementation Considerations - -The following are recommendations for interoperable implementations. They are not part of the normative specification. The full host-integration sketch and SDK guidance are maintained with [SEP-2640](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2640). - -### Hosts - -- **One registry, keyed by identity.** A host assembles a single skill registry from every origin it supports (filesystem skill directories, and `skills/list` results from each connected server declaring the extension), keyed by origin and `SKILL.md` URI together rather than by name. Assembling the registry reads only entries, and no `SKILL.md` or supporting file is fetched at this stage. -- **One loading path, taking server and URI.** The host's skill-loading tool accepts the originating server and the `SKILL.md` URI, so a URI handed to the model by the user, by server instructions, or by another skill can be passed to the tool directly. A pair the registry has not seen triggers `skills/get`, and the resulting entry is verified and approved on the same terms as a listed one. This tool is the only route by which a skill is activated. A generic resource read of the same URI returns content but does not load the skill. -- **Supporting files by resource read or virtual mount.** Once a `SKILL.md` is in context, relative references resolve against the skill's root. A host either exposes a server-scoped resource-reading tool and tells the model the skill's server and base URI, or mounts each server's skill namespace at a per-server virtual path and translates file reads under it into `resources/read`. A virtual mount resolves reads on access and is never pre-populated. In both cases each read is verified against the held entry. -- **Directories.** When the originating server declares `directoryRead`, a read or `ls` of a directory path routes to `resources/directory/read`. - -### SDKs - -SDKs **SHOULD** provide thin wrappers in skill-specific terms. - -- Server side: register a skill from a directory at a given skill path, populate resource metadata from frontmatter, compute entry digests and sizes, answer `skills/get` (and `skills/list` where the skill set is bounded), and warn when a registered skill exceeds the [Limits](#limits). -- Client side: `listSkills`, `getSkill`, a `resources/read` wrapper for skill URIs, and `readDirectory`.