fix: count severities correctly, gate remote-ref on $id, order --strict checks

Copilot review round 5.

- The sidebar tooltip applied `hasError` to the TOTAL, so a tool with one
  error and three warnings announced as "4 schema portability errors" —
  a defect the previous round introduced while adding that wording, and
  worse than the vague text it replaced. Both the tooltip and the
  accessible label now carry the breakdown, formatted by a new shared
  `summarizeToolFindings` so the fourth place to phrase a severity count
  cannot drift from the other three.

- Suppress `remote-ref` when the document declares an `$id` anywhere.
  `$id` establishes a base URI, so with a root `$id` of
  `https://example.com/root` the ref `https://example.com/root#/$defs/x`
  resolves to THIS document and needs no fetch — the finding and its
  "inline the referenced schema" remediation would both be wrong.
  Classifying correctly needs full RFC 3986 resolution against nested
  embedded resources, which this module does not do, so it declines
  rather than guessing. A missed finding is the acceptable direction.

- Run the `--strict` validations before every short-circuit return.
  `servers/list`, `servers/show`, `--list-stored-auth` and
  `--print-handoff` all return from `parseArgs` before any connect, so
  the checks further down let `--strict --method servers/list` succeed
  while silently ignoring a tools/list-only flag.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UL9retfvAXRgvi6EWk4SY4
Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
cliffhall 2026-08-25 02:54:21 -04:00
commit 68c4abf37a
2 changed files with 233 additions and 25 deletions

View file

@ -86,7 +86,28 @@ describe("ToolListItem", () => {
/>,
);
expect(
screen.getByLabelText("Schema portability errors"),
screen.getByLabelText("Schema portability: 1 error"),
).toBeInTheDocument();
});
it("breaks a mixed tool down rather than labelling the total", () => {
// One error plus one warning must not announce as "2 errors".
renderWithMantine(
<ToolListItem
tool={{
...tool,
inputSchema: {
type: "object",
properties: { a: { type: ["null", "boolean"] } },
},
outputSchema: { type: "object", properties: { data: true } },
}}
selected={false}
onClick={() => {}}
/>,
);
expect(
screen.getByLabelText("Schema portability: 1 error, 1 warning"),
).toBeInTheDocument();
});
@ -105,7 +126,7 @@ describe("ToolListItem", () => {
/>,
);
expect(
screen.getByLabelText("Schema portability warnings"),
screen.getByLabelText("Schema portability: 1 warning"),
).toBeInTheDocument();
});
});

View file

@ -10,7 +10,10 @@ import {
import { useMemo } from "react";
import { RiAlertLine, RiErrorWarningLine } from "react-icons/ri";
import type { Tool } from "@modelcontextprotocol/client";
import { lintToolSchemas } from "@inspector/core/json/schemaLint.js";
import {
lintToolSchemas,
summarizeToolFindings,
} from "@inspector/core/json/schemaLint.js";
import { resolveDisplayLabel } from "../../../utils/toolUtils";
export interface ToolListItemProps {
@ -73,6 +76,9 @@ export function ToolListItem({ tool, selected, onClick }: ToolListItemProps) {
const iconSrc = icons?.[0]?.src;
const findings = useMemo(() => lintToolSchemas(tool), [tool]);
const hasError = findings.some((f) => f.severity === "error");
// "1 error, 3 warnings" — the breakdown, not the total labelled with the
// worst severity, which would announce a mixed tool as "4 errors".
const summary = summarizeToolFindings(findings);
return (
<ListItemButton
@ -87,7 +93,7 @@ export function ToolListItem({ tool, selected, onClick }: ToolListItemProps) {
</ItemBody>
{findings.length > 0 && (
<Tooltip
label={`${findings.length} schema portability ${hasError ? "error" : "warning"}${findings.length === 1 ? "" : "s"} — select the tool for detail`}
label={`Schema portability: ${summary} — select the tool for detail`}
withArrow
>
<SchemaFlagIcon
@ -96,11 +102,7 @@ export function ToolListItem({ tool, selected, onClick }: ToolListItemProps) {
? "var(--inspector-danger-text)"
: "var(--inspector-warning-text)"
}
aria-label={
hasError
? "Schema portability errors"
: "Schema portability warnings"
}
aria-label={`Schema portability: ${summary}`}
>
{/* Distinct SHAPES, not just distinct colours: an octagon for an
error and a triangle for a warning, so the severity survives