perf(mcp): share repeated output schema definitions

This commit is contained in:
Ysopwnable
2026-10-09 08:46:59 +08:00
committed by GitHub
parent c12bad7f13
commit 441813c070
6 changed files with 413 additions and 5 deletions
+4
View File
@@ -31,6 +31,10 @@ check the complete catalog after SDK conversion and its generated counterpart.
This is REA's local compatibility profile; individual model APIs can impose
additional limits.
Output schemas share repeated definitions through local references while
preserving their complete fields and validation rules. Canonical Zod validation
and the input compatibility profile continue to apply.
For passive `compare_web_captures` inputs, pass each complete
`inspect_web_page` result in `before.inspection` or `after.inspection`, with an
optional complete `discover_webmcp_tools` result in the matching `webmcp` field.
+177
View File
@@ -0,0 +1,177 @@
import { Ajv } from "ajv";
import { Ajv2020 } from "ajv/dist/2020.js";
import { expect, it } from "vitest";
import { z } from "zod";
import { inlineLocalJsonSchemaReferences } from "../../tests/fixtures/localJsonSchemaReferences.js";
import { toolContract } from "./toolContracts.js";
import { toolOutputSchemaWithMetadata } from "./toolSchemaMetadata.js";
it.each(["draft-2020-12", "draft-07"] as const)(
"preserves refined numeric bounds and strict repeated objects through %s references",
(target) => {
const integer = z.number().int().nonnegative();
const range = z
.object({
offset: integer.max(65535),
length: integer.min(1),
exclusive: integer.positive(),
})
.strict();
const canonical = z.object({ before: range, after: range }).strict();
const advertised = toolOutputSchemaWithMetadata({
...toolContract("open_binary"),
outputSchema: canonical,
})["~standard"].jsonSchema.output({ target });
const ajv =
target === "draft-2020-12"
? new Ajv2020({ strict: false })
: new Ajv({ strict: false });
const validate = ajv.compile(advertised);
const valid = {
before: { offset: 0, length: 1, exclusive: 1 },
after: { offset: 65535, length: 9007199254740991, exclusive: 1 },
};
const samples: readonly unknown[] = [
valid,
...[
{ offset: -1 },
{ offset: 65536 },
{ offset: 0.5 },
{ length: 0 },
{ length: 9007199254740992 },
{ exclusive: 0 },
{ exclusive: -1 },
{ length: null },
{ unexpected: true },
].map((change) => ({ ...valid, before: { ...valid.before, ...change } })),
{ ...valid, before: { offset: 0, exclusive: 1 } },
{ ...valid, after: null },
{ ...valid, unexpected: true },
];
expect(canonical.safeParse(valid).success).toBe(true);
for (const sample of samples)
expect(validate(sample)).toBe(canonical.safeParse(sample).success);
expect(inlineLocalJsonSchemaReferences(advertised)).toEqual(
inlineLocalJsonSchemaReferences(
canonical["~standard"].jsonSchema.output({ target }),
),
);
},
);
it("keeps schema data containing reference-shaped objects untouched", () => {
const literal = { $ref: "https://example.invalid/value" };
const expanded = inlineLocalJsonSchemaReferences({
$defs: {
"a/b": {
type: "object",
properties: { $defs: { const: literal } },
examples: [literal],
},
},
$ref: "#/$defs/a~1b",
description: "Keep the caller-facing annotation",
});
expect(expanded).toEqual({
type: "object",
properties: { $defs: { const: literal } },
examples: [literal],
description: "Keep the caller-facing annotation",
});
});
it.each(["draft-2020-12", "draft-07"] as const)(
"preserves layered native-selector string patterns through %s references",
(target) => {
const selector = z
.string()
.min(1)
.regex(/^[^\0]*$/u)
.regex(/^[^\s[\]]+$/u);
const canonical = z.object({ before: selector, after: selector }).strict();
const advertised = toolOutputSchemaWithMetadata({
...toolContract("open_binary"),
outputSchema: canonical,
})["~standard"].jsonSchema.output({ target });
const ajv =
target === "draft-2020-12"
? new Ajv2020({ strict: false })
: new Ajv({ strict: false });
const validate = ajv.compile(advertised);
for (const value of [
"setFoo:",
"dataTaskWithRequest:completionHandler:",
"",
"set\0Foo:",
"bad selector:",
"bad[selector]:",
]) {
const sample = { before: value, after: "setFoo:" };
expect(validate(sample)).toBe(canonical.safeParse(sample).success);
}
expect(inlineLocalJsonSchemaReferences(advertised)).toEqual(
inlineLocalJsonSchemaReferences(
canonical["~standard"].jsonSchema.output({ target }),
),
);
},
);
it("rejects unresolved and cyclic references rather than concealing schema drift", () => {
expect(() =>
inlineLocalJsonSchemaReferences({ $ref: "#/$defs/missing" }),
).toThrow("Missing schema reference");
expect(() =>
inlineLocalJsonSchemaReferences({
$defs: { loop: { $ref: "#/$defs/loop" } },
$ref: "#/$defs/loop",
}),
).toThrow("Recursive schema reference");
});
it("intersects referenced numeric bounds and rejects conflicting validation keywords", () => {
expect(
inlineLocalJsonSchemaReferences({
$defs: { bounded: { type: "number", minimum: 1, maximum: 10 } },
$ref: "#/$defs/bounded",
minimum: 0,
maximum: 9,
}),
).toEqual({ type: "number", minimum: 1, maximum: 9 });
expect(() =>
inlineLocalJsonSchemaReferences({
$defs: { strict: { type: "object" } },
$ref: "#/$defs/strict",
type: "string",
}),
).toThrow("Conflicting schema reference sibling: type");
});
it("preserves strict-object evaluation scope across references and singleton intersections", () => {
const strict = {
type: "object",
properties: { value: { type: "string" } },
additionalProperties: false,
};
const schemas = [
{
$defs: { strict },
$ref: "#/$defs/strict",
additionalProperties: false,
},
{ allOf: [strict], additionalProperties: false },
];
for (const schema of schemas) {
const expanded = z
.record(z.string(), z.unknown())
.parse(inlineLocalJsonSchemaReferences(schema));
const ajv = new Ajv2020({ strict: false });
const original = ajv.compile(schema);
const normalized = ajv.compile(expanded);
expect(original({})).toBe(true);
expect(original({ value: "kept" })).toBe(false);
for (const sample of [{}, { value: "kept" }, { unexpected: true }, null])
expect(normalized(sample)).toBe(original(sample));
}
});
+50 -2
View File
@@ -1,5 +1,6 @@
import { describe, expect, it } from "vitest";
import { inlineLocalJsonSchemaReferences } from "../../tests/fixtures/localJsonSchemaReferences.js";
import { TOOL_CONTRACTS, toolContract } from "./toolContracts.js";
import {
toolInputSchemaWithMetadata,
@@ -19,8 +20,13 @@ describe("advertised tool JSON Schema", () => {
expect(advertisedInput.examples, contract.name).toEqual(
contract.examples.map(({ input: example }) => example),
);
expect(advertisedOutput, contract.name).toEqual(
contract.outputSchema["~standard"].jsonSchema.output({ target }),
expect(
inlineLocalJsonSchemaReferences(advertisedOutput),
contract.name,
).toEqual(
inlineLocalJsonSchemaReferences(
contract.outputSchema["~standard"].jsonSchema.output({ target }),
),
);
}
});
@@ -34,4 +40,46 @@ describe("advertised tool JSON Schema", () => {
expect(latest.$schema).toBe("https://json-schema.org/draft/2020-12/schema");
expect(draft07.$schema).toBe("http://json-schema.org/draft-07/schema#");
});
it("preserves complete output meaning across JSON Schema dialects", () => {
for (const dialect of ["draft-2020-12", "draft-07"] as const)
for (const contract of TOOL_CONTRACTS) {
const advertised = toolOutputSchemaWithMetadata(contract)[
"~standard"
].jsonSchema.output({ target: dialect });
const canonical = contract.outputSchema["~standard"].jsonSchema.output({
target: dialect,
});
expect(
inlineLocalJsonSchemaReferences(advertised),
contract.name,
).toEqual(inlineLocalJsonSchemaReferences(canonical));
}
});
it("retains memoization and explicit caller projection options", () => {
const contract = toolContract("analyze_javascript_application");
const output =
toolOutputSchemaWithMetadata(contract)["~standard"].jsonSchema;
const shared = output.output({ target });
expect(shared.$defs).toBeDefined();
expect(output.output({ target })).toBe(shared);
const options = { target, libraryOptions: { reused: "inline" } };
const inline = output.output(options);
expect(inline).toEqual(
contract.outputSchema["~standard"].jsonSchema.output(options),
);
expect(inline.$defs).toBeUndefined();
expect(output.output({ target })).toBe(shared);
const draft07 = output.output({ target: "draft-07" });
expect(draft07.$schema).toBe("http://json-schema.org/draft-07/schema#");
expect(inlineLocalJsonSchemaReferences(draft07)).toEqual(
inlineLocalJsonSchemaReferences(
contract.outputSchema["~standard"].jsonSchema.output({
target: "draft-07",
}),
),
);
expect(output.output({ target: "draft-07" })).toBe(draft07);
});
});
+10 -2
View File
@@ -63,11 +63,19 @@ export const toolInputSchemaWithMetadata = <Contract extends ToolContract>(
}),
);
/** Advertise a canonical output schema without reconverting it per listing. */
/** Share repeated output definitions without changing canonical validation. */
export const toolOutputSchemaWithMetadata = <Contract extends ToolContract>(
contract: Contract,
): Contract["outputSchema"] =>
withAdvertisedJsonSchema(contract.outputSchema, "output");
withAdvertisedJsonSchema(
contract.outputSchema,
"output",
(project) => (options) =>
project({
...options,
libraryOptions: { reused: "ref", ...options.libraryOptions },
}),
);
const withAdvertisedJsonSchema = <Schema extends z.ZodType>(
canonical: Schema,
@@ -17,6 +17,7 @@ import {
import { nativeUiResultSchema } from "../../../src/domain/native/nativeUiObservation.js";
import { processCaptureSchema } from "../../../src/domain/process/processCapture.js";
import { toolRegistrationOptions } from "../../../src/server/toolRegistrationOptions.js";
import { inlineLocalJsonSchemaReferences } from "../../fixtures/localJsonSchemaReferences.js";
const record = (value: unknown): Record<string, unknown> => {
if (typeof value !== "object" || value === null || Array.isArray(value))
@@ -66,7 +67,7 @@ it("advertises semantic output invariants and keeps producers aligned with Zod",
: { $defs: outputSchema.$defs }),
});
return {
result,
result: record(inlineLocalJsonSchemaReferences(standaloneResultSchema)),
validate: new Ajv2020({
strict: false,
validateFormats: false,
+170
View File
@@ -0,0 +1,170 @@
const schemaMaps = new Set([
"properties",
"patternProperties",
"dependentSchemas",
]);
const schemaArrays = new Set(["allOf", "anyOf", "oneOf", "prefixItems"]);
const schemaChildren = new Set([
"items",
"additionalItems",
"contains",
"additionalProperties",
"unevaluatedProperties",
"unevaluatedItems",
"propertyNames",
"not",
"if",
"then",
"else",
]);
const lowerBounds = new Set(["minimum", "exclusiveMinimum"]);
const upperBounds = new Set(["maximum", "exclusiveMaximum"]);
const scopedKeywords = new Set([
"properties",
"patternProperties",
"additionalProperties",
"items",
"additionalItems",
"prefixItems",
"contains",
"unevaluatedProperties",
"unevaluatedItems",
]);
const record = (value: unknown): value is Record<string, unknown> =>
typeof value === "object" && value !== null && !Array.isArray(value);
/** Inline finite local references for representation-independent schema assertions. */
export const inlineLocalJsonSchemaReferences = (
root: Readonly<Record<string, unknown>>,
): unknown => {
const resolve = (reference: string): Record<string, unknown> => {
if (!reference.startsWith("#/"))
throw new Error(`Expected a local schema reference: ${reference}`);
let current: unknown = root;
for (const encoded of reference.slice(2).split("/")) {
const key = encoded.replaceAll("~1", "/").replaceAll("~0", "~");
if (!record(current) || !Object.hasOwn(current, key))
throw new Error(`Missing schema reference: ${reference}`);
current = current[key];
}
if (!record(current))
throw new Error(`Expected a referenced schema object: ${reference}`);
return current;
};
const visit = (value: unknown, active: ReadonlySet<string>): unknown => {
if (!record(value)) return value;
if (typeof value.$ref === "string") {
const { $ref: reference, ...siblings } = value;
if (active.has(reference))
throw new Error(`Recursive schema reference: ${reference}`);
const referenced = resolve(reference);
// Keep declarations in their original evaluation scope.
if (Object.keys(siblings).some((key) => scopedKeywords.has(key)))
return {
allOf: [
visit(referenced, new Set([...active, reference])),
visit(siblings, active),
],
};
const merged = { ...referenced, ...siblings };
for (const [key, entry] of Object.entries(siblings)) {
const previous = referenced[key];
if (
previous === undefined ||
JSON.stringify(previous) === JSON.stringify(entry)
)
continue;
if (typeof previous === "number" && typeof entry === "number") {
if (lowerBounds.has(key)) {
merged[key] = Math.max(previous, entry);
continue;
}
if (upperBounds.has(key)) {
merged[key] = Math.min(previous, entry);
continue;
}
}
throw new Error(`Conflicting schema reference sibling: ${key}`);
}
return visit(merged, new Set([...active, reference]));
}
if (Array.isArray(value.allOf) && value.allOf.length === 1) {
const child = visit(value.allOf[0], active);
const { allOf: _allOf, ...siblings } = value;
if (
record(child) &&
!Object.keys(siblings).some((key) => scopedKeywords.has(key)) &&
Object.entries(siblings).every(
([key, entry]) =>
child[key] === undefined ||
JSON.stringify(child[key]) === JSON.stringify(entry) ||
(typeof child[key] === "number" &&
typeof entry === "number" &&
(lowerBounds.has(key) || upperBounds.has(key))),
)
) {
const merged = { ...child, ...siblings };
for (const [key, entry] of Object.entries(siblings)) {
const previous = child[key];
if (typeof previous !== "number" || typeof entry !== "number")
continue;
if (lowerBounds.has(key)) merged[key] = Math.max(previous, entry);
else if (upperBounds.has(key))
merged[key] = Math.min(previous, entry);
}
return visit(merged, active);
}
}
const normalized = { ...value };
if (
typeof normalized.pattern === "string" &&
Array.isArray(normalized.allOf) &&
normalized.allOf.some(
(child: unknown) =>
record(child) && child.pattern === normalized.pattern,
)
)
delete normalized.pattern;
if (
typeof normalized.minimum === "number" &&
typeof normalized.exclusiveMinimum === "number" &&
normalized.exclusiveMinimum >= normalized.minimum
)
delete normalized.minimum;
if (
typeof normalized.maximum === "number" &&
typeof normalized.exclusiveMaximum === "number" &&
normalized.exclusiveMaximum <= normalized.maximum
)
delete normalized.maximum;
return Object.fromEntries(
Object.entries(normalized)
.filter(([key]) => key !== "$defs" && key !== "definitions")
.map(([key, child]) => {
if (schemaMaps.has(key) && record(child))
return [
key,
Object.fromEntries(
Object.entries(child).map(([name, schema]) => [
name,
visit(schema, active),
]),
),
];
if (schemaArrays.has(key) && Array.isArray(child))
return [key, child.map((schema) => visit(schema, active))];
if (schemaChildren.has(key))
return [
key,
Array.isArray(child)
? child.map((schema) => visit(schema, active))
: visit(child, active),
];
return [key, child];
}),
);
};
return visit(root, new Set());
};