Files
reverse-engineering-anything/src/server/toolResultEncoding.ts
T
Ysopwnable a1ed4232de fix(mcp): retain complete application results across response limits
Retain complete authenticated application Evidence when the repeated MCP representation exceeds the default receive frame or Node string limit. Return a typed transport constraint, keep useful followup queries available, and stream full canonical bundles to atomic destinations.

Verify complete Notion and Obsidian workflows on Linux and native Windows, full CLI/Evidence parity, and real large Chrome screenshot recovery. Preserve complete success contracts and caller-selected evidence paths.

Refs #1065, #1053.
2026-10-08 23:46:02 +08:00

56 lines
2.0 KiB
TypeScript

import { STDIO_DEFAULT_MAX_BUFFER_SIZE } from "@modelcontextprotocol/server";
import { constants as bufferConstants } from "node:buffer";
import type { JsonValue } from "../domain/jsonValue.js";
import { bufferedJsonParts, jsonParts } from "../domain/jsonSerialization.js";
// Leave room for the SDK's JSON-RPC envelope and protocol metadata. This budget
// follows the pinned client's actual default framing limit, not a result-row cap.
export const MCP_RESULT_BUDGET_BYTES = STDIO_DEFAULT_MAX_BUFFER_SIZE - 1024;
export const MCP_RESULT_STRING_LIMIT = bufferConstants.MAX_STRING_LENGTH - 1024;
const RESULT_ENVELOPE_BYTES =
Buffer.byteLength(
JSON.stringify({
content: [{ type: "text", text: "" }],
structuredContent: null,
}),
) - 4;
/** Result of encoding the complete repeated MCP representations within a wire budget. */
export type ToolResultEncoding =
| { readonly ok: true; readonly text: string; readonly bytes: number }
| {
readonly ok: false;
readonly bytesAtLeast: number;
readonly codeUnitsAtLeast: number;
readonly constraint: "receive-buffer" | "string-length";
};
/** Account for structured JSON and escaped text before allocating the complete text. */
export const encodeToolResult = (
value: JsonValue,
budgetBytes = MCP_RESULT_BUDGET_BYTES,
): ToolResultEncoding => {
const parts: string[] = [];
let bytes = RESULT_ENVELOPE_BYTES;
let codeUnits = RESULT_ENVELOPE_BYTES;
for (const part of bufferedJsonParts(jsonParts(value))) {
const escapedPart = JSON.stringify(part);
bytes += Buffer.byteLength(part) + Buffer.byteLength(escapedPart) - 2;
codeUnits += part.length + escapedPart.length - 2;
if (bytes > budgetBytes || codeUnits > MCP_RESULT_STRING_LIMIT)
return {
ok: false,
bytesAtLeast: bytes,
codeUnitsAtLeast: codeUnits,
constraint:
codeUnits > MCP_RESULT_STRING_LIMIT
? "string-length"
: "receive-buffer",
};
parts.push(part);
}
return { ok: true, text: parts.join(""), bytes };
};