[Schema][Server] Add Skills extension (io.modelcontextprotocol/skills) support - #372
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.
|
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.
|
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 |
SkillArchiver now emits application/zip alongside application/gzip, giving hosts the two formats SEP-2640 expects every conforming host to support. The ZIP is built deterministically (sorted entries, DEFLATE, fixed 1980-01-01 timestamp) so the archive bytes -- and therefore the digest advertised in the discovery index -- stay stable across rebuilds.
…or coding agents (wachterjohannes) This PR was squashed before being merged into the main branch. Discussion ---------- [Mate] Add skills:install to install extension skills for coding agents | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | Docs? | yes | Issues | - | License | MIT Agent Skills (`SKILL.md`) shipped by Mate extensions are now installed onto the filesystem where coding agents read them — a polyfill until skills can be served over MCP directly. ### Background — MCP Skills (SEP-2640) This is the **consumer/polyfill side** of the MCP Skills effort: - **SEP-2640** proposes serving Agent Skills over MCP's Resources primitive as `skill://` resources — modelcontextprotocol/modelcontextprotocol#2640. - **#2132** already added skill *content* to the Mate Symfony bridge, anticipating the extension (agentskills.io + SEP-2640 layout) — #2132. - **modelcontextprotocol/php-sdk#372** implements the *serving* half in the PHP SDK (`skill://` resources + discovery index). The missing piece is **getting those skills onto the filesystem**: Claude Code and other agents do not consume MCP-served skills yet (June 2026) — they only read skills from disk. This PR bridges that gap and can be retired once agents consume MCP-served skills end-to-end. ### How it works Extensions declare one or more skills directories via a new `extra.ai-mate.skills` key: ```json { "extra": { "ai-mate": { "scan-dirs": ["src"], "skills": ["skills"] } } } ``` Each immediate subdirectory holding a `SKILL.md` is one skill. Mate **symlinks** each one — under a `mate-` prefixed directory name (e.g. `mate-system-information`) to avoid clashing with skills you maintain from other sources — into `.agents/skills/` (read by Codex, OpenCode and GitHub Copilot) and mirrors it into `.claude/skills/` for Claude Code, which only reads its own directory. The links point into the gitignored `vendor/`, so skills auto-update on `composer update`. ### Usage Skills install automatically as part of `discover`, which already runs after `composer require`, so there's usually nothing to run: ```terminal $ vendor/bin/mate discover ``` For an explicit re-sync: ```terminal $ vendor/bin/mate skills:install ``` The core package ships a built-in `system-information` skill to demonstrate the format and dogfood the mechanism. ### Notes - Documentation added under `docs/components/mate.rst` and `docs/components/mate/creating-extensions.rst`; `CHANGELOG.md` updated. - Covered by tests (installer, command, discovery, and the `discover` auto-sync); PHPStan clean. Commits ------- 6a88faf [Mate] Add skills:install to install extension skills for coding agents
|
Even without mate needing this, this could become interesting - keeping this open! I guess it needs a heavier rebase nowadays tho, since the extension part changed with v0.8.0 |
|
@chr-hertel yeah i also think that this is something important for the future! will rebase that in the next days |
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.