@@ -150,7 +150,7 @@ TEST_F(McpFilterTest, RejectModeRejectsNonJsonRpc) {
150150 Buffer::OwnedImpl buffer (body);
151151 Buffer::OwnedImpl decoding_buffer;
152152
153- EXPECT_CALL (decoder_callbacks_, addDecodedData (_, true ))
153+ EXPECT_CALL (decoder_callbacks_, addDecodedData (_, false ))
154154 .WillOnce ([&decoding_buffer](Buffer::Instance& data, bool ) { decoding_buffer.move (data); });
155155 EXPECT_CALL (decoder_callbacks_, decodingBuffer ()).WillRepeatedly (Return (&decoding_buffer));
156156
@@ -192,7 +192,7 @@ TEST_F(McpFilterTest, DynamicMetadataSet) {
192192 Buffer::OwnedImpl buffer (json);
193193 Buffer::OwnedImpl decoding_buffer;
194194
195- EXPECT_CALL (decoder_callbacks_, addDecodedData (_, true ))
195+ EXPECT_CALL (decoder_callbacks_, addDecodedData (_, false ))
196196 .WillOnce ([&decoding_buffer](Buffer::Instance& data, bool ) { decoding_buffer.move (data); });
197197 EXPECT_CALL (decoder_callbacks_, decodingBuffer ()).WillRepeatedly (Return (&decoding_buffer));
198198
@@ -241,7 +241,7 @@ TEST_F(McpFilterTest, WrongJsonRpcVersion) {
241241 Buffer::OwnedImpl buffer (wrong_version);
242242 Buffer::OwnedImpl decoding_buffer;
243243
244- EXPECT_CALL (decoder_callbacks_, addDecodedData (_, true ))
244+ EXPECT_CALL (decoder_callbacks_, addDecodedData (_, false ))
245245 .WillOnce ([&decoding_buffer](Buffer::Instance& data, bool ) { decoding_buffer.move (data); });
246246 EXPECT_CALL (decoder_callbacks_, decodingBuffer ()).WillRepeatedly (Return (&decoding_buffer));
247247
@@ -282,6 +282,233 @@ TEST_F(McpFilterTest, PostWithWrongContentType) {
282282 EXPECT_EQ (Http::FilterHeadersStatus::Continue, filter_->decodeHeaders (headers, false ));
283283}
284284
285+ // Test default max body size configuration
286+ TEST_F (McpFilterTest, DefaultMaxBodySizeIsEightKB) {
287+ envoy::extensions::filters::http::mcp::v3::Mcp proto_config;
288+ // Don't set max_request_body_size, should default to 8KB
289+ auto config = std::make_shared<McpFilterConfig>(proto_config);
290+ EXPECT_EQ (8192u , config->maxRequestBodySize ());
291+ }
292+
293+ // Test custom max body size configuration
294+ TEST_F (McpFilterTest, CustomMaxBodySizeConfiguration) {
295+ envoy::extensions::filters::http::mcp::v3::Mcp proto_config;
296+ proto_config.mutable_max_request_body_size ()->set_value (16384 );
297+ auto config = std::make_shared<McpFilterConfig>(proto_config);
298+ EXPECT_EQ (16384u , config->maxRequestBodySize ());
299+ }
300+
301+ // Test disabled max body size (0 = no limit)
302+ TEST_F (McpFilterTest, DisabledMaxBodySizeConfiguration) {
303+ envoy::extensions::filters::http::mcp::v3::Mcp proto_config;
304+ proto_config.mutable_max_request_body_size ()->set_value (0 );
305+ auto config = std::make_shared<McpFilterConfig>(proto_config);
306+ EXPECT_EQ (0u , config->maxRequestBodySize ());
307+ }
308+
309+ // Test request body under the limit succeeds
310+ TEST_F (McpFilterTest, RequestBodyUnderLimitSucceeds) {
311+ envoy::extensions::filters::http::mcp::v3::Mcp proto_config;
312+ proto_config.mutable_max_request_body_size ()->set_value (1024 ); // 1KB limit
313+ config_ = std::make_shared<McpFilterConfig>(proto_config);
314+ filter_ = std::make_unique<McpFilter>(config_);
315+ filter_->setDecoderFilterCallbacks (decoder_callbacks_);
316+
317+ Http::TestRequestHeaderMapImpl headers{{" :method" , " POST" },
318+ {" content-type" , " application/json" },
319+ {" accept" , " application/json" },
320+ {" accept" , " text/event-stream" }};
321+
322+ EXPECT_CALL (decoder_callbacks_, setDecoderBufferLimit (1024 ));
323+ filter_->decodeHeaders (headers, false );
324+
325+ // Create a JSON-RPC body that's under 1KB
326+ std::string json = R"( {"jsonrpc": "2.0", "method": "test", "params": {"key": "value"}, "id": 1})" ;
327+ Buffer::OwnedImpl buffer (json);
328+ Buffer::OwnedImpl decoding_buffer;
329+
330+ EXPECT_CALL (decoder_callbacks_, addDecodedData (_, false ))
331+ .WillOnce ([&decoding_buffer](Buffer::Instance& data, bool ) { decoding_buffer.move (data); });
332+ EXPECT_CALL (decoder_callbacks_, decodingBuffer ()).WillRepeatedly (Return (&decoding_buffer));
333+ EXPECT_CALL (decoder_callbacks_.stream_info_ , setDynamicMetadata (" mcp_proxy" , _));
334+
335+ EXPECT_EQ (Http::FilterDataStatus::Continue, filter_->decodeData (buffer, true ));
336+ }
337+
338+ // Test request body exceeding the limit gets 413
339+ TEST_F (McpFilterTest, RequestBodyExceedingLimitRejected) {
340+ envoy::extensions::filters::http::mcp::v3::Mcp proto_config;
341+ proto_config.mutable_max_request_body_size ()->set_value (100 ); // Very small limit
342+ config_ = std::make_shared<McpFilterConfig>(proto_config);
343+ filter_ = std::make_unique<McpFilter>(config_);
344+ filter_->setDecoderFilterCallbacks (decoder_callbacks_);
345+
346+ Http::TestRequestHeaderMapImpl headers{{" :method" , " POST" },
347+ {" content-type" , " application/json" },
348+ {" accept" , " application/json" },
349+ {" accept" , " text/event-stream" }};
350+
351+ EXPECT_CALL (decoder_callbacks_, setDecoderBufferLimit (100 ));
352+ filter_->decodeHeaders (headers, false );
353+
354+ // Create a JSON body that exceeds 100 bytes
355+ std::string json =
356+ R"( {"jsonrpc": "2.0", "method": "test", "params": {"key": "value", "longkey": "this is a very long string to exceed the limit"}, "id": 1})" ;
357+ Buffer::OwnedImpl buffer (json);
358+ Buffer::OwnedImpl decoding_buffer;
359+
360+ EXPECT_CALL (decoder_callbacks_, addDecodedData (_, false ))
361+ .WillOnce ([&decoding_buffer](Buffer::Instance& data, bool ) { decoding_buffer.move (data); });
362+ EXPECT_CALL (decoder_callbacks_, decodingBuffer ()).WillRepeatedly (Return (&decoding_buffer));
363+
364+ EXPECT_CALL (decoder_callbacks_,
365+ sendLocalReply (Http::Code::PayloadTooLarge,
366+ testing::HasSubstr (" Request body size exceeds maximum allowed size" ),
367+ _, _, " mcp_filter_body_too_large" ));
368+
369+ EXPECT_EQ (Http::FilterDataStatus::StopIterationNoBuffer, filter_->decodeData (buffer, true ));
370+ }
371+
372+ // Test request body with limit disabled (0 = no limit) allows large bodies
373+ TEST_F (McpFilterTest, RequestBodyWithDisabledLimitAllowsLargeBodies) {
374+ envoy::extensions::filters::http::mcp::v3::Mcp proto_config;
375+ proto_config.mutable_max_request_body_size ()->set_value (0 ); // Disable limit
376+ config_ = std::make_shared<McpFilterConfig>(proto_config);
377+ filter_ = std::make_unique<McpFilter>(config_);
378+ filter_->setDecoderFilterCallbacks (decoder_callbacks_);
379+
380+ Http::TestRequestHeaderMapImpl headers{{" :method" , " POST" },
381+ {" content-type" , " application/json" },
382+ {" accept" , " application/json" },
383+ {" accept" , " text/event-stream" }};
384+
385+ // Should NOT call setDecoderBufferLimit when limit is 0
386+ EXPECT_CALL (decoder_callbacks_, setDecoderBufferLimit (_)).Times (0 );
387+ filter_->decodeHeaders (headers, false );
388+
389+ // Create a large JSON-RPC body
390+ std::string large_data (50000 , ' x' ); // 50KB of data
391+ std::string json = R"( {"jsonrpc": "2.0", "method": "test", "params": {"data": ")" + large_data +
392+ R"( "}, "id": 1})" ;
393+ Buffer::OwnedImpl buffer (json);
394+ Buffer::OwnedImpl decoding_buffer;
395+
396+ EXPECT_CALL (decoder_callbacks_, addDecodedData (_, false ))
397+ .WillOnce ([&decoding_buffer](Buffer::Instance& data, bool ) { decoding_buffer.move (data); });
398+ EXPECT_CALL (decoder_callbacks_, decodingBuffer ()).WillRepeatedly (Return (&decoding_buffer));
399+ EXPECT_CALL (decoder_callbacks_.stream_info_ , setDynamicMetadata (" mcp_proxy" , _));
400+
401+ // Should succeed even with large body
402+ EXPECT_EQ (Http::FilterDataStatus::Continue, filter_->decodeData (buffer, true ));
403+ }
404+
405+ // Test request body exactly at the limit succeeds
406+ TEST_F (McpFilterTest, RequestBodyExactlyAtLimitSucceeds) {
407+ envoy::extensions::filters::http::mcp::v3::Mcp proto_config;
408+ proto_config.mutable_max_request_body_size ()->set_value (100 ); // 100 byte limit
409+ config_ = std::make_shared<McpFilterConfig>(proto_config);
410+ filter_ = std::make_unique<McpFilter>(config_);
411+ filter_->setDecoderFilterCallbacks (decoder_callbacks_);
412+
413+ Http::TestRequestHeaderMapImpl headers{{" :method" , " POST" },
414+ {" content-type" , " application/json" },
415+ {" accept" , " application/json" },
416+ {" accept" , " text/event-stream" }};
417+
418+ EXPECT_CALL (decoder_callbacks_, setDecoderBufferLimit (100 ));
419+ filter_->decodeHeaders (headers, false );
420+
421+ // Create a JSON body that's exactly 100 bytes
422+ std::string json =
423+ R"( {"jsonrpc": "2.0", "method": "testMethod", "params": {"key": "val"}, "id": 1})" ; // 81
424+ // bytes
425+ // Pad to exactly 100 bytes
426+ while (json.size () < 100 ) {
427+ json.insert (json.size () - 1 , " " );
428+ }
429+ json = json.substr (0 , 100 );
430+
431+ Buffer::OwnedImpl buffer (json);
432+ Buffer::OwnedImpl decoding_buffer;
433+
434+ EXPECT_CALL (decoder_callbacks_, addDecodedData (_, false ))
435+ .WillOnce ([&decoding_buffer](Buffer::Instance& data, bool ) { decoding_buffer.move (data); });
436+ EXPECT_CALL (decoder_callbacks_, decodingBuffer ()).WillRepeatedly (Return (&decoding_buffer));
437+
438+ // Should NOT be rejected
439+ EXPECT_CALL (decoder_callbacks_, sendLocalReply (Http::Code::PayloadTooLarge, _, _, _, _)).Times (0 );
440+
441+ // Note: This might fail JSON parsing due to padding, but should not trigger size limit
442+ filter_->decodeData (buffer, true );
443+ }
444+
445+ // Test that buffer limit is set for valid MCP POST requests
446+ TEST_F (McpFilterTest, BufferLimitSetForValidMcpPostRequest) {
447+ envoy::extensions::filters::http::mcp::v3::Mcp proto_config;
448+ proto_config.mutable_max_request_body_size ()->set_value (8192 );
449+ config_ = std::make_shared<McpFilterConfig>(proto_config);
450+ filter_ = std::make_unique<McpFilter>(config_);
451+ filter_->setDecoderFilterCallbacks (decoder_callbacks_);
452+
453+ Http::TestRequestHeaderMapImpl headers{{" :method" , " POST" },
454+ {" content-type" , " application/json" },
455+ {" accept" , " application/json" },
456+ {" accept" , " text/event-stream" }};
457+
458+ EXPECT_CALL (decoder_callbacks_, setDecoderBufferLimit (8192 ));
459+ EXPECT_EQ (Http::FilterHeadersStatus::StopIteration, filter_->decodeHeaders (headers, false ));
460+ }
461+
462+ // Test that buffer limit is NOT set when limit is disabled
463+ TEST_F (McpFilterTest, BufferLimitNotSetWhenDisabled) {
464+ envoy::extensions::filters::http::mcp::v3::Mcp proto_config;
465+ proto_config.mutable_max_request_body_size ()->set_value (0 ); // Disabled
466+ config_ = std::make_shared<McpFilterConfig>(proto_config);
467+ filter_ = std::make_unique<McpFilter>(config_);
468+ filter_->setDecoderFilterCallbacks (decoder_callbacks_);
469+
470+ Http::TestRequestHeaderMapImpl headers{{" :method" , " POST" },
471+ {" content-type" , " application/json" },
472+ {" accept" , " application/json" },
473+ {" accept" , " text/event-stream" }};
474+
475+ EXPECT_CALL (decoder_callbacks_, setDecoderBufferLimit (_)).Times (0 );
476+ EXPECT_EQ (Http::FilterHeadersStatus::StopIteration, filter_->decodeHeaders (headers, false ));
477+ }
478+
479+ // Test body size check in PASS_THROUGH mode
480+ TEST_F (McpFilterTest, BodySizeLimitInPassThroughMode) {
481+ envoy::extensions::filters::http::mcp::v3::Mcp proto_config;
482+ proto_config.set_traffic_mode (envoy::extensions::filters::http::mcp::v3::Mcp::PASS_THROUGH);
483+ proto_config.mutable_max_request_body_size ()->set_value (50 ); // Small limit
484+ config_ = std::make_shared<McpFilterConfig>(proto_config);
485+ filter_ = std::make_unique<McpFilter>(config_);
486+ filter_->setDecoderFilterCallbacks (decoder_callbacks_);
487+
488+ Http::TestRequestHeaderMapImpl headers{{" :method" , " POST" },
489+ {" content-type" , " application/json" },
490+ {" accept" , " application/json" },
491+ {" accept" , " text/event-stream" }};
492+
493+ EXPECT_CALL (decoder_callbacks_, setDecoderBufferLimit (50 ));
494+ filter_->decodeHeaders (headers, false );
495+
496+ // Large body should be rejected even in PASS_THROUGH mode
497+ std::string json =
498+ R"( {"jsonrpc": "2.0", "method": "test", "params": {"key": "value with lots of data"}, "id": 1})" ;
499+ Buffer::OwnedImpl buffer (json);
500+ Buffer::OwnedImpl decoding_buffer;
501+
502+ EXPECT_CALL (decoder_callbacks_, addDecodedData (_, false ))
503+ .WillOnce ([&decoding_buffer](Buffer::Instance& data, bool ) { decoding_buffer.move (data); });
504+ EXPECT_CALL (decoder_callbacks_, decodingBuffer ()).WillRepeatedly (Return (&decoding_buffer));
505+
506+ EXPECT_CALL (decoder_callbacks_,
507+ sendLocalReply (Http::Code::PayloadTooLarge, _, _, _, " mcp_filter_body_too_large" ));
508+
509+ EXPECT_EQ (Http::FilterDataStatus::StopIterationNoBuffer, filter_->decodeData (buffer, true ));
510+ }
511+
285512// Test route cache is NOT cleared by default when metadata is set
286513TEST_F (McpFilterTest, RouteCacheNotClearedByDefault) {
287514 Http::TestRequestHeaderMapImpl headers{{" :method" , " POST" },
@@ -295,7 +522,7 @@ TEST_F(McpFilterTest, RouteCacheNotClearedByDefault) {
295522 Buffer::OwnedImpl buffer (json);
296523 Buffer::OwnedImpl decoding_buffer;
297524
298- EXPECT_CALL (decoder_callbacks_, addDecodedData (_, true ))
525+ EXPECT_CALL (decoder_callbacks_, addDecodedData (_, false ))
299526 .WillOnce ([&decoding_buffer](Buffer::Instance& data, bool ) { decoding_buffer.move (data); });
300527 EXPECT_CALL (decoder_callbacks_, decodingBuffer ()).WillRepeatedly (Return (&decoding_buffer));
301528
@@ -323,7 +550,7 @@ TEST_F(McpFilterTest, RouteCacheNotClearedWhenDisabled) {
323550 Buffer::OwnedImpl buffer (json);
324551 Buffer::OwnedImpl decoding_buffer;
325552
326- EXPECT_CALL (decoder_callbacks_, addDecodedData (_, true ))
553+ EXPECT_CALL (decoder_callbacks_, addDecodedData (_, false ))
327554 .WillOnce ([&decoding_buffer](Buffer::Instance& data, bool ) { decoding_buffer.move (data); });
328555 EXPECT_CALL (decoder_callbacks_, decodingBuffer ()).WillRepeatedly (Return (&decoding_buffer));
329556
@@ -351,7 +578,7 @@ TEST_F(McpFilterTest, RouteCacheClearedWhenExplicitlyEnabled) {
351578 Buffer::OwnedImpl buffer (json);
352579 Buffer::OwnedImpl decoding_buffer;
353580
354- EXPECT_CALL (decoder_callbacks_, addDecodedData (_, true ))
581+ EXPECT_CALL (decoder_callbacks_, addDecodedData (_, false ))
355582 .WillOnce ([&decoding_buffer](Buffer::Instance& data, bool ) { decoding_buffer.move (data); });
356583 EXPECT_CALL (decoder_callbacks_, decodingBuffer ()).WillRepeatedly (Return (&decoding_buffer));
357584
0 commit comments