fix(server): keep the event stream change independent of response size fields

The event stream change used the maxBytes and responseBytes fields of
McpHttpResponseError, and it carried the oversized-response tests that
belong to them. Master does not have these fields. This commit removes
that part, so the change applies to master alone:

- readMcpHttpResponse throws the size error without the size fields.
- The tool call failure does not copy responseBytes into the execution
  audit.
- The test file keeps only the progress test of this change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
pavel.guseynovandClaude Opus 5.5 committed 2026-11-07 11:31:09 +05:00
1 parent 996e1b46b8
commit 6742569933
3 files changed
+2 -57

No files matched your search

+1 -55
View File
@@ -8,7 +8,7 @@ import {
readMcpHttpResponse,
} from "../services/mcp-http.js";
describe("bounded MCP responses", () => {
describe("MCP response progress", () => {
it("retains consumed SSE progress when a partial event fails and releases the reader", async () => {
const partial = 'data: {"jsonrpc":"2.0","method":"notifications/progress"}\n\ndata: {"jsonrpc":';
const failure = new Error("peer reset");
@@ -26,60 +26,6 @@ describe("bounded MCP responses", () => {
expect(progress).toEqual({ bytesReceived: Buffer.byteLength(partial), chunksReceived: 1, eventsReceived: 1, matchedResponse: false, ended: false });
expect(body.locked).toBe(false);
});
for (const contentType of ["application/json", "text/event-stream"]) {
it(`rejects declared ${contentType} overflow before consuming the body`, async () => {
let reads = 0;
let cancelled = false;
const body = new ReadableStream<Uint8Array>({
pull(controller) { reads += 1; controller.enqueue(new Uint8Array(1)); },
cancel() { cancelled = true; },
}, { highWaterMark: 0 });
const response = new Response(body, {
headers: { "content-type": contentType, "content-length": "101" },
});
await expect(readMcpHttpResponse(response, "request", { maxBytes: 100 })).rejects.toMatchObject({
name: "McpHttpResponseError", reason: "too_large", maxBytes: 100, responseBytes: 101,
});
expect(reads).toBe(0);
expect(cancelled).toBe(true);
expect(body.locked).toBe(false);
});
it(`cancels ${contentType} at the first streamed byte beyond the limit`, async () => {
let reads = 0;
let cancelled = false;
const body = new ReadableStream<Uint8Array>({
pull(controller) { reads += 1; controller.enqueue(new Uint8Array(reads === 1 ? 100 : 1)); },
cancel() { cancelled = true; },
}, { highWaterMark: 0 });
await expect(readMcpHttpResponse(new Response(body, {
headers: { "content-type": contentType, "content-length": "invalid" },
}), "request", { maxBytes: 100 })).rejects.toMatchObject({
reason: "too_large", maxBytes: 100, responseBytes: 101,
});
expect(reads).toBe(2);
expect(cancelled).toBe(true);
expect(body.locked).toBe(false);
});
it(`reports UTF-8 byte size for buffered ${contentType} overflow`, async () => {
const response = new Response(null, { headers: { "content-type": contentType } });
response.text = async () => "\u00e9".repeat(51);
await expect(readMcpHttpResponse(response, "request", { maxBytes: 100 })).rejects.toMatchObject({
reason: "too_large", maxBytes: 100, responseBytes: 102,
});
});
it(`accepts ${contentType} at exactly the byte limit`, async () => {
const payload = { jsonrpc: "2.0", id: "request", result: {} };
const json = JSON.stringify(payload);
const body = contentType === "text/event-stream" ? `data: ${json}\n\n` : json;
expect(await readMcpHttpResponse(new Response(body, {
headers: { "content-type": contentType, "content-length": String(Buffer.byteLength(body)) },
}), "request", { maxBytes: Buffer.byteLength(body) })).toEqual(payload);
});
}
});
describe("mcpHttpRequestHeaders", () => {
+1 -1
View File
@@ -238,7 +238,7 @@ export async function readMcpHttpResponse(
progress.bytesReceived = bytes;
if (value) progress.chunksReceived += 1;
progress.ended = done;
if (bytes > maxBytes) throw new McpHttpResponseError("too_large", "MCP response exceeded the size limit", { maxBytes, responseBytes: bytes });
if (bytes > maxBytes) throw new McpHttpResponseError("too_large", "MCP response exceeded the size limit");
buffer += decoder.decode(value, { stream: !done });
if (isStream) {
// Normalize CRLF after concatenating chunks, including split CR/LF pairs.
-1
View File
@@ -6666,7 +6666,6 @@ export function createToolGatewayService(
}
if (error instanceof McpHttpResponseError) {
execution.failureKind = "protocol_failure";
if (execution.response && error.responseBytes != null) execution.response.bodySizeBytes = error.responseBytes;
const failure = error.reason === "too_large" ? responseTooLargeError()
: error.reason === "malformed_response" ? malformedRemoteMcpResponse()
: new ToolGatewayHttpError(502, "Remote MCP server returned invalid JSON", "mcp_remote_invalid_json");