fix: address Copilot review round 5 on #2251

Two of these reverse round 4, which asked for the opposite. Where the
readings conflict I took the permissive one: a wrong tightening rejects
conforming servers, a wrong loosening only fails to report something.

- skills.ts: stop requiring the `skill:` scheme. SEP-2640 says a server
  SHOULD use `skill://` and explicitly allows a domain-native scheme, so
  round 4's requirement handed a conforming `github://…` skill a false
  `malformed-uri` and skipped its name and root checks. Containment is
  scheme-independent — it compares against THIS entry's own root — so the
  traversal fix that requirement came packaged with is unaffected.
- skills.ts: the 512-entry and 16 MiB limits are warnings. Both are
  SHOULD NOTs for a server and MAYs for a host, so calling them errors
  contradicted the "error = a MUST was broken" contract round 4
  established and told authors a permitted skill was invalid.
- skills.ts: `skillEntriesMatch` compares canonically — keys sorted,
  manifest sorted by URI. `JSON.stringify` treated key order and manifest
  order as differences, though neither carries meaning.
- SkillsScreen: a differing `skills/get` is "a different snapshot"
  (yellow), not a disagreement (red). The SEP defines it as a fresh
  point-in-time read, so a skill that changed since the listing
  legitimately differs.
- useServerCommands: route `onGetSkill` through the command-scoped OAuth
  recovery like every other server command. An expired authorization
  rendered an error and stopped there — no reauthorization, no retry.
- test-servers/skills.ts: stamp the modern base result envelope
  (`resultType` / `ttlMs` / `cacheScope`) on every skills result. The SDK
  stamps it only for methods in its own codec, and `skills/*` are
  consumer-owned, so a 2026-era connection was receiving results without
  it. Unconditional rather than per era: the modern leg builds a server
  per request, so there is no era to branch on at registration time, and
  on legacy they are three members no codec inspects.
- composable-test-server: remove the `skills.directoryRead` option. It was
  publicly settable and advertised a method nothing serves, producing the
  exact false capability the fixture exists to help catch. `skills` is now
  a plain boolean; the flag returns in phase 3 (#2248) with its handler.

Still not changed: whether a modern `skills/list` RESULT SCHEMA must
require that envelope. Stamping it server-side is free; requiring it
client-side would reject servers on a reading nobody has checked against
the normative text. #2248 owns settling it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018kfrH3uRNUM4JAk1reKtyY
Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
cliffhall 2026-09-04 23:11:46 -04:00
commit 754ce5bd95

View file

@ -60,13 +60,21 @@ as a missing capability rather than an error.
## Skills (SEP-2640)
`skills-http.json` advertises the `io.modelcontextprotocol/skills` extension
and serves four skills over two `skills/list` pages. It declares the extension
**bare** — `directoryRead` stays off until the fixture actually serves
`resources/directory/read` (phase 3, [#2248](https://github.com/modelcontextprotocol/inspector/issues/2248)), so
Connection Info never reports a sub-option this server would answer `-32601`
for. Set `"skills": { "directoryRead": true }` in a config to exercise the
advertised-flag path.
`skills-http.json` sets `"skills": true` and serves four skills over two
`skills/list` pages. The extension is advertised **bare**: there is deliberately
no `directoryRead` option to turn on, because nothing here serves
`resources/directory/read` and a config that advertised it would produce exactly
the false capability this fixture helps catch — Connection Info reporting a
sub-option supported while the method answers `-32601`. Both come back in
phase 3 ([#2248](https://github.com/modelcontextprotocol/inspector/issues/2248)).
Every result carries the modern base envelope (`resultType` / `ttlMs` /
`cacheScope`). `skills/*` are consumer-owned, so the SDK stamps nothing for
them; without it a 2026-era connection would receive a result missing the
envelope. It is stamped unconditionally rather than per era — the modern leg
builds a fresh server per request, so there is no era to branch on when the
handlers are registered, and on the legacy leg they are three extra members no
codec inspects.
It works on **either era**: `skills/list`, `skills/get` and
`resources/directory/read` are consumer-owned extension methods that neither
era codec defines, so the SDK's era gate skips them entirely — which is why
@ -82,11 +90,11 @@ the Skills tab runs are untestable without them:
| `dynamic-report` | `resources: "dynamic"` — a generated file set, so integrity cannot be verified at all and the tab says so rather than staying silent. |
| `wrong-folder` | A URI path segment (`wrong-folder`) that disagrees with `frontmatter.name` (`right-name`), the one structural invariant SEP-2640 states outright. |
Connection Info shows the extension and its `directoryRead` sub-flag; the
Inspector surfaces that flag but does not call `resources/directory/read` yet.
The wire schema for that result is deliberately absent from
`core/mcp/skillsSchemas.ts` too — phase 3 adds it against the normative text
rather than shipping a guess nothing exercises.
Connection Info's **Skills Extension Options** section shows the `directoryRead`
sub-flag — against this fixture, a red ✗. The Inspector surfaces the flag but
does not call `resources/directory/read` yet, and the wire schema for that
result is deliberately absent from `core/mcp/skillsSchemas.ts` too: phase 3 adds
it against the normative text rather than shipping a guess nothing exercises.
## Cancelling a call