Files
rea/tests/fixtures/mcpClientSchemaConstraints.ts
T
morluto ba98130049 fix(mcp): distinguish schema references from literal metadata
Share schema keyword roles and preserve reference bases for explicit local pointer targets and uncertain intermediate scopes. Retain literal values and canonical validation. Refs #4936.
2026-10-18 09:32:28 +08:00

75 lines
2.5 KiB
TypeScript

import {
schemaArrays,
schemaChildren,
schemaMaps,
} from "../../src/contracts/jsonSchemaKeywords.js";
const isRecord = (value: unknown): value is Record<string, unknown> =>
typeof value === "object" && value !== null && !Array.isArray(value);
/** VS Code Copilot rejects falsy `items` for any type that admits arrays. */
export const arrayItemSchemaError = (
schema: unknown,
path: string,
): string | undefined => {
if (!isRecord(schema)) return undefined;
const admitsArrays =
schema.type === "array" ||
(Array.isArray(schema.type) && schema.type.includes("array"));
if (admitsArrays && !schema.items)
return `${path}: tool parameters array type must have items`;
for (const [key, value] of Object.entries(schema)) {
const children =
schemaMaps.has(key) && isRecord(value)
? Object.entries(value).map(
([name, child]) => [`${key}.${name}`, child] as const,
)
: (schemaArrays.has(key) || schemaChildren.has(key)) &&
Array.isArray(value)
? value.map(
(child: unknown, index) => [`${key}.${index}`, child] as const,
)
: schemaChildren.has(key)
? [[key, value] as const]
: [];
for (const [suffix, child] of children) {
const error = arrayItemSchemaError(child, `${path}.${suffix}`);
if (error !== undefined) return error;
}
}
return undefined;
};
/** Reject Gemini-incompatible boolean literals in advertised input positions. */
export const booleanLiteralSchemaError = (
schema: unknown,
path: string,
): string | undefined => {
if (!isRecord(schema)) return undefined;
if (typeof schema.const === "boolean") return `${path}.const is boolean`;
if (
Array.isArray(schema.enum) &&
schema.enum.some((value) => typeof value === "boolean")
)
return `${path}.enum contains a boolean`;
for (const [key, value] of Object.entries(schema)) {
const children =
schemaMaps.has(key) && isRecord(value)
? Object.entries(value).map(
([name, child]) => [`${key}.${name}`, child] as const,
)
: schemaArrays.has(key) && Array.isArray(value)
? value.map(
(child: unknown, index) => [`${key}[${index}]`, child] as const,
)
: schemaChildren.has(key)
? [[key, value] as const]
: [];
for (const [suffix, child] of children) {
const error = booleanLiteralSchemaError(child, `${path}.${suffix}`);
if (error !== undefined) return error;
}
}
return undefined;
};