import { constants as bufferConstants } from "node:buffer"; import type { JsonValue } from "../domain/jsonValue.js"; import { bufferedJsonParts, jsonParts } from "../domain/jsonSerialization.js"; 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; const TEXT_RESULT_ENVELOPE_BYTES = Buffer.byteLength( JSON.stringify({ content: [{ type: "text", text: "" }] }), ); /** * MCP representations a result carries on the wire. Successful results repeat * the JSON as structured content and escaped text; error results carry text only. */ export type ToolResultRepresentations = "structured-and-text" | "text"; /** 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 the delivered JSON representations before allocating the complete text. */ export const encodeToolResult = ( value: JsonValue, budgetBytes: number, representations: ToolResultRepresentations = "structured-and-text", ): ToolResultEncoding => { const structured = representations === "structured-and-text"; const parts: string[] = []; let bytes = structured ? RESULT_ENVELOPE_BYTES : TEXT_RESULT_ENVELOPE_BYTES; let codeUnits = bytes; for (const part of bufferedJsonParts(jsonParts(value))) { const escapedPart = JSON.stringify(part); bytes += (structured ? Buffer.byteLength(part) : 0) + Buffer.byteLength(escapedPart) - 2; codeUnits += (structured ? part.length : 0) + 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 }; };