Files
reverse-engineering-anything/scripts/lib/verify-package-core.mjs
T
morluto d0fee4e58e fix(mcp): re-emit advertised patterns in a portable regex grammar (#10582)
* test(mcp): check advertised schemas against a portable dialect in every profile

* fix(mcp): re-emit advertised patterns in a portable regex grammar

* fix(mcp): validate advertised dialect by selected profile

* fix(mcp): preserve schema widening through references

* fix(mcp): refuse zero-padded repeat bounds and record host-specific path patterns

* fix(mcp): keep references valid when presentation widens their targets

* fix(mcp): relax references into negations and conditionals presentation may remove

* fix(mcp): keep astral literals and dynamic references widening in every mode

* fix(mcp): omit astral literals and relax untracked references when presentation widens

* fix(mcp): read reference pointers by schema position, not segment spelling

* fix(mcp): protect references into collapsing boolean unions and keep referencing branches out of the collapse

* fix(mcp): omit patterns whose classes name astral characters

* fix(mcp): decide union collapse and pointer protection from canonical branches

* docs(mcp): document non-u astral matching and pin BMP agreement across V8 modes

* test(mcp): expect the advertised base64 schema to omit the unbounded pad-bit probe

* fix(mcp): protect references into unevaluated keywords lost evaluation removes

* fix(mcp): emit stack-safe classes with non-ASCII members and protect draft-07 reference siblings

* fix(mcp): protect references into literal siblings of relaxed draft-07 references
2026-11-15 17:06:43 +08:00

105 lines
4.0 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,
environment = {},
) => {
const [
{ requireMcpLifecycleResult, sortVerifierNames },
{ TOOL_CONTRACTS },
dialect,
{ ADVERTISED_INPUT_DIALECT_BASELINE },
{ mcpInputSchemaProfileSchema },
] = await Promise.all([
import("./mcp-verifier-results.mjs"),
import("../../dist/contracts/toolContracts.js"),
import("../../dist/contracts/advertisedDialect.js"),
import("../../dist/contracts/advertisedDialectBaseline.js"),
import("../../dist/config/mcpInputSchemaProfile.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)}`,
);
}
}
// Compare the advertisement with the profiles actually passed to the server.
const inputProfile = mcpInputSchemaProfileSchema.parse(
environment.REA_MCP_INPUT_SCHEMA_PROFILE ?? "compact",
);
const regressions = [
...dialect.advertisedDialectRegressions(
dialect.countAdvertisedDialectViolations(
listed.tools.flatMap(dialect.advertisedInputDialectViolations),
),
ADVERTISED_INPUT_DIALECT_BASELINE[inputProfile],
),
...dialect.advertisedDialectRegressions(
dialect.countAdvertisedDialectViolations(
listed.tools.flatMap(dialect.advertisedOutputPatternViolations),
),
{},
),
];
if (regressions.length > 0)
throw new Error(
`packaged MCP advertises constructs outside the portable schema dialect: ${regressions.join("; ")}`,
);
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;
}
};