Files
reverse-engineering-anything/src/contracts/compactInputSchema.ts
T
Christopher Pruijsen 5b953e13c4 fix(mcp): keep detail guidance in compact input schemas (#8034)
Compact input schemas are the default advertisement since #5975, and the
profile drops every property description. On 23 of the 31 tools with a
guided `detail` option, including inspect_artifact and inspect_macho,
`detail` is advertised as a bare complete|summary enum, so callers take the
complete default. On a universal macOS app that reply was about 699k
characters for inspect_artifact and 336k for inspect_macho, against about
4.4k and 3.2k with summary.

Keep the canonical description of a root `detail` property in the inline
compact input form. The reduced form already keeps first-level guidance, and
compact output schemas are unchanged. Advertised compact input schemas grow
from 127,616 to 133,358 bytes across 233 tools; the largest schema stays at
4,107 bytes, within the 13,312-byte budget.

The new boundary test compares `detail` guidance between the full and compact
tools/list and fails before the change.
2026-11-04 19:14:52 +08:00

112 lines
3.9 KiB
TypeScript

import {
COMPACT_SCHEMA_MAX_DEPTH,
hasGatewayIncompatibleShape,
inlineWithinBudget,
isSchemaObject,
reduceToShallowProperties,
schemaDepth,
serializedBytes,
stripAnnotations,
typeArrayItemUnions,
} from "./compactJsonSchema.js";
// Kimi expands schema-local references before sending tools to Moonshot.
// Compact advertisements must fit after expansion, independently of client
// reference handling. Canonical server-side validation remains unchanged.
export type AdvertisedInputSchemaPresentation = "inline" | "reduced";
/** Budget selected by the caller for compact MCP input-schema presentation. */
export interface CompactInputSchemaPresentation {
readonly budgetBytes: number;
}
export interface CompactedInputSchema {
readonly schema: Record<string, unknown>;
readonly presentation: AdvertisedInputSchemaPresentation;
}
const INPUT_REDUCED_ANNOTATION =
"Advertised in reduced form by the compact input schema profile; the server still validates complete canonical inputs.";
// `detail` selects between complete Evidence and a bounded summary. Without
// its guidance, callers default to complete replies that can exceed context.
const retainedRootPropertyDescriptions = ["detail"];
/** Restore the description of root properties whose choice bounds the reply. */
const retainRootPropertyGuidance = (
schema: Record<string, unknown>,
stripped: Record<string, unknown>,
): Record<string, unknown> => {
if (
!isSchemaObject(schema.properties) ||
!isSchemaObject(stripped.properties)
)
return stripped;
const properties = { ...stripped.properties };
for (const name of retainedRootPropertyDescriptions) {
const source = schema.properties[name];
const kept = properties[name];
if (
isSchemaObject(source) &&
isSchemaObject(kept) &&
typeof source.description === "string"
)
properties[name] = { ...kept, description: source.description };
}
return { ...stripped, properties };
};
/**
* Render an advertised input schema within a per-schema byte budget for
* providers that validate the literal serialized schema size. Validation
* keywords and the canonical contract are preserved; only
* annotation prose and, for structurally oversized schemas, nested
* validation detail leave the advertised form.
*/
export const compactAdvertisedInputSchema = (
schema: Record<string, unknown>,
budgetBytes: number,
): CompactedInputSchema => {
const expanded = inlineWithinBudget(stripAnnotations(schema), budgetBytes);
const inline =
expanded === undefined
? undefined
: typeArrayItemUnions(retainRootPropertyGuidance(schema, expanded));
if (
inline !== undefined &&
serializedBytes(inline) <= budgetBytes &&
schemaDepth(inline) <= COMPACT_SCHEMA_MAX_DEPTH &&
!hasGatewayIncompatibleShape(inline)
)
return { schema: inline, presentation: "inline" };
const reduced = reduceToShallowProperties(schema, INPUT_REDUCED_ANNOTATION);
if (serializedBytes(reduced) <= budgetBytes)
return { schema: reduced, presentation: "reduced" };
// A future contract with an extreme root-property count still advertises
// an honest, budget-fitting shape instead of an oversized one.
return {
schema: reduceToShallowProperties(
{
type: "object",
description:
(typeof schema.description === "string"
? `${schema.description} `
: "") + INPUT_REDUCED_ANNOTATION,
},
INPUT_REDUCED_ANNOTATION,
),
presentation: "reduced",
};
};
/** Return the JSON schema clients receive for the selected input presentation. */
export const presentAdvertisedInputSchema = (
schema: Record<string, unknown>,
compactPresentation: CompactInputSchemaPresentation | undefined,
): Record<string, unknown> =>
compactPresentation === undefined
? schema
: compactAdvertisedInputSchema(schema, compactPresentation.budgetBytes)
.schema;