Skip to content

Add draft SEP for Skills Extension#69

Open
pja-ant wants to merge 4 commits intomainfrom
sep-draft-skills-extension
Open

Add draft SEP for Skills Extension#69
pja-ant wants to merge 4 commits intomainfrom
sep-draft-skills-extension

Conversation

@pja-ant
Copy link
Contributor

@pja-ant pja-ant commented Mar 17, 2026

Pre-submission Extensions Track SEP defining the skill:// resource convention.

Summary

Skills are MCP Resources following the Agent Skills spec. Each file in a skill directory is exposed under skill://<skill-path>/<file-path>; SKILL.md is always explicit in the URI. The skill format itself (YAML frontmatter, naming, directory layout, progressive disclosure) is delegated entirely to agentskills.io — this SEP is a transport binding only.

Key design decisions

  • Final path segment MUST equal frontmatter name. <skill-path> may be nested (acme/billing/refunds) but the last segment is the skill name, mirroring agentskills.io's parent-directory rule. Prefix segments are the server's organizational choice. The name is recoverable from the URI alone.
  • Direct readability is the baseline. A skill:// URI is always valid for resources/read whether or not it appears in any index. Enumeration and templates are layered on top.
  • Enumeration via skill://index.json. Optional well-known resource whose format mirrors the Agent Skills discovery index — same $schema, same skills[] shape. url holds the full skill:// URI; digest is omitted. Servers with large/generated/unenumerable catalogs are not required to expose it.
  • Resource templates are MAY, user-facing. Wired to the completion API for interactive browsing, not injected into model context.
  • Zero protocol dependencies. No new methods, no schema changes, no dependency on scoped resources/list.

Security

Skill content MUST be treated as untrusted model input. Hosts MUST NOT honor local-execution mechanisms (hooks, pre/post scripts, shell-in-frontmatter) from MCP-served skills without explicit per-skill user opt-in — silently executing server-provided code is an RCE vector. Hosts SHOULD let users inspect skill content before loading.

Implementation guidelines

  • Hosts provide a read_resource(server, uri) tool for model-driven loading (signature illustrative; prefix-on-conflict or other disambiguation is fine).
  • Hosts SHOULD load skill frontmatter into model context and surface skills in UI for inspect/enable/disable.
  • Filesystem and MCP skills treated identically — same discovery surface, same loading tool, same relative-path resolution.
  • SDK wrappers: @server.skill(path) decorator, client.list_skills(), client.read_skill_uri().

Extension identifier: io.modelcontextprotocol/skills.

pja-ant added 2 commits March 17, 2026 15:34
Pre-submission Extensions Track SEP defining the skill:// resource
convention: skills as MCP resources following the Agent Skills spec,
discovered via scoped resources/list (SEP-2093). Includes implementation
guidelines for host-provided read_resource tools, virtual-filesystem
unification with local skills, and SDK convenience wrappers.
- skill-path may be arbitrarily nested and need not match the
  frontmatter name; the URI is a locator, identity lives in SKILL.md
- resources/list(uri="skill://") is SHOULD not MUST; response is
  SKILL.md entries only, supporting files excluded
- resource templates are MAY, framed as user-facing (completion API)
- direct URI readability is the baseline; hosts must support loading
  skills they have never seen listed
- SEP-2093 dependency is now conditional on whether the server
  supports enumeration
@pja-ant pja-ant requested a review from a team as a code owner March 17, 2026 19:12

The resource for the skill's required `SKILL.md` is therefore always addressable as `skill://<skill-path>/SKILL.md`, and the skill's root directory is the URI obtained by stripping the trailing `SKILL.md`.

The `<skill-path>` is a locator, not an identifier. It need not match the skill's `name` (which comes from frontmatter), and servers MAY organize skills hierarchically by domain, team, version, or any other axis. Two constraints follow:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit odd that the skill name doesn't have to match the skill path, given the skill URI is used to access the resource.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fair, originally they did match, but we wanted to support nested skills (a request from github), e.g. skill://foo/bar/code-review/SKILL.md and skill names cannot contain / so you're either forced to not have nested skills, or replace the / with :. You could perhaps use the last path segment as the skill name (code-review). wdyt @SamMorrowDrums

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Last path segment as name seems totally reasonable to me. You cannot assume all servers are going to be able to be to find skills in certain places if you cannot provide the completions.

That would rule out skill discovery via resource templates entirely. Which is a valid approach, but it doesn't feel complex enough of an idea to reject.

Why shouldn't the spec let somebody install a skill from a known repo this way for example? GH server cannot enumerate them all.

The main alternative we discussed still has this problem, which is resource links could be returned from tools providing skill search but still need paths.


- `mimeType` SHOULD be `text/markdown`.
- `name` SHOULD be set from the `name` field of the `SKILL.md` YAML frontmatter.
- `description` SHOULD be set from the `description` field of the `SKILL.md` YAML frontmatter.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Why SHOULD vs MUST? Is there a potential that you might have a description that doesn't match the frontmatter that a user could load that would be deceptively?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could be convinced. I think if we say MUST then we need to decide what to do when they don't match. Is that an error and you can't use the skill? If it is an error, it feels like a bit of an unnecessary footgun. If it's not an error, is the MUST accomplishing anything?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't we gain anything by enforcing a mimeType, and we add compatible with patterns like zipped skills if we don't. Clients will still validate skills. I think SHOULD serves the purpose. It goes for all resources that they should have the correct mimeType for what they actually are.

"inputSchema": {
"type": "object",
"properties": {
"server": { "type": "string", "description": "Name of the connected MCP server" },

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK, it's atypical for the client to expose the server names to the agent.

The way this is handled for tools, is server names are prefixed only if there is a conflict. Would it be better to follow this standard and only expose resource names, and prefix server name if a conflict occurs?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, this section is just guidelines and clients are free to expose resource reading in whatever way they find appropriate. The intent was to just guide people to consider that URIs could conflict, so you need some server disambiguation. I could make that clearer. Your suggestion is also fine.

Copy link
Member

@PederHP PederHP Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kurtisvg This used to be atypical, but I've noticed that with many clients prefixing server names to tools, the models will infer the name of the server quite often - even if it is not explicitly stated.

As for resource reading - they differ from tools in that that aliasing via name-spacing is awkward, so in many cases a client-side list resource tool will include the server name, as otherwise resolving a resource name conflict is impossible.

So I would agree if this was about tools, where server names are very rarely explicitly stated, but for resources it seems the pattern is to expose server names. At least in the agents I'm most familiar with.


### Why Resources Instead of a New Primitive?

The Interest Group's [decision log](decisions.md#2026-02-26-prioritize-skills-as-resources-with-client-helper-tools) records this as settled. Skills are files; Resources exist to expose files. Reusing Resources inherits URI addressability, `resources/read`, `resources/subscribe`, templates, and the existing client tooling for free. A new primitive would duplicate most of this and add [ecosystem complexity the community has explicitly pushed back on](https://github.com/modelcontextprotocol/experimental-ext-skills/issues/14).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My 2c is it feels like there's more complexity being added by the use of resources for everything vs and a new primitive. (I also think that 1 open issue is not necessarily "community pushback").

E.g. using resources as the distribution mechanism, we lose the ability to have resources that are used for other things being included in a skill. We also have to list every file in skill individually, before the skill is even activated (which is very verbose). There are also the conflicts with how resources are used in existing applications today.

In contrast, a new primitive is clear and unambiguous in it's intent, and could include non-file objects such as tools, resources, and prompts.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My 2c is it feels like there's more complexity being added by the use of resources for everything vs and a new primitive. (I also think that 1 open issue is not necessarily "community pushback").

I agree that 1 issue is sparse evidence on its own, but the sentiment behind it seems common.

E.g. using resources as the distribution mechanism, we lose the ability to have resources that are used for other things being included in a skill.

I'm not sure this follows? The skill:// URI scheme scopes skill content specifically. A server can still expose other resources under any other URI scheme (file://, db://, etc.), and skills can reference those as dependencies in their frontmatter or content. The skill is a resource and the things it orchestrates like tools, other resources, prompts are still separate and fully available.

There are also the conflicts with how resources are used in existing applications today.

Could you elaborate on which specific conflicts you're seeing? it seems like structured markdown context fits naturally within modeling other entities like databases, ui views, repos, etc. The skill:// URI scheme exists to scope skill content away from other resource usage.

In contrast, a new primitive is clear and unambiguous in it's intent, and could include non-file objects such as tools, resources, and prompts.

A skill referencing tools, resources, and prompts as dependencies seems like it would work the same way regardless of whether the skill itself is delivered as a resource or a new primitive. The SKILL.md frontmatter declares what the skill needs and the host mediates availability. The transport mechanism for the skill content doesn't seem like it would change this.

Also noting a couple of the MCP design principles that seem especially relevant here:

  • Composability over specificity: "MCP provides foundational primitives: resources, tools, prompts, and tasks. We don't add protocol features for use cases that can be constructed from these existing building blocks." Skills are structured context for language models, which seems like exactly what resources are designed to deliver.

  • Standardization over innovation: "MCP standardizes patterns that have already proven valuable. We look for conventions that work across multiple implementations and codify them, rather than inventing new paradigms." Multiple implementations have independently referenced skill:// resources, and it seems like skills are commonly used because they're just files (a.k.a. simple resources) and not an abstract concept.

I think the bar for introducing a new primitive should be demonstrating something that resources genuinely can't do based on evidence in real implementations. Part of this proposal allows for experimenting with resources to be more accessible in general, for example by suggesting common client tools to interact with them. It also allows clients to follow progressive disclosure patterns and prioritize resources using existing annotations (which follows the other unique properties of skills). If there are specific capabilities we think require a new primitive, would love to dig into those concretely.

The previous draft fully decoupled <skill-path> from the skill name,
which meant the name could not be read from the URI without fetching
and parsing SKILL.md.

Now: the final segment of <skill-path> MUST equal the frontmatter
name. Preceding segments remain an optional server-chosen prefix for
organizational hierarchy. This keeps the hierarchy capability while
making the skill name recoverable from the URI alone.

On top of that baseline, three discovery mechanisms are defined. A server MAY support any combination.

#### Enumeration via `resources/list`

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we have a initialize as well which is called on server initialize where the client would know what this Skills MCP server is about and what it contains?
Example, I might have a 3 Skills MCP servers included and which server brings which skills would be important without loading all the list of skills

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should tie this explicitly to connection initialization (I assume that is what you reference). Tools also leave it up to the client when to do this. Having primitives enumerated on server initialized isn't done for any other primitives.

This would also be in conflict with stateless MCP where there are no connections. Enumeration should be a host-design decision. (in practice it often happens after initializing the connection, but it shouldn't have to)


Including the server name disambiguates identical `skill://` URIs served by different connected servers. This tool is general-purpose — it reads any MCP resource — and benefits resource use cases beyond skills.

A typical flow: the host calls `resources/list(uri="skill://")` on each connected server at initialization and surfaces any returned skill entries in the model's context. The model calls `read_resource` with a concrete URI — one returned by enumeration, one handed to it by the user (who may have found it via a `skill://` resource template in the host UI), or one obtained out-of-band — when a skill is relevant to the task.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the host calls resources/list(uri="skill://") on each connected server at initialization and surfaces any returned skill entries in the model's context.

Isn't this causing a lot of bloat for the LLM that it would read all the skills even though its not required? Like for a question about git-workflows the LLM would only want to list git-workflows and not skills for acme workflow

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The results of the list call aren't put into context as-is, the frontmatter is. Most hosts would make it possible to enable/disable skills - similar to tools. And the frontmatter is required for a model to know if a skill is relevant to load, so if it is not in the frontmatter it cannot be accessed at all - regardless of relevance, unless it done in a non-skill like way (ie direct resource reads without progressive disclosure).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it. I am curious to know how would use this in practice when we want to wrap a stateless agent with MCP servers. Example: I have build a marketing agent which has access to Facebook skills and tools + Youtube Skills and tools. When a question comes in about facebook marketing, how would the agent know to only get facebook skills and tools into the context and not anything else without know which MCP server is for what without reading the tools description. The way that it works right now is that agents call the initialization here to know the description of the MCP server to know what this server does.
But I am curious if this can be solved differently. :) thank you for your insights


Hosts SHOULD expose a tool to the model that reads MCP resources by server and URI, enabling the model to load skill content on demand:

```json

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we include a standard response format to share what dependencies and scopes that might be required to run this skill?

{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "contents": [
      {
        "uri": "file:///project/src/main.py",
        "mimeType": "text/x-python",
        "dependencies": [
          {
            "name": "requests",
            "version": ">=2.0.0",
            "description": "Used to make HTTP requests to external APIs such as GitHub"
          }
        ],
        "scopes": [
          {
            "name": "github:repo:read",
            "description": "Allows reading repository metadata and contents from GitHub"
          }
        ],
        "text": "# Example: Fetch repository details from GitHub API\n\nimport requests\n\nrepo = \"octocat/Hello-World\"\nurl = f\"https://api.github.com/repos/{repo}\"\n\nresponse = requests.get(url)\nif response.status_code == 200:\n    data = response.json()\n    print(f\"Repo: {data['full_name']}\")\n    print(f\"Stars: {data['stargazers_count']}\")\nelse:\n    print(\"Failed to fetch repository data\")\n"
      }
    ]
  }
}

It might add new optional primitives to the MCP spec for resources. But it might be good to add them?


## Abstract

This SEP defines a convention for serving [Agent Skills](https://agentskills.io/) over MCP using the existing Resources primitive. A _skill_ is a directory of files (minimally a `SKILL.md`) that provides structured workflow instructions to an agent. This extension specifies that each file in a skill directory is exposed as an MCP resource under the `skill://` URI scheme. Skills are addressed by URI and may be read directly; enumeration via `resources/list` and discovery via resource templates are supported but not required, accommodating servers whose skill catalogs are large, generated, or otherwise unenumerable. The skill format itself — directory structure, YAML frontmatter, naming rules — is delegated entirely to the [Agent Skills specification](https://agentskills.io/specification); this SEP defines only the transport binding.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest changing directory to folder to match agentskills.io verbiage.


> This document is a pre-submission draft maintained by the [Skills Over MCP Interest Group](https://github.com/modelcontextprotocol/experimental-ext-skills). It has not yet been submitted to the main MCP repository. Discussion welcome via [GitHub Issues](https://github.com/modelcontextprotocol/experimental-ext-skills/issues) or [Discord #skills-over-mcp-ig](https://discord.com/channels/1358869848138059966/1464745826629976084).

## Abstract
Copy link
Member

@PederHP PederHP Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should mention that the skill format has design that facilitates progressive disclosure, whether this is implemented and if so, how, is up to the client host.

I think we should use client host over agent to match general MCP verbiage. I could be mistaken, but I don't think we use agent elsewhere to describe client host? (It might actually be a good idea, though - as client and client host are so often conflated).


### Hosts: Model-Driven Resource Loading

Hosts SHOULD expose a tool to the model that reads MCP resources by server and URI, enabling the model to load skill content on demand:
Copy link
Member

@PederHP PederHP Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to mention frontmatter loading if we begin to give guidance on implementation.

Something like

Hosts SHOULD load the frontmatter of all available and enabled tools into model context in an implementation-specific manner, so that the model has the server and URI information to be able call the exposed tool when a skill is relevant.

Hosts SHOULD make expose the available tools to the user so they can be inspected and enabled/disabled in a similar manner to tools.

@PederHP
Copy link
Member

PederHP commented Mar 22, 2026

I think a security guidance section for client hosts would be good. Skills are high risk in that they are context injection. Users should as a minimum be able to inspect skills from connected servers if the host loads them into context / makes them available to the model. In some cases HITL approval before even making the skills available is advisably.

Malicious skills are very common, and while connecting to an MCP server is always a high-trust action, I think we should make an effort to mitigate unsafe use of skills as that is currently happening a lot in the wild with npm based skill distribution.


Further constraints:

- A `SKILL.md` MUST NOT appear in an ancestor directory of another `SKILL.md` under the same `skill://` root. The skill directory is the boundary; skills do not nest inside other skills.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- A `SKILL.md` MUST NOT appear in an ancestor directory of another `SKILL.md` under the same `skill://` root. The skill directory is the boundary; skills do not nest inside other skills.
- A `SKILL.md` MUST NOT appear in any descendant directory of a skill under the same `skill://` root. The skill directory is the boundary; skills do not nest inside other skills.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I took the meaning, but still, this line felt less concise than it should be.


Skill content is instructional text delivered to a model, which makes it a prompt-injection surface. The Interest Group's position, recorded in [open-questions.md §10](open-questions.md#10-how-should-skills-handle-security-and-trust-boundaries), is:

- **Trust inherits from the server.** A user who connects a server has already extended their trust boundary to it; a malicious server can cause more harm via tools than via a skill document. Skills do not introduce a new trust tier.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- **Trust inherits from the server.** A user who connects a server has already extended their trust boundary to it; a malicious server can cause more harm via tools than via a skill document. Skills do not introduce a new trust tier.
- **Trust inherits from the server.** A user who connects a server has already extended their trust boundary to it; a malicious server can cause as much harm via tools as via a skill document. Skills as a class, whether served by MCP or any other agent affordance, have an inherent prompt injection risk. No additional risk from serving them as MCP resources has been identified.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The claim of tools posing more of a risk than skills seems hard to substantiate; skills literally tell the model how to behave.

IMO, it's best to not downplay the risk but to make it clear that Skills over MCP isn't adding any new risk to what already exists with skills.

Enumeration now uses a well-known skill://index.json resource whose
format mirrors the Agent Skills .well-known discovery index: same
$schema, same skills[] shape. Two deltas from the HTTP index: url
holds the full skill:// URI, and digest is omitted (transport handles
integrity over an authenticated connection). type MUST be "skill-md"
since archives don't apply when every file is individually
addressable.

This drops the SEP-2093 dependency entirely — the extension now has
zero protocol dependencies beyond resources/read.

Also includes review feedback from PR #69:
- Security: skill content MUST be treated as untrusted input; hosts
  MUST NOT honor local-execution mechanisms (hooks, scripts) without
  explicit opt-in; "more harm" softened to "as much harm"; user
  inspection SHOULD be supported
- Cite agentskills.io parent-directory rule for the final-segment
  constraint
- No-nesting constraint reworded per cliffhall suggestion
- Abstract mentions progressive disclosure as delegated concern
- Hosts section: SHOULD load frontmatter into context, SHOULD surface
  skills for user enable/disable
- read_resource signature marked illustrative
- "community has explicitly pushed back" softened
@pja-ant
Copy link
Contributor Author

pja-ant commented Mar 24, 2026

Updated to use a skill://index.json file instead of resources/list("skill://") to remove dependency on next spec version (and unaccepted SEP). Address many comments.

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.

7 participants