mirror of
https://github.com/morluto/rea
synced 2026-10-27 14:42:18 -04:00
6eb37a7749
Use one typed-checker worker by default without changing semantic scope or caller overrides. Reuse the bounded CLI worker for pure argument validation while retaining fresh-process coverage and all upstream cases. Keep registration tests independent of real package-runner downloads while asserting the actual prewarm command and startup context. Pass the advertised binary_session definition by name to package verification, retaining default SDK validation and complete catalog/contract checks while avoiding compilation of unrelated tool outputs. Preserve all four installed-tarball scenarios and existing deadlines. Strengthen Hermes preview operation/backup assertions and clarify physical-path routing documentation. Assisted-by: Suika (gensokyo-skills:suika-scatter-gather-planning)
71 lines
2.9 KiB
JavaScript
71 lines
2.9 KiB
JavaScript
import { lstat } from "node:fs/promises";
|
|
import { Ajv2020 } from "ajv/dist/2020.js";
|
|
|
|
/**
|
|
* Verify canonical names, converted JSON Schemas, and session catalog metadata.
|
|
* Only this operation needs built contracts; path checks remain source-only.
|
|
*/
|
|
export const verifyCompleteToolCatalog = async (client, options) => {
|
|
const [{ requireMcpLifecycleResult, sortVerifierNames }, { TOOL_CONTRACTS }] =
|
|
await Promise.all([
|
|
import("./mcp-verifier-results.mjs"),
|
|
import("../../dist/contracts/toolContracts.js"),
|
|
]);
|
|
const listed = await client.listTools(undefined, options);
|
|
const canonical = sortVerifierNames(TOOL_CONTRACTS.map(({ name }) => name));
|
|
const observed = sortVerifierNames(listed.tools.map(({ name }) => name));
|
|
if (JSON.stringify(observed) !== JSON.stringify(canonical)) {
|
|
const canonicalNames = new Set(canonical);
|
|
const observedNames = new Set(observed);
|
|
const unknown = observed.filter((name) => !canonicalNames.has(name));
|
|
const missing = canonical.filter((name) => !observedNames.has(name));
|
|
const duplicates = observed.filter(
|
|
(name, index) => index > 0 && name === observed[index - 1],
|
|
);
|
|
throw new Error(
|
|
`packaged MCP exposed an invalid canonical tool catalog (${observed.length} tools, unknown: ${unknown.join(", ") || "none"}, missing: ${missing.join(", ") || "none"}, duplicates: ${duplicates.join(", ") || "none"})`,
|
|
);
|
|
}
|
|
const ajv = new Ajv2020({ strict: false, validateFormats: false });
|
|
for (const tool of listed.tools) {
|
|
for (const kind of ["inputSchema", "outputSchema"]) {
|
|
const schema = tool[kind];
|
|
if (schema !== undefined && !ajv.validateSchema(schema))
|
|
throw new Error(
|
|
`packaged MCP has invalid ${tool.name}.${kind}: ${ajv.errorsText(ajv.errors)}`,
|
|
);
|
|
}
|
|
}
|
|
const sessionDefinition = listed.tools.find(
|
|
({ name }) => name === "binary_session",
|
|
);
|
|
if (sessionDefinition === undefined)
|
|
throw new Error("packaged MCP omitted the binary_session definition");
|
|
const status = await client.callTool(
|
|
{ name: "binary_session", arguments: {} },
|
|
// The SDK otherwise compiles every listed output schema on the first call.
|
|
// Keep its default validation for the actual named tool being exercised.
|
|
{ ...options, toolDefinition: sessionDefinition },
|
|
);
|
|
const expanded = requireMcpLifecycleResult(status, "binary_session");
|
|
const availability = expanded.tool_availability;
|
|
const expected = sortVerifierNames(availability.map(({ name }) => name));
|
|
if (JSON.stringify(observed) !== JSON.stringify(expected))
|
|
throw new Error(
|
|
"packaged MCP tool inventory diverged from session metadata",
|
|
);
|
|
return observed;
|
|
};
|
|
|
|
/** Check whether a path exists on disk. */
|
|
export const pathExists = async (path) => {
|
|
try {
|
|
await lstat(path);
|
|
return true;
|
|
} catch (cause) {
|
|
if (cause instanceof Error && "code" in cause && cause.code === "ENOENT")
|
|
return false;
|
|
throw cause;
|
|
}
|
|
};
|