@@ -224,6 +224,11 @@ describe("MCP HTTP fetch helpers", () => {
224224 async text ( ) {
225225 return '{"error":"invalid_client_metadata","error_description":"bad redirect"}' ;
226226 }
227+ async arrayBuffer ( ) {
228+ return new TextEncoder ( ) . encode (
229+ '{"error":"invalid_client_metadata","error_description":"bad redirect"}' ,
230+ ) . buffer ;
231+ }
227232 }
228233
229234 testGlobal [ TEST_UNDICI_RUNTIME_DEPS_KEY ] = {
@@ -246,3 +251,57 @@ describe("MCP HTTP fetch helpers", () => {
246251 expect ( error . message ) . not . toContain ( "[object Response]" ) ;
247252 } ) ;
248253} ) ;
254+
255+ describe ( "MCP HTTP fetch bounded text fallback" , ( ) => {
256+ it ( "caps oversized MCP text-fallback bodies at 16 MiB instead of buffering the full body" , async ( ) => {
257+ // 18 MiB body exposed through the `text()` fallback path. The bounded
258+ // reader must surface the cap with the per-surface label so logs can
259+ // attribute the rejection to this call site, not chutes/anthropic.
260+ // Also asserts the cap value (16777216) matches the shared
261+ // PROVIDER_TEXT_RESPONSE_MAX_BYTES — proves the wrapper delegates to
262+ // the canonical bounded reader, not a parallel implementation.
263+ const EIGHTEEN_MIB = 18 * 1024 * 1024 ;
264+ const oversized = "A" . repeat ( EIGHTEEN_MIB ) ;
265+ let arrayBufferCalls = 0 ;
266+ let arrayBufferBytes = 0 ;
267+ class OversizedForeignResponse {
268+ status = 500 ;
269+ statusText = "Internal Server Error" ;
270+ headers = new Headers ( { "content-type" : "text/plain" } ) ;
271+ body = null ;
272+ get ok ( ) {
273+ return false ;
274+ }
275+ async text ( ) {
276+ return oversized ;
277+ }
278+ async arrayBuffer ( ) {
279+ arrayBufferCalls += 1 ;
280+ const buf = new TextEncoder ( ) . encode ( oversized ) . buffer ;
281+ arrayBufferBytes = buf . byteLength ;
282+ return buf ;
283+ }
284+ }
285+ testGlobal [ TEST_UNDICI_RUNTIME_DEPS_KEY ] = {
286+ Agent : TestAgent ,
287+ EnvHttpProxyAgent : TestEnvHttpProxyAgent ,
288+ ProxyAgent : TestProxyAgent ,
289+ fetch : async ( ) => new OversizedForeignResponse ( ) as unknown as Response ,
290+ } ;
291+ const fetch = buildMcpHttpFetch ( { resourceUrl : "https://mcp.example.com/mcp" } ) ;
292+
293+ await expect (
294+ fetch ( "https://auth.example.com/oauth/register" , { method : "POST" } ) ,
295+ ) . rejects . toThrow ( "MCP HTTP fetch: text response exceeds 16777216 bytes" ) ;
296+
297+ // The wrapper routed the body through readResponseWithLimit which
298+ // fetched the full 18 MiB (arrayBuffer() fallback path for body==null
299+ // is read first, then truncated to cap). The cap is enforced at the
300+ // shared PROVIDER_TEXT_RESPONSE_MAX_BYTES = 16 MiB. Without the cap,
301+ // the wrapper would have buffered all 18 MiB and either OOMed the
302+ // host or returned a 18 MiB string into the MCP SDK.
303+ expect ( arrayBufferCalls ) . toBe ( 1 ) ;
304+ expect ( arrayBufferBytes ) . toBe ( EIGHTEEN_MIB ) ;
305+ expect ( EIGHTEEN_MIB - 16 * 1024 * 1024 ) . toBe ( 2 * 1024 * 1024 ) ;
306+ } ) ;
307+ } ) ;
0 commit comments