Skip to content

skills: add SEP-2640 protocol support - #1238

Open
sambhav wants to merge 2 commits into
modelcontextprotocol:mainfrom
sambhav:skills-sep-2640
Open

skills: add SEP-2640 protocol support#1238
sambhav wants to merge 2 commits into
modelcontextprotocol:mainfrom
sambhav:skills-sep-2640

Conversation

@sambhav

@sambhav sambhav commented Sep 4, 2026

Copy link
Copy Markdown
Member

Summary

Add generic Go SDK support for accepted SEP-2640, allowing applications to serve and consume skills through typed APIs.

This includes:

  • Typed skills/list, skills/get, and optional resources/directory/read clients and handlers.
  • Cursor-aware pagination helpers and reusable, non-mutating iterators.
  • Complete resource manifests and the explicit "dynamic" marker.
  • SEP and Agent Skills validation by default, custom validators, and explicit unsafe overrides.
  • Digest, size, and frontmatter verification.
  • A compile-checked custom-server example.

Review order

This is PR 1 of 2 and can be reviewed and merged independently.

The optional filesystem provider is split into draft #1240. Its helper-only diff is available at sambhav/go-sdk#1. After this PR merges, #1240 can be rebased onto main and marked ready.

Filesystem discovery, hashing, caching, and refresh policy belong to that follow-up.

Server usage

entry := &skills.Skill{
    URI: "skill://generated/SKILL.md",
    Frontmatter: skills.Frontmatter{
        "name":        "generated",
        "description": "Instructions generated on demand.",
    },
    Resources: skills.DynamicResources(),
}

err := skills.AddHandlers(server, &skills.Handlers{
    List: func(context.Context, *mcp.ServerSession, *skills.ListSkillsParams) (*skills.ListSkillsResult, error) {
        return &skills.ListSkillsResult{Skills: []*skills.Skill{entry}}, nil
    },
    Get: func(_ context.Context, _ *mcp.ServerSession, p *skills.GetSkillParams) (*skills.GetSkillResult, error) {
        if p.URI != entry.URI {
            return nil, &jsonrpc.Error{
                Code:    jsonrpc.CodeInvalidParams,
                Message: "unknown skill",
            }
        }
        return &skills.GetSkillResult{Skill: entry}, nil
    },
}, nil)

Handlers run when requests arrive, so applications can serve changing catalogs. Resource content is registered separately through Server.AddResource or Server.AddResourceTemplate.

DynamicResources() means a complete manifest with stable digests is unavailable. A catalog that changes over time can still return complete static manifests.

Client usage

client := mcp.NewClient(&mcp.Implementation{
    Name: "client", Version: "v1",
}, nil)

if err := skills.AddClient(client); err != nil {
    return err
}

for skill, err := range skills.All(ctx, session, nil) {
    if err != nil {
        return err
    }
    fmt.Println(skill.URI, skill.Frontmatter["description"])
}

ReadDirectory and DirectoryEntries expose the optional directory capability. Content remains readable through ClientSession.ReadResource.

Compatibility and dependencies

The core addition is (*Server).AddExtension, which lets extension packages advertise capability settings after construction without mutating caller-owned capabilities.

Existing resource API signatures remain unchanged. This PR uses existing resource registration and template dispatch; #1240 supplies live filesystem enumeration through existing middleware.

skills/list emits SEP-2549 cache fields only for protocol 2026-07-28 and later. Older connections omit ttlMs and cacheScope, with regression coverage for serialization.

Adds gopkg.in/yaml.v3 for parsing and comparing YAML frontmatter. No dependency type appears in the public API.

Conformance and validation

Validated on 2026-09-06 using conformance #330 at fa8aaec3611e387249da6adc826b02dd77372a3e.

The following results cover the combined #1238 + #1240 stack at a33c199eead5817160581ac834fb0e11e7e0a390. The fixture includes multiple and nested skills, supporting directories, an ordinary registered resource, and page size 1.

Protocol / transport Enumeration Manifest Directory Total
2025-11-25 / stateful 29 pass, 1 warning 6 pass 7 pass 42 pass, 0 failures, 1 warning
2026-07-28 / stateless 30 pass 6 pass 7 pass 43 pass, 0 failures, 0 warnings

Totals include each scenario’s wire-schema-valid check. Both resource metadata SHOULD checks are exercised through #1240’s live resource listing.

The sole legacy warning is a conformance-harness false positive: sep-2640-skills-list-cache-attributes warns about omitted cache fields without checking the effective protocol version. Omission is correct on 2025-11-25. The harness should report this requirement as SKIPPED on older versions and enforce it on 2026-07-28 and later.

All upstream workflows passed on the tested helper commit: Test, Docs Check, Conformance Tests, and CodeQL. Local skills race tests, vet, and build also passed.

For legacy conformance runs, use --spec-version 2025-11-25 --force. The modern fixture uses StreamableHTTPOptions{Stateless: true}.

@sambhav
sambhav force-pushed the skills-sep-2640 branch 3 times, most recently from adc1ddf to ca89d93 Compare September 4, 2026 17:02
@panyam

panyam commented Sep 4, 2026

Copy link
Copy Markdown

Woooot great to see this @sambhav. I ran the SEP-2640 conformance scenarios against this branch (conformance PR 330, the traceability extraction and server scenarios Il share soon for the skills extension). 41 checks, 0 failures.

Scenario Result
sep-2640-skills-enumeration 30/30
sep-2640-skills-manifest 4/4, 2 untestable
sep-2640-skills-directory 7/7

Repro, pointing a minimal skills.AddDirectory server at any skills tree:

node dist/index.js server --url http://localhost:18299/ \
  --scenario sep-2640-skills-enumeration --spec-version 2025-11-25 --force

Got three tiny notes (none blocking):

1. ttlMs and cacheScope are emitted on protocol 2025-11-25. In the SEP - "In protocol versions 2026-07-28 and later, the result also carries the base protocol's list-caching attributes". ttlMs does not appear in the 2025-11-25 schema at all, so this is emitting a field that is not defined in the negotiated version. We might want a version guard if it was not deliberate? If it was, this is a nice data point. I pointed out in PR 138 that it dropped the condition and was wondering if it was intention so looks like two efforts came to this point independently. So the condition itself may need to be removed instead of changing impls.

2. Two untestable SHOULD rows instead of passing. sep-2640-skillmd-metadata-name and -description check that the SKILL.md resource carries name and description from frontmatter. Since the dir utility serves through resource-template dispatch, SKILL.md is not in resources/list, so the metadata is not observable. I dont think this is a bug and I also do not think it needs changing, but figured Id flag it. A server registering SKILL.md as a listed resource does/would exercise those two.

3. Possible doc gap dueto needing flags against this branch. The runner defaults to the draft stateless wire. here it asserts MCP-Protocol-Version: 2026-07-28 with no handshake, so the server correctly refuses with -32022. --spec-version 2025-11-25 --force and selects the stateful wire and overrides the extension-applicability skip. The scenarios themselves are version-portable, they just do not advertise that. Just wanted to call this out.

@sambhav
sambhav force-pushed the skills-sep-2640 branch 2 times, most recently from 60744e4 to 36000a2 Compare September 4, 2026 21:59
@sambhav sambhav changed the title skills: add SEP-2640 support skills: add SEP-2640 protocol support Sep 4, 2026
@sambhav

sambhav commented Sep 4, 2026

Copy link
Copy Markdown
Member Author

Thanks for running the SEP scenarios. The PR is now split so this one contains only the generic protocol layer. I addressed note 1 in the latest revision: skills/list now omits ttlMs and cacheScope before protocol version 2026-07-28, with regression coverage. Notes 2 and 3 make sense and do not require changes in this generic layer; the filesystem metadata behavior and usage notes will remain explicit in the follow-up helper PR.

@sambhav

sambhav commented Sep 4, 2026

Copy link
Copy Markdown
Member Author

I reran conformance PR 330 locally against the split filesystem helper. It caught a nested YAML mapping normalization bug (metadata decoded to a non-JSON concrete map shape), now fixed with regression coverage. Final 2025-11-25 results: enumeration 29/29, manifest 4/4 with the two expected untestable metadata SHOULDs, and directory 7/7; no failures. The runner still needs --spec-version 2025-11-25 --force for this stateful fixture, as documented in conformance PR 330.

@sambhav

sambhav commented Sep 4, 2026

Copy link
Copy Markdown
Member Author

The optional filesystem layer is now advertised as upstream draft #1240. While #1238 is pending, its GitHub Files changed view necessarily includes both stack commits; the clean helper-only diff is sambhav#1. I will rebase #1240 onto upstream main after this PR merges, then mark it ready.

@sambhav

sambhav commented Sep 5, 2026

Copy link
Copy Markdown
Member Author

@guglielmo-san 🙏 this is ready. There is another PR stacked on top which provides a neater abstraction and utility for skills with filesystems (#1240). Would appreciate it if we can land this one first and the other one as a quick follow.

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.

2 participants