Compare commits

...
Author SHA1 Message Date
Kaoru0822andClaude Opus 5.5 314c98f6ec test(mcp): force the reduced form when checking array item schemas
Use a large nested property with a 1 KiB budget so the schema is
reduced while its shallow array property remains, and assert the
presentation and that property before checking for missing items.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 04:31:43 +00:00
Kaoru0822andClaude Opus 5.5 b02e1c2452 fix(mcp): give the compact any-value schema an item schema
The compact input-schema profile advertises an unconstrained JSON value
as {type: [null, boolean, object, array, number, string]}. That union
declares an array type without items, which strict clients reject: VS
Code Copilot refused build_call_path with "tool parameters array type
must have items", and 21 tool input schemas carried the same shape.

Use one owned any-value schema that adds items: {} for both the
budget-inlined empty schema and reduced array items.

Fixes #3838

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 04:23:57 +00:00
2 changed files with 60 additions and 5 deletions

No files matched your search

+55 -1
View File
@@ -191,10 +191,19 @@ describe("compact advertised input schema", () => {
const properties = schema.properties as Record<string, unknown>;
expect(properties.normalized_result).toEqual({
type: ["null", "boolean", "object", "array", "number", "string"],
items: {},
});
expect(properties.literal).toEqual({ const: {} });
const validate = new Ajv2020({ strict: false }).compile(schema);
for (const normalized_result of [null, true, {}, [], 42, "result"])
for (const normalized_result of [
null,
true,
{},
[],
[[1], "x"],
42,
"result",
])
expect(validate({ normalized_result })).toBe(true);
});
@@ -267,6 +276,7 @@ describe("compact input schema budget and reduction", () => {
type: "array",
items: {
type: ["null", "boolean", "object", "array", "number", "string"],
items: {},
},
},
label: { type: ["string", "null"] },
@@ -335,3 +345,47 @@ describe("compact input schema budget and reduction", () => {
expect(serializedBytes(schema)).toBeLessThanOrEqual(13312);
});
});
it("gives every advertised array type an item schema for strict clients", () => {
const arraysWithoutItems = (value: unknown): number => {
if (Array.isArray(value))
return value.reduce(
(count: number, child) => count + arraysWithoutItems(child),
0,
);
if (typeof value !== "object" || value === null) return 0;
const record = value as Record<string, unknown>;
const types = Array.isArray(record.type) ? record.type : [record.type];
return (
(types.includes("array") && record.items === undefined ? 1 : 0) +
Object.values(record).reduce(
(count: number, child) => count + arraysWithoutItems(child),
0,
)
);
};
// A large nested property forces the reduced form at 1 KiB while the
// shallow properties, including the array, remain advertised.
for (const [budget, expected] of [
[13312, "inline"],
[1024, "reduced"],
] as const) {
const { schema, presentation } = compactAdvertisedInputSchema(
{
type: "object",
properties: {
capture: budget === 1024 ? oversizedPairSchema() : {},
value: {},
values: { type: "array", items: {} },
},
},
budget,
);
expect(presentation, String(budget)).toBe(expected);
expect(
(schema.properties as Record<string, unknown>).values,
String(budget),
).toMatchObject({ type: "array", items: expect.any(Object) });
expect(arraysWithoutItems(schema), String(budget)).toBe(0);
}
});
+5 -4
View File
@@ -23,6 +23,9 @@ const jsonValueTypes = [
"number",
"string",
];
// Strict clients such as VS Code reject an array type without `items`, so
// the explicit any-value form also accepts every array element.
const jsonValueSchema = { type: jsonValueTypes, items: {} };
export type AdvertisedInputSchemaPresentation = "inline" | "reduced";
@@ -157,7 +160,7 @@ const inlineWithinBudget = (
yield "]";
} else if (isSchemaObject(value)) {
if (position === "schema" && Object.keys(value).length === 0) {
yield JSON.stringify({ type: jsonValueTypes });
yield JSON.stringify(jsonValueSchema);
return;
}
if (position === "schema" && typeof value.$ref === "string") {
@@ -293,9 +296,7 @@ const reduceToShallowProperties = (
...(type === undefined ? {} : { type }),
// Reduced inputs intentionally omit nested validation, but clients
// still require an item schema to accept an array declaration.
...(types?.includes("array")
? { items: { type: jsonValueTypes } }
: {}),
...(types?.includes("array") ? { items: jsonValueSchema } : {}),
...description,
},
];