|
| 1 | +--- |
| 2 | +layout: default |
| 3 | +title: Skills |
| 4 | +nav_order: 4 |
| 5 | +--- |
| 6 | + |
| 7 | +# Skills |
| 8 | + |
| 9 | +Skills (SEP-2640) is a Final extension (negotiated via [Capability Extensions](/extensions/capability-extensions/)) |
| 10 | +that serves [Agent Skills](https://agentskills.io/specification) over the existing Resources primitive. |
| 11 | +A skill is a directory of files, minimally a `SKILL.md`, and each of those files is an ordinary MCP resource |
| 12 | +under the `skill://` scheme. A host that already treats resources as a virtual filesystem consumes an |
| 13 | +MCP-served skill exactly as it consumes one from disk. |
| 14 | + |
| 15 | +The extension adds three methods on top of that: `skills/list` enumerates the skills a server serves, |
| 16 | +`skills/get` returns one entry by URI, and the optional `resources/directory/read` lists a directory's |
| 17 | +direct children. Skill *content* is always read through ordinary `resources/read`. |
| 18 | + |
| 19 | +```ruby |
| 20 | +skill_md = File.read("skills/refunds/SKILL.md") |
| 21 | +email_md = File.read("skills/refunds/examples/email.md") |
| 22 | +uri = MCP::Skills.uri_for("acme/billing/refunds") # => "skill://acme/billing/refunds/SKILL.md" |
| 23 | + |
| 24 | +capabilities = MCP::Server::Capabilities.new |
| 25 | +capabilities.support_resources # required: skill files are read through `resources/read` |
| 26 | +capabilities.support_extensions(MCP::Skills.capability(directory_read: true)) |
| 27 | + |
| 28 | +server = MCP::Server.new( |
| 29 | + name: "billing_server", |
| 30 | + capabilities: capabilities, |
| 31 | + skills: [ |
| 32 | + MCP::Skill.new( |
| 33 | + uri: uri, |
| 34 | + # The verbatim SKILL.md frontmatter: every field the author wrote, not a curated subset. |
| 35 | + frontmatter: { "name" => "refunds", "description" => "Process customer refund requests per company policy" }, |
| 36 | + # The complete manifest: every file of the skill, SKILL.md included, with digests and sizes. |
| 37 | + resources: [ |
| 38 | + { uri: uri, digest: MCP::Skills.digest(skill_md), size: skill_md.bytesize }, |
| 39 | + { uri: MCP::Skills.resolve(uri, "examples/email.md"), digest: MCP::Skills.digest(email_md), size: email_md.bytesize }, |
| 40 | + ], |
| 41 | + ), |
| 42 | + ], |
| 43 | +) |
| 44 | + |
| 45 | +# Skill files are served as ordinary resources. |
| 46 | +server.resources_read_handler do |params| |
| 47 | + [{ uri: params[:uri], mimeType: "text/markdown", text: load_skill_file(params[:uri]) }] |
| 48 | +end |
| 49 | +``` |
| 50 | + |
| 51 | +That server answers `skills/list`, `skills/get` and `resources/directory/read` with no further wiring: |
| 52 | +directory children are derived from the registered skills' manifests. |
| 53 | + |
| 54 | +## Entries |
| 55 | + |
| 56 | +A `skills/list` entry is a complete manifest rather than a summary, so a host that pages the listing has, |
| 57 | +in that one pass, everything it needs to build its registry, present the skill for approval, bind that |
| 58 | +approval to content, and verify every file it later reads. `skills/get` returns the identical shape for a |
| 59 | +single skill and is never a step a host must take to complete a listed entry; it exists to refresh one |
| 60 | +entry's digests, and to answer for a skill the listing omitted. |
| 61 | + |
| 62 | +`MCP::Skill` enforces the extension's structural rules at construction: the URI addresses the skill's |
| 63 | +`SKILL.md`, the frontmatter carries `name` and `description`, the frontmatter `name` equals the final |
| 64 | +segment of the skill path, and the manifest is complete, duplicate-free and confined to the skill's root. |
| 65 | + |
| 66 | +{: .important } |
| 67 | +A skill whose content is generated per request cannot publish stable digests. Pass |
| 68 | +`resources: MCP::Skill::DYNAMIC` instead of a manifest. Such a skill offers no content integrity and |
| 69 | +cannot be content-bound, and hosts MAY decline to load it. |
| 70 | + |
| 71 | +## Limits |
| 72 | + |
| 73 | +SEP-2640 fixes two per-skill limits every conforming host accepts: 512 resources and 16 MiB in total. |
| 74 | +A registered skill that exceeds either is kept and warned about rather than refused — servers SHOULD stay |
| 75 | +within them, but only the host decides whether to load an oversized skill. `MCP::Skill#limit_violations` |
| 76 | +reports what a given skill exceeds. |
| 77 | + |
| 78 | +## Unenumerable catalogs |
| 79 | + |
| 80 | +A server whose skill catalog is large, generated, or otherwise unenumerable MAY return an empty or partial |
| 81 | +`skills/list`; hosts MUST NOT read that as proof the server has no skills. Such a server replaces the |
| 82 | +default lookups, and MUST still answer `skills/get` for every skill it serves: |
| 83 | + |
| 84 | +```ruby |
| 85 | +server.skills_list_handler { |_params| [] } |
| 86 | +server.skills_get_handler { |params| SkillCatalog.find(params[:uri]) } |
| 87 | +server.resources_directory_read_handler { |params| SkillCatalog.children_of(params[:uri]) } |
| 88 | +``` |
| 89 | + |
| 90 | +Each block may declare `server_context:` to receive the request context, the same opt-in |
| 91 | +`resources_list_handler` uses. |
| 92 | + |
| 93 | +## Directory reads |
| 94 | + |
| 95 | +`resources/directory/read` is gated behind the `directoryRead` setting, which |
| 96 | +`MCP::Skills.capability(directory_read: true)` declares; a client MUST NOT call it otherwise. It returns the |
| 97 | +direct children of a directory resource as the same `Resource` objects `resources/list` returns, |
| 98 | +subdirectories carrying `mimeType: "inode/directory"`. The listing is never recursive: a client descends by |
| 99 | +calling the method again on a child directory. A URI that does not exist, or that is not a directory |
| 100 | +resource, answers `-32602`, as `skills/get` does for an unknown skill. |
| 101 | + |
| 102 | +{: .note } |
| 103 | +For a skill whose entry carries a manifest, a directory read tells a host nothing the manifest did not. |
| 104 | +It earns its place for dynamically generated skills, for resource trees that are not skills at all, and for |
| 105 | +observing a directory without refreshing the entry. A host MUST NOT treat the result as extending a manifest. |
| 106 | + |
| 107 | +See the [Skills extension specification](https://modelcontextprotocol.io/seps/2640-skills-extension). |
0 commit comments