mirror of
https://github.com/modelcontextprotocol/servers.git
synced 2026-10-20 06:19:36 +08:00
Port the MCP Inspector's skills infrastructure for #4858 Part 3: - scripts/verify-skills.mjs, verify-skills-cli.mjs, skill-eval.mjs and scripts/lib/{skill-manifest,claude-cli,npm-scripts,win-shell-args}.mjs, with all their *.test.mjs files - root npm scripts verify:skills, verify:skills:cli, skills:eval and test:scripts; yaml as a root devDependency - docs/skill-authoring.md, with paths and provenance adapted - a root-guards job in typescript.yml running verify:skills and verify:skills:cli on every push and pull request - .claude/skills/ (kept by a .gitkeep until the first skill lands) - un-ignore scripts/lib/, which the Python `lib/` pattern swallowed Adapted to this repo: checkWiring reads typescript.yml, and carries two temporary allowances (validate -> verify:format-coverage, removed by #4864; local:gate -> verify:skills:cli, removed by #4871) that excuse a link only while its pending script is absent and fail as stale once it exists. The "no skills found" failure is kept, with a temporary EMPTY_SKILLS_BOOTSTRAP allowance that fails as stale once a skill exists. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: cliffhall <cliff@futurescale.com>
107 lines
3.3 KiB
JavaScript
107 lines
3.3 KiB
JavaScript
// Tests for the one place that decides how `claude` is spawned (inspector#2163, Copilot).
|
|
//
|
|
// The behavior under test only differs ON Windows, and these run on whatever
|
|
// the contributor has — so the platform is injected rather than sniffed. A
|
|
// win32-only bug that no CI runner here executes is precisely the kind that
|
|
// ships.
|
|
|
|
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import {
|
|
claudeSpawnArgs,
|
|
cliSpawnArgs,
|
|
probeClaudeVersion,
|
|
probeCliVersion,
|
|
} from "./claude-cli.mjs";
|
|
|
|
test("spawns without a shell off Windows", () => {
|
|
const { command, args, options } = claudeSpawnArgs(
|
|
["plugin", "validate", ".claude/skills"],
|
|
{ cwd: "/repo" },
|
|
"linux",
|
|
);
|
|
assert.equal(command, "claude");
|
|
assert.deepEqual(args, ["plugin", "validate", ".claude/skills"]);
|
|
assert.equal(options.shell, false);
|
|
assert.equal(options.cwd, "/repo");
|
|
});
|
|
|
|
test("uses a shell on Windows, where the CLI is a .cmd shim", () => {
|
|
// Node has refused shell-free `.cmd` spawns since CVE-2024-27980, so without
|
|
// this the CLI reads as absent even when it is installed.
|
|
const { options } = claudeSpawnArgs(["--version"], {}, "win32");
|
|
assert.equal(options.shell, true);
|
|
});
|
|
|
|
test("quotes arguments that cmd.exe would otherwise re-parse", () => {
|
|
const { args } = claudeSpawnArgs(["-p", "does it? (yes & no)"], {}, "win32");
|
|
assert.deepEqual(args, ["-p", '"does it? (yes & no)"']);
|
|
// The same arguments are untouched off Windows.
|
|
assert.deepEqual(
|
|
claudeSpawnArgs(["-p", "does it? (yes & no)"], {}, "linux").args,
|
|
["-p", "does it? (yes & no)"],
|
|
);
|
|
});
|
|
|
|
test("probeClaudeVersion returns the parsed version on success", () => {
|
|
const version = probeClaudeVersion(
|
|
(t) => (t.trim() === "2.1.250" ? [2, 1, 250] : null),
|
|
{
|
|
spawn: () => ({ status: 0, stdout: "2.1.250" }),
|
|
platform: "linux",
|
|
},
|
|
);
|
|
assert.deepEqual(version, [2, 1, 250]);
|
|
});
|
|
|
|
test("probeClaudeVersion returns null for every unusable outcome", () => {
|
|
for (const res of [
|
|
{ error: new Error("ENOENT") },
|
|
{ status: 1, stdout: "" },
|
|
{ status: 0, stdout: "something else" },
|
|
]) {
|
|
assert.equal(
|
|
probeClaudeVersion(() => null, { spawn: () => res, platform: "linux" }),
|
|
null,
|
|
);
|
|
}
|
|
});
|
|
|
|
test("probeClaudeVersion asks for the shell on Windows", () => {
|
|
let seen;
|
|
probeClaudeVersion(() => [2, 1, 250], {
|
|
spawn: (_c, _a, options) => {
|
|
seen = options;
|
|
return { status: 0, stdout: "2.1.250" };
|
|
},
|
|
platform: "win32",
|
|
});
|
|
assert.equal(seen.shell, true);
|
|
assert.equal(seen.encoding, "utf8");
|
|
});
|
|
|
|
test("the generic helpers name the command they were given (inspector#2397)", () => {
|
|
// `copilot` is an npm `.cmd` shim on Windows as well, so it takes the same
|
|
// shell-and-quoting decision rather than a second copy of it.
|
|
const { command, options, args } = cliSpawnArgs(
|
|
"copilot",
|
|
["--prompt", "a & b"],
|
|
{},
|
|
"win32",
|
|
);
|
|
assert.equal(command, "copilot");
|
|
assert.equal(options.shell, true);
|
|
assert.deepEqual(args, ["--prompt", '"a & b"']);
|
|
|
|
let spawned;
|
|
const version = probeCliVersion("copilot", (t) => t.trim(), {
|
|
spawn: (c) => {
|
|
spawned = c;
|
|
return { status: 0, stdout: "GitHub Copilot CLI 1.0.85.\n" };
|
|
},
|
|
platform: "linux",
|
|
});
|
|
assert.equal(spawned, "copilot");
|
|
assert.equal(version, "GitHub Copilot CLI 1.0.85.");
|
|
});
|