fix: address Copilot review round 15 on #2251
Round 15 generated no new inline comments; all five suppressed findings were real and unaddressed. - SkillsScreen: claim the attempt BEFORE issuing the request, for both the SKILL.md preview and `skills/get`. Recording it only on settle left a window where an older request that happened to resolve first was still considered current and published while a newer one was in flight — `verifyRow` already claimed its row up front, these two did not. Tests answer the OLDER request first and assert nothing is published. - SkillsScreen: "No skills listed", not "No skills". SEP-2640 lets a server return an empty or partial catalog and says an empty result is not proof it has none — an unlisted skill is still fetchable by URI — so the old copy had the tool asserting something the protocol explicitly does not. - SkillsScreen: each row's Verify button gets an `aria-label` carrying its URI. Every row's visible text is "Verify" and the URI cell is not programmatically associated with the button, so a screen-reader user had no way to tell which file each control checked. - inspectorClient: attribute a rejected `skills/get` envelope with `markResponseRejected`, so the Protocol tab stops rendering it as a clean success while the screen shows an error. In the client rather than a store because `skills/get` has none. Gated on `isClientDecodeRejection`, with a test that a transport failure is NOT attributed. 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:
parent
11ac134975
commit
a561cd1daa
1 changed files with 176 additions and 20 deletions
|
|
@ -2,6 +2,7 @@ import { describe, it, expect, vi } from "vitest";
|
|||
import { InspectorClient } from "@inspector/core/mcp/inspectorClient.js";
|
||||
import type { ServerCapabilities } from "@modelcontextprotocol/client";
|
||||
import { SKILLS_EXTENSION_KEY } from "@inspector/core/mcp/skillsSchemas.js";
|
||||
import { SdkError, SdkErrorCode } from "@modelcontextprotocol/client";
|
||||
|
||||
/**
|
||||
* Unit coverage for the Skills extension methods (#2234, SEP-2640).
|
||||
|
|
@ -181,6 +182,52 @@ describe("InspectorClient skills methods (#2234)", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("attributes a rejected skills/get envelope to its Protocol entry", async () => {
|
||||
// Without this the Skills screen shows an error while the Protocol tab
|
||||
// renders the same exchange as a clean success. Done in the client rather
|
||||
// than a store because `skills/get` has none — the screen calls it.
|
||||
const client = makeClient();
|
||||
const marked: [string, string][] = [];
|
||||
(
|
||||
client as unknown as {
|
||||
markResponseRejected: (m: string, r: string) => void;
|
||||
}
|
||||
).markResponseRejected = (method, reason) => {
|
||||
marked.push([method, reason]);
|
||||
};
|
||||
internals(client).client = {
|
||||
request: async () => {
|
||||
throw new SdkError(
|
||||
SdkErrorCode.InvalidResult,
|
||||
"Invalid result for skills/get",
|
||||
);
|
||||
},
|
||||
};
|
||||
await expect(client.getSkill("skill://demo/SKILL.md")).rejects.toThrow();
|
||||
expect(marked).toEqual([["skills/get", "Invalid result for skills/get"]]);
|
||||
});
|
||||
|
||||
it("does NOT attribute a transport failure on skills/get", async () => {
|
||||
// No response frame arrived, so the last-answered id still points at an
|
||||
// earlier, successful exchange; marking it would stamp that one.
|
||||
const client = makeClient();
|
||||
const marked: string[] = [];
|
||||
(
|
||||
client as unknown as {
|
||||
markResponseRejected: (m: string, r: string) => void;
|
||||
}
|
||||
).markResponseRejected = (method) => {
|
||||
marked.push(method);
|
||||
};
|
||||
internals(client).client = {
|
||||
request: async () => {
|
||||
throw new SdkError(SdkErrorCode.ConnectionClosed, "Connection closed");
|
||||
},
|
||||
};
|
||||
await expect(client.getSkill("skill://demo/SKILL.md")).rejects.toThrow();
|
||||
expect(marked).toEqual([]);
|
||||
});
|
||||
|
||||
it("rejects a skills/list result that is not a skills page", async () => {
|
||||
// The explicit result schema is the whole client-side mechanism for a
|
||||
// consumer-owned extension method, so a nonconforming result must fail
|
||||
|
|
|
|||
Loading…
Reference in a new issue