Tools give an agent capabilities, but not the know-how to do the job well. With the Skills over MCP extension, a Model Context Protocol (MCP) server can publish that know-how as Agent Skills: workflow instructions plus supporting files that a client discovers and loads on demand. I like that the operating manual ships with the service it describes, and that the host can verify it.
This is part 27 of my series on what an MCP server does under the 2026-07-28 specification. Part 26 covered Tasks. The facts are as I read them in October 2026.
In brief #
- A skill is a folder with a
SKILL.mdand supporting files; the extension adds only discovery and retrieval over MCP. - The extension commits your server to
skills/listandskills/get, whose entries list every file’s Uniform Resource Identifier (URI), Secure Hash Algorithm 256 (SHA-256) digest and size. - Hosts MUST verify every loaded file against that manifest, and any changed, added or removed file revokes a persisted approval. Approval is optional, except for running code,
allowed-toolsgrants, reads across servers and nested skills. - On the server, the likeliest failure, in my view, is byte drift between what you hashed and what you served.
- Support is early as of October 2026, so ship the same workflows as prompts and instruction pointers too.
What your server publishes #
A skill is a directory with a SKILL.md and optional supporting files, in the Agent Skills format. The extension is io.modelcontextprotocol/skills, defined by SEP-2640, a Specification Enhancement Proposal, now Final. It keeps that format and serves the files as resources, the primitive of part 10.
skills/
code-review/
SKILL.md # frontmatter: name: code-review, description: ...
references/checklist.md
scripts/lint.sh
incident-postmortem/
SKILL.md
templates/postmortem.md
SKILL.mdMUST begin with YAML frontmatter containingnameanddescription, and the final segment of its directory’s path MUST equalname.- Files map to URIs such as
skill://code-review/references/checklist.md. Servers SHOULD use theskill://scheme but MAY use another; hosts MUST NOT identify a skill by its scheme alone.
Servers MUST declare both resources and the extension (see part 23):
"capabilities": {
"resources": {},
"extensions": { "io.modelcontextprotocol/skills": { "directoryRead": true } }
}
Declaring the extension commits the server to two methods, with the files served through resources/read. The optional directoryRead, false by default, adds a third:
| Method | Returns |
|---|---|
skills/list |
Skill entries: uri (ofSKILL.md ),frontmatter (all fields, unchanged) andresources , a complete manifest of every file’s URI, SHA-256 digest and byte size, or"dynamic" . Paginated and cacheable |
skills/get |
One entry by URI, in the same shape. It MUST answer for every skill the server serves, including skills absent from the listing |
resources/directory/read (optional) |
The direct children of a directory URI, with no trailing slash. Subdirectories carry mimeType: "inode/directory" |
The entry for a cut-down code-review, with only SKILL.md and the checklist, as the sample below computes it:
{
"uri": "skill://code-review/SKILL.md",
"frontmatter": {
"name": "code-review",
"description": "Review code using the team's checklist."
},
"resources": [
{
"uri": "skill://code-review/SKILL.md",
"digest": "sha256:62b2013dc8abfd4431cb25175d3ff048a5b80f140202365e903678c1b84e2e5d",
"size": 167
},
{
"uri": "skill://code-review/references/checklist.md",
"digest": "sha256:f0c66a85eb8555b827d992a399f404da47db40b90c656f784224fbd46af45cf5",
"size": 56
}
]
}
List and get results carry resultType: "complete", ttlMs and cacheScope (part 6). A skill’s manifest MUST NOT be split across pages. A server MAY return an empty or partial listing, for example to hide internal skills. An unknown skill or file gets -32602 (Invalid params).
How a host loads a skill #
Reading SKILL.md through resources/read doesn’t activate a skill; the host’s skill- path does, after verification and any required approval:
A host MAY gate behind approval, per skill or per server. The Skills overview makes approval mandatory for running code on the host, allowed-tools grants, reads across servers and activating nested skills.
Integrity: the bytes must match
A host that loads a skill with a manifest MUST:
- restrict file reads to URIs in the manifest it retained;
- verify each file’s raw byte size and SHA-256 digest before use;
- compare the frontmatter of
SKILL.md, field by field, with the listed entry.
Hosts MUST NOT use content that fails verification; they refresh the entry with skills/get. A persisted approval MUST bind to the complete set of URIs and digests, so any changed, added or removed file revokes it. Hosts MUST NOT fetch files ahead of need: not on connect, listing or approval.
A skill with generated content can declare "resources": "dynamic" instead. Hosts MAY decline it, and they MUST NOT treat a persisted approval as covering arbitrary future content.
Building the catalog on your server #
Since hosts verify every file they load, my advice is to build the catalog once and serve from it:
The specification only asks that the bytes match. Here’s where I’d expect them to drift:
- Line endings. Git’s
autocrlfcan turn line feed (LF) endings into carriage return and line feed (CRLF) on Windows build agents. Normalize at build time, then hash and serve the same bytes. - Byte order marks. One added by an editor changes the hash.
- Text or blob. A
textresult’s UTF-8 encoding must match the hashed bytes exactly. For binary files, or when in doubt, returnblob(base64). - Order. Enumerate files in a fixed order, so manifests match between processes.
The official Python software development kit (SDK) has no Skills extension yet (pull request #3485 was still open on 9 October 2026), so my builder is plain Python:
import hashlib
from dataclasses import dataclass
from pathlib import Path
@dataclass(frozen=True)
class SkillFile:
uri: str
digest: str
size: int
def build_manifest(skill_dir: Path, skill_name: str) -> list[SkillFile]:
files = {
p.relative_to(skill_dir).as_posix(): p # forward slashes on every platform
for p in skill_dir.rglob("*")
if p.is_file() and not p.is_symlink()
}
manifest = []
for rel in sorted(files): # a fixed order, so the manifest is stable
data = files[rel].read_bytes() # the exact bytes you will serve
digest = "sha256:" + hashlib.sha256(data).hexdigest()
manifest.append(SkillFile(f"skill://{skill_name}/{rel}", digest, len(data)))
return manifest
It hashes regular files only, skipping symlinks, and hexdigest() already gives the 64 lowercase hex characters SEP-2640 requires. A few lines not shown here wrap it into the entry above. Until the SDK ships Skills, you’d serve both methods through its extension API (part 23).
Security, discovery and support today #
Limits and duties:
- Servers SHOULD NOT exceed 512 files or 16 MiB per skill,
SKILL.mdincluded; hosts MUST support skills up to that size. - Hosts MUST stop same-named skills from silently replacing each other and tag loaded content with its origin server.
- Skill content is untrusted input; part 21 lists malicious skills among the threats.
- A nested skill, a subdirectory with its own
SKILL.md, is published as a separate skill.
My advice: treat everything in a skill as public, so keep secrets and internal URLs out of it.
Server instructions can also point to a skill by URI (“For pull request reviews, load skill://code-review/SKILL.md”), and hosts MUST support by URI, unlisted skills included. Keep each description sharp: it’s how a model decides whether to load the skill.
Support as of October 2026:
| Implementation | Skills support |
|---|---|
| ChatGPT, fast-agent, MCP Inspector | Partial support in the official client matrix ; ChatGPT’s is a “bounded, static subset” |
Apify’s mcpc command-line client |
Full support in the same matrix |
| Go SDK | Open pull request #1238, which already passes the conformance suite |
| TypeScript, Python and C# SDKs | Open pull requests #2818, #3485 and #1864, still in progress ( list of implementations ) |
A survey on a third-party blog, published on 22 September 2026 (the day of the working group’s call), sent an anonymous handshake to hosted MCP endpoints. Of the 572 that answered, only two declared the extension; 699 more asked for credentials.
My advice is to ship the same workflows in two more forms. One is prompts, which Claude, Claude Code, VS Code, Cursor and Gemini CLI already surface as of October 2026, and which part 12 gives as a skill’s fallback. The other is pointers in your server instructions. Then turn on Skills, so capable hosts get the richer, verified version. In TypeScript, mcp-use turns a conventional skills/ folder into the full extension.
Method and caveats #
- Built from my guide’s chapter on Skills, against the 2026-07-28 specification; support as of October 2026. The pipeline and the points on byte drift and secrets are my advice, not the specification’s.
- The builder, ported from my guide’s C#, uses no SDK; it ran on 9 October 2026 with Python 3.13 and printed the entry shown.
- Where the extension’s documentation is more precise than my notes (when approval is required, which SDKs have open pull requests, the survey’s day), this article follows the sources linked above.