Repository navigation
Add MCP Skills extension support to McpServer and McpClient - #10082
Conversation
🦋 Changeset detectedLatest commit: 8838fef The changes in this PR will be included in the next version bump. This PR includes changesets to release 33 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
9cb43cf to
23ff4c0
Compare
Bundle Size AnalysisGenerated from PR build output; treat the content below as untrusted.
|
10b6d56 to
454054e
Compare
a2ce78a to
78475e1
Compare
|
Why was this closed, @tim-smart? |
|
It's a GitHub thing when the base branch gets removed. |
lloydrichards
left a comment
There was a problem hiding this comment.
The API looks like what I had in mind 👍 great work.
Could we reuse the same file metadata for resource listings and directory listings? Currently, SKILL.md has the skill’s name, description and size in resources/list, but the directory listing rebuilds the entry and loses some of that information.
I’ve left a few inline comments about keeping the published files and their manifests consistent as well as some suggestions for reusing Mime and Schema where possible.
…l fields `resources/list` and `resources/templates/list` built each entry with the wire schema's `make` and then decoded the result as wire JSON. `make` keeps a key explicitly set to `undefined`, which the wire decoder rejects, so registering a resource with, say, `description: undefined` turned both lists into a `-32603` internal error. Transcode through the public schemas instead, as tools and prompts already do, so `undefined` keys are dropped on encode.
Add `McpServer.skill` and `McpServer.registerSkill`, which publish an Agent Skill under the Skills extension (SEP-2640). Each file of the skill is served as a resource, and the server answers `skills/list`, `skills/get` and `resources/directory/read`. Manifests carry the SHA-256 digest and size of the exact bytes served. `SKILL.md` is either rendered from the frontmatter and instructions, or given as a file and served verbatim. Registration fails on skills that break the extension's rules. A skill served over MCP must conform to the Agent Skills specification, so its frontmatter is checked against the new `McpSchema.SkillFrontmatter` schema of that specification, which keeps fields it does not define. The frontmatter of a verbatim `SKILL.md` is read with `Yaml.parse`: it becomes the listed frontmatter when none is given, and otherwise must equal the given one, since hosts reject a skill whose two descriptions differ. The extension is declared once a skill is registered, and served on every protocol revision, as the official SDKs do. `v2026_07_28` results also carry `resultType` and the `ttlMs`/`cacheScope` caching attributes, set by the new `skills` cache hint. Without registered skills, the methods answer `Method not found`. The core registry stores a skill as its entry plus a directory lookup, so a skill whose `resources` is `"dynamic"` can be added later without changing it.
`McpServer.visibility` already hid a skill's files, which are resources, but the skill itself stayed in `skills/list` and directory reads and answered `skills/get`. Add `skills/list`, `skills/get` and `resources/directory/read` as middleware methods, with the skill's `SKILL.md` URI as `target`. A hidden skill is left out of listings and directory reads, and `skills/get` fails as if it was not registered. A directory read checks only the skills that have the directory, so a shared prefix lists the visible skills under it.
toSkillSourceFile first reduces the three input forms to content and MIME type, then encodes or copies once, instead of repeating the encode and copy in each branch.
Effect's MCP server and client had no support for the Skills extension (SEP-2640). A server could register a
SKILL.mdas a plain resource, but it couldn't answerskills/list,skills/getorresources/directory/read, so it couldn't publish skills that hosts discover and verify. This adds both sides, following the maintained specification.Changes
Server (
McpServer.skill/registerSkill)SKILL.mdURI plus its files,SKILL.mdincluded. Files are served verbatim as resources, copied at registration, and the listed frontmatter is parsed fromSKILL.md, so the two cannot differ.skills/list,skills/getandresources/directory/read. Manifests carry the SHA-256 digest and size of the bytes served. Directory reads list files with the same metadata asresources/list, and prefix directories shared by several skills are merged.McpSchema.SkillFrontmatter(the Agent Skills fields; others are kept) and plain JSON.IllegalArgumentErroron a name that doesn't match the URI, a path that leaves the skill, more than 512 files or 16 MiB, a skill directory that contains or lies inside another skill's, or a file URI already registered as a resource. Registering a resource at a skill file's URI afterwards is a defect. Nested skills are not supported.io.modelcontextprotocol/skillsextension, withdirectoryRead, is declared once a skill is registered, on every protocol revision.v2026_07_28results carryttlMs/cacheScopefrom the newskillscache hint. Without skills, the methods answerMethod not found.McpServer.visibilityhides skills through the newskills/list,skills/getandresources/directory/readmiddleware methods, with theSKILL.mdURI astarget.Mime.getType, with.py,.shand.tsmapped to script types.Client (
McpClient, opt-in withextensions: { skills: true })listSkills,getSkill,readResourceDirectoryandreadSkillResource. Without the option, the client declares no extension and these fail withConfigurationError. They run only against a server that declares the extension.ValidationError, or are skipped and reported whenonInvalidSkillis given.listSkillsandreadResourceDirectoryshare the discoverytimeoutandlimitsoflistTools.readSkillResourcereads only files in the manifest and checks their digest and size, and checks aSKILL.md's frontmatter against the listing. For a"dynamic"skill it reads only files inside the skill directory, without digests, and still checksSKILL.md.McpSchema.Skill,McpSchema.SkillResource(with digest and size checks) andMcpSchema.SkillFrontmatter.Fix found along the way
v2026_07_28,resources/listandresources/templates/listfailed with-32603when a resource set an optional field toundefined. Resources are now transcoded through the public schemas, as tools and prompts already were. Own commit and changeset.Coordination with #8631
#8631 (Tasks) adds a top-level
tasksoption toMcpClientand builds its own client capabilities object. This PR usesextensions: { skills }, meant to grow toextensions: { skills, tasks }. The two need to agree on that shape before either merges, or whichever lands second drops the other'sextensionskey. Both also add methods to the2026-07-28adapter and schema maps.Tests
test/ai/McpServer/Skills.test.ts: discovery; manifests matching the served bytes; copied bytes;skills/get; directory reads with resource metadata;-32602/-32601errors; cache hints;2025-11-25and2024-11-05; visibility; registration rules, overlap and resource collisions.test/ai/McpClient.test.ts: round trip againstMcpServeron2025-11-25and2026-07-28; opt-in; invalid entries; tampered bytes, wrong size and changed frontmatter; unlisted files; dynamic skills.test/ai/McpSchema.test.ts:SkillResourcedigest and size checks.test/ai/McpServer/v2026_07_28.test.ts: optional fields set toundefined(fails without the fix).The official conformance runner (
@modelcontextprotocol/conformance@0.2.0-alpha.12) passed against the original head, before the redesign. It has not been re-run against this head.Closes #10016
Closes EFF-3092