diff --git a/src/mcp/runtime/truncate.ts b/src/mcp/runtime/truncate.ts index d192eee0..5568d8e6 100644 --- a/src/mcp/runtime/truncate.ts +++ b/src/mcp/runtime/truncate.ts @@ -3,11 +3,17 @@ * characters. OpenAPI-generated MCP servers regularly emit 30+ KB * descriptions; without truncation a single tool can blow up the system * prompt and break provider-side caches. + * + * The cap is a hard limit on the returned string: the truncation marker is + * reserved inside {@link MAX_MCP_TOOL_DESCRIPTION_LENGTH}, not appended past it. */ export const MAX_MCP_TOOL_DESCRIPTION_LENGTH = 2048; +const TRUNCATION_MARKER = "… [truncated]"; + export function truncateMcpToolDescription(value: string): string { if (value.length <= MAX_MCP_TOOL_DESCRIPTION_LENGTH) return value; - return value.slice(0, MAX_MCP_TOOL_DESCRIPTION_LENGTH) + "… [truncated]"; + const headLength = MAX_MCP_TOOL_DESCRIPTION_LENGTH - TRUNCATION_MARKER.length; + return value.slice(0, headLength) + TRUNCATION_MARKER; } diff --git a/tests/mcp/truncate.spec.ts b/tests/mcp/truncate.spec.ts new file mode 100644 index 00000000..ab205b72 --- /dev/null +++ b/tests/mcp/truncate.spec.ts @@ -0,0 +1,32 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { + MAX_MCP_TOOL_DESCRIPTION_LENGTH, + truncateMcpToolDescription, +} from "../../src/mcp/runtime/truncate.js"; + +test("truncateMcpToolDescription leaves descriptions at the limit unchanged", () => { + const atLimit = "x".repeat(MAX_MCP_TOOL_DESCRIPTION_LENGTH); + assert.equal(truncateMcpToolDescription(atLimit), atLimit); +}); + +test("truncateMcpToolDescription keeps one-over-limit output within the cap", () => { + const overLimit = "x".repeat(MAX_MCP_TOOL_DESCRIPTION_LENGTH + 1); + const result = truncateMcpToolDescription(overLimit); + + assert.ok( + result.length <= MAX_MCP_TOOL_DESCRIPTION_LENGTH, + `expected <= ${MAX_MCP_TOOL_DESCRIPTION_LENGTH}, got ${result.length}`, + ); + assert.equal(result.length, MAX_MCP_TOOL_DESCRIPTION_LENGTH); + assert.ok(result.endsWith("… [truncated]")); +}); + +test("truncateMcpToolDescription clamps very large descriptions to the limit", () => { + const huge = "x".repeat(30_000); + const result = truncateMcpToolDescription(huge); + + assert.equal(result.length, MAX_MCP_TOOL_DESCRIPTION_LENGTH); + assert.ok(result.endsWith("… [truncated]")); +});