Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions docs/decisions.md
Original file line number Diff line number Diff line change
Expand Up @@ -332,3 +332,37 @@ Keeping optionality to a single feature flag responds to the CM position that op
- [Core-maintainer alignment document](https://docs.google.com/document/d/1llJ667kyIu5ZA_-A8U1AntWUxMW65iXaLT-3elfi3J4/edit) — reviewed with the CMs early July 2026.
- [Supporting-file digests thread](https://discord.com/channels/1358869848138059966/1524467339674910901) — design discussion behind the per-file `resources` manifest (Peter Alexander, Cliff Hall, Aditya, Peder), 2026-07-08 through 07-10; registry/CVE-style provenance ideas raised there were explicitly deferred beyond v1.
- [PR #108](https://github.com/modelcontextprotocol/experimental-ext-skills/pull/108) — companion threat-model document, in review.

---

### 2026-09-08: Stable spec page: caching attributes on `skills/list` and `skills/get`, and the `resources` capability dependency

**Status:** Proposed

**Context:** [`specification/stable/skills.mdx`](../specification/stable/skills.mdx) ([PR #138](https://github.com/modelcontextprotocol/ext-skills/pull/138)) renders SEP-2640 as a spec page written against base protocol revision `2026-07-28`. Review by @panyam against the traceability extraction maintained for the conformance suite ([conformance#330](https://github.com/modelcontextprotocol/conformance/pull/330)) found three places where the page diverged from, or went beyond, the SEP text without recording it:

1. `ListSkillsResult` was declared as `extends PaginatedResult`. In the base schema `PaginatedResult` carries only `nextCursor`; `ttlMs` and `cacheScope` live on `CacheableResult`, where both are required, and the base list results extend both. As written the page dropped the caching attributes the SEP says `skills/list` carries.
2. SEP-2640 says of `skills/get`: "whether the result should also carry the base protocol's caching attributes (`ttlMs` and `cacheScope`), as `resources/read` results do, is left open." The page's `GetSkillResult extends Result` closed that question in the negative without saying so.
3. The page added "a server declaring this extension MUST also declare the `resources` capability", which has no counterpart sentence in the SEP. A server conformant to the SEP text alone could read as non-conformant to the page.

The page also did not state its protocol-revision baseline, so dropping the SEP's "in protocol versions 2026-07-28 and later" conditional was not visibly justified.

**Decision:**

1. `ListSkillsResult extends PaginatedResult, CacheableResult`. `ttlMs` and `cacheScope` are required on `skills/list` results, as on `tools/list` and `resources/list`. The page states its `2026-07-28` baseline in a Protocol Revision section instead of carrying the SEP's per-version conditional.
2. `GetSkillResult extends CacheableResult`. `ttlMs` and `cacheScope` are required on `skills/get` results, as on `resources/read`. This resolves the question SEP-2640 left open.
3. The `resources`-capability requirement stays, worded as a consequence of the base Resources specification (a server that serves `resources/read` declares the `resources` capability) rather than as a free-standing rule. The conformance suite adds a check for it.

**Rationale:**

(1) is what the SEP intended by "carries the base protocol's list-caching attributes ... as defined for `tools/list` and `resources/list`"; the page named the wrong base type. Making the fields required rather than conditional follows from the page targeting `2026-07-28` or later, where `CacheableResult` makes them required on every list result.

(2) `skills/get` is a single-item fetch whose closest base analogue, `resources/read`, is already a `CacheableResult`. Giving the entry a `ttlMs` gives hosts a server-supplied hint for how long to treat a held entry as current before re-calling `skills/get`, and keeps every result type in the extension uniform with the base protocol. Neither field is an integrity property. Digest verification, the held-entry rules, and content-bound approval are unaffected: a host acting on a skill still verifies every read against the held entry regardless of `ttlMs`, and a changed `resources` set still revokes approval regardless of how the refreshed entry was obtained.

(3) A server cannot serve skill files without implementing `resources/read`, and the base specification already requires such a server to declare `resources`. Stating it on the page makes the dependency checkable by the conformance suite without adding an obligation a base-conformant server does not already have.

**References:**
- [PR #138 review comment](https://github.com/modelcontextprotocol/ext-skills/pull/138#issuecomment-5545405326) from @panyam raising these points.
- [conformance#330](https://github.com/modelcontextprotocol/conformance/pull/330), the SEP-2640 conformance suite.
- [SEP-2549](https://modelcontextprotocol.io/seps/2549-TTL-for-list-results), which defines `CacheableResult`.
- SEP-2640, "Retrieval via `skills/get`", final semantics bullet (the "left open" sentence this entry resolves).
33 changes: 24 additions & 9 deletions specification/stable/skills.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ Skills are useful for shipping the instructions for using a server together with

This extension is identified as: `io.modelcontextprotocol/skills`.

## Protocol Revision

This page specifies the extension against base protocol revision `2026-07-28` or later. Type names it references without defining (`Result`, `PaginatedRequest`, `PaginatedResult`, `CacheableResult`, `Resource`, `Cursor`) are those of the base schema at that revision.

## Capability Negotiation

Servers declare support for this extension in the `extensions` field of their capabilities:
Expand Down Expand Up @@ -55,7 +59,7 @@ An empty object indicates support for the extension with no optional features.
- A server that declares this extension **MUST** implement [`skills/list`](#listing-skills) and [`skills/get`](#getting-a-skill).
- A server that additionally declares `directoryRead: true` **MUST** implement [`resources/directory/read`](#reading-directories).
- Clients **MUST NOT** call `resources/directory/read` against a server that has not declared `directoryRead: true`.
- This extension depends only on the base protocol's Resources primitive. A server declaring it **MUST** also declare the `resources` capability and serve skill files through `resources/read`.
- This extension depends only on the base protocol's Resources primitive. Skill files are served through `resources/read`, and the base Resources specification requires any server that supports resources to declare the `resources` capability. A server declaring this extension therefore **MUST** also declare the `resources` capability.

Clients issue `skills/list` and `skills/get` only after observing the server's declaration. To a client that does not implement this extension, `skill://` resources are ordinary resources.

Expand Down Expand Up @@ -257,7 +261,7 @@ interface ListSkillsRequest extends PaginatedRequest {
### Response

```typescript
interface ListSkillsResult extends PaginatedResult {
interface ListSkillsResult extends PaginatedResult, CacheableResult {
skills: Skill[];
}
```
Expand Down Expand Up @@ -311,7 +315,9 @@ interface ListSkillsResult extends PaginatedResult {
},
"resources": "dynamic"
}
]
],
"ttlMs": 300000,
"cacheScope": "public"
}
}
```
Expand All @@ -322,7 +328,7 @@ Semantics:
- The result **MAY** be empty. A server whose skill catalog is large, generated on demand, or otherwise unenumerable **MAY** return an empty or partial listing.
- Hosts **MUST NOT** treat an empty or partial listing as proof that a server has no skills, and **MUST** support loading a skill given only its URI ([Getting a Skill](#getting-a-skill)).
- Pagination mirrors the base protocol's list methods: the request accepts an optional `cursor`, and when the result includes `nextCursor` the client passes it back to retrieve the next page. An entry is atomic, and a skill's `resources` set is never split across pages.
- `ListSkillsResult` also carries the base protocol's list-caching attributes, `ttlMs` and `cacheScope`, as defined for `tools/list` and `resources/list`, with the same semantics. They are a freshness hint and a cache-scope marker for the listing, not an integrity property. [Integrity and Verification](#integrity-and-verification) governs content regardless of how fresh a cached listing is.
- `ListSkillsResult` extends `CacheableResult`, so `ttlMs` and `cacheScope` are **REQUIRED**, as they are on `tools/list` and `resources/list`, and carry the same semantics. They are a freshness hint and a cache-scope marker for the listing, not an integrity property. [Integrity and Verification](#integrity-and-verification) governs content regardless of how fresh a cached listing is.
- The method serves entries for a server's skills regardless of URI scheme.

A `skills/list` entry is a complete manifest of the skill rather than a summary. After paging through the listing, a host has everything it needs to build its registry, present a skill for approval, bind the approval to content, and verify every file it later reads. A host does not need to call `skills/get` to complete a listed entry.
Expand Down Expand Up @@ -357,7 +363,7 @@ interface GetSkillRequest extends Request {
### Response

```typescript
interface GetSkillResult extends Result {
interface GetSkillResult extends CacheableResult {
skill: Skill;
}
```
Expand Down Expand Up @@ -407,7 +413,9 @@ interface GetSkillResult extends Result {
"size": 1472
}
]
}
},
"ttlMs": 300000,
"cacheScope": "public"
}
}
```
Expand All @@ -421,6 +429,7 @@ Semantics:
- If the URI does not identify a skill the server serves, the server **MUST** return error `-32602` (Invalid params), the same code `resources/read` uses for unknown resources.
- A server **MUST** answer for every skill it serves, whether or not that skill appears in its `skills/list` result. A skill absent from a partial listing is still retrievable by URI.
- The result is a point-in-time snapshot, as a listing entry is. A host re-calls the method to refresh one skill's digests without re-enumerating the catalog.
- `GetSkillResult` extends `CacheableResult`, so `ttlMs` and `cacheScope` are **REQUIRED**, as they are on `resources/read`, and carry the same semantics. `ttlMs` is the server's hint for how long a host may treat the entry as current before re-calling `skills/get`. Neither field is an integrity property.
- A skill whose content is generated dynamically carries `"resources": "dynamic"`, per [Resources](#resources), whether it is reached through `skills/list` or `skills/get`.
- The result carries no pagination cursor.

Expand Down Expand Up @@ -619,7 +628,9 @@ Consider a host connected to a server it labels `docs-server`, which declares th
}
]
}
]
],
"ttlMs": 300000,
"cacheScope": "public"
}
}
```
Expand Down Expand Up @@ -651,7 +662,9 @@ Later the model asks to load the skill. The host obtains user approval, binding
"mimeType": "text/markdown",
"text": "---\nname: pdf-processing\ndescription: Extract, fill, and assemble PDF documents\n---\n\n# PDF processing\n\nChoose the matching template from `templates/` ..."
}
]
],
"ttlMs": 300000,
"cacheScope": "public"
}
}
```
Expand Down Expand Up @@ -747,7 +760,9 @@ To make `credit-note.md` available, the host refreshes the entry:
"size": 1104
}
]
}
},
"ttlMs": 300000,
"cacheScope": "public"
}
}
```
Expand Down