86 lines
3.0 KiB
TypeScript
86 lines
3.0 KiB
TypeScript
import type {
|
|
JsonSchemaType,
|
|
JsonSchemaValidator,
|
|
} from "@modelcontextprotocol/client";
|
|
import {
|
|
addFormats,
|
|
AjvJsonSchemaValidator,
|
|
} from "@modelcontextprotocol/client/validators/ajv";
|
|
import { Ajv2020 } from "ajv/dist/2020.js";
|
|
import type { ValidateFunction } from "ajv";
|
|
|
|
/** Share immutable schema preparation within one test file, never its sessions. */
|
|
export const createMcpSchemaValidation = () => {
|
|
const sdkValidators = new Map<string, JsonSchemaValidator<unknown>>();
|
|
const assertionValidators = new Map<string, ValidateFunction>();
|
|
const meta = new Ajv2020({ strict: false, validateFormats: false });
|
|
let errors: unknown = null;
|
|
return {
|
|
getValidator<T>(
|
|
schema: JsonSchemaType | Readonly<Record<string, unknown>>,
|
|
): JsonSchemaValidator<T> {
|
|
const key = JSON.stringify(schema);
|
|
let validator = sdkValidators.get(key);
|
|
if (validator === undefined) {
|
|
// Keep shared definitions as compiled functions instead of expanding
|
|
// them at every reference. Other dialects retain SDK dispatch.
|
|
const provider =
|
|
schema.$schema === undefined ||
|
|
schema.$schema === "https://json-schema.org/draft/2020-12/schema"
|
|
? new AjvJsonSchemaValidator(
|
|
addFormats(
|
|
new Ajv2020({
|
|
strict: false,
|
|
validateFormats: true,
|
|
validateSchema: false,
|
|
allErrors: true,
|
|
inlineRefs: false,
|
|
code: { optimize: false },
|
|
}),
|
|
),
|
|
)
|
|
: new AjvJsonSchemaValidator();
|
|
// Each full schema owns its provider, including dialect dispatch.
|
|
// Distinct schemas may legally reuse the same $id in separate servers.
|
|
validator = provider.getValidator(JSON.parse(key));
|
|
sdkValidators.set(key, validator);
|
|
}
|
|
return validator as JsonSchemaValidator<T>;
|
|
},
|
|
assertions: {
|
|
get errors() {
|
|
return errors;
|
|
},
|
|
validateSchema(schema: Record<string, unknown>) {
|
|
const valid = meta.validateSchema(schema);
|
|
errors = meta.errors;
|
|
return valid;
|
|
},
|
|
validate(schema: Record<string, unknown>, value: unknown) {
|
|
const key = JSON.stringify(schema);
|
|
let validator = assertionValidators.get(key);
|
|
if (validator === undefined) {
|
|
// Preserve the explicit assertions' existing options, independently
|
|
// of the SDK default provider's format/all-errors policy.
|
|
validator = new Ajv2020({
|
|
strict: false,
|
|
validateFormats: false,
|
|
inlineRefs: false,
|
|
code: { optimize: false },
|
|
}).compile(JSON.parse(key));
|
|
assertionValidators.set(key, validator);
|
|
}
|
|
const valid = validator(value);
|
|
errors = validator.errors;
|
|
return valid;
|
|
},
|
|
},
|
|
clear() {
|
|
sdkValidators.clear();
|
|
assertionValidators.clear();
|
|
meta.removeSchema();
|
|
errors = null;
|
|
},
|
|
};
|
|
};
|