Add Inspector support for the Skills extension, SEP-2640 (PR #2640) — servers serving Agent Skills (a SKILL.md plus its supporting files) over MCP. Design history and experimental findings remain in the Skills Over MCP WG incubation repo.
Status: Accepted (Extensions Track; extension identifier io.modelcontextprotocol/skills). Filed against the draft, now unblocked — the normative text is settled and the wire shapes below are the accepted ones. The Inspector is where server authors validate an implementation, and there is currently no way to exercise a skills server other than hand-rolled JSON-RPC; the same argument that carried MCP Apps (#1633) and Tasks (#1631).
What the extension is
Server-declared in the initialize response:
"capabilities": {
"extensions": {
"io.modelcontextprotocol/skills": { "directoryRead": true }
}
}
| Method |
Required? |
Purpose |
skills/list |
✅ |
Enumerate skills; cursor / nextCursor pagination |
skills/get |
✅ |
One skill entry by uri |
resources/directory/read |
Gated on directoryRead: true |
Direct (non-recursive) children of a directory resource; mimeType: "inode/directory" marks a directory |
A skill entry:
{
"uri": "skill://<skill-path>/SKILL.md",
"frontmatter": { "name": "skill-name", "description": "…" },
"resources": [{ "uri": "skill://<skill-path>/ref.md", "digest": "sha256:<64 hex>", "size": 12345 }]
}
frontmatter is the verbatim YAML frontmatter as JSON. resources is either the full file manifest or the literal string "dynamic" (generated content, no integrity verification possible). Skill files are fetched on demand through ordinary resources/read, not pre-fetched, and relative references resolve against the skill root.
Constraints worth checking against: the final <skill-path> segment — the segment before /SKILL.md, not the filename — must equal frontmatter.name, so the name is recoverable from the URI alone; the entry's frontmatter must match the fetched SKILL.md's frontmatter field-by-field; digests are sha256: + 64 lowercase hex; per-skill limits are 512 resource entries and 16 MiB total.
No SDK change is needed — confirmed against @modelcontextprotocol/client 2.0.0
Extensions are meant to be usable without SDK work, and SDK v2 encodes that explicitly. Verified by reading the installed client bundle:
- The era gate skips non-spec methods.
Protocol._assertOutboundRequestInEra (client/dist/src-D_zzAWoS.mjs:6021-6033) fires only when isSpecRequestMethod(method) is true, and that predicate (:4056) is "does either era codec define it". skills/list, skills/get and resources/directory/read are in neither codec, so they are era-blind — the SDK's own comment says so: "Methods outside the spec universe are consumer-owned extension methods and stay era-blind." They go out unchanged on both 2025- and 2026-era connections.
- No capability assertion blocks them.
Client.assertCapabilityForMethod (client/dist/index.mjs:3440) is a switch over spec methods that falls through to a no-op for anything else, so the SDK will not refuse to send a method it has never heard of.
- The only requirement is an explicit result schema.
Protocol.request (:5978) throws TypeError: '<method>' is not a spec method; pass a result schema as the second argument to request() when one is omitted. So the entire client-side mechanism is client.request({ method: "skills/list", params: {} }, SkillsListResultSchema, this.getRequestOptions()).
Two consequences for the plan below:
- ⚠️ Do not reach for
rawWireRequest. That escape hatch (core/mcp/inspectorClient.ts:2786) exists for the opposite problem — modern tasks/*, which are spec method names the 2026 codec deleted, so the era gate kills them before they reach the transport. Skills has no such collision, so ordinary client.request is correct and this is materially simpler than what Tasks needed. Using the raw path anyway would bypass the SDK's response correlation for no reason.
- The test-server side is unblocked too.
Protocol.setRequestHandler (:6207) rejects a non-spec method only when schemas are omitted; passing explicit request/result schemas lets @modelcontextprotocol/server serve skills/list and skills/get directly, so the fixtures below need nothing from upstream either.
So this issue depends on no upstream release, and an eventual @modelcontextprotocol/ext-skills (or SDK convenience wrappers — the SEP's Implementation Guidelines recommend list_skills / get_skill / read_skill_uri / read_directory, but they are non-normative) would be a later simplification, not a prerequisite.
Why this is more than a viewer
The SEP puts real obligations on the host, and every one of them is a conformance check the Inspector is well placed to run and report — this is the same angle as --strict tool-schema linting (clients/cli/src/handlers/schema-lint-report.ts) rather than a passive list view:
- Verify each fetched file against its digest and treat a mismatch as a failure. An inspector that shows a digest mismatch loudly is the whole value proposition.
- Flag
resources: "dynamic" — it means integrity cannot be verified at all. That belongs on screen as a warning, not buried.
- Check
basename(uri) === frontmatter.name, the one structural invariant the spec states outright.
- Report a skill that exceeds 512 entries or 16 MiB, since hosts must support up to those limits and servers must not exceed them.
- Content-bound approval — approval is bound to the
resources manifest observed at approval time and revoked when that set changes. Worth surfacing as a visible diff, since it is the part server authors are most likely to get wrong.
Integration points
Grounded in what is already here; nothing below needs a new dependency.
No SDK package exists. @modelcontextprotocol/ext-skills is a 404 on npm and the incubation repo carries no reference implementation, so the schemas are hand-rolled with Zod (already a root dependency) — the same shape as core/mcp/modernTaskSchemas.ts. Per Dependency placement this adds nothing to any manifest.
⚠️ This is a server-declared extension, so it does not belong in ADVERTISABLE_EXTENSIONS. That registry in core/mcp/extensions.ts is the catalog of extensions the Inspector advertises and the user toggles in Server Settings. Skills is read off the server's capabilities instead — the existing precedent is core/mcp/appElicitation.ts, which reads capabilities?.extensions?.[UI_EXTENSION_KEY] from the server side. Getting this backwards would put a meaningless toggle in Server Settings.
| Area |
Work |
core/mcp/ |
skills.ts (detection off server capabilities, digest verification, the conformance checks above) + skillsSchemas.ts (Zod). SKILLS_EXTENSION_KEY = "io.modelcontextprotocol/skills" alongside the existing key constants in extensions.ts. |
core/mcp/state/ |
managedSkillsState.ts following managedPromptsState.ts / managedListState.ts — cursor pagination is in the SEP, so it fits the existing paged-list machinery. |
core/react/ |
useManagedSkills.ts + usePagedSkills.ts, built on useStoreSnapshot per the store-subscription rules in AGENTS.md (never useState + a subscribing effect). |
clients/web |
A Skills tab: add to INSPECTOR_TAB_IDS in src/utils/inspectorTabs.ts, SkillsScreen under src/components/screens/, modeled on AppsScreen. Shows frontmatter, the resource manifest with digest + size, verification status per file, and renders a fetched SKILL.md. |
clients/cli |
skills/list / skills/get through handlers/method-types.ts + handlers/run-method.ts; a --verify mode reporting digest and conformance results is the natural CLI equivalent of --app-info. |
clients/tui |
A Skills pane, once the web shape settles. |
test-servers/ |
A composable fixture + configs/skills-http.json serving several skills — deliberately including one with resources: "dynamic", one with a deliberate digest mismatch, and one violating the name/path-segment rule, so every check above is exercisable. Without the bad cases the verification code is untestable. |
| Connection Info |
Show skills support and the directoryRead sub-flag, same as the existing extension rows. |
Suggested phasing
Splittable into sub-issues when picked up; #1633 is the precedent for phasing extension work.
- Detect + list — capability detection, Zod schemas,
skills/list with pagination, a read-only web tab. Cheap, and enough for a server author to see their skills at all.
- Fetch + verify —
skills/get, resources/read of skill files, digest verification, the conformance checks, and the test-server fixtures that exercise the failure paths.
- Reach — CLI methods, TUI pane,
resources/directory/read when the server declares directoryRead.
Risk
The spec-churn risk that motivated this section is largely retired: SEP-2640 is Accepted, so method names and the entry shape are settled. Keep the wire types isolated in core/mcp/skillsSchemas.ts anyway — the SEP leaves one point open (whether a skills/get result should also carry the SEP-2549 caching attributes ttlMs / cacheScope, as resources/read results do), and the skill format itself is delegated wholesale to the Agent Skills specification, which versions independently and whose future revisions apply automatically.
The remaining risk is scope, not churn: the SEP places heavy obligations on a host — activation, per-skill consent, content-bound approval, the window during which a host is "acting on" a skill. The Inspector is an inspector, not a host, and the SEP is explicit that a resources/read of a SKILL.md is not a load and confers no standing. Surface and verify; do not implement the activation and approval machinery.
Acceptance
- The Inspector detects
io.modelcontextprotocol/skills on a connecting server and surfaces it in Connection Info, including directoryRead.
skills/list is paginated through and rendered in a web Skills tab; skills/get retrieves a single entry.
- Skill files are fetched via
resources/read on demand and verified against their digests, with a mismatch reported as a visible failure rather than swallowed.
resources: "dynamic", a name/path-segment mismatch, and a limits violation each produce a clear, distinguishable warning.
- Test-server fixtures cover the clean case and the dynamic, digest-mismatch and bad-name cases.
- Wire types live in one module so a spec change is a single-file edit.
- Per-file coverage clears the ≥90 gate on all four dimensions.
Add Inspector support for the Skills extension, SEP-2640 (PR #2640) — servers serving Agent Skills (a
SKILL.mdplus its supporting files) over MCP. Design history and experimental findings remain in the Skills Over MCP WG incubation repo.Status: Accepted (Extensions Track; extension identifier
io.modelcontextprotocol/skills). Filed against the draft, now unblocked — the normative text is settled and the wire shapes below are the accepted ones. The Inspector is where server authors validate an implementation, and there is currently no way to exercise a skills server other than hand-rolled JSON-RPC; the same argument that carried MCP Apps (#1633) and Tasks (#1631).What the extension is
Server-declared in the
initializeresponse:skills/listcursor/nextCursorpaginationskills/geturiresources/directory/readdirectoryRead: truemimeType: "inode/directory"marks a directoryA skill entry:
{ "uri": "skill://<skill-path>/SKILL.md", "frontmatter": { "name": "skill-name", "description": "…" }, "resources": [{ "uri": "skill://<skill-path>/ref.md", "digest": "sha256:<64 hex>", "size": 12345 }] }frontmatteris the verbatim YAML frontmatter as JSON.resourcesis either the full file manifest or the literal string"dynamic"(generated content, no integrity verification possible). Skill files are fetched on demand through ordinaryresources/read, not pre-fetched, and relative references resolve against the skill root.Constraints worth checking against: the final
<skill-path>segment — the segment before/SKILL.md, not the filename — must equalfrontmatter.name, so the name is recoverable from the URI alone; the entry'sfrontmattermust match the fetchedSKILL.md's frontmatter field-by-field; digests aresha256:+ 64 lowercase hex; per-skill limits are 512 resource entries and 16 MiB total.No SDK change is needed — confirmed against
@modelcontextprotocol/client2.0.0Extensions are meant to be usable without SDK work, and SDK v2 encodes that explicitly. Verified by reading the installed client bundle:
Protocol._assertOutboundRequestInEra(client/dist/src-D_zzAWoS.mjs:6021-6033) fires only whenisSpecRequestMethod(method)is true, and that predicate (:4056) is "does either era codec define it".skills/list,skills/getandresources/directory/readare in neither codec, so they are era-blind — the SDK's own comment says so: "Methods outside the spec universe are consumer-owned extension methods and stay era-blind." They go out unchanged on both 2025- and 2026-era connections.Client.assertCapabilityForMethod(client/dist/index.mjs:3440) is aswitchover spec methods that falls through to a no-op for anything else, so the SDK will not refuse to send a method it has never heard of.Protocol.request(:5978) throwsTypeError: '<method>' is not a spec method; pass a result schema as the second argument to request()when one is omitted. So the entire client-side mechanism isclient.request({ method: "skills/list", params: {} }, SkillsListResultSchema, this.getRequestOptions()).Two consequences for the plan below:
rawWireRequest. That escape hatch (core/mcp/inspectorClient.ts:2786) exists for the opposite problem — moderntasks/*, which are spec method names the 2026 codec deleted, so the era gate kills them before they reach the transport. Skills has no such collision, so ordinaryclient.requestis correct and this is materially simpler than what Tasks needed. Using the raw path anyway would bypass the SDK's response correlation for no reason.Protocol.setRequestHandler(:6207) rejects a non-spec method only when schemas are omitted; passing explicit request/result schemas lets@modelcontextprotocol/serverserveskills/listandskills/getdirectly, so the fixtures below need nothing from upstream either.So this issue depends on no upstream release, and an eventual
@modelcontextprotocol/ext-skills(or SDK convenience wrappers — the SEP's Implementation Guidelines recommendlist_skills/get_skill/read_skill_uri/read_directory, but they are non-normative) would be a later simplification, not a prerequisite.Why this is more than a viewer
The SEP puts real obligations on the host, and every one of them is a conformance check the Inspector is well placed to run and report — this is the same angle as
--stricttool-schema linting (clients/cli/src/handlers/schema-lint-report.ts) rather than a passive list view:resources: "dynamic"— it means integrity cannot be verified at all. That belongs on screen as a warning, not buried.basename(uri) === frontmatter.name, the one structural invariant the spec states outright.resourcesmanifest observed at approval time and revoked when that set changes. Worth surfacing as a visible diff, since it is the part server authors are most likely to get wrong.Integration points
Grounded in what is already here; nothing below needs a new dependency.
No SDK package exists.
@modelcontextprotocol/ext-skillsis a 404 on npm and the incubation repo carries no reference implementation, so the schemas are hand-rolled with Zod (already a root dependency) — the same shape ascore/mcp/modernTaskSchemas.ts. Per Dependency placement this adds nothing to any manifest.ADVERTISABLE_EXTENSIONS. That registry incore/mcp/extensions.tsis the catalog of extensions the Inspector advertises and the user toggles in Server Settings. Skills is read off the server's capabilities instead — the existing precedent iscore/mcp/appElicitation.ts, which readscapabilities?.extensions?.[UI_EXTENSION_KEY]from the server side. Getting this backwards would put a meaningless toggle in Server Settings.core/mcp/skills.ts(detection off server capabilities, digest verification, the conformance checks above) +skillsSchemas.ts(Zod).SKILLS_EXTENSION_KEY = "io.modelcontextprotocol/skills"alongside the existing key constants inextensions.ts.core/mcp/state/managedSkillsState.tsfollowingmanagedPromptsState.ts/managedListState.ts— cursor pagination is in the SEP, so it fits the existing paged-list machinery.core/react/useManagedSkills.ts+usePagedSkills.ts, built onuseStoreSnapshotper the store-subscription rules inAGENTS.md(neveruseState+ a subscribing effect).clients/webSkillstab: add toINSPECTOR_TAB_IDSinsrc/utils/inspectorTabs.ts,SkillsScreenundersrc/components/screens/, modeled onAppsScreen. Shows frontmatter, the resource manifest with digest + size, verification status per file, and renders a fetchedSKILL.md.clients/cliskills/list/skills/getthroughhandlers/method-types.ts+handlers/run-method.ts; a--verifymode reporting digest and conformance results is the natural CLI equivalent of--app-info.clients/tuitest-servers/configs/skills-http.jsonserving several skills — deliberately including one withresources: "dynamic", one with a deliberate digest mismatch, and one violating thename/path-segment rule, so every check above is exercisable. Without the bad cases the verification code is untestable.directoryReadsub-flag, same as the existing extension rows.Suggested phasing
Splittable into sub-issues when picked up; #1633 is the precedent for phasing extension work.
skills/listwith pagination, a read-only web tab. Cheap, and enough for a server author to see their skills at all.skills/get,resources/readof skill files, digest verification, the conformance checks, and the test-server fixtures that exercise the failure paths.resources/directory/readwhen the server declaresdirectoryRead.Risk
The spec-churn risk that motivated this section is largely retired: SEP-2640 is Accepted, so method names and the entry shape are settled. Keep the wire types isolated in
core/mcp/skillsSchemas.tsanyway — the SEP leaves one point open (whether askills/getresult should also carry the SEP-2549 caching attributesttlMs/cacheScope, asresources/readresults do), and the skill format itself is delegated wholesale to the Agent Skills specification, which versions independently and whose future revisions apply automatically.The remaining risk is scope, not churn: the SEP places heavy obligations on a host — activation, per-skill consent, content-bound approval, the window during which a host is "acting on" a skill. The Inspector is an inspector, not a host, and the SEP is explicit that a
resources/readof aSKILL.mdis not a load and confers no standing. Surface and verify; do not implement the activation and approval machinery.Acceptance
io.modelcontextprotocol/skillson a connecting server and surfaces it in Connection Info, includingdirectoryRead.skills/listis paginated through and rendered in a web Skills tab;skills/getretrieves a single entry.resources/readon demand and verified against their digests, with a mismatch reported as a visible failure rather than swallowed.resources: "dynamic", aname/path-segment mismatch, and a limits violation each produce a clear, distinguishable warning.