fix: address Copilot review round 4 on #2251

- SkillsScreen: actually call `skills/get`. The client method and its
  tests existed but no production caller invoked it, so #2234's acceptance
  criterion ("skills/get retrieves a single entry") was unmet and a server
  author's required handler could not be exercised. The detail pane now
  fetches the selected URI on demand and reports whether the fetched entry
  AGREES with the one skills/list advertised — both describe the same
  skill, so a disagreement is a server bug only a side-by-side fetch shows.
- core/mcp/sha256.ts: a dependency-free SHA-256, used when `crypto.subtle`
  is absent. `SubtleCrypto` needs a secure context, and this app is
  documented as servable over plain HTTP on a LAN IP — where every skill
  verification threw and the UI reported a read failure for files it had
  fetched fine. Checked against the FIPS 180-4 vectors and differentially
  against WebCrypto.
- skills.ts: require the `skill:` scheme in `normalizeSkillUri`. Checking
  only that a URI was hierarchical let `https://demo/SKILL.md` pass the
  name and root checks — a manifest pointing anywhere on the web, reported
  as conforming.
- skills.ts: `missing-digest` and `missing-size` are errors. Both are
  required fields, and an omitted `size` is what lets a server slip past
  the 16 MiB pre-fetch limit while the UI reports zero errors. `warning`
  is now reserved for what is legal yet unverifiable — `"dynamic"`.
- SkillsScreen: key the "Verify all" batch guard to the manifest. A global
  flag left a newly selected skill's button disabled until the previous
  skill's reads settled — forever, if one hung.

Not changed, deliberately: whether a modern-era `skills/list` result MUST
carry the SEP-2549 caching attributes. #2234's analysis records it as open
and the review asserts the opposite; neither reading was checked against
the normative text. Leaving the schema permissive accepts a server that
omits them, while tightening on a wrong reading would reject conforming
responses — the more expensive direction. `skillsSchemas.ts` states this
and #2248 settles 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 22:38:03 -04:00
commit 1cb05ca573

View file

@ -35,6 +35,7 @@ import type {
import type { GetPromptState } from "../components/screens/PromptsScreen/PromptsScreen";
import type { ReadResourceState } from "../components/screens/ResourcesScreen/ResourcesScreen";
import type { SkillFileContents } from "../utils/skillFileBytes";
import type { SkillEntry } from "@inspector/core/mcp/skillsSchemas.js";
import { UrlElicitationErrorToastMessage } from "../components/elements/Toasts/UrlElicitationErrorToastMessage";
import {
errorCodeOf,
@ -219,6 +220,8 @@ export interface ServerCommands {
* the digest is taken over.
*/
onReadSkillFile: (uri: string) => Promise<SkillFileContents>;
/** Re-fetch one skill entry through `skills/get` (SEP-2640). */
onGetSkill: (uri: string) => Promise<SkillEntry>;
onSubscribeResource: (uri: string) => void;
onUnsubscribeResource: (uri: string) => void;
onCompleteArgument: (
@ -973,6 +976,17 @@ export function useServerCommands({
[onReadResourceContents],
);
// `skills/get` is the extension's second required method, and the Skills tab
// calls it on demand so a server author can see their own handler answer —
// and see whether it agrees with what their `skills/list` advertised.
const onGetSkill = useCallback(
async (uri: string): Promise<SkillEntry> => {
if (!inspectorClient) throw new Error("Client is not connected");
return inspectorClient.getSkill(uri);
},
[inspectorClient],
);
const onRefreshSkills = useCallback(() => {
runCommandInBackground(
() => refreshSkills(),
@ -997,6 +1011,7 @@ export function useServerCommands({
onReadResource,
onReadResourceContents,
onReadSkillFile,
onGetSkill,
onSubscribeResource,
onUnsubscribeResource,
onCompleteArgument,