Skip to content

[Schema][Server] Add Skills extension (io.modelcontextprotocol/skills) support#372

Draft
wachterjohannes wants to merge 1 commit into
modelcontextprotocol:mainfrom
wachterjohannes:feature/skills-extension
Draft

[Schema][Server] Add Skills extension (io.modelcontextprotocol/skills) support#372
wachterjohannes wants to merge 1 commit into
modelcontextprotocol:mainfrom
wachterjohannes:feature/skills-extension

Conversation

@wachterjohannes
Copy link
Copy Markdown
Contributor

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 are served through the existing Resources primitive with zero protocol changes, mirroring the existing MCP Apps extension (io.modelcontextprotocol/ui).

A server can expose a whole directory of skills in one line:

$server = Server::builder()
    ->setServerInfo('My Server', '1.0.0')
    ->addSkillsFromDirectory(__DIR__.'/skills')
    ->build();

This registers each SKILL.md (and its supporting files) as skill:// resources, derives name/description from the YAML frontmatter, and serves a skill://index.json discovery index.

What's included

  • Schema/Extension/Skills/McpSkills — extension marker (EXTENSION_ID, MIME_TYPE, URI_SCHEME, ENTRY_POINT, DISCOVERY_URI, META_PREFIX).
  • DTOsSkillType (enum), SkillDiscoveryEntry, SkillDiscoveryIndex, SkillMetadata, following the existing Apps DTO conventions (readonly, fromArray, omit-null jsonSerialize).
  • Server/Skill/FrontmatterParser — splits SKILL.md into YAML frontmatter + body (handles BOM/CRLF; rejects non-mapping frontmatter).
  • Server/Skill/SkillProvider — walks a directory, registers each skill and its supporting files as skill:// 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, and serves the discovery index.
  • Server/Builder::addSkillsFromDirectory() — convenience that auto-enables the extension.

Notable fix

ServerCapabilities::jsonSerialize() only cast the outer extensions map, 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

  • Unit tests for the extension, DTOs, FrontmatterParser, and SkillProvider (with fixtures), plus a serialization regression test for the {} fix.
  • Inspector stdio snapshot test (resources/list, resources/read of a SKILL.md, a supporting file, and the discovery index).
  • Example server at examples/server/skills/ (flat, nested, and supporting-file URIs).
  • Docs in docs/extensions.md and docs/examples.md.

Verified: phpstan (level 6) clean, php-cs-fixer clean, full unit suite green (792 tests), inspector skills test green.

Deferred / follow-up

The discovery schema supports a mcp-resource-template skill type for parameterized skill namespaces. The SkillType enum already includes the value for forward-compat, but the directory provider currently emits only skill-md entries; template-backed skills are left as a follow-up.

…) 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant