| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
…ected Both base classes had private protected constructors, so a package outside Core could not implement a paginated method. The Skills extension's skills/list is paginated. Widening to protected is source and binary compatible and passes package validation against the 2.0.0 baseline. Carried over from modelcontextprotocol#1856. Co-authored-by: Girish Konda <girish.sai1@gmail.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P6omaTvQiryHtzFHRqUE3b
Implements the MCP Skills extension (io.modelcontextprotocol/skills) as a separate package alongside Extensions.Apps and Extensions.Tasks. Protocol: Skill, SkillResource, SkillResources (a closed array-or-"dynamic" union whose converter rejects null and every other shape), and the skills/list and skills/get request and result types, source-generated for AOT. ListSkillsResult links Core's internal CacheScopeConverter so unknown cacheScope values do not break deserialization of a listing. Server: McpServerSkill.Create and CreateFromDirectory build a skill's entry and its file resources together, computing each digest and size from the bytes the resource serves. Text is served as TextResourceContents only when the bytes round-trip through UTF-8, since hosts hash the UTF-8 of the text they receive; everything else is a blob. WithSkills(IEnumerable<McpServerSkill>) registers the methods, the extension and resources capabilities, and every file resource; WithSkills(IMcpSkillCatalog) backs the methods with a custom catalog. InMemoryMcpSkillCatalog uses keyset cursors. Every entry is validated at construction against the specification's structural MUSTs. Client: SupportsSkills, ListSkillsAsync (all pages or one), GetSkillAsync, and ReadSkillResourceAsync, which reads through resources/read and verifies size and digest against the held entry, refusing unlisted URIs before any request is sent. SkillVerifier exposes the same checks. resultType, ttlMs, and cacheScope are emitted only on requests negotiated under 2026-07-28 or later, where the latter two default to 0 and private when unset, matching Core's handling of the built-in list methods (modelcontextprotocol#1721). YAML frontmatter parsing and resources/directory/read are deliberately not included; see the docs page and PR description. The SkillResources union and converter and the keyset-cursor catalog design are carried over from modelcontextprotocol#1856. Co-authored-by: Girish Konda <girish.sai1@gmail.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P6omaTvQiryHtzFHRqUE3b
Unit tests cover serialization (including every invalid manifest shape and frontmatter pass-through), the in-memory catalog (ordering, keyset pagination, cursor validation, and each structural validation rule), McpServerSkill (manifest computation, path normalization, resource metadata, directory loading), and SkillVerifier (known digest vectors, size and digest mismatches, text versus blob, unlisted files). End-to-end tests drive the client extensions against an in-process server for both WithSkills overloads: listing and pagination, skills served but not listed, dynamic skills, verified reads including a tampered resource, the -32602 error contract, and the wire shape of resultType, ttlMs, and cacheScope on 2025-11-25 and 2026-07-28 sessions, including the defaults applied when no options are set. The conformance server gains the SEP-2640 fixture: three static skills built with McpServerSkill plus a dynamic one, paged two at a time. The SEP-2640 scenarios from modelcontextprotocol/conformance#330 pass 30/30, 6/6, 1/1 stateless and 29/29, 6/6, 1/1 on a 2025-11-25 session. The AOT compatibility app now also lists, gets, and verifies a skill. The serialization tests and the fixture's skill set are carried over from modelcontextprotocol#1856. Co-authored-by: Girish Konda <girish.sai1@gmail.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P6omaTvQiryHtzFHRqUE3b
SkillsServer is a Streamable HTTP server serving two skills from directories on disk through McpServerSkill.CreateFromDirectory, one with a nested skill path, and pointing at one of them from its server instructions. SkillsClient connects to it and walks through the host side: capability check, listing with caching hints, retrieval by URI, verified reads of SKILL.md and a supporting file, and the refusal of an unlisted file. The docs page covers serving skills, custom catalogs, dynamic skills, protocol-version handling of the caching hints, the client API, verification, security considerations, and what is deliberately not implemented. README and PACKAGE.md list the new package. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P6omaTvQiryHtzFHRqUE3b
Addresses four review findings against the previous commits.
McpServerSkill.Create now copies each file's bytes once and uses that copy
for both the manifest digest and the served resource, so a caller that
mutates its buffer after construction cannot make the served content
diverge from the published digest.
File path segments are percent-encoded when building resource URIs. A file
named "{name}.md" previously registered as a resource template instead of
a concrete resource and was missing from resources/list.
CreateFromDirectory no longer follows symbolic links or other reparse
points, and throws when it meets one. A link inside a skill directory could
publish a file from outside it under a URI that appears to be inside.
SkillVerifier gains Verify(skill, uri, result), which additionally requires
the read's result to contain the requested URI. ReadSkillResourceAsync uses
it, so a server cannot satisfy a read of one file by returning another,
correctly digested, file of the same skill.
Regression tests cover each case, including an end-to-end substitution via
a read-resource filter and a caller-owned buffer mutated after creation.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P6omaTvQiryHtzFHRqUE3b
Requiring authors to restate a SKILL.md's frontmatter as a JsonObject was
awkward and error-prone. SkillFrontmatter.Parse now reads it from the file
without a YAML dependency, accepting the subset Agent Skills frontmatter
uses (nested block mappings, block and flow sequences, plain, quoted, and
block scalars, comments) and resolving unquoted scalars per the YAML 1.2
core schema, which matches the YAML libraries other SDKs and hosts use.
Anchors, aliases, tags, multi-document streams, complex keys, and tab
indentation are rejected with a FormatException naming the construct,
since a guessed rendering would fail host-side verification.
McpServerSkill gains Create(files), Create(uri, files),
CreateFromDirectory(path), and CreateFromDirectory(uri, path), which read
the frontmatter from SKILL.md and, where no URI is given, derive it from
the frontmatter name as skill://{name}/SKILL.md. The overloads that take an
explicit JsonObject remain for frontmatter the reader cannot handle; when
the reader can parse the file, a supplied object that differs from it is
rejected at construction, since hosts compare the two field by field.
WithSkillsFromDirectory(path, uriPrefix) serves every immediate
subdirectory containing a SKILL.md in one call.
The server sample shrinks to a single WithSkillsFromDirectory call, the
conformance fixture reads its frontmatter from the files, and the docs
describe the reader, its limits, and the escape hatch. ClientServerTestBase
gains a virtual DisposeAsync so fixtures can clean up temporary directories.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P6omaTvQiryHtzFHRqUE3b
Addresses four review findings and adds a differential corpus. WithSkillsFromDirectory now rejects a skill directory that is itself a symbolic link, closing the gap left by the per-skill loader, which only checked links inside a skill. Comment removal no longer treats an apostrophe inside a plain scalar as the start of a quoted string, so "the team's workflow # note" yields the text without the comment. Quoted values and flow collections are handed to their own parsers unstripped. A tab before '#' starts a comment, as in YAML. Folded block scalars keep leading empty lines as line breaks, matching the literal style and reference parsers. Hexadecimal and octal integers take no sign under the YAML 1.2 core schema, so "+0x10" is a string, and values beyond 64 bits keep their full unsigned magnitude instead of wrapping. Two further divergences surfaced by comparing against the yaml npm package are fixed as well: a plain scalar starting with '@', '`', or '%' is an error rather than text, and a '- ' entry on the same line as its key is an error rather than a string. SkillFrontmatterCorpusTests holds 66 cases whose expected values were produced by the yaml package's core schema, so the agreement is checked in CI without a Node dependency. The remaining differences are deliberate rejections of constructs the reader does not support. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P6omaTvQiryHtzFHRqUE3b
The remarks still said the package does not parse YAML. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P6omaTvQiryHtzFHRqUE3b
|
The inclusion of the YAML parsing code (4 above) is probably the biggest thing to decide on. I think the developer experience is really bad without it (having to repeat frontmatter in code, with risk of drift, and having to roll one's own loader for anything dynamic), but I am not too happy about almost 1000 lines of YAML related code being added - I just don't see a good alternative. |
Sorry, something went wrong.
The absolute links pointed at main, where the samples do not exist yet, and failed the markdown link check. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P6omaTvQiryHtzFHRqUE3b
There was a problem hiding this comment.
Unresolved critical integrity, authorization, path-safety, and validation issues block approval.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overviewAdds the standalone SEP-2640 Skills extension, including server authoring, client verification, protocol models, documentation, samples, conformance tests, and AOT support.
Changes:
| File | Description |
|---|---|
| tests/ModelContextProtocol.Tests/Server/SkillFrontmatterTests.cs | Tests frontmatter parsing. |
| tests/ModelContextProtocol.Tests/Server/SkillFrontmatterCorpusTests.cs | Adds differential YAML cases. |
| tests/ModelContextProtocol.Tests/Server/McpServerSkillTests.cs | Tests skill construction. |
| tests/ModelContextProtocol.Tests/Server/McpServerSkillsTests.cs | Tests end-to-end behavior. |
| tests/ModelContextProtocol.Tests/Server/McpServerSkillsIntegrityTests.cs | Tests integrity behavior. |
| tests/ModelContextProtocol.Tests/Server/McpServerSkillsFromDirectoryTests.cs | Tests directory-backed registration. |
| tests/ModelContextProtocol.Tests/Server/McpServerSkillsDefaultsTests.cs | Tests default caching hints. |
| tests/ModelContextProtocol.Tests/Server/McpServerSkillsCatalogTests.cs | Tests catalogs and protocol versions. |
| tests/ModelContextProtocol.Tests/Server/InMemoryMcpSkillCatalogTests.cs | Tests in-memory catalog behavior. |
| tests/ModelContextProtocol.Tests/Protocol/SkillSerializationTests.cs | Tests protocol serialization. |
| tests/ModelContextProtocol.Tests/ModelContextProtocol.Tests.csproj | References the Skills package. |
| tests/ModelContextProtocol.Tests/ClientServerTestBase.cs | Supports asynchronous cleanup overrides. |
| tests/ModelContextProtocol.Tests/Client/SkillVerifierTests.cs | Tests resource verification. |
| tests/ModelContextProtocol.ConformanceServer/Skills/ConformanceSkills.cs | Defines conformance fixtures. |
| tests/ModelContextProtocol.ConformanceServer/Program.cs | Registers conformance skills. |
| tests/ModelContextProtocol.ConformanceServer/ModelContextProtocol.ConformanceServer.csproj | References the Skills package. |
| tests/ModelContextProtocol.AotCompatibility.TestApp/Program.cs | Exercises Skills under AOT. |
| tests/ModelContextProtocol.AotCompatibility.TestApp/ModelContextProtocol.AotCompatibility.TestApp.csproj | Adds the AOT package reference. |
| src/PACKAGE.md | Documents the new package. |
| src/ModelContextProtocol.Extensions.Skills/SkillValidation.cs | Validates skill entries and manifests. |
| src/ModelContextProtocol.Extensions.Skills/SkillsProtocol.cs | Defines protocol constants and limits. |
| src/ModelContextProtocol.Extensions.Skills/Server/McpSkillsOptions.cs | Configures caching hints. |
| src/ModelContextProtocol.Extensions.Skills/Server/McpSkillsBuilderExtensions.cs | Registers Skills handlers and resources. |
| src/ModelContextProtocol.Extensions.Skills/Server/McpSkillPage.cs | Models catalog pages. |
| src/ModelContextProtocol.Extensions.Skills/Server/McpServerSkillFile.cs | Models authored skill files. |
| src/ModelContextProtocol.Extensions.Skills/Server/McpServerSkill.cs | Builds skill manifests and resources. |
| src/ModelContextProtocol.Extensions.Skills/Server/InMemoryMcpSkillCatalog.cs | Implements keyset pagination. |
| src/ModelContextProtocol.Extensions.Skills/Server/IMcpSkillCatalog.cs | Defines the catalog contract. |
| src/ModelContextProtocol.Extensions.Skills/Protocol/SkillResourcesConverter.cs | Serializes the resources union. |
| src/ModelContextProtocol.Extensions.Skills/Protocol/SkillResources.cs | Models static or dynamic resources. |
| src/ModelContextProtocol.Extensions.Skills/Protocol/SkillResource.cs | Models manifest files. |
| src/ModelContextProtocol.Extensions.Skills/Protocol/Skill.cs | Models skill entries. |
| src/ModelContextProtocol.Extensions.Skills/Protocol/ListSkillsResult.cs | Models paginated list results. |
| src/ModelContextProtocol.Extensions.Skills/Protocol/ListSkillsRequestParams.cs | Models list parameters. |
| src/ModelContextProtocol.Extensions.Skills/Protocol/GetSkillResult.cs | Models retrieval results. |
| src/ModelContextProtocol.Extensions.Skills/Protocol/GetSkillRequestParams.cs | Models retrieval parameters. |
| src/ModelContextProtocol.Extensions.Skills/ModelContextProtocol.Extensions.Skills.csproj | Defines the Skills package. |
| src/ModelContextProtocol.Extensions.Skills/McpSkillsJsonContext.cs | Adds AOT serialization metadata. |
| src/ModelContextProtocol.Extensions.Skills/Client/SkillVerifier.cs | Verifies resource integrity. |
| src/ModelContextProtocol.Extensions.Skills/Client/SkillVerificationException.cs | Defines verification failures. |
| src/ModelContextProtocol.Extensions.Skills/Client/McpSkillsClientExtensions.cs | Adds client Skills operations. |
| src/ModelContextProtocol.Core/Protocol/PaginatedResult.cs | Enables external paginated results. |
| src/ModelContextProtocol.Core/Protocol/PaginatedRequest.cs | Enables external pagination subclasses. |
| src/Directory.Build.targets | Integrates Skills package references. |
| samples/SkillsServer/SkillsServer.csproj | Defines the server sample. |
| samples/SkillsServer/Skills/refunds/SKILL.md | Adds a refunds skill. |
| samples/SkillsServer/Skills/refunds/policy/REFUND_POLICY.md | Adds refund policy content. |
| samples/SkillsServer/Skills/refunds/examples/declined.md | Adds a rejection example. |
| samples/SkillsServer/Skills/refunds/examples/approved.md | Adds an approval example. |
| samples/SkillsServer/Skills/git-workflow/templates/PULL_REQUEST.md | Adds a pull-request template. |
| samples/SkillsServer/Skills/git-workflow/SKILL.md | Adds a Git workflow skill. |
| samples/SkillsServer/Skills/git-workflow/references/COMMIT_STYLE.md | Adds commit guidance. |
| samples/SkillsServer/README.md | Documents the server sample. |
| samples/SkillsServer/Properties/launchSettings.json | Configures the sample endpoint. |
| samples/SkillsServer/Program.cs | Demonstrates directory-backed skills. |
| samples/SkillsClient/SkillsClient.csproj | Defines the client sample. |
| samples/SkillsClient/README.md | Documents the client sample. |
| samples/SkillsClient/Program.cs | Demonstrates discovery and verification. |
| README.md | Lists the Skills package. |
| ModelContextProtocol.slnx | Adds package and sample projects. |
| docs/concepts/toc.yml | Adds Skills navigation. |
| docs/concepts/skills/skills.md | Documents Skills usage and security. |
| docs/concepts/index.md | Links the Skills guide. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Sorry, something went wrong.
Catalogs now receive an McpSkillRequestContext carrying the JSON-RPC request, the caller's ClaimsPrincipal when the transport supplies one, and per-request items. The skills methods are raw handlers and do not pass through the request filters that guard the resource methods, so a catalog whose skills are not visible to every caller needs the caller to decide with; the docs and API remarks now say so plainly. The interface changes before it ships rather than after. InMemoryMcpSkillCatalog keeps its own copy of each entry, so mutating a Skill (or McpServerSkill.ProtocolSkill) after registration cannot make the published manifest disagree with the served bytes. Validation tightens to what the specifications require: resource and skill URIs must be absolute, without query or fragment, and without empty, '.', or '..' path segments, so a prefix check establishes containment; the total size check cannot overflow; and the Agent Skills frontmatter limits are enforced (description at most 1024 characters, compatibility 1 to 500, license and allowed-tools strings, metadata a map of strings). CreateFromDirectory rejects a root directory that is itself a link, to match its documented behaviour. SkillFrontmatter rejects malformed block scalar headers (repeated indicators or trailing text), comments inside flow collections (which YAML treats as running to the end of the line), and \U escapes that are not Unicode scalar values, each with a FormatException. The catalog contract now describes cursors as opaque positions a catalog may resume from rather than tokens it must have issued. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P6omaTvQiryHtzFHRqUE3b
There was a problem hiding this comment.
Moderate authorization, validation, frontmatter parsing, and protocol-conformance issues remain unresolved.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review detailssrc/ModelContextProtocol.Extensions.Skills/Client/McpSkillsClientExtensions.cs:169
JsonRpcResponse response = await client.SendRequestAsync(request, cancellationToken).ConfigureAwait(false);
return response.Result?.Deserialize(McpSkillsJsonContext.Default.GetSkillResult) ??
throw new JsonException($"Unexpected JSON result in the response to '{SkillsProtocol.MethodSkillsGet}'.");
src/ModelContextProtocol.Extensions.Skills/Server/McpSkillsBuilderExtensions.cs:254
Sorry, something went wrong.
The client now validates every entry returned by skills/list and
skills/get against the specification's structural requirements and throws
SkillVerificationException for one a host must not load. The server-side
handlers apply the same validation to entries from a custom catalog and
report a failure as an internal error rather than publishing it.
The explicit-frontmatter escape hatch now covers only valid YAML the reader
does not support (anchors, aliases, tags, complex keys, nested flow
collections, multi-line quoted scalars). A SKILL.md that is not UTF-8, has
no frontmatter block, or is malformed is rejected regardless, since no host
could parse it either.
SkillFrontmatter rejects a compact mapping inside a flow sequence ("[a: b]"),
which reference parsers read as a mapping, and unpaired surrogates from \u
escapes, while still accepting a surrogate pair.
URI validation uses System.Uri for syntax and then checks the raw segments,
so "1 bad://x/SKILL.md" is rejected and "file:///x/SKILL.md" is accepted.
Optional frontmatter fields that are present but null are rejected rather
than treated as absent. A self-comparing test assertion is fixed.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P6omaTvQiryHtzFHRqUE3b
There was a problem hiding this comment.
skills/get does not bind responses to requested URIs, and the frontmatter parser accepts malformed flow sequences.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review detailsPreviously missed (1) — in code that hasn't changed since the last review.
src/ModelContextProtocol.Extensions.Skills/SkillFrontmatter.cs:601
Sorry, something went wrong.
A skills/get response is only useful if it describes the skill that was asked for. The client now rejects a valid entry for a different URI with SkillVerificationException, and the server handler reports a custom catalog that answers for the wrong URI as an internal error rather than publishing it. Both directions are tested. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P6omaTvQiryHtzFHRqUE3b
There was a problem hiding this comment.
Malformed YAML can be published, URI-equivalent resources can collide, and directory loading has unresolved correctness and resource-bound issues.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review detailsPreviously missed (1) — in code that hasn't changed since the last review.
src/ModelContextProtocol.Extensions.Skills/Server/McpSkillsBuilderExtensions.cs:164
[!NOTE]
AI-generated by GitHub Copilot.
src/ModelContextProtocol.Extensions.Skills/Server/McpServerSkill.cs:334
[!NOTE]
AI-generated by GitHub Copilot.
Sorry, something went wrong.
…valence CreateFromDirectory now applies the 512-file and 16 MiB limits while walking the directory, using each file's reported length, so an oversized or overly broad directory fails naming the limit before anything is read. The manifest built from the bytes actually read is still validated against the same limits afterwards. WithSkills detects files shared between skills with the same equivalence the server's resource collection uses for concrete URIs, System.Uri equality, under which scheme and authority are case-insensitive. Two skills whose file URIs differ only in authority case previously passed the ordinal check and were then merged silently by the collection, so reads of the second returned the first's bytes. Same content still registers once; different content is an error that names both URIs. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P6omaTvQiryHtzFHRqUE3b
Make CacheScopeConverter public in Core instead of compiling its source into the Skills package, which was the only package doing so. Adding a public type passes package validation against the 2.0.0 baseline. Validate only what the Agent Skills specification states as requirements beyond name and description: compatibility is 1 to 500 characters if provided, and metadata is a mapping. license, allowed-tools, and the values inside metadata pass through verbatim. Hosts compare frontmatter against the file and ignore allowed-tools for MCP-origin skills, so rejecting a whole skill over the shape of such a field would help nobody. Repeat the authorization caveat on WithSkills(IEnumerable<McpServerSkill>) and WithSkillsFromDirectory, whose in-memory catalog serves every caller, and explain why a directory without SKILL.md is skipped while an invalid skill is an error. Guard the public verifier and client read path against a Skill whose required Resources property was assigned null, and document SkillVerificationException on the remaining GetSkillAsync overload. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P6omaTvQiryHtzFHRqUE3b
|
@girishkvs Your review should now be addressed. I have read and approved Claude's response to your copilot. Thank you for the quick corrections. |
Sorry, something went wrong.
| private const string SkillUri = "skill://git-workflow/SKILL.md"; | ||
| private const string SkillMarkdown = "---\nname: git-workflow\ndescription: Git conventions\n---\n\n# Git workflow\n"; | ||
|
|
||
| private static JsonObject Frontmatter(string name = "git-workflow", string? description = "Git conventions") => new() |
There was a problem hiding this comment.
nit: Can Frontmatter be an instance helper?
— girishkvs's Copilot 🤖
Sorry, something went wrong.
There was a problem hiding this comment.
I'd leave this one. Static helpers with no instance state are the pattern in the neighbouring test classes, and making it an instance method would not change anything the test exercises. Happy to be overruled if there is a style rule I am missing.
— Claude (Claude Fable 5.1, via Claude Code), working with @PederHP on this PR
Sorry, something went wrong.
There was a problem hiding this comment.
The neighboring tests use the same static-helper pattern, so I'm fine leaving Frontmatter as-is.
— girishkvs's Copilot 🤖
Sorry, something went wrong.
|
These threads are marked resolved but the code still has the issue. I replied in each one with what I found:
I can’t reopen them myself, no write access. — girishkvs's Copilot 🤖 |
Sorry, something went wrong.
…rsers
Five earlier review threads were addressed for the case raised but not
its neighbour. This commit closes them properly:
- InMemoryMcpSkillCatalog now hands out copies from ListAsync and GetAsync,
not only on construction, so a mutated result cannot change later responses.
- URI validation decodes each path segment before checking for '.', '..',
and separators, since System.Uri (and therefore the resource collection)
treats "%2e%2e" as "..".
- An unterminated quoted scalar is malformed and no longer reaches the
explicit-frontmatter escape hatch; a quote closed on a later line is
still classified as unsupported multi-line YAML and remains bypassable.
- Directory loading reads each file with a buffer of the length it
checked and fails if the file changed size, so a file growing between
the check and the read can neither exceed the limit nor be served with
a manifest that does not describe it.
- URI aliases that differ only in scheme or authority case are rejected
even with identical content, because the resource collection would
serve one resource whose contents carry the first URI, failing a
verified read of the second.
Further alignment with reference YAML parsers, each confirmed against the
yaml npm package: plain mapping keys resolve like values ("TRUE:" is the
key "true", "0x10:" is "16"), flow mappings honour YAML's separator rules
("{version:1}" is the key "version:1" with a null value), a plain scalar
ended by a comment cannot continue on the next line, and only space and
tab are trimmed, so non-breaking spaces stay in scalars. Nesting is capped
at 32 levels so a hostile file cannot exhaust the stack.
Skill names accept Unicode lowercase and caseless letters, per the Agent
Skills wording "unicode lowercase alphanumeric characters". A test that
relied on reflection-based serialization now uses the SDK's options, so
the skills tests pass on net9.0 and net8.0 as well as net10.0.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P6omaTvQiryHtzFHRqUE3b
|
You're right on all five, and thank you for checking the code rather than the thread state. Each of my earlier fixes handled the case that was raised and not its neighbour: the catalog copied on construction but not on read, the URI check caught .. but not %2e%2e, the escape hatch still swallowed an unterminated quote, the size limit was enforced before the read but the read itself was unbounded, and the alias check only failed on differing content. All five are closed in 150d9f9 with a test for each; details on the individual threads. I also confirmed your net9.0 observation by running the suite there, which I had not been doing. Fixed as well. — Claude (Claude Fable 5.1, via Claude Code), working with @PederHP on this PR |
Sorry, something went wrong.
…iew pass found Frontmatter reader, checked against the yaml 2.8 reference parser: a trailing comment on a continuation line ends the plain scalar; plain scalars and keys cannot start with a flow indicator, a block scalar header, or a block indicator followed by white space, and keys are held to the same reserved-start rules as values; empty flow entries are rejected; -0 and -0.0 render as 0; block scalars keep white-space-only lines wider than their indentation and reject a wider leading empty line; tabs are accepted where YAML allows them (blank lines, after ':' and '-', inside block scalars) and rejected only as indentation; a sequence item with a quoted key is a compact mapping; and a flow collection that closes on a later line is valid-but-unsupported YAML rather than malformed, so the explicit frontmatter escape hatch covers it. McpServerSkill keeps a private copy of its entry and ProtocolSkill returns a copy, so the manifest WithSkills registers cannot drift from the served bytes through the caller's objects. Entry validation errors from the directory overloads name directoryPath rather than a frontmatter parameter those overloads do not have. The client rejects a skills/list page without a skills array instead of dereferencing null, and SkillVerifier treats null text contents as a verification failure. Replace a test that compared a value with itself, add tests for the cases above and for invalid and same-named skill directories, root the Skills assembly in the AOT compatibility app, fix the missing using in the docs snippet, and link the docs page to the samples on GitHub so the links work on the published site. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P6omaTvQiryHtzFHRqUE3b
| Back | FazBrowse Home | New Git URL |
Closes #1863.
Summary
Adds ModelContextProtocol.Extensions.Skills, an implementation of the MCP Skills extension (SEP-2640, io.modelcontextprotocol/skills, specification) as a separate package alongside Extensions.Apps and Extensions.Tasks.
This is an alternative to #1856 and reuses several parts of it (see Relationship to #1856). Compared to that PR it adds a client API, a server authoring model that computes manifests from the served bytes, entry validation against the specification's MUSTs, protocol-version gating that matches Core, a server and a client sample, and a docs page.
What's in the package
Protocol types (Skill, SkillResource, SkillResources, ListSkillsRequestParams/Result, GetSkillRequestParams/Result), source-generated for AOT. SkillResources is a closed array-or-"dynamic" union; an entry with a missing, null, or otherwise malformed resources fails deserialization, as the spec requires hosts to reject it.
Server
Client
Protocol-version handling. resultType, ttlMs, and cacheScope are emitted only on requests negotiated under 2026-07-28 or later, where ttlMs and cacheScope are required and default to 0 / private when unset, exactly as Core does for tools/list. On 2025-11-25 sessions they are omitted (#1721). Both directions are tested.
Also: conformance server fixture, docs page (docs/concepts/skills), README/PACKAGE.md entries, and two samples (samples/SkillsServer, samples/SkillsClient).
Core change
PaginatedRequestParams and PaginatedResult had private protected constructors, so a package outside Core could not implement a paginated method. They are widened to protected. Source and binary compatible; package validation passes against the 2.0.0 baseline. Happy to split this into its own PR if preferred.
CacheScopeConverter (Core's lenient read-side converter for cacheScope) was internal; it is now public so that extension packages defining their own ICacheableResult types get the same read-side leniency. This was originally done by compiling the Core source file into the package; @girishkvs pointed out that made Skills the only package reaching into Core's source tree, and making the type public is cleaner.
Frontmatter without a YAML dependency. SkillFrontmatter.Parse reads the YAML subset that Agent Skills frontmatter uses (nested block mappings, block and flow sequences, plain/quoted/block scalars, comments) and resolves unquoted scalars per the YAML 1.2 core schema, which is what the yaml package, yaml.v3, and therefore other SDKs and hosts do. Anchors, aliases, tags, multi-document streams, complex keys, and tabs are rejected with a FormatException naming the construct rather than guessed at, since a wrong rendering is a host-side verification failure. It is public so hosts can use the same reader for the spec's frontmatter verification. Its output is checked against the yaml npm package's core-schema resolution on a 92-case corpus (block and flow collections, every block-scalar style and chomping mode, comments, quoting, and numeric edge cases); the 66 cases both accept are committed as SkillFrontmatterCorpusTests with the reference parser's output as expected values, and the rest are the reader's deliberate rejections.
Deliberately not included
Open questions for maintainers
Relationship to #1856
@girishkvs opened #1856 first, and I reviewed it before deciding that the shape changes I wanted (client API, authoring model, frontmatter reading, validation, samples, docs) were better written than requested through review rounds. Girish has agreed to the co-authorship on Discord. I reused these parts of #1856, and the commits carrying them name @girishkvs as co-author:
Authorship
This PR was written by Claude (Claude Fable 5.1, via Claude Code) with me directing the design, reviewing every file, and making the calls on scope and API shape. The commit trailers carry the session link. I am listing this explicitly because I think who and what wrote a PR should be visible to reviewers.
Validation