Files
reverse-engineering-anything/tests/boundary/mcp/mcpSchemaValidation.test.ts
T
06e6401750 test: compile MCP output validators on first use
After tools/list, the MCP SDK client builds an output validator for every
listed tool before its first tools/call. Against REA's 255-tool catalog
that compiled for about 15 seconds of CPU (5 seconds for
inspect_analysis_view alone), whichever tool a test called. The shared
test validation now snapshots each schema when asked and compiles it when
it first validates.

Co-Authored-By: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-14 15:01:36 +08:00

115 lines
4.2 KiB
TypeScript

import { AjvJsonSchemaValidator } from "@modelcontextprotocol/client/validators/ajv";
import { expect, it, vi } from "vitest";
import { createMcpSchemaValidation } from "../../fixtures/mcpSchemaValidation.js";
it("compiles a listed tool's validator only when it first validates", () => {
// The SDK requests a validator for every listed tool before its first call.
const compile = vi.spyOn(AjvJsonSchemaValidator.prototype, "getValidator");
const validation = createMcpSchemaValidation();
try {
const listed = [
{ type: "string" },
{ type: "integer" },
{ type: "boolean" },
];
const validators = listed.map((schema) => validation.getValidator(schema));
expect(compile).not.toHaveBeenCalled();
expect(validators[1]?.(7).valid).toBe(true);
expect(validators[1]?.("seven").valid).toBe(false);
expect(compile).toHaveBeenCalledTimes(1);
} finally {
compile.mockRestore();
validation.clear();
}
});
it("reuses equal schemas and recompiles changed schemas sharing an id", () => {
const validation = createMcpSchemaValidation();
try {
const schema = {
$schema: "https://json-schema.org/draft/2020-12/schema",
$id: "https://fixture.test/result",
$defs: { value: { type: "string" as "string" | "integer" } },
$ref: "#/$defs/value",
};
const first = validation.getValidator(schema);
expect(validation.getValidator(structuredClone(schema))).toBe(first);
expect(first("recorded").valid).toBe(true);
expect(first(42).valid).toBe(false);
expect(validation.assertions.validate(schema, "recorded")).toBe(true);
expect(validation.assertions.validate(schema, 42)).toBe(false);
schema.$defs.value.type = "integer";
const changed = validation.getValidator(schema);
expect(changed).not.toBe(first);
expect(changed(42).valid).toBe(true);
expect(changed("recorded").valid).toBe(false);
expect(validation.assertions.validate(schema, 42)).toBe(true);
expect(validation.assertions.validate(schema, "recorded")).toBe(false);
validation.clear();
expect(validation.getValidator(schema)).not.toBe(changed);
} finally {
validation.clear();
}
});
it.each([
"https://json-schema.org/draft/2020-12/schema",
"https://json-schema.org/draft/2019-09/schema",
"http://json-schema.org/draft-07/schema#",
])("preserves SDK format validation and references for %s", (dialect) => {
const validation = createMcpSchemaValidation();
try {
const schema = {
$schema: dialect,
$defs: { address: { type: "string", format: "email" } },
$ref: "#/$defs/address",
} as const;
const validator = validation.getValidator(schema);
expect(validator("analyst@example.test").valid).toBe(true);
expect(validator("invalid email").valid).toBe(false);
// Explicit schema assertions retain their separate format-off policy.
expect(
validation.assertions.validate(
{ type: "string", format: "email" },
"invalid email",
),
).toBe(true);
expect(
validation.assertions.validateSchema({ type: "string", minLength: -1 }),
).toBe(false);
} finally {
validation.clear();
}
});
it("owns nested schema values independently of later caller mutation", () => {
const validation = createMcpSchemaValidation();
try {
const schema = { enum: [{ state: "recorded" }, { state: "other" }] };
const originalSchema = structuredClone(schema);
const original = validation.getValidator(schema);
expect(validation.assertions.validate(schema, { state: "recorded" })).toBe(
true,
);
schema.enum[0]!.state = "changed";
const changed = validation.getValidator(schema);
expect(original({ state: "recorded" }).valid).toBe(true);
expect(original({ state: "changed" }).valid).toBe(false);
expect(changed({ state: "changed" }).valid).toBe(true);
expect(changed({ state: "recorded" }).valid).toBe(false);
expect(
validation.assertions.validate(originalSchema, { state: "recorded" }),
).toBe(true);
expect(validation.assertions.validate(schema, { state: "recorded" })).toBe(
false,
);
expect(validation.assertions.validate(schema, { state: "changed" })).toBe(
true,
);
} finally {
validation.clear();
}
});