skills.mdx: CacheableResult on list/get, state 2026-07-28 baseline, record decisions (#139)

Claude-Session: https://claude.ai/code/session_01Sure6Sknngow87Q6TkzTfL

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Peter Alexander 2026-09-10 18:14:01 +01:00 • committed by GitHub
commit d866efdba2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 58 additions and 9 deletions

View file

@ -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).

View file

@ -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:
@ -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.
@ -257,7 +261,7 @@ interface ListSkillsRequest extends PaginatedRequest {
### Response
```typescript
interface ListSkillsResult extends PaginatedResult {
interface ListSkillsResult extends PaginatedResult, CacheableResult {
skills: Skill[];
}
```
@ -311,7 +315,9 @@ interface ListSkillsResult extends PaginatedResult {
},
"resources": "dynamic"
}
]
],
"ttlMs": 300000,
"cacheScope": "public"
}
}
```
@ -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.
@ -357,7 +363,7 @@ interface GetSkillRequest extends Request {
### Response
```typescript
interface GetSkillResult extends Result {
interface GetSkillResult extends CacheableResult {
skill: Skill;
}
```
@ -407,7 +413,9 @@ interface GetSkillResult extends Result {
"size": 1472
}
]
}
},
"ttlMs": 300000,
"cacheScope": "public"
}
}
```
@ -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.
@ -619,7 +628,9 @@ Consider a host connected to a server it labels `docs-server`, which declares th
}
]
}
]
],
"ttlMs": 300000,
"cacheScope": "public"
}
}
```
@ -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"
}
}
```
@ -747,7 +760,9 @@ To make `credit-note.md` available, the host refreshes the entry:
"size": 1104
}
]
}
},
"ttlMs": 300000,
"cacheScope": "public"
}
}
```