Skip to content

Public session-scoped skill provider API (lazy list/read callbacks) #2831

Description

@vatsa-msft

Problem

Today the SDK can only load Agent Skills from the local filesystem (skill_directories / skillDirectories). Applications whose skills live somewhere else have to write every SKILL.md to disk before the session starts. Examples of "somewhere else":

  • remote MCP servers that implement the MCP Skills extension
  • a database
  • a per-user or per-tenant store
  • a content service

Writing the files to disk up front causes several problems:

  • Everything is fetched eagerly. Skills are designed for progressive disclosure: the model sees only names and descriptions until it activates a skill. Writing files to disk means downloading every skill's full content at session start, including skills that are never used.
  • Content goes stale. Files written at startup can't reflect a source that changes during a long-lived session, and invalidating or refreshing them means rewriting the filesystem.
  • Content can't be verified at activation. An application that wants to check content when the skill actually loads, for example against an expected digest or size, has no hook. Once a file is on disk, it can change between listing and activation.
  • Sessions aren't isolated. Multi-session hosts must create and clean up a separate directory tree per session to keep one session's skills out of another's.
  • The host needs extra write access. Environments with read-only or restricted filesystems must grant write access just to stage skills.
  • The source is invisible. Skills from different sources are indistinguishable once copied to disk, so name collisions and provenance are hard to handle.

Proposed solution

Add a public, session-scoped skill provider option to session configuration in every SDK language. The host supplies callbacks instead of files:

class SkillProvider(Protocol):
    async def list_skills(self) -> list[SkillDescriptor]:
        """Return metadata only (name, description, optional frontmatter). No content."""

    async def read_skill(self, name: str) -> str | bytes:
        """Return the full SKILL.md. Called lazily, only when the skill is activated."""

session = await client.create_session(
    SessionConfig(skill_provider=MyProvider(), ...)
)

(TypeScript and .NET would expose the equivalent skillProvider option.)

Requested semantics

  1. Lazy content. list returns metadata only; read is called only when the model or user activates that skill.
  2. Session scope. A provider is bound to one session and is never shared with or visible to another session.
  3. Async and cancellable. Both callbacks are async, honor cancellation, and run under a documented timeout.
  4. Failure stays local. A list or read error makes the affected skill (or catalog) unavailable and surfaces a typed error or event. It does not fail the session or turn.
  5. Works alongside skill_directories. The provider and directories can be used together, with documented precedence or name-collision rules.
  6. Lifecycle. The provider is released when the session closes or disconnects. Resuming a session either re-binds a provider or documents that the host must supply one again.
  7. Documented limits. Maximum catalog size, description length and SKILL.md size are documented so hosts can enforce the same bounds.
  8. Capability detection. Hosts can detect whether the connected CLI/runtime supports providers (for example a capability flag or version check), so they can fall back to skill_directories on older runtimes.
  9. Optional provenance. A descriptor can carry an origin label, so the UI and model can tell skills from different sources apart.

Why this belongs in the SDK

The CLI's JSON-RPC surface appears to already have provider-style skill callbacks (list/read). The main gap seems to be a stable, documented, public SDK API over that surface. Without one, consumers either write files to disk (with the problems above) or depend on unpublished protocol details that can break between releases.

Relevance to the MCP Skills extension

The MCP Skills extension (io.modelcontextprotocol/skills, SEP-2640, Final) standardizes how MCP servers serve Agent Skills. Its model matches the provider proposed here, which makes a provider API the natural way for SDK hosts to support MCP-served skills without writing files to disk:

MCP Skills extension Skill provider
skills/list returns metadata only: complete frontmatter plus a file manifest with SHA-256 digest and size, and no content list_skills() returns descriptors only
Content is read separately with resources/read, and reading does not itself activate a skill read_skill() is called lazily, only on activation
"The host routes the read through its skill-loading path, which verifies the content and applies any required user approval before loading it into model context" The read callback is the host's hook to check digest and size and apply approval before content reaches the model. Writing files to disk up front removes that hook.
Skill identity is the originating server identity plus the URI. Names are labels, not unique, and hosts must preserve server identity Descriptors need a stable identity or origin plus collision rules (semantics 5 and 9)
List results carry ttlMs and cacheScope Hosts need a way to refresh the catalog during a session (semantic 10)
SKILL.md may reference supporting files resolved against the skill's root directory The provider needs to serve supporting files, not only SKILL.md (semantic 11)

The extension notes that SDK and host support is still being implemented (see the client support matrix). A public provider API would let any Copilot SDK host bridge an MCP Skills server to a session with a thin adapter that maps skills/list to list and resources/read to read, and would make the SDK a natural entry in that matrix.

Additional semantics implied by the extension

  1. Catalog refresh. A host can tell the session that its catalog changed (or the runtime re-calls list after a host-supplied TTL), so updated or withdrawn skills don't stay visible for the rest of a long session.
  2. Supporting files. read can fetch files relative to a skill's root (for example read_skill_file(name, "references/checklist.md")), so multi-file skills work without writing anything to disk.

Acceptance criteria

  • Public skill_provider / skillProvider session option in the Python, TypeScript and .NET SDKs
  • Lazy read verified: no content is requested during listing
  • Per-session isolation and cleanup on session close
  • Errors are isolated to skills, not sessions
  • Capability detection for older runtimes
  • Documentation and a sample, for example a provider backed by an MCP Skills server or an in-memory dictionary
  • Supporting-file reads and catalog refresh, so an MCP Skills extension server can be bridged without writing files to disk

Related

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions