diff --git a/docs/decisions.md b/docs/decisions.md index 1ab54a0..831c4aa 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -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). diff --git a/specification/stable/skills.mdx b/specification/stable/skills.mdx index e632647..e65b881 100644 --- a/specification/stable/skills.mdx +++ b/specification/stable/skills.mdx @@ -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" } } ```