diff --git a/src/server/toolRegistrationOptions.ts b/src/server/toolRegistrationOptions.ts index c94b89db7..d7e1b189a 100644 --- a/src/server/toolRegistrationOptions.ts +++ b/src/server/toolRegistrationOptions.ts @@ -23,13 +23,9 @@ const advertisedInputSchema = (schema: z.ZodObject) => ({ validate: (input: unknown) => ({ value: input }), jsonSchema: { input: () => - compactJsonSchema( - describeProperties(closeObjectSchemas(inputJsonSchema(schema))), - ), + describeProperties(closeObjectSchemas(inputJsonSchema(schema))), output: () => - compactJsonSchema( - describeProperties(closeObjectSchemas(inputJsonSchema(schema))), - ), + describeProperties(closeObjectSchemas(inputJsonSchema(schema))), }, }, }); @@ -45,14 +41,9 @@ const advertisedOutputSchema = (schema: z.ZodObject) => { "~standard": { ...standard, jsonSchema: { - input: () => - compactJsonSchema( - closeObjectSchemas(converter({ target: "draft-2020-12" })), - ), + input: () => closeObjectSchemas(converter({ target: "draft-2020-12" })), output: () => - compactJsonSchema( - closeObjectSchemas(converter({ target: "draft-2020-12" })), - ), + closeObjectSchemas(converter({ target: "draft-2020-12" })), }, }, }; @@ -118,102 +109,3 @@ const describeValue = (value: unknown): unknown => { const isObject = (value: unknown): value is Readonly> => typeof value === "object" && value !== null && !Array.isArray(value); - -/** Replace repeated schema fragments with deterministic tool-local definitions. */ -const compactJsonSchema = ( - schema: Readonly>, -): Record => { - const { $schema: _dialect, ...schemaBody } = schema; - const counts = new Map(); - collectSchemas(schemaBody, counts, true); - const candidates = [...counts.entries()] - .filter(([, count]) => count > 1) - .sort( - ([left], [right]) => - right.length - left.length || left.localeCompare(right), - ) - .map(([encoded]) => encoded); - if (candidates.length === 0) return schemaBody; - let compacted: Record = schemaBody; - let compactedBytes = JSON.stringify(compacted).length; - const selected: Array = []; - for (const encoded of candidates) { - const proposed = [ - ...selected, - [encoded, `rea_${String(selected.length)}`] as const, - ]; - const candidate = compactWithDefinitions(schemaBody, proposed); - const candidateBytes = JSON.stringify(candidate).length; - if (candidateBytes >= compactedBytes) continue; - selected.push([encoded, `rea_${String(selected.length)}`]); - compacted = candidate; - compactedBytes = candidateBytes; - } - return compacted; -}; - -const compactWithDefinitions = ( - schemaBody: Readonly>, - candidates: readonly (readonly [string, string])[], -): Record => { - const names = new Map(candidates); - const definitions = Object.fromEntries( - candidates.map(([encoded, name]) => [ - name, - replaceSchemas(JSON.parse(encoded), names, encoded), - ]), - ); - const compacted = replaceSchemas(schemaBody, names); - if (!isObject(compacted)) - throw new TypeError("Compacted schema root is not an object"); - return { - ...compacted, - $defs: { - ...(isObject(schemaBody.$defs) ? schemaBody.$defs : {}), - ...definitions, - }, - }; -}; - -const collectSchemas = ( - value: unknown, - counts: Map, - root = false, - keywordContainer = false, -): void => { - if (Array.isArray(value)) { - for (const item of value) collectSchemas(item, counts); - return; - } - if (!isObject(value)) return; - if (!root && !keywordContainer && value.$ref === undefined) { - const encoded = JSON.stringify(value); - counts.set(encoded, (counts.get(encoded) ?? 0) + 1); - } - for (const [key, child] of Object.entries(value)) - collectSchemas(child, counts, false, schemaMapKeyword(key)); -}; - -const replaceSchemas = ( - value: unknown, - names: ReadonlyMap, - skipped?: string, - keywordContainer = false, -): unknown => { - if (Array.isArray(value)) - return value.map((item) => replaceSchemas(item, names, skipped)); - if (!isObject(value)) return value; - const encoded = JSON.stringify(value); - const name = names.get(encoded); - if (!keywordContainer && name !== undefined && encoded !== skipped) - return { $ref: `#/$defs/${name}` }; - return Object.fromEntries( - Object.entries(value).map(([key, child]) => [ - key, - replaceSchemas(child, names, skipped, schemaMapKeyword(key)), - ]), - ); -}; - -const schemaMapKeyword = (key: string): boolean => - key === "properties" || key === "patternProperties" || key === "$defs"; diff --git a/tests/integration/serverIntegration.test.ts b/tests/integration/serverIntegration.test.ts index 7d35a505a..1e3194593 100644 --- a/tests/integration/serverIntegration.test.ts +++ b/tests/integration/serverIntegration.test.ts @@ -210,17 +210,10 @@ describe("full MCP integration with multi-tool sequences", () => { expect(names).toContain("binary_overview"); expect(names).toContain("batch_decompile"); - expect( - Buffer.byteLength(JSON.stringify(listed), "utf8"), - ).toBeLessThanOrEqual(512 * 1024); const contracts = new Map( TOOL_CONTRACTS.map((contract) => [contract.name, contract]), ); for (const tool of listed.tools) { - expect( - Buffer.byteLength(JSON.stringify(tool), "utf8"), - tool.name, - ).toBeLessThanOrEqual(64 * 1024); const contract = contracts.get(tool.name); expect(contract, tool.name).toBeDefined(); expect(tool.title, tool.name).toBe(contract?.title);