Files
reverse-engineering-anything/src/server/toolResultEncoding.test.ts
T
Kaoru0822andClaude Opus 5.5 7b51121f7c fix(mcp): measure tool errors against their text-only delivery
Since #1277 an error result reaches the client without structuredContent,
but toErrorToolResult and the transport recovery still budgeted it as
structured JSON plus escaped text. An error whose text fit between about
half and all of the response budget was replaced by resource_constraint,
and without Evidence retention the original diagnostic was lost.

Count only the escaped text for errors. The boundary, process-global and
real Ghidra large-result probes now use diagnostics that exceed the
text-only budget.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 14:03:12 +00:00

77 lines
2.9 KiB
TypeScript

import { describe, expect, it } from "vitest";
import type { JsonValue } from "../domain/jsonValue.js";
import { encodeToolResult } from "./toolResultEncoding.js";
const projectedResult = (candidate: JsonValue) => ({
content: [{ type: "text", text: JSON.stringify(candidate) }],
structuredContent: candidate,
});
describe("MCP result encoding budget", () => {
it.each([
{ result: { value: 1 } },
{ result: ["中文", "😀", "\ud800", '"\\\n'] },
{ result: "x".repeat(8191) + "😀" + '中文\n"'.repeat(20000) },
])("counts actual UTF-8 structured and escaped text bytes", (candidate) => {
const encoded = encodeToolResult(
candidate,
Buffer.byteLength(JSON.stringify(projectedResult(candidate))),
);
if (!encoded.ok) throw new Error("Expected complete encoding");
expect(encoded.text).toBe(JSON.stringify(candidate));
expect(encoded.bytes).toBe(
Buffer.byteLength(JSON.stringify(projectedResult(candidate))),
);
});
it("accounts for complete Evidence in structured and text representations", () => {
const normalized = { value: '中文"'.repeat(30000) };
const candidate = {
normalized_result: normalized,
raw_result: { observation: "distinct provider representation" },
};
const completeBytes = Buffer.byteLength(
JSON.stringify(projectedResult(candidate)),
);
expect(encodeToolResult(candidate, completeBytes).ok).toBe(true);
expect(encodeToolResult(candidate, completeBytes - 1)).toMatchObject({
ok: false,
bytesAtLeast: completeBytes,
});
});
it.each([
{ error: { code: "invalid_request", message: '中文"\n'.repeat(30000) } },
{ error: { message: "😀\ud800" } },
])("counts only escaped text for text-only error delivery", (candidate) => {
const textBytes = Buffer.byteLength(
JSON.stringify({
content: [{ type: "text", text: JSON.stringify(candidate) }],
}),
);
const encoded = encodeToolResult(candidate, textBytes, "text");
if (!encoded.ok) throw new Error("Expected complete encoding");
expect(encoded.text).toBe(JSON.stringify(candidate));
expect(encoded.bytes).toBe(textBytes);
expect(encodeToolResult(candidate, textBytes - 1, "text")).toMatchObject({
ok: false,
bytesAtLeast: textBytes,
});
});
it("stops before reading the remainder of an oversized result", () => {
const candidate: JsonValue = { text: "x".repeat(1000000) };
Object.defineProperty(candidate, "remainder", {
enumerable: true,
get() {
throw new Error("The remainder must not be read");
},
});
// Only an already-known first property is visited before exhausting the
// budget. The serializer's property discovery must not eagerly read values.
expect(() => encodeToolResult(candidate, 1000)).not.toThrow();
expect(encodeToolResult(candidate, 1000).ok).toBe(false);
});
});