fix: address Copilot review on the schema portability lint
- Scope `non-object-root` to `inputSchema`. The pinned SDK types
`inputSchema` with `type: literal("object")` but `outputSchema` as a
bare `looseObject`, so a `{"type":"string"}` output is conforming; the
rule was claiming a requirement MCP does not make, at error severity.
- Decide `untyped-schema` from an allowlist of constraining keywords
rather than a denylist of annotations. JSON Schema ignores keywords it
does not recognize, so `{"vendorHint": true}` accepts every value and a
denylist passed it as constrained. `$defs`-only schemas now flag too.
- Walk `dependentSchemas` and draft-07 `dependencies`, with a test
pinning that the property-name-array form is not a schema position.
- Suggest `anyOf` branches for an array-form `type`, never "drop it from
`required`" — accepting `null` and permitting absence are independent
contracts, so the old suggestion silently changed the schema's meaning.
- Reject `--strict` alongside `--app-info`. That path returns NDJSON from
`runMethod` and never reaches the lint, so the flag was a no-op gate
that could never fail.
- Colour the web sidebar flag from the `--inspector-*` severity tokens,
matching the detail panel and avoiding the default yellow that failed
the a11y check.
- Add a live integration test for `rawToolSchemas`. It caught that an
`outputSchema` override on a preset returning no structured content
makes every call to that tool fail client-side validation — the
showcase config did exactly that, so the flagged tool could not be
run. Moved the bare `true` onto `get_temp`, documented the caveat, and
pinned both halves.
- Drop the double cast from the lint test fixture.
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:
parent
2f9e0901c1
commit
c389565783
2 changed files with 566 additions and 66 deletions
|
|
@ -207,6 +207,9 @@ describe("emitResult — schema lint wiring (#1005)", () => {
|
|||
});
|
||||
|
||||
it("does not lint an --app-info run", async () => {
|
||||
// `runCli` rejects `--strict --app-info` outright (see below), so this is
|
||||
// the defensive half: any other Node runner reusing `emitResult` gets the
|
||||
// app-info early return rather than a report interleaved with its NDJSON.
|
||||
await emitResult(
|
||||
DIRTY_LIST,
|
||||
{ hasApp: true, toolName: "info" },
|
||||
|
|
@ -248,4 +251,23 @@ describe("--strict argument validation", () => {
|
|||
]),
|
||||
).rejects.toThrow("--strict requires --method tools/list.");
|
||||
});
|
||||
|
||||
it("is rejected alongside --app-info rather than silently ignored", async () => {
|
||||
// `tools/list --app-info` returns NDJSON straight from `runMethod` and
|
||||
// never reaches `emitResult`, so accepting the pair would hand a CI caller
|
||||
// a `--strict` gate that can never fail.
|
||||
await expect(
|
||||
runCli([
|
||||
"node",
|
||||
"cli",
|
||||
"--cli",
|
||||
"--method",
|
||||
"tools/list",
|
||||
"--strict",
|
||||
"--app-info",
|
||||
"--server-url",
|
||||
"http://127.0.0.1:1/mcp",
|
||||
]),
|
||||
).rejects.toThrow("--strict cannot be combined with --app-info");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1013,6 +1013,17 @@ async function parseArgs(argv?: string[]): Promise<ParseResult> {
|
|||
throw new Error("--strict requires --method tools/list.");
|
||||
}
|
||||
|
||||
// `tools/list --app-info` takes a different path out of `runMethod` — it
|
||||
// returns NDJSON that the caller writes directly, never reaching
|
||||
// `emitResult` where the lint runs. Rejecting the pair is the honest
|
||||
// outcome: silently accepting it would give a CI caller a `--strict` gate
|
||||
// that can never fail.
|
||||
if (options.strict && options.appInfo) {
|
||||
throw new Error(
|
||||
"--strict cannot be combined with --app-info; run tools/list twice, once for each.",
|
||||
);
|
||||
}
|
||||
|
||||
// --tool-args-json passes arguments verbatim with no key=value coercion (so
|
||||
// `"012"` stays a string and nested objects work without shell escaping).
|
||||
let toolArg = options.toolArg;
|
||||
|
|
|
|||
Loading…
Reference in a new issue