Repository navigation
Support the Skills extension (SEP-2640) #1391
Description
Summary
Add support for the Skills extension (SEP-2640, io.modelcontextprotocol/skills) to rmcp. The extension adds three methods: skills/list, skills/get, and the optional resources/directory/read. This issue covers both the server side and the client side.
- Spec: https://github.com/modelcontextprotocol/ext-skills/blob/main/specification/stable/skills.mdx
- Extension tracking issue: Rust SDK — SEP-2640 Skills support ext-skills#155
- Downstream: Support SEP-2640 Skills Extension (skills over MCP) as host aaif-goose/goose#12068 needs the client side.
Requirements
A server built with rmcp can:
- Declare the extension in
capabilities.extensions, withdirectoryReadonly when it servesresources/directory/read. - Answer
skills/listandskills/getwith typed results, andresources/directory/readwhen declared. - Serve the files of a skill through
resources/read, so that the bytes match the size and digest in the manifest. - Return the error codes that the spec defines:
-32601for a method it does not serve,-32602for an unknown skill or a non-directory URI. - Send
ttlMsandcacheScopeto peers on protocol version 2026-07-28.
A client built with rmcp can:
- Detect the extension and its
directoryReadsetting from the server capabilities. - Call the three methods through typed
Peermethods, and receive server errors unchanged. - Verify a fetched file against the size and digest in the manifest.
Both sides pass the sep-2640-* conformance scenarios.
The spec targets protocol revision 2026-07-28 or later. rmcp also serves the methods to peers on an older revision and removes the cache hints from those responses, the same as the TypeScript, Python, and Go SDKs do. This needs the legacy strip helper to clear ttlMs and cacheScope as well as resultType, which also closes the same gap for the existing list results.
Proposal
This is how I plan to design and implement it. The choices below are open for discussion.
PR 1: protocol surface
Everything the spec defines, wired the same way as the tasks extension and without a feature flag.
- Model types for the skill manifest and the three methods in
model/skill.rs, with the matchingClientRequestandServerResultvariants.ReadResourceDirectoryResultis a type alias forListResourcesResult, andServerResultreuses the existingListResourcesResultvariant for it. No new directory variant: the two schemas are identical and the untaggedServerResultcannot tell them apart (see fix: prevent CallToolResult from shadowing CustomResult in untagged enum deserialization #771). - Three
ServerHandlermethods with capability-gated dispatch. Dispatch checks what the result object alone can show: the skill URI matches the request, the name matches the last URI segment, the manifest containsSKILL.mdand no duplicate URIs, and every digest has thesha256:form. A result that fails these checks returns-32603. Whether the bytes behind each URI match the manifest is checked by the catalog (PR 2) and by the host that reads them, not by dispatch. enable_skills()andenable_skills_with_directory_read()on the capability builder, with the matchingsupports_*getters. Whether the builder rejects skills withoutresourcesor only documents it is open.- The matching
Peermethods for the client. They accept a 2026-07-28 response that omits the cache hints, the same aslist_toolsdoes today; the TypeScript and Go clients reject such a response, rmcp does not.SkillResource::verify(&[u8])checks the size and thesha256:digest of one file. Frontmatter verification againstskills/getis left to the host.sha2becomes a regular dependency of rmcp: it is pure Rust, already in the tree forrequest-state, and a client must not have to enable a filesystem feature to verify a download. - Integration tests through
Peerover a real transport: pagination acrossskills/list, a-32602fromskills/getarriving unchanged,read_resource_directorynot sent whendirectoryReadis absent, the directory result arriving asListResourcesResult, andverifyfailing on a wrong size and on a wrong digest. - Conformance fixtures with a nested skill, a subdirectory, and a
"dynamic"skill, and the scenarios inconformance.yml.
PR 2: filesystem catalog
Behind a skills-fs feature. SkillCatalog::from_dir walks a directory tree and builds the manifests.
- The catalog is a snapshot. It holds the file bytes and their digests, so
resources/readserves the same bytes that the manifest describes, even if the files change on disk later. reload()builds a new snapshot and swaps it in. If the rebuild fails, the old snapshot stays.- The walk rejects paths that leave the root, including through symlinks, and validates that each skill name matches its directory name. It warns at 512 files or 16 MiB.
- A nested skill appears in the listing on its own, and its files also appear in the manifest of the parent skill.
Follow-ups, not blocking
- PR 1 adds the two new results to the hand-written lists in
strip_result_type_for_legacy_peerandfill_missing_cache_hints. A non-breaking refactor right after PR 1 replaces both lists with a trait on each cacheable result and a dispatch thatts_union!generates, since the macro already enumerates everyServerResultvariant. - A
ResultEnvelope { result_type, meta, ttl_ms, cache_scope }flattened into each cacheable result, with the result structs marked#[non_exhaustive], so that the next mixin field from the spec is not a major release. Breaking, so it waits for the next major batch. - An
Extensiontrait for declaring and reading extension capabilities, so that each extension adds one impl instead of four functions. Skills and tasks move onto it. Non-breaking.
Acceptance
- The
sep-2640-*conformance checks pass on the fixture server. The PR body lists the numbers. - Goose can list, fetch, and verify remote skills through
Peerwithout custom requests. - For PR 2: a file served through
resources/readalways matches the manifest, a path outside the root is never served, and the files of a nested skill are listed in both manifests.
Activity
github-actions commentedon Oct 23, 2026
@DaleSeo (assignment failed) is on triage rotation this week and is the reviewer for this one.
Codex labeled this enhancement / P2 (T-model, T-handler). Please confirm or correct those labels.
Automated triage. Labels are a first pass and the assigned maintainer has the final say.