Skip to content

feat: support the Skills extension (SEP-2640) - #1392

Open
DaleSeo wants to merge 6 commits into
mainfrom
DaleSeo/skills-support
Open

DaleSeo wants to merge 6 commits into
mainfrom
DaleSeo/skills-support

Conversation

@DaleSeo

@DaleSeo DaleSeo commented Oct 23, 2026 •

Copy link
Copy Markdown
Member

Motivation and Context

SEP-2640 serves Agent Skills through the Resources primitive, so an MCP server can ship workflow instructions alongside its tools. This PR adds the Skills extension, io.modelcontextprotocol/skills, with skills/list, skills/get, and the optional resources/directory/read methods on both the server and client sides. It only adds the protocol surface. A filesystem catalog will come in a second PR.

How Has This Been Tested?

Added tests

Breaking Changes

None

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

@github-actions github-actions Bot added T-test Testing related changes T-CI Changes to CI/CD workflows and configuration T-config Configuration file changes T-core Core library changes T-handler Handler implementation changes T-model Model/data structure changes T-service Service layer changes labels Oct 23, 2026
@github-actions

Copy link
Copy Markdown
Contributor

@DaleSeo (assignment failed) is on triage rotation this week and is the reviewer for this one.

Labels come from the PR labeler. Please review the change.

Automated triage. Labels are a first pass and the assigned maintainer has the final say.

@DaleSeo DaleSeo linked an issue Oct 23, 2026 that may be closed by this pull request
@DaleSeo DaleSeo self-assigned this Oct 26, 2026
@DaleSeo
DaleSeo marked this pull request as ready for review October 26, 2026 20:22
@DaleSeo
DaleSeo requested a review from a team as a code owner October 26, 2026 20:22
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-28T17:56:51.685753Z 74d97f8 New commits
🔒 Security Review ✅ Completed 2026-10-28T17:58:10.175307Z 74d97f8 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

Comment thread crates/rmcp/src/model.rs
///
/// Same shape as `resources/list`, so the untagged [`ServerResult`] carries
/// it as [`ServerResult::ListResourcesResult`].
pub type ReadResourceDirectoryResult = ListResourcesResult;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alias, not a new struct. The spec's directory result has the same schema as ListResourcesResult. With the untagged ServerResult, decoding would never choose a separate struct, which is the same kind of issue as #771. So read_resource_directory returns the existing ListResourcesResult variant.

if !declared {
return Err(McpError::method_not_found::<M>());
}
if capabilities.resources.is_none() {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are two different failures. If skills aren't declared, it's -32601, as specified for undeclared methods. If skills are declared without resources, it's -32603 because the extension serves its files through resources/read. That means the server configuration is wrong, and the client can't fix it.

}
ClientRequest::GetSkillRequest(request) => {
validate_skills_capability::<GetSkillRequestMethod, _>(self, false)?;
if skill_name_from_uri(&request.params.uri).is_none() {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a URI can't point to a SKILL.md file, it's rejected with -32602 before the handler runs. That means the handler only sees plausible URIs. After it runs, the returned entry must match the requested URI. If it doesn't, that's a handler bug, so it's reported as -32603.

) -> Result<ReadResourceDirectoryResult, ServiceError> {
let declared = self
.peer_info()
.is_some_and(|info| info.capabilities.supports_directory_read());

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The spec says clients must not call resources/directory/read unless the server has declared directoryRead. Otherwise, the call fails locally with -32601 and never reaches the server. The test checks how many calls the server receives to prove it.

/// Settings that do not parse count as declared with the defaults.
pub fn skills(&self) -> Option<SkillsCapability> {
let settings = self.extensions.as_ref()?.get(super::SKILLS_EXTENSION_ID)?;
Some(serde_json::from_value(Value::Object(settings.clone())).unwrap_or_default())

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lenient on purpose here. If the settings object doesn't parse, the extension is still considered declared, and we use the defaults, with directoryRead set to false. That way, skills keep working, and we don't call the optional method. I'm happy to make this stricter if reviewers prefer.

Comment thread crates/rmcp/Cargo.toml
base64 = { version = "0.23", optional = true }

# SHA-256 for SEP-2640 skill digests and SEP-2322 requestState sealing
sha2 = "0.11"

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sha2 is now a regular dependency instead of an optional one. Clients that verify downloaded skill files shouldn't need filesystem or server features to do that. It's pure Rust and was already in the dependency tree for request-state, which now uses it without dep:.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

T-CI Changes to CI/CD workflows and configuration T-config Configuration file changes T-core Core library changes T-handler Handler implementation changes T-model Model/data structure changes T-service Service layer changes T-test Testing related changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support the Skills extension (SEP-2640)

1 participant