11# Skills
22
33[ SEP-2640] ( https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2640 ) defines a
4- convention for serving [ Agent Skills] ( https://agentskills.io/ ) over MCP: a skill is a directory
5- of files — minimally a ` SKILL.md ` with YAML frontmatter — exposed as ordinary MCP resources,
6- conventionally under a ` skill:// ` URI. A server enumerates its skills with ` skills/list ` ,
7- answers for any one of them by URI with ` skills/get ` , and — optionally — lists a directory's
8- direct children with ` resources/directory/read ` .
9-
10- The SDK ships this as the built-in ` Skills ` extension (` io.modelcontextprotocol/skills ` ). If
11- [ Extensions] ( extensions.md ) are new to you, skim that page first.
12-
13- ` Skills ` provides the ** protocol** primitives: request/response handling, capability
14- advertisement, and SEP-2640 conformance validation. It does not discover, read, or hash skills
15- from a filesystem — you supply handlers that answer from wherever your catalog actually lives
16- (a database, a generated index, an in-memory list, or a directory you walk yourself), and serve
17- each skill's files as ordinary resources through ` MCPServer.add_resource ` or an
18- ` @mcp.resource(...) ` template handler.
4+ convention for serving [ Agent Skills] ( https://agentskills.io/ ) over MCP. A skill is just a
5+ directory of files — at minimum a ` SKILL.md ` with YAML frontmatter — that you expose as ordinary
6+ MCP resources, conventionally under a ` skill:// ` URI.
7+
8+ A server enumerates its skills with ` skills/list ` , answers for any single one by URI with
9+ ` skills/get ` , and — optionally — lists a directory's direct children with
10+ ` resources/directory/read ` .
11+
12+ The SDK ships this as the built-in ` Skills ` extension (` io.modelcontextprotocol/skills ` ). There's
13+ one on the server side and one on the client side. If [ Extensions] ( extensions.md ) are new to you,
14+ skim that page first.
15+
16+ !!! info
17+ ` Skills ` gives you the ** protocol** primitives: request/response handling, capability
18+ advertisement, and SEP-2640 conformance validation.
19+
20+ It does **not** discover, read, or hash skills from a filesystem. You supply handlers that
21+ answer from wherever your catalog actually lives — a database, a generated index, an in-memory
22+ list, or a directory you walk yourself — and you serve each skill's files as ordinary resources
23+ through `MCPServer.add_resource` or an `@mcp.resource(...)` template handler.
1924
2025## Serving a skill
2126
27+ Here's a server that serves one skill:
28+
2229``` python title="server.py" hl_lines="31-41 44-51 55"
2330-- 8 < -- " docs_src/skills/tutorial001.py"
2431```
2532
26- Three moves:
33+ There are three moves here :
2734
28- * ` Skill(uri=..., frontmatter=..., resources=[...]) ` : one entry, identical in shape whether it
35+ * ` Skill(uri=..., frontmatter=..., resources=[...]) ` is one entry. It has the same shape whether it
2936 comes back from ` skills/list ` or ` skills/get ` . ` resources ` is the skill's complete file
3037 manifest — every file, ` SKILL.md ` included, each with a ` sha256:... ` digest and byte size — or
3138 the string ` "dynamic" ` for content generated on demand.
32- * ` list_skills ` / ` get_skill ` : plain async callables, invoked per request. ` get_skill ` ** must **
33- answer for a skill even if a real ` list_skills ` implementation omitted it — SEP-2640 requires
34- a server to answer by URI for every skill it serves, listed or not.
35- * ` mcp.add_resource(TextResource(uri=SKILL_URI, ...)) ` : the skill's actual file content, served
36- through the SDK's ordinary resource machinery. ` Skills ` never reads or writes resource content
37- itself.
39+ * ` list_skills ` and ` get_skill ` are plain async callables, invoked once per request. ` get_skill `
40+ ** must ** answer for a skill even if your ` list_skills ` left it out — SEP-2640 requires a server
41+ to answer by URI for every skill it serves, listed or not.
42+ * ` mcp.add_resource(TextResource(uri=SKILL_URI, ...)) ` registers the skill's actual file content,
43+ served through the SDK's ordinary resource machinery. ` Skills ` never reads or writes resource
44+ content itself.
3845
39- ` Skills(list_skills=..., get_skill=...) ` is all a server needs; ` resources/directory/read ` is
40- optional (below).
46+ And that's it. ` Skills(list_skills=..., get_skill=...) ` is all a server needs;
47+ ` resources/directory/read ` is optional (more on that below).
4148
4249## Fetching a skill
4350
44- ``` python title="client.py" hl_lines="5"
51+ On the client side, ` Skills ` is a [ ` ClientExtension ` ] ( extensions.md ) . You register it the same way
52+ you register any other one — by passing it to ` Client(extensions=[...]) ` — and then call ` bind ` to
53+ get the verbs tied to that connection:
54+
55+ ``` python title="client.py" hl_lines="9-11"
4556-- 8 < -- " docs_src/skills/tutorial001_client.py"
4657```
4758
48- ` list_skills ` and ` read_directory ` follow ` nextCursor ` to completion, so you get every page's
49- skills or resources in one call; ` get_skill ` costs exactly one request. These three validate the
50- server's response against the SEP-2640 conformance rules before returning it — a name that doesn't
51- match its URI, a digest in the wrong shape, or an incomplete manifest raises ` ValueError ` rather
52- than reaching your code. ` read_skill_uri ` is the exception: a thin, discoverable alias for
53- ` resources/read ` that returns a ` ReadResourceResult ` (text or blob contents) and validates nothing
54- itself (see the next paragraph).
59+ ` skills.bind(client) ` hands you a ` BoundSkills ` , and its methods are the SEP-2640 verbs:
5560
56- ` verify_skill_resource(skill, uri, content) ` checks a file's bytes — size, then SHA-256 digest —
57- against the entry you hold for it. Call it after ` read_skill_uri ` and before treating the content
58- as trustworthy: ` resources/read ` returns whatever bytes the server sends * right now* , verification
59- is what ties those bytes back to the manifest you already validated. It applies to a static
60- manifest only — a ` "dynamic" ` skill carries no digests, so calling it on one raises ` ValueError ` .
61+ * ` list_skills ` and ` read_directory ` follow ` nextCursor ` to completion, so a single call gives you
62+ every page's skills or resources.
63+ * ` get_skill ` costs exactly one request.
64+
65+ These three validate the server's response against the SEP-2640 conformance rules before returning
66+ it. A name that doesn't match its URI, a digest in the wrong shape, or an incomplete manifest raises
67+ ` ValueError ` rather than reaching your code.
68+
69+ ` read_skill_uri ` is the exception. It's a thin, discoverable alias for ` resources/read ` that returns
70+ a ` ReadResourceResult ` (text or blob contents) and validates nothing itself — that's the next step.
71+
72+ !!! tip
73+ ` verify_skill_resource(skill, uri, content) ` checks a file's bytes — size, then SHA-256
74+ digest — against the entry you hold for it. Call it after ` read_skill_uri ` and * before* you
75+ treat the content as trustworthy.
76+
77+ `resources/read` returns whatever bytes the server sends *right now*; verification is what ties
78+ those bytes back to the manifest you already validated. It applies to a static manifest only —
79+ a `"dynamic"` skill carries no digests, so calling it on one raises `ValueError`.
6180
6281!!! warning
6382 Skill content is untrusted model input, exactly like any other server-provided text. SEP-2640
6483 requires a host to tag it with its originating server before it reaches the model, and to
6584 never grant the frontmatter's ` allowed-tools ` field (or any other permission-widening field)
66- without explicit per-skill user approval. Both are host responsibilities the SDK cannot
67- discharge for you — see the SEP's [ Security Implications] ( https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2640 )
85+ without explicit per-skill user approval.
86+
87+ Both are host responsibilities the SDK cannot discharge for you — read the SEP's
88+ [Security Implications](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2640)
6889 section before building a host on top of this extension.
6990
7091## Directory reads
7192
7293A skill's instructions often point at a directory rather than a file ("pick the matching
73- template from ` templates/ ` "). ` resources/list ` cannot answer that — it enumerates a server's
94+ template from ` templates/ ` "). ` resources/list ` can't answer that — it enumerates a server's
7495entire resource space, not one subtree — so SEP-2640 adds ` resources/directory/read ` , gated
7596behind the ` directoryRead ` capability setting:
7697
@@ -88,22 +109,24 @@ mcp = MCPServer(
88109```
89110
90111Supplying ` read_directory ` advertises ` {"directoryRead": true} ` under the extension's
91- capabilities; omitting it advertises neither the setting nor the method — a client calling
92- ` resources/directory/read ` against such a server gets ` METHOD_NOT_FOUND ` .
93- ` mcp.client.skills. read_directory` raises before sending if the connected server hasn't
94- advertised the setting.
112+ capabilities. Omitting it advertises neither the setting nor the method — a client calling
113+ ` resources/directory/read ` against such a server gets ` METHOD_NOT_FOUND ` . On the client side,
114+ ` read_directory ` raises before it sends anything if the connected server hasn't advertised the
115+ setting.
95116
96117## Protocol version and caching
97118
98119In protocol version ` 2026-07-28 ` and later, ` skills/list ` and ` skills/get ` results carry the base
99- protocol's caching fields, [ ` ttlMs ` and ` cacheScope ` ] ( https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2549 ) — the
100- same freshness hint ` tools/list ` , ` resources/list ` , and ` resources/read ` carry. ` Skills ` fills
120+ protocol's caching fields, [ ` ttlMs ` and ` cacheScope ` ] ( https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2549 ) —
121+ the same freshness hint ` tools/list ` , ` resources/list ` , and ` resources/read ` carry. ` Skills ` fills
101122` cacheScope ` with ` "public" ` when your handler leaves it unset, and omits both fields entirely on an
102- older connection, so you don't have to branch on protocol version yourself.
123+ older connection. So you don't have to branch on protocol version yourself.
103124
104125## What this SDK doesn't do
105126
106- ` Skills ` is a protocol adapter, not a skills provider. It has no opinion on where a skill's
107- bytes live, how they're indexed, or when a catalog is refreshed — that's for a higher-level
108- library, or your own handler, to decide. If you're looking for "scan this directory and serve
109- whatever's in it," you're looking for a provider built on top of ` Skills ` , not ` Skills ` itself.
127+ ` Skills ` is a protocol adapter, not a skills provider. It has no opinion on where a skill's bytes
128+ live, how they're indexed, or when a catalog is refreshed — that's for a higher-level library, or
129+ your own handler, to decide.
130+
131+ If you're looking for "scan this directory and serve whatever's in it," you're looking for a
132+ provider built on top of ` Skills ` , not ` Skills ` itself.
0 commit comments