Skip to content

Add TypeScript SDK for the Skills Extension SEP#71

Closed
olaservo wants to merge 34 commits into
mainfrom
add-sep-example
Closed

Add TypeScript SDK for the Skills Extension SEP#71
olaservo wants to merge 34 commits into
mainfrom
add-sep-example

Conversation

@olaservo

@olaservo olaservo commented Mar 22, 2026

Copy link
Copy Markdown
Member

Summary

A TypeScript SDK + reference client/server demonstrating SEP-2640 (Skills Extension), serving Agent Skills as skill:// resources over MCP. Tracks the current SEP text on the sep/skills-extension branch, including the latest decision-log ADRs (2026-06-02 / 06-05 / 06-09). No protocol dependency beyond resources/read and the one new extension method below.

Note: this PR has evolved well past its original description. The latest commit (d12cc55) realigns the SDK to the current index schema and adds resources/directory/read; the summary below reflects the branch as it stands now.

skill:// resource convention

  • Multi-segment paths with organizational prefixes (skill://acme/billing/refunds/SKILL.md)
  • Final-path-segment = frontmatter name, and the no-nesting constraint — both enforced at discovery time
  • Scheme-agnostic discovery: skill:// is SHOULD; index entries may use any scheme (repo://, github://, …)

skill://index.json (current SEP-2640 schema)

The index is the WG's own schema (decoupled from the agentskills.io .well-known format) with type-less entries:

  • frontmatter — the skill's SKILL.md frontmatter copied verbatim as JSON (carries name/description and anything else authored)
  • url + digest — when served as individual files; digest is sha256:{hex} over the SKILL.md raw bytes
  • archives[]{ url, mimeType, digest } per packed form
  • Every entry MUST have a url, a non-empty archives, or both. No $schema/version marker.

This replaces the earlier discriminated type: "skill-md" | "archive" | "mcp-resource-template" schema.

resources/directory/read (new extension method)

  • Enumerate the files under a skill directory without knowing every URI — an ls-style, metadata-only, paginated, non-recursive listing. Directories are identified by mimeType: "inode/directory"; -32602 on a non-directory/unknown URI.
  • Server: buildDirectoryTree() + makeDirectoryReadHandler(), installed on the low-level Server. Client: serverSupportsDirectoryRead(), readDirectory(), walkDirectory().
  • Gated by a new capability flag directoryRead (default off).

Digest: integrity + caching

  • verifyDigest() / readSkillUriVerified() — tamper/integrity check against the index digest (UTF-8 round-trip is byte-exact for SKILL.md, so a faithful read always matches).
  • Caching pattern (the digest's headline purpose) documented: compare a stored index digest against the freshly-read one to skip refetching unchanged skills — no content hashing.

Extension declaration

  • io.modelcontextprotocol/skills via native SDK registerCapabilities (v1.29+)
  • declareSkillsExtension(server, { directoryRead: true }) to advertise the directory-read capability (paired with registerSkillResources(..., { directoryRead: true }))

Subpath exports

Import path Purpose
root Shared types, URI utilities, directory constants/schema
/server Discover skills, register resources, generate the index, directory-read handler
/client Discovery, reads, archive unpack, digest verify, directory read, catalog building

Removed since the original PR

  • The mcp-resource-template index entry type and the parameterized-template serving feature (SkillTemplateDeclaration, listSkillTemplatesFromIndex) — it only existed to back the dropped index type. The catch-all supporting-files template stays.
  • $schema field + validation and the SKILL_INDEX_SCHEMA constants.
  • The well-known HTTP bridge (removed earlier in the branch).

Breaking changes

Yes — this is a clean rewrite of the index schema and removes the template surface. Package version bumped to 0.10.0 (pre-1.0).

Verification / test plan

  • cd typescript/sdk && npm run build && npm test — clean tsc, 163 tests pass (new directory.test.ts; rewritten index-json.test.ts / register.test.ts)
  • Reference client + server run end-to-end over stdio — index discovery, resources/directory/read enumeration + walkDirectory, archive fetch+unpack, digest verification (matches ✓), supporting-file flow
  • Examples type-check against the built SDK

🦉 Generated with Claude Code

olaservo and others added 7 commits April 14, 2026 07:00
Reference implementation demonstrating the Skills Extension SEP with:
- skill:// resource convention with multi-segment paths
- Extension declaration (io.modelcontextprotocol/skills) via SEP-2133
- SEP-2093 shims: resources/metadata, URI-scoped resources/list,
  per-resource capabilities via _meta
- SDK with server-side discovery/registration and client-side wrappers
  (listSkills, readSkillUri, fetchSkillMetadata, READ_RESOURCE_TOOL)
- 4 sample skills: 2 single-segment, 2 multi-segment under acme/
- End-to-end client demo exercising all 7 features

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Each shim function now has a TODO with the specific PR link for
when/if it can be removed. Uses "if" not "when" since the
upstream PRs are drafts not guaranteed to merge.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ASCII diagrams showing: overall system structure, URI anatomy,
discovery flow, SDK-to-protocol mapping, and what's permanent
SEP design vs temporary SDK shims.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Align the reference implementation with PR #69's SEP spec, which defines
skill://index.json as the primary enumeration mechanism and requires zero
protocol dependencies beyond resources/read.

Added:
- skill://index.json well-known discovery index (Agent Skills format)
- generateSkillIndex() server-side, listSkillsFromIndex() client-side
- SkillIndex/SkillIndexEntry types and INDEX_JSON_URI constant
- 53 tests across URI utilities, index round-trip, and client parsing

Removed (SEP-2093 features not in the SEP):
- resources/metadata handler and fetchSkillMetadata()
- Scoped resources/list(uri=...) and listSkillsScoped()
- Per-resource _meta capabilities on all resource registrations
- ResourceCapabilities/ResourceMetadataResult types
- Zod schemas and RequestHandlerRegistrar interface

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bump @modelcontextprotocol/sdk from 1.27.1 to 1.29.0, which includes
native extensions capability support (typescript-sdk#1811, backport of
#1630) and the missing size field on ResourceSchema (#1575).

- Replace private _capabilities hack in declareSkillsExtension with
  the SDK's native registerCapabilities API
- Add size field to SKILL.md resource metadata in registerSkillResources
- Keep ServerInternals as a deprecated alias for backward compatibility
- Update README shims table to reflect resolved workarounds

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@olaservo olaservo changed the title Add Skills Extension SEP reference implementation Add TypeScript SDK and examples for the Skills Extension SEP Apr 14, 2026
@olaservo olaservo marked this pull request as ready for review April 14, 2026 14:15
olaservo and others added 6 commits April 16, 2026 05:15
- Gap 1: Add $schema validation in listSkillsFromIndex() with
  KNOWN_SKILL_INDEX_SCHEMAS set. Warns on unrecognized schema but
  continues processing (SHOULD, not MUST per the SEP).

- Gap 2: Extend SkillIndexEntry to discriminated union supporting
  skill-md, mcp-resource-template, and archive entry types. Add
  listSkillTemplatesFromIndex() client function and templates
  parameter to generateSkillIndex().

- Gap 3: Add well-known HTTP bridge module (well-known.ts) that
  fetches skills from /.well-known/agent-skills/index.json, verifies
  SHA-256 digests, supports archive extraction, and caches to a local
  directory for use with discoverSkills() + registerSkillResources().

- Gap 4: Digest-based caching in the bridge module — refreshFromWellKnown()
  skips unchanged entries based on stored digests.

86 tests passing across 4 test files.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- McpResourceTemplateIndexEntry: use `url` instead of `uriTemplate`
  per the SEP field table, and make `name` optional (SEP says omitted
  for mcp-resource-template entries)
- SkillIndexEntry: remove ArchiveIndexEntry from the union — the SEP
  restricts skill://index.json to skill-md and mcp-resource-template
  only. ArchiveIndexEntry remains as a standalone type for the
  well-known HTTP bridge.
- SkillTemplateEntry.name: make optional to match

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
olaservo and others added 5 commits April 17, 2026 13:17
…y fixes

- discoverSkills(): index-first discovery with resources/list fallback
- discoverAndBuildCatalog(): one-call discover + catalog for agent integration
- listSkillsFromIndex() now explicitly handles any URI scheme in index entries
- readSkillUri() documented as scheme-agnostic primary read function
- buildSkillsCatalog() restored with scheme-agnostic XML generation
- Case-insensitive skill name matching in server discovery
- Export SkillSummary, SkillsCatalogOptions, and new types from client subpath
- Merge duplicate JSDoc on READ_RESOURCE_TOOL
- README updated with quick start and step-by-step client usage
- 107 tests across 4 test files

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Align SDK resource metadata with skill-meta-keys.md recommendations:
- Default audience to ["assistant"], configurable per-skill via
  SkillMetadata.audience or globally via RegisterSkillResourcesOptions
- Add lastModified to index.json, template, and prompt-xml resources
  (derived from most recent skill mtime)
- Add size to manifest, index.json, and prompt-xml resources
  (pre-computed from serialized content)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
olaservo and others added 3 commits April 18, 2026 12:25
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@olaservo

Copy link
Copy Markdown
Member Author

Disclaimer: have been iterating through a few versions of this while clarifying the implementation guidance we'd want to add to the SEP or other docs. Mostly opened this for extra 👀 in case any of this was way off track. I'm not sure if we actually plan to publish a dedicated SDK around this extension, or just refer to code examples and link to implemenations.

olaservo and others added 4 commits April 20, 2026 04:40
The server clause in the catalog prompt is only useful when the reader
tool accepts a `server` parameter. For tools already scoped to a single
server (taking only `uri`), the clause mentions an argument the tool
doesn't accept. Make `serverName` optional on `SkillsCatalogOptions` and
drop the clause when omitted. `DiscoverCatalogOptions.serverName` stays
required since the default `READ_RESOURCE_TOOL` takes a server param.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Rescopes the SDK under the MCP org and adds the `experimental-` prefix
to signal the SEP is still in draft. Version resets to 0.1.0 for the
first publish under the new name. Also removes the `examples/` tree,
which lived outside the SDK and isn't part of the published package.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@olaservo olaservo changed the title Add TypeScript SDK and examples for the Skills Extension SEP Add TypeScript SDK for the Skills Extension SEP Apr 23, 2026
@olaservo olaservo added the SEP-2640 pull requests related to SEP-2640 label Apr 27, 2026
olaservo added 9 commits May 2, 2026 15:22
The well-known bridge fetches /.well-known/agent-skills/index.json,
verifies digests, and unpacks tar.gz archives to a local cache. That's
agent-skills-spec scope, not SEP-2640 scope, and bundling them produced
an awkward split where ArchiveIndexEntry was annotated "HTTP bridge
only, not valid in skill://index.json" because both type systems lived
in the same package.

Drops src/well-known.{ts,test.ts}, src/well-known/index.ts, the four
WellKnown* types and ArchiveIndexEntry from types.ts, the ./well-known
subpath export, the tar dependency, and the README HTTP-bridge section.
Build clean, 87 vitest cases still pass.
SEP-2640 defines `type: "archive"` as a normative entry type in
`skill://index.json` and requires hosts to support both `.tar.gz`
(application/gzip) and `.zip` (application/zip). The SDK previously
filtered archives to skill-md only on the client side and had no way
to declare them on the server side; both gaps are now closed.

Server: `registerSkillResources()` accepts `archives:
SkillArchiveDeclaration[]` and `templates:
SkillTemplateDeclaration[]`. Each archive declaration is read from
disk, served as a single MCP resource at `skill://<skillPath>.<format>`
with the SEP-mandated mimeType, and emitted as a `type: "archive"`
entry in the generated index. The SDK enforces the path-name rule
(final segment of skillPath MUST equal frontmatter name) at
declaration time.

Client: `listSkillsFromIndex()` returns archive entries with `type:
"archive"`, `uri` set to the archive resource URL, and `skillPath`
derived by stripping the archive suffix. New `readSkillArchive()`
fetches via `resources/read` and dispatches to in-memory extraction
based on the resource's mimeType (with URL-suffix fallback per the
SEP), returning `Map<string, Buffer>` keyed by the archive-relative
path so post-unpack files match the `skill://<skillPath>/<file-path>`
namespace exactly.

Archive safety per Agent Skills spec: rejects path-traversal segments,
absolute paths, drive-letter prefixes, and symlinks resolving outside
the skill directory; bounds total uncompressed size, per-file size,
and entry count to defend against decompression bombs; requires
SKILL.md at archive root (no wrapper directory).

Adds tar-stream + yauzl as runtime deps; yazl as devDep for tests.
112 → 122 vitest cases passing.
The previous example sources were removed in fab1009 ("examples lived
outside the SDK and aren't part of the published package"). Bringing
them back as the reference end-to-end demo, since the README still
points at them and SEP-2640 calls out the TypeScript SDK + reference
implementations as a review prerequisite.

Layout:

  sample-skills/             — 4 skill-md skills (single + multi-segment)
    code-review/             — w/ supporting REFERENCE.md
    git-commit-review/
    acme/onboarding/
    acme/billing/refunds/    — w/ supporting refund-email-template.md

  sample-archive-source/     — packed at server startup → archive entry
    pdf-processing/          — w/ references/FORMS.md

  skills-server/typescript/  — discovers, packs the archive, registers
                               all three index entry types
  skills-client/typescript/  — walks 7 client-side surfaces:
                               READ_RESOURCE_TOOL, listSkillsFromIndex,
                               listSkillTemplatesFromIndex, listSkills,
                               readSkillContent, readSkillArchive,
                               readSkillManifest + readSkillDocument

The pdf-processing skill is the archive demo: server packs it to a
temp .tar.gz at startup, registers it as `skill://pdf-processing.tar.gz`
with mimeType application/gzip, and lists it in skill://index.json with
type="archive". Client fetches it via resources/read and unpacks
in-memory through readSkillArchive() with full archive-safety checks.

Verified: client run prints all 7 sections successfully against the
spawned server.
Server: validate skill-name against ^[a-z0-9-]+$, add `index: false`
opt-out for unenumerable catalogs, throw when template `complete` is set
without `read`.

Client: switch frontmatter parser to the yaml package so block scalars
work, and extract `<skill-path>` per SEP from any-scheme URIs ending in
/SKILL.md (was falling back to entry name).

Plus accumulated alignment work: instructions-mining discovery path,
READ_SKILL_TOOL export, xml.ts cleanup, examples/READMEs/CLAUDE.md
updates, and 15 new test cases covering the above.
…directory/read

Brings @olaservo/ext-skills into line with the latest SEP-2640 (ADRs of
2026-06-02/05/09). Breaking change; version bumped to 0.10.0.

Index schema (skill://index.json):
- Type-less entries: verbatim `frontmatter` + `url`/`digest` and/or per-skill
  `archives[]` ({url, mimeType, digest}). Drops the skill-md/archive/
  mcp-resource-template discriminator and the $schema/version marker.
- Server computes sha256:{hex} digests over SKILL.md raw bytes (and each
  archive); captures the full frontmatter block in discoverSkills.
- Client reads name/description from frontmatter; carries digest/archives on
  SkillSummary; skips entries with neither url nor archives.

resources/directory/read (new directory.ts):
- inode/directory model, buildDirectoryTree + makeDirectoryReadHandler
  (paginated, metadata-only, non-recursive, -32602 on misuse), registered on
  the low-level Server.
- directoryRead capability via declareSkillsExtension(server, {directoryRead})
- Client: serverSupportsDirectoryRead, readDirectory, walkDirectory.

Digest integrity/caching:
- verifyDigest / readSkillUriVerified (UTF-8 round-trip is byte-exact for
  SKILL.md, so a faithful read always matches).

Removes the parameterized-template serving feature (SkillTemplateDeclaration,
listSkillTemplatesFromIndex, mcp-resource-template) — it only existed to back
the dropped index entry type; the catch-all supporting-files template stays.

Tests rewritten/added (163 passing); examples + README + CLAUDE.md updated;
verified end-to-end via the reference client/server.

🦉 Generated with Claude Code
- parseFrontmatter (server): use line-anchored regex instead of
  content.split("---") so a `---` inside frontmatter (e.g. an indented
  block scalar) no longer truncates the parse; server now matches the
  client parser.
- archive (tar.gz): stream gunzip with a running decompressed-byte
  counter that aborts past maxTotalSize, instead of gunzipSync fully
  inflating a possibly-huge payload before the size check.
- verifyDigest: throw on a malformed expected digest rather than
  silently returning false, so callers distinguish tampering from an
  uninterpretable digest (SEP-2640 verification is a MUST).
- walkDirectory: fix doc comment (FIFO queue is breadth-first).

Adds regression tests: gzip bomb rejection, verifyDigest throw on
malformed digest, and discoverSkills retaining a description whose block
scalar contains a `---` line. 166 tests pass.

🦉 Generated with [Claude Code](https://claude.com/claude-code)
…directoryRead no-op

- loadDocument: reject `..` as a path segment, not a substring, so valid
  filenames like `notes..final.md` are no longer wrongly rejected
  (real traversal still blocked by isPathWithinBase).
- registerSkillResources: compute each archive digest once from the bytes
  it serves and reuse it for the index via GenerateSkillIndexOptions
  .archiveDigests, so the advertised digest can't drift from the served
  artifact and each archive is read once instead of twice.
- registerSkillResources: warn when directoryRead handler is installed
  without the matching declareSkillsExtension capability, instead of
  silently no-opping for spec-conformant clients.

🦉 Generated with [Claude Code](https://claude.com/claude-code)
- readSkillUri: accept legitimately empty text content (typeof check
  instead of falsy `content.text`), matching the archive read path
- walkDirectory: guard against cyclic listings (visited set) and
  non-advancing pagination cursors (throw instead of looping forever)
- listSkillsFromInstructions: read named URIs concurrently via
  Promise.allSettled instead of serially
- uri: extract isSkillContentFilename() and route the four inline
  SKILL.md checks through it

Adds 6 regression tests; full suite passes (172).

🦉 Generated with [Claude Code](https://claude.com/claude-code)
@olaservo

Copy link
Copy Markdown
Member Author

Closing since we will add this to the typescript SDK instead.

@olaservo olaservo closed this Jun 18, 2026
@github-project-automation github-project-automation Bot moved this from In review to Done in Skills Over MCP Working Group Jun 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

SEP-2640 pull requests related to SEP-2640

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant