Skip to content

skills: add SEP-2640 protocol support - #1

Closed
vijaydeepsinha wants to merge 9 commits into
mainfrom
sep-2640-python-sdk-support
Closed

skills: add SEP-2640 protocol support#1
vijaydeepsinha wants to merge 9 commits into
mainfrom
sep-2640-python-sdk-support

Conversation

@vijaydeepsinha

@vijaydeepsinha vijaydeepsinha commented Sep 9, 2026

Copy link
Copy Markdown
Owner

Summary

Adds Python SDK support for [SEP-2640 (Skills Extension)]): skills/list, skills/get, and resources/directory/read as protocol primitives, SEP-2640 conformance validation, and capability negotiation. This SDK does not provide filesystem discovery, catalog indexing, or caching/refresh policy — those belong to a higher-level provider built on top of this.

Motivation and Context

SEP-2640 defines a convention for serving Agent Skills over MCP using the Resources primitive. The Python SDK has no support for it today. This PR adds the extension using the SDK's existing Extension/MethodBinding mechanism (the same one backing the shipped Apps extension, SEP-2133) — no schema or codegen changes, no new required dependencies.

What's included

  • src/mcp/shared/skills.py — wire types (Skill, SkillResource, params/results), SEP-2640 conformance validation (name/URI/frontmatter consistency, digest format, resource-manifest completeness, the 512-entry/16 MiB limits), and verify_skill_resource (digest+size integrity check for content already read).
  • src/mcp/server/skills.py — the Skills extension: handler-based (list_skills, get_skill, optional read_directory), validates results against SEP-2640 before they hit the wire, gates the SEP-2549 ttlMs/cacheScope fields to protocol version 2026-07-28+.
  • src/mcp/client/skills.pylist_skills/get_skill/read_directory (auto-paginating, with cursor-repeat detection), read_skill_uri (a thin, discoverable resources/read alias), verify_skill_resource re-exported for client use.
  • docs/advanced/skills.md + docs_src/skills/ — a new doc page with a runnable example, explicitly scoping what the SDK does and doesn't do.

Server usage

from mcp.server.skills import Skills
from mcp.shared.skills import ListSkillsResult, GetSkillResult

async def list_skills(ctx, params):
    return ListSkillsResult(skills=[...])

async def get_skill(ctx, params):
    if params.uri != "skill://git-workflow/SKILL.md":
        raise MCPError(code=INVALID_PARAMS, message="unknown skill")
    return GetSkillResult(skill=...)

mcp = MCPServer("catalog", extensions=[Skills(list_skills=list_skills, get_skill=get_skill)])
mcp.add_resource(TextResource(uri="skill://git-workflow/SKILL.md", ...))  # file content is served normally

Client usage

from mcp.client.skills import get_skill, list_skills, read_skill_uri, verify_skill_resource

skills = await list_skills(client.session)
skill = await get_skill(client.session, "skill://git-workflow/SKILL.md")
result = await read_skill_uri(client.session, skill.uri)
verify_skill_resource(skill, skill.uri, result.contents[0].text.encode())

Protocol version / compatibility notes

  • capabilities.extensions (SEP-2133) and ttlMs/cacheScope (SEP-2549) are 2026-07-28+-only wire fields in this SDK's existing type surface — this is pre-existing, documented SDK behavior (docs/advanced/extensions.md), not something this PR changes. Skills gates its own cache fields to match.
  • A JSON-RPC -32602 error returns HTTP 200 on the classic (pre-2026-07-28) wire and HTTP 400 on the modern (2026-07-28+) wire. This is existing, spec-mandated (SEP-2575) SDK-wide transport behavior — every handler in the SDK gets it automatically via the shared ERROR_CODE_HTTP_STATUS table; nothing Skills-specific.
  • No breaking changes to any existing public API. All new files; the only touched existing file is mkdocs.yml (one nav entry).

How Has This Been Tested?

  • tests/{shared,server,client,docs_src}/test_skills.py — 100% line+branch coverage on all three new modules (shared/skills.py, server/skills.py, client/skills.py), verified via coverage report --fail-under=0.
  • Full suite: ./scripts/test → 6000+ passed, 0 failed, 100% total coverage, strict-no-cover clean.
  • ruff format/ruff check, pyright, markdownlint, mkdocs build --strict (Zensical), README-snippet check: all clean.
  • Cross-version: uv run --python 3.10 pytest tests/*/test_skills.py passes.

Conformance

Ran the SEP-2640 scenarios end-to-end against a real server and client built on this implementation (server scenarios exercise this PR's server; client scenarios exercise this PR's client against a hostile server the harness stands up):

Scenario Checks Result
sep-2640-skills-enumeration (skills/list + skills/get) 30 ✅ 30/30
sep-2640-skills-manifest (SKILL.md resource metadata) 6 ✅ 6/6
sep-2640-skills-directory (resources/directory/read) 7 ✅ 7/7
sep-2640-client-no-prefetch 1 ✅ PASS
sep-2640-client-verify-digest 1 ✅ PASS
sep-2640-client-verify-size 1 ✅ PASS
sep-2640-client-verify-frontmatter 1 ✅ PASS
sep-2640-client-verify-unlisted 1 ✅ PASS

43 wire checks + 5 client checks, 0 failures, 0 warnings.

Also manually verified via a live server against a Postman collection covering both the session-based (2025-11-25) and stateless (2026-07-28) wires.

Breaking Changes

None. New files only; no existing public API is modified.

Deliberate scope exclusions (and why)

  • Filesystem discovery/indexing/caching: this SDK gives the protocol primitives only; a higher-level provider library (e.g. one that walks a directory tree and computes digests) builds on top of Skills, list_skills, and get_skill.
  • Client-side frontmatter parsing: SEP-2640 requires hosts to parse a SKILL.md's YAML frontmatter and compare it field-by-field against the held entry. This SDK does not ship that comparison, to avoid adding a new required YAML dependency to the core SDK for a check any host already has the means to do with whatever YAML library it uses elsewhere. verify_skill_resource covers the digest/size half (no new dependency needed for that). Documented explicitly in docs/advanced/skills.md's "What this SDK doesn't do".
  • Skills types living in mcp-types instead of mcp.shared: mcp-types is generated from the official, versioned MCP JSON Schema; SEP-2640 is an extension, not core spec vocabulary, so its types are hand-written and live alongside the extension code — the same placement the existing Apps extension (SEP-2133) uses.

Types of changes

  • New feature (non-breaking change which adds functionality)
  • Documentation update

Checklist

  • I am assigned to the linked issue (or it is labeled help wanted, or I'm a maintainer) — N/A while targeting my own fork; will file/link before retargeting to upstream
  • I have disclosed any AI assistance and can explain the change in my own words
  • 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

Additional context

Implemented with AI assistance (Claude Code). Commits carry Co-Authored-By trailers for the Claude models used (Claude Sonnet 5 and Claude Opus 4.8). I've reviewed the implementation, reasoning, and conformance results directly, and can explain and defend any part of it in my own words.

vijay and others added 5 commits September 9, 2026 20:07
Wire types, request/result models, and SEP-2640 conformance validation
(name/URI/frontmatter rules, resource-manifest completeness, digest and
size verification) for the Skills extension, shared by the server and
client surfaces.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Skills extension (io.modelcontextprotocol/skills): serves skills/list,
skills/get, and the optional resources/directory/read behind the
directoryRead capability setting. Handlers are supplied by the server
author; the extension validates results against SEP-2640 before they
reach the wire and gates the SEP-2549 ttlMs/cacheScope fields to
protocol version 2026-07-28+.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Thin client wrappers for skills/list, skills/get, resources/directory/read,
and resources/read: list_skills and read_directory follow nextCursor to
completion, all four validate the server's response before returning it,
and verify_skill_resource checks a read's bytes against a held skill's
manifest entry.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds the Skills page under Advanced, with a runnable server/client
example, and tests proving every claim the page makes against the real
SDK.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Parametrize the digest-format rejection test over near-miss cases
(uppercase, wrong length, missing/wrong prefix), and add explicit JSON
round-trip tests for both shapes of the resources union type (a static
array and the "dynamic" marker) to prove neither collapses or mistags
on the wire.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@vijaydeepsinha vijaydeepsinha changed the title Sep 2640 python sdk support skills: add SEP-2640 protocol support Sep 9, 2026
vijay and others added 4 commits September 9, 2026 22:12
_resource_uri_in_skill reads like a boolean predicate but returns None
and raises; rename to _validate_resource_uri_in_skill to match its
sibling validators (validate_skill, validate_list_result,
validate_directory_result) and signal that it asserts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
_handle_read_directory inlined the same "validate incoming URI, convert
ValueError to MCPError" pattern that _handle_get had already extracted
into a helper. Add a parallel _require_directory_uri so both handlers
open with a symmetric one-line precondition check, matching the
_require_ui_scheme helper idiom from the Apps extension.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
validate_skill already rejects names that violate the Agent Skills
grammar (SEP-2640 defers to it), but nothing pinned the edge cases.
Add a parametrized test covering consecutive, leading, and trailing
hyphens, uppercase, underscores, and the 64-character ceiling.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Correct the server/client snippet hl_lines, which highlighted blank
  and unrelated lines after the example imports were expanded.
- Fix "all four validate": read_skill_uri is a thin resources/read
  pass-through that validates nothing, contradicting the same section's
  own next paragraph. Only list_skills/get_skill/read_directory validate.
- Replace the phantom add_resource_template API (no such method) with the
  @mcp.resource(...) template decorator, in both the guide and the
  mcp.server.skills module docstring.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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