Skip to content

Commit e3d8c63

Browse files
committed
Add the Skills extension (SEP-2640)
## Motivation and Context Issue #568 asks for the Skills extension (SEP-2640, Final): a server publishes Agent Skills, folders holding a `SKILL.md` with YAML frontmatter plus supporting files, through two methods, `skills/list` and `skills/get`, whose entries repeat the frontmatter and list every file with its SHA-256 digest and size, and a host reads the files through `resources/read` and verifies each one against the entry before loading it. Server side, `MCP::Server.new(skills:)` takes the catalog and advertises `io.modelcontextprotocol/skills`, with `resources` required alongside it as the SEP demands. `MCP::Skill.from_directory` covers the common case: every directory under a root that holds a `SKILL.md` becomes a skill, the frontmatter is read, digests and sizes are computed, a nested skill is published on its own and listed by the enclosing skill as supporting files (its manifest must be complete), and the default `resources/read` serves the files, answering `-32602` for an unserved URI under a skill. `resources/list` carries each skill's `SKILL.md` with the frontmatter `name` and `description`, and `skills/list` and `skills/get` carry the cache hints, both as the specification asks. `MCP::Skill.new` builds a skill in code and checks at definition time what a host may reject (URI grammar, `name` equal to the last path segment, entries under the root with the skill's own `SKILL.md` among them, the SEP limits). A skill defined locally is also held to the Agent Skills rules the specification requires of a served skill and the Python SDK enforces: a `name` of 1 to 64 lowercase letters, digits, and single hyphens, and a `description` of at most 1024 characters. An entry a client receives is not, as Agent Skills advises clients. `Server#skills_handlers(list:, get:)` serves catalogs that depend on the caller, the shape the Python SDK's `Skills(list_skills=, get_skill=)` converged on and the one a Rails user asked for on #568; both handlers are required because a declaring server must answer `skills/get` for every skill it serves. Handlers do not combine with a registry that holds skills: a registered skill, its `SKILL.md` entry in `resources/list`, and its files are served to every caller, so `skills_handlers` refuses such a server, and a catalog that hides a skill from some callers serves the files under the same rule, with `MCP::Skill.read_file`. The files of a nested skill belong to the enclosing skill as well, so such a catalog hides a nested skill only by withholding both, which the documentation spells out. What the handlers answer is never stamped `cacheScope: "public"`, but `"private"` where the server-wide `cache_scope` would make it so: a handler answers per caller and, returning skills, has no result of its own to carry a scope in. Client side, `MCP::Client#list_skills`, `#get_skill`, and `#read_skill_resource` are admitted only after a handshake of the current connection advertised the extension and `resources`, as the SEP asks of clients and as the Python and TypeScript clients do. `read_skill_resource` verifies size, digest, and (for a `SKILL.md`) the frontmatter field by field, withholds content that does not match, and latches the skill so no further read of it is sent on this connection; that is what keeps a host from loading part of a tampered skill and what the `sep-2640-client-verify-*` conformance scenarios check. The SEP and the Python SDK name their thin, unverified wrapper `read_skill_uri`; the verified read gets its own name so the two never mean different things, and `MCP::Skill.verify_resource` and `verify_frontmatter` give hosts that read raw the same checks. Frontmatter is read under rules stricter than YAML so every library reads it the same way: one document, no tags, aliases, or merge keys, String keys, JSON-representable values, and no plain scalar whose YAML 1.1 reading (Psych's) differs from its YAML 1.2 reading (the JavaScript and Go libraries'). Files of directory-backed skills are looked up by exact URI in a map built at construction, opened without following a symbolic link in the last path component (hard links are refused), and refused when their inode, size, link count, or digest changed since registration. The advertised capabilities and the dispatch gate are computed from the configured state, so a server never advertises what it does not serve. `resources/directory/read`, which the specification leaves optional, is not served in this release; declaring `directoryRead` is refused so it can be added later without a change in meaning. `skills/list` and `skills/get` join the protocol request methods that are not run without an id. The entry's `_meta`, which the stable text declares on `Skill` and the Python and TypeScript entries carry, is passed through on both sides: `MCP::Skill.new(meta:)` puts it on the `skills/list` and `skills/get` entries, and a client entry exposes what the server sent as `#meta`, under the JSON rules of the frontmatter and with no key acted on, so an annotation a catalog attaches reaches the host whichever SDK is on the other side. Strings JSON cannot encode (bytes invalid in their labeled encoding, or a binary String with a byte beyond ASCII, which `valid_encoding?` alone lets through) are refused in the frontmatter and `_meta` at definition time as well: json 3.1 cannot generate the response carrying them, while older json still generates a binary String holding valid UTF-8 bytes, recent versions with a deprecation warning announcing that change. Frontmatter read from a file was already held to valid UTF-8. Closes #568. ## How Has This Been Tested? test/mcp/skill_test.rb, test/mcp/skill/resource_entry_test.rb, test/mcp/skill/frontmatter_test.rb, test/mcp/skill/directory_test.rb, test/mcp/server_skills_test.rb, test/mcp/client_skills_test.rb, test/mcp/methods_test.rb, and the `sep-2640-client-*` drivers in conformance/client.rb; the pending `sep-2640-skills-enumeration` and `-manifest` server scenarios pass when run by name against conformance/server.rb, and `-directory` skips because `directoryRead` is not declared. Against the library at cc88656, `skills/list` answers `-32601` and `MCP::Client#read_resource` hands back a tampered `SKILL.md` without complaint. The server test also checks that neither skills method runs without a request id, however the skills were configured. The `_meta` of an entry is checked in the skill, server, and client tests, on the wire and as `#meta`. skill_test.rb requires digest itself: alone, it computed digests before anything autoloaded `MCP::Skill` and failed under most seeds. Without the encoding check, `MCP::Skill.new` accepted a binary string with a byte beyond ASCII and `JSON.generate` then failed on `to_h`. ## Breaking Changes None for servers that neither configure skills nor declare the extension by hand: `Server#capabilities=` now also accepts an `MCP::Server::Capabilities` builder and raises `ArgumentError` when the Hash declares the Skills extension without skills being configured, and `Server#define_resource` and `#resources=` refuse a URI that a directory-backed skill serves. `MCP::Client#connect` keeps its signature and return value. The server test helper takes the request params positionally only, since Ruby 2.7 reads a trailing Hash as keywords when the method also takes one.
1 parent 26eabbc commit e3d8c63

25 files changed

Lines changed: 4880 additions & 16 deletions

‎conformance/client.rb‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,9 +297,21 @@ def build_provider_for(scenario, context)
297297
abort("No tool exposed by conformance server for #{scenario}") unless tool
298298
client.call_tool(tool: tool, arguments: {})
299299
end
300+
when "sep-2640-client-no-prefetch"
301+
# SEP-2640: assembling a registry reads only the listing; the check is that no skill file is fetched.
302+
client.list_skills
303+
when "sep-2640-client-verify-digest", "sep-2640-client-verify-size", "sep-2640-client-verify-frontmatter"
304+
# SEP-2640: the server tampers with `SKILL.md` on the wire. The verified read raises and latches the skill,
305+
# so the supporting file below is never requested, which is how the harness detects the rejection.
306+
client.list_skills
307+
client.read_skill_resource(uri: "skill://pdf-processing/SKILL.md")
308+
client.read_skill_resource(uri: "skill://pdf-processing/references/FORMS.md")
300309
else
301310
abort("Unknown or unsupported scenario: #{scenario}")
302311
end
312+
rescue MCP::Client::SkillVerificationError => e
313+
warn("#{scenario}: client rejected the skill content as expected (#{e.reason})")
314+
exit
303315
rescue MCP::Client::OAuth::Flow::AuthorizationError, MCP::Client::RequestHandlerError, Faraday::Error => e
304316
warn("#{scenario}: client ended in the expected terminal failure (#{e.class}: #{e.message})")
305317
exit

‎conformance/server.rb‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -947,6 +947,7 @@ def build_server
947947
],
948948
resources: resources,
949949
resource_templates: resource_templates,
950+
skills: skills,
950951
# A per-boot random key is enough for conformance: every MRTR round trip completes
951952
# within one server process, and sealing keeps the fixture's `requestState` values
952953
# tamper-evident without any tool-level verification code.
@@ -995,6 +996,12 @@ def resource_templates
995996
]
996997
end
997998

999+
# SEP-2640: the skill directories under conformance/skills, so the `sep-2640-skills-*` server scenarios have
1000+
# a catalog to enumerate; the files are served through `MCP::Skill.read_file` in the read handler below.
1001+
def skills
1002+
MCP::Skill.from_directory(File.expand_path("skills", __dir__))
1003+
end
1004+
9981005
def configure_handlers(server)
9991006
server.logging_message_notification = MCP::LoggingMessageNotification.new(level: "debug")
10001007
server.server_context = server
@@ -1008,6 +1015,10 @@ def configure_resources_read_handler(server)
10081015
server.resources_read_handler do |params|
10091016
uri = params[:uri].to_s
10101017

1018+
# A custom reader replaces the default one, skill files included (SEP-2640).
1019+
skill_file = MCP::Skill.read_file(server.skills, uri)
1020+
next skill_file if skill_file
1021+
10111022
case uri
10121023
when "test://static-text"
10131024
[
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
name: pdf-processing
3+
description: Extract PDF text, fill forms, merge files. Use when handling PDFs.
4+
---
5+
6+
# PDF processing
7+
8+
Use the forms reference in `references/FORMS.md` when a task involves filling a PDF form.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Forms
2+
3+
Fill AcroForm fields by name; flatten the document before delivery.

‎docs/_client/index.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ This class supports:
2424
- Resource listing via the `resources/list` method (`MCP::Client#resources`)
2525
- Resource template listing via the `resources/templates/list` method (`MCP::Client#resource_templates`)
2626
- Resource reading via the `resources/read` method (`MCP::Client#read_resource`)
27+
- Skills (SEP-2640) via the `skills/list` and `skills/get` methods and digest-verified reads (`MCP::Client#list_skills`, `#get_skill`, `#read_skill_resource`), see [Skills](/extensions/skills/)
2728
- Prompt listing via the `prompts/list` method (`MCP::Client#prompts`)
2829
- Prompt retrieval via the `prompts/get` method (`MCP::Client#get_prompt`)
2930
- Completion requests via the `completion/complete` method (`MCP::Client#complete`); see [Completion](/server/completion/)

‎docs/_extensions/index.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ as described on [Capability Extensions](/extensions/capability-extensions/).
1515
The extensions this SDK ships support for:
1616

1717
- [MCP Apps](/extensions/mcp-apps/) (SEP-1865) - interactive HTML user interfaces rendered by the host for tool results
18+
- [Skills](/extensions/skills/) (SEP-2640) - Agent Skills served over MCP resources, with digest-verified reads on the client

‎docs/_extensions/skills.md‎

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
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/)) that lets a server
10+
publish Agent Skills: folders holding a `SKILL.md` with YAML frontmatter plus supporting files, served as ordinary MCP resources
11+
under `skill://` URIs. The extension adds two methods, `skills/list` and `skills/get`, whose entries name a skill's `SKILL.md`,
12+
repeat its frontmatter, and list its files with SHA-256 digests and sizes, so a host can approve a skill from the listing and
13+
verify every file it later fetches. See the [Skills specification](https://github.com/modelcontextprotocol/ext-skills/blob/main/specification/stable/skills.mdx),
14+
the stable text behind SEP-2640.
15+
16+
## Serving Skills
17+
18+
The common case is a directory of skill folders. `MCP::Skill.from_directory` reads the frontmatter, computes the digests and
19+
sizes, and the server serves `skills/list`, `skills/get`, and `resources/read` for every file:
20+
21+
```ruby
22+
# skills/
23+
# pdf-processing/
24+
# SKILL.md <- frontmatter `name` must equal the directory name
25+
# references/FORMS.md
26+
server = MCP::Server.new(name: "catalog", skills: MCP::Skill.from_directory("skills"))
27+
```
28+
29+
Passing `skills:` advertises `io.modelcontextprotocol/skills` under `capabilities.extensions`; `resources`, which the SEP requires
30+
alongside it, is part of the default capabilities. An empty Array serves an empty catalog; `nil` (the default) serves no skills,
31+
and `skills/list` then answers `-32601`. `MCP::Skill.capability` exists for a hand-built `MCP::Server::Capabilities`; such
32+
a declaration must be paired with a non-`nil` `skills:` in the same `MCP::Server.new` call.
33+
34+
Skills assembled in code are `MCP::Skill` values whose files the application serves itself:
35+
36+
```ruby
37+
skill_md = File.read("git-workflow/SKILL.md")
38+
skill = MCP::Skill.new(
39+
uri: "skill://git-workflow/SKILL.md",
40+
frontmatter: { name: "git-workflow", description: "Branching and commit conventions. Use when preparing a commit or a pull request." },
41+
resources: [MCP::Skill::ResourceEntry.from_content(uri: "skill://git-workflow/SKILL.md", content: skill_md)],
42+
)
43+
server = MCP::Server.new(name: "catalog", skills: [skill])
44+
server.define_resource(
45+
uri: "skill://git-workflow/SKILL.md",
46+
name: "git-workflow", # a SKILL.md resource takes its name and description from the frontmatter
47+
description: "Branching and commit conventions. Use when preparing a commit or a pull request.",
48+
mime_type: "text/markdown",
49+
) { skill_md }
50+
```
51+
52+
Every URI a skill lists must be served, or a client reading it fails closed. `MCP::Skill.new` checks what a host may reject:
53+
the URI shape (`skill://<path>/SKILL.md`, segments limited to letters, digits, `-`, `.`, `_`, and `~`), `name` equal to the last
54+
path segment and following the Agent Skills naming rules (1 to 64 lowercase letters, digits, and single hyphens between them),
55+
a `description` of 1 to 1024 characters, JSON-representable frontmatter values, entries under the skill root with
56+
the skill's own `SKILL.md` among them, and the SEP limits of 512 entries and 16 MiB. `resources: "dynamic"` declares a skill
57+
whose files are generated per request and carry no digests. `meta:` is the entry's `_meta`,
58+
passed through under the same JSON rules as the frontmatter and given no meaning by the SDK;
59+
the `io.modelcontextprotocol.skills/` key prefix is reserved for the extension,
60+
so a server's own annotations use its own reverse-domain prefix.
61+
A skill nested in another skill's directory is a skill of its own
62+
and, as the specification requires of a complete manifest, its files (its `SKILL.md` included) are supporting files of
63+
the enclosing skill as well: `from_directory` lists them in both entries, and a hand-built enclosing skill lists them itself.
64+
65+
With the default `resources/list` handler, the listing carries the `SKILL.md` of every registered skill with `name` and
66+
`description` taken from the frontmatter and `mimeType` `text/markdown`, as the specification asks of a `SKILL.md` resource,
67+
unless the application registered a resource at that URI itself (as the example above does in order to serve the file).
68+
69+
{: .important }
70+
> Files of a directory-backed skill are looked up by exact URI in a map built at construction, so request data never reaches
71+
> the filesystem. Dotfiles, symbolic links, FIFOs, and devices are skipped, a hard link is refused, and every other regular file
72+
> is published, editor backups included. The tree is trusted as the operator's: keep it writable only by the operator while
73+
> skills are built and served. Each file is read at request time and refused with `-32603` when its inode, size, or digest
74+
> no longer matches what was registered, or when it has gained a hard link. A read of a URI under such a skill that names
75+
> none of its files answers `-32602`, as the specification requires, so resource templates are shadowed for the whole subtree
76+
> under the skill root; URIs outside every skill root keep the server's usual behavior. A resource registered with the URI of
77+
> a skill file is refused. `resources_read_handler`
78+
> replaces the default reader, skill files included; delegate with `MCP::Skill.read_file(server.skills, params[:uri]) ||
79+
> your_contents`, which returns the `contents` Array or `nil`, and answer `-32602` yourself for a `nil` under a skill root.
80+
81+
### Catalogs That Depend on the Caller
82+
83+
A catalog that depends on the caller is served from handlers; both are required because a server declaring the extension
84+
must answer `skills/get` for every skill it serves, listed or not:
85+
86+
```ruby
87+
server.skills_handlers(
88+
# Array<MCP::Skill>; the server paginates
89+
list: ->(params, server_context:) { skills_for(server_context) },
90+
# MCP::Skill, or nil for -32602
91+
get: ->(params, server_context:) { skills_for(server_context).find { |skill| skill.uri == params[:uri] } },
92+
)
93+
server.resources_read_handler do |params, server_context:|
94+
MCP::Skill.read_file(skills_for(server_context), params[:uri]) || other_contents(params[:uri])
95+
end
96+
```
97+
98+
Handlers may receive `server_context:` when they declare the keyword. A result outside this contract
99+
(not an `MCP::Skill`, a duplicate URI, a skill for another URI) is answered with `-32603`.
100+
`skills/list` and `skills/get` carry the `ttlMs`/`cacheScope` cache hints the way `resources/list` and `resources/read` do.
101+
102+
{: .important }
103+
> A server serves skills from the `skills:` registry or from handlers, never both: `skills_handlers` raises `ArgumentError`
104+
> while the registry holds skills, because a registered skill, its `SKILL.md` entry in `resources/list`, and its files
105+
> are served to every caller.
106+
107+
A catalog that hides a skill from some callers applies its rule everywhere itself:
108+
109+
- `server_context` is the value the server was built with, plus the `_meta` of the request under `:_meta`,
110+
so the catalog needs a server built for each caller from the authenticated request
111+
(see [Server Context](/server/server-context/)).
112+
- Do not reassign the `server_context` of a server that concurrent requests share,
113+
and do not decide from `_meta`, which the client chooses.
114+
- Filter `get` as well as `list`, and the reads, as the `resources_read_handler` above does.
115+
`MCP::Skill.read_file` serves the files of the directory-backed skills among those it is given
116+
and returns `nil` for any other URI.
117+
- A handler that answers `-32602` for an unserved URI under a skill root, as the specification asks,
118+
judges that against the caller's skills too, so that a hidden skill answers exactly like a URI that does not exist.
119+
- What the handlers answer is never marked `cacheScope: "public"`:
120+
where the `cache_scope` of the server would make it so, the result says `"private"`.
121+
- The reads are not covered: on a server configured with `cache_scope: "public"`,
122+
return `{ contents: contents, cacheScope: "private" }` from the `resources_read_handler` for files that depend on the caller.
123+
- Leaving a nested skill out of a caller's catalog does not hide it from a caller who is given the enclosing skill:
124+
the enclosing skill lists the nested files as its own, with their URIs, digests, and sizes, as a complete manifest must,
125+
and serves them.
126+
- Keep a skill that some callers must not see out of the directory of a skill they may see,
127+
or withhold the enclosing skill from them as well.
128+
129+
## Reading Skills
130+
131+
```ruby
132+
page = client.list_skills # ListSkillsResult(skills:, next_cursor:, meta:, ttl_ms:, cache_scope:)
133+
entry = client.get_skill(uri: "skill://pdf-processing/SKILL.md").skill
134+
entry.meta # the entry's _meta as a frozen Hash, or nil
135+
result = client.read_skill_resource(uri: entry.uri, skill: entry)
136+
result.bytes # the verified bytes, frozen
137+
client.read_skill_resource(uri: "skill://pdf-processing/references/FORMS.md", skill: entry).bytes
138+
client.skills_capability # the extension settings, or nil
139+
```
140+
141+
Nothing is read ahead of need: the specification forbids fetching a skill's files on connection, on listing, or at approval,
142+
so call `read_skill_resource` for `SKILL.md` when the skill is loaded and for a supporting file when it is used.
143+
A relative reference in a `SKILL.md` resolves against the directory of that `SKILL.md`, which for a nested skill is its own root
144+
and not the enclosing one; `read_skill_resource` takes the resulting URI.
145+
146+
`read_skill_resource` fetches a file through `resources/read` and checks it against the entry remembered from `list_skills` or
147+
`get_skill` (or passed as `skill:`): exactly one content item for the URI, the size before anything is decoded, the SHA-256
148+
digest, and for a `SKILL.md` the frontmatter compared field by field with the entry. A mismatch raises
149+
`MCP::Client::SkillVerificationError` (`reason` `:contents`, `:size`, `:digest`, `:parse`, or `:frontmatter`), withholds
150+
the content, and latches the skill: no further read of it is sent on this connection, even after a new listing, so reading
151+
a latched skill again takes a new connection. A supporting file is read only after the skill's `SKILL.md` verified;
152+
without `skill:` it resolves to the most specific remembered skill that contains it, never to an enclosing one,
153+
and a `SKILL.md` URI resolves only to its own entry, never as a supporting file of an enclosing skill.
154+
The specification resolves a read against the entry the host loaded the skill from, so a host acting on a skill
155+
keeps that entry at least until the skill's `SKILL.md` leaves the model's context and passes it as `skill:`;
156+
when an enclosing skill lists the file too and no `skill:` is given, the read is refused as `:ambiguous` rather than guessed.
157+
Calls refused before anything is sent raise `MCP::Client::SkillReadRefusedError` with `reason` `:not_advertised`
158+
(no handshake of this connection advertised the extension and `resources`), `:connection_changed`, `:latched`,
159+
`:unknown_skill` (pass `skill:` or call `get_skill` first; also the answer for a URI longer than 8 KiB or deeper than
160+
128 segments, which this SDK does not resolve), `:ambiguous`, `:dynamic`, `:not_in_manifest`, or `:skill_md_unverified`;
161+
later releases may add reasons, so treat an unknown one as a refusal.
162+
For a skill the host is acting on, `:not_in_manifest` is what the specification calls a read of an unlisted file,
163+
a verification failure equivalent to a digest mismatch: no content is fetched, the SDK latches nothing
164+
(the URI may come from model output), and a host that takes it as a sign that the skill changed fetches the entry again
165+
with `get_skill`. Both classes inherit from `MCP::Client::SkillError`, and their messages show URIs escaped to ASCII,
166+
since a server chooses them. `meta:` and `cancellation:` work as on the other methods.
167+
168+
Entries are remembered per client and bound to the connection: `connect` forgets them, and a `connect` that performs no new
169+
handshake (the transport is still connected) keeps the latches. A listing or `get_skill` that replaces an entry drops its
170+
verified status, so supporting reads of that skill are refused until its `SKILL.md` is read again, and the specification asks
171+
the host to treat a changed `resources` set as revoking any approval bound to the old one. `skills_capability` returning `nil`
172+
means not ready or not advertised on the current connection, never that skill files may be read raw.
173+
174+
Hosts that load a skill with `read_resource` instead own the obligations above: stop loading the skill on any raise and
175+
verify `SKILL.md` before any other file. `MCP::Skill.verify_resource(skill, uri, bytes)` checks size and digest (pass `text.b`
176+
for a text item, the decoded `blob` otherwise) and `MCP::Skill.verify_frontmatter(skill, text)` compares the frontmatter; a static
177+
`SKILL.md` needs both, and a `"dynamic"` skill can only be checked with the second.
178+
179+
{: .important }
180+
> The frontmatter of a skill entry is untrusted input, and so is its `_meta`. The SDK compares the frontmatter and passes both
181+
> through; it never acts on a field.
182+
> The SEP requires hosts to ignore or approval-gate `allowed-tools`, hooks, and scripts in skills that arrive over MCP, and to treat
183+
> `name` and `description` as display text. `mime_type` on a read result is the server's unverified display metadata. These are
184+
> the obligations the SDK can state here; the Security Considerations of the specification list the rest a host must implement
185+
> itself: tagging content with its originating server, binding resource reads to that server, resolving skill names per origin,
186+
> binding persisted approval to the entry's `resources` set, and isolating any disk cache from filesystem-skill discovery.
187+
188+
{: .note }
189+
> Frontmatter is read under rules stricter than YAML so that every YAML library reads it the same way: one document without
190+
> directives or document markers, no tags, aliases, or merge keys, keys that are untagged strings, values JSON can carry, and no
191+
> plain scalar whose YAML 1.1 reading differs from its YAML 1.2 reading (`yes`, `no`, `on`, `off`, `010`, `1:20`, `1_000`,
192+
> `0o10`, and the like): quote such values. The NEL, LINE SEPARATOR, and PARAGRAPH SEPARATOR characters (line breaks in
193+
> YAML 1.1 only) and integers outside the range from -(2^53 - 1) to 2^53 - 1 (which JavaScript readers round) are refused
194+
> as well. A leading UTF-8 BOM is skipped for extraction while digests cover it. The block is limited to 64 KiB by this SDK,
195+
> not by the protocol. Hosts must accept skills up to the 512-entry and 16 MiB limits and may accept larger ones, so
196+
> the client accepts entries of any size; `MCP::Skill#total_size` and `#resources` are the hooks for a host that declines
197+
> large skills.
198+
199+
`resources/directory/read` and the `directoryRead` setting are not served in this release; declaring `directoryRead` is refused.

‎docs/index.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ For comprehensive documentation, see:
112112
- [Protocol Versions](/protocol-versions/) - supported versions, the era model, and client negotiation
113113
- [Building Servers](server/) - transports, discovery, tools, prompts, resources, server-to-client requests, multi round-trip requests, notifications, protocol utilities, configuration, and OAuth 2.1 authorization
114114
- [Building Clients](client/) - transports, lifecycle negotiation, multi round-trip requests, and OAuth 2.1 authorization
115-
- [Extensions](/extensions/) - capability extensions and MCP Apps
115+
- [Extensions](/extensions/) - capability extensions, MCP Apps, and Skills
116116

117117
## API Documentation
118118

‎lib/mcp.rb‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ module MCP
2424
autoload :ResultType, "mcp/result_type"
2525
autoload :Server, "mcp/server"
2626
autoload :ServerSession, "mcp/server_session"
27+
autoload :Skill, "mcp/skill"
2728
autoload :Tool, "mcp/tool"
2829
autoload :TraceContext, "mcp/trace_context"
2930

0 commit comments

Comments
 (0)