[Schema][Server] Add Skills extension (io.modelcontextprotocol/skills) support#372
Draft
wachterjohannes wants to merge 3 commits into
Draft
Conversation
…) support
Implement the MCP Skills extension (SEP-2640): serve skills — multi-step
workflow instructions — through the existing Resources primitive with zero
protocol changes, mirroring the MCP Apps extension pattern.
- Add McpSkills extension marker and discovery/metadata DTOs
(SkillType, SkillDiscoveryEntry, SkillDiscoveryIndex, SkillMetadata).
- Add FrontmatterParser and SkillProvider to expose a directory of skills as
skill:// resources, deriving name/description from SKILL.md frontmatter,
enforcing the name<->final-path-segment rule, and serving skill://index.json.
- Add Builder::addSkillsFromDirectory() convenience that auto-enables the
extension.
- Fix ServerCapabilities::jsonSerialize() so an empty extension payload
serializes to {} instead of [].
- Add symfony/yaml dependency for frontmatter parsing.
- Add example server, unit tests, and inspector snapshot tests.
Contributor
Author
|
For cross-reference, the downstream pieces are now open:
Together: this PR is the transport, symfony/ai#2132 is the content, modelcontextprotocol/experimental-ext-skills#95 is the write-up. |
…2640 format
The index format was redesigned upstream (decoupled from the agentskills.io
.well-known schema): entries now mirror the SKILL.md frontmatter verbatim and
carry a content digest, archives become an optional additional serving form,
and the type/$schema fields are dropped.
* SkillDiscoveryIndex: drop $schema/SCHEMA_URL; serialize { skills: [...] }
* SkillDiscoveryEntry: { frontmatter, url?, digest?, archives? } with the
invariant that digest accompanies url and an entry has url, archives, or both
* Add SkillArchive DTO (url, mimeType, digest) for the archives form
* Remove the now-unused SkillType enum
* SkillProvider: emit verbatim frontmatter + sha256 digest of SKILL.md
* Update unit tests, the discovery-index snapshot, and docs
Archive emission and resources/directory/read are left as follow-ups.
Add optional archive serving to the Skills extension: passing archiveFormats to addSkillsFromDirectory() packs each skill into one resource per format (e.g. skill://<skill-path>.tar.gz) and lists it under the index entry's archives, so a host can fetch a whole multi-file skill in one resources/read. * SkillArchiver: deterministic ustar + gzip writer (no temp files, zeroed mtime/owner) with SKILL.md at the archive root per the SEP layout * SkillProvider: build each archive once so its digest matches the served blob; emit SkillArchive index entries; default stays archive-free * Builder::addSkillsFromDirectory(): archiveFormats parameter * Tests for the writer (PharData round-trip, determinism), archive registration, and the digest-matches-served-bytes invariant; docs Currently emits application/gzip; zip is left as a follow-up.
Contributor
Author
|
Updated to track the recent SEP-2640 redesign and to round out the index entries:
All green: phpstan (level 6), php-cs-fixer, 812 unit tests, inspector snapshot. Description updated accordingly. Remaining SEP surface, as a follow-up: the optional |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements the MCP Skills extension (SEP-2640): a way for servers to ship skills — multi-step workflow instructions that tell an agent how to orchestrate tools to reach a goal. Skills ride on the existing Resources primitive, mirroring the MCP Apps extension (
io.modelcontextprotocol/ui). This PR adds no new protocol methods (the SEP's one optional method,resources/directory/read, is a planned follow-up — see below).A server can expose a whole directory of skills in one line:
This registers each
SKILL.md(and its supporting files) asskill://resources, derives the resource metadata from the YAML frontmatter, and serves askill://index.jsondiscovery index whose entries mirror each skill's frontmatter verbatim and carry a SHA-256digestof theSKILL.md.Optionally, skills can also be served as one-shot archives:
What's included
Schema/Extension/Skills/McpSkills— extension marker (EXTENSION_ID,MIME_TYPE,URI_SCHEME,ENTRY_POINT,DISCOVERY_URI,META_PREFIX).SkillMetadata,SkillDiscoveryEntry(frontmatter+ optionalurl/digest+ optionalarchives),SkillDiscoveryIndex,SkillArchive— readonly,fromArray, omit-nulljsonSerialize.Server/Skill/FrontmatterParser— splitsSKILL.mdinto YAML frontmatter + body (handles BOM/CRLF; rejects non-mapping frontmatter).Server/Skill/SkillProvider— walks a directory, registers each skill and its supporting files asskill://resources, enforces the spec's name↔final-path-segment rule, sanitizes schema-valid resource names (URIs keep real paths), guesses MIME types, guards against path traversal, mirrors frontmatter + digest into the index, and (when requested) serves packed archives.Server/Skill/SkillArchiver— deterministic ustar + gzip writer (no temp files, zeroed mtime/owner;SKILL.mdat the archive root) forapplication/gzip.Server/Builder::addSkillsFromDirectory()— convenience that auto-enables the extension; takesarchiveFormats.Notable fix
ServerCapabilities::jsonSerialize()only cast the outerextensionsmap, so an extension with an empty payload (like Skills) serialized to[]instead of{}. The Apps extension never hit this because its payload is non-empty. Empty inner payloads are now coerced to{}.Dependency
Adds
symfony/yaml(^5.4 || ^6.4 || ^7.3 || ^8.0) for frontmatter parsing — the feature is non-functional without it.Tests & docs
FrontmatterParser,SkillProvider, andSkillArchiver(PharData round-trip, determinism, the digest-matches-served-bytes invariant), plus a serialization regression test for the{}fix.resources/list,resources/readof aSKILL.md, a supporting file, and the discovery index).examples/server/skills/; docs indocs/extensions.md.Verified:
phpstan(level 6) clean,php-cs-fixerclean, full unit suite green (812 tests), inspector skills test green.Updates tracking the SEP
The SEP redesigned the discovery index after this PR opened; it has been brought in line:
SKILL.mdfrontmatter verbatim and carry adigest; dropped the$schemafield and thetype/SkillTypeenum (themcp-resource-templatetype was removed from the index in the redesign).application/gzip), listed under each entry'sarchives.Deferred / follow-up
resources/directory/read— the SEP's one optional method (scoped directory listing) plus thedirectoryReadcapability flag.application/zip) — the SEP requires at least one of gzip-tar or zip; gzip-tar is implemented.