Skip to content

Commit dfaba4d

Browse files
oratisclaude
andcommitted
feat(mcp): cap MCP tool output fed back to the model
A runaway MCP tool response could blow the context window unbounded. Truncate each MCP tool's text output at 50k chars (capMcpOutput) with a visible "[… N characters truncated]" notice. +2 tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent cdce610 commit dfaba4d

2 files changed

Lines changed: 29 additions & 1 deletion

File tree

packages/core/src/mcp/client.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { mkdtemp, rm } from 'node:fs/promises';
1111
import { tmpdir } from 'node:os';
1212
import { join } from 'node:path';
1313
import {
14+
capMcpOutput,
1415
connectAllMcpServers,
1516
connectMcpServer,
1617
expandMcpResourceRefs,
@@ -442,6 +443,19 @@ describe('parseHelperOutput', () => {
442443
});
443444
});
444445

446+
describe('capMcpOutput', () => {
447+
it('passes through output under the cap', () => {
448+
expect(capMcpOutput('short', 100)).toBe('short');
449+
});
450+
it('truncates over-long output with a notice', () => {
451+
const big = 'x'.repeat(120);
452+
const out = capMcpOutput(big, 100);
453+
expect(out.startsWith('x'.repeat(100))).toBe(true);
454+
expect(out).toMatch(/20 characters truncated/);
455+
expect(out).toMatch(/100-char cap/);
456+
});
457+
});
458+
445459
describe('parseResourceRefs', () => {
446460
it('finds @server:scheme://path references', () => {
447461
const refs = parseResourceRefs(

packages/core/src/mcp/client.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,20 @@ interface FinishableTransport extends Transport {
179179
finishAuth(code: string): Promise<void>;
180180
}
181181

182+
/** Max characters of MCP tool output fed back to the model (keeps a runaway
183+
* server response from blowing the context window). */
184+
export const MCP_OUTPUT_CAP = 50_000;
185+
186+
/** Truncate over-long MCP output with a visible notice. Exported for testing. */
187+
export function capMcpOutput(text: string, cap = MCP_OUTPUT_CAP): string {
188+
if (text.length <= cap) return text;
189+
const omitted = text.length - cap;
190+
return (
191+
text.slice(0, cap) +
192+
`\n\n[… ${omitted} characters truncated — MCP output exceeded the ${cap}-char cap]`
193+
);
194+
}
195+
182196
/**
183197
* Connect to one MCP server (stdio / http / sse). Returns a handle containing
184198
* the registered tools (qualified as `mcp__<server>__<tool>`).
@@ -264,7 +278,7 @@ export async function connectMcpServer(
264278
.map((c) => c.text ?? '')
265279
.join('\n') || '';
266280
return {
267-
content: textParts || '(MCP tool returned no text content)',
281+
content: textParts ? capMcpOutput(textParts) : '(MCP tool returned no text content)',
268282
isError: result.isError === true,
269283
data: { serverName, serverToolName: t.name },
270284
};

0 commit comments

Comments
 (0)