Skip to content

fix(transport): remove manual Content-Length headers causing compression errors#5461

Open
bogdanmariusc10 wants to merge 1 commit into
mainfrom
5457-bug-content-length-mismatch-when-compression-enabled
Open

fix(transport): remove manual Content-Length headers causing compression errors#5461
bogdanmariusc10 wants to merge 1 commit into
mainfrom
5457-bug-content-length-mismatch-when-compression-enabled

Conversation

@bogdanmariusc10

Copy link
Copy Markdown
Collaborator

Pull Request

🔗 Related Issue

Closes #5457


📝 Summary

Fixes "Too much data for declared Content-Length" errors occurring when compression is enabled by removing manual Content-Length headers that are set before the compression middleware runs.

Root Cause: When COMPRESSION_ENABLED=true (default), the streamable HTTP transport and ValidationMiddleware were manually setting Content-Length headers based on uncompressed body sizes. The compression middleware then compressed the response body but could not update the already-sent headers, causing a mismatch between the declared size and actual transmitted bytes.

Solution:

  1. Remove manual Content-Length headers from transport layer (4 locations in streamablehttp_transport.py)
  2. Skip sanitization for compressed responses in ValidationMiddleware to prevent header corruption
  3. Let compression middleware set Content-Length correctly after compression

Impact: Eliminates client-side connection errors for responses larger than 500 bytes when compression is active, which was blocking production usage of MCP tool calling in Stage environment.


📏 Reviewability

  • This PR has one clear purpose
  • The linked issue is not labeled triage
  • Unrelated bugs or improvements are tracked in separate issues/PRs
  • Tests are included with the code they validate
  • If AI-assisted, I understand and can explain the generated changes

🏷️ Type of Change

  • Bug fix
  • Feature / Enhancement
  • Documentation
  • Refactor
  • Chore (deps, CI, tooling)
  • Other (describe below)

🧪 Verification

Automated Testing

Check Command Status
Lint suite make lint ✅ Pass
Unit tests make test ✅ Pass
Regression tests uv run pytest tests/unit/mcpgateway/transports/test_streamablehttp_compression_contentlength.py -v ✅ 6/6 pass
ValidationMiddleware tests uv run pytest tests/unit/mcpgateway/middleware/test_validation_middleware_contentlength.py -v ✅ 10/10 pass
Coverage ≥ 80% make coverage ✅ Pass

Test Coverage

New Regression Tests Added (16 total):

Transport Layer Tests (test_streamablehttp_compression_contentlength.py):

  • test_send_json_response_no_content_length - Verifies helper function doesn't set Content-Length
  • test_compression_middleware_sets_content_length_correctly - E2E flow with compression
  • test_loopback_rpc_routing_no_content_length - Static analysis of source code
  • test_redis_forwarded_response_no_content_length - Multi-worker forwarding path
  • test_content_length_with_compression_disabled - Behavior without compression
  • test_large_response_with_compression - Large payloads (10KB+)

ValidationMiddleware Tests (test_validation_middleware_contentlength.py):

  • test_validation_middleware_skips_gzip_compressed_responses
  • test_validation_middleware_skips_brotli_compressed_responses
  • test_validation_middleware_skips_zstd_compressed_responses
  • test_validation_middleware_skips_deflate_compressed_responses
  • test_validation_middleware_sanitizes_uncompressed_responses
  • test_validation_middleware_updates_content_length_only_if_modified
  • test_validation_middleware_handles_missing_content_encoding
  • test_validation_middleware_preserves_large_compressed_responses
  • test_validation_middleware_handles_empty_content_encoding
  • test_validation_middleware_case_insensitive_content_encoding

Files Modified

Core Fixes:

  • mcpgateway/transports/streamablehttp_transport.py - Removed 4 manual Content-Length locations
    • Line 1312: _send_streamable_http_json_response() helper
    • Line 4188: Loopback /rpc routing
    • Line 4256: Redis-forwarded responses
    • Line 4358: Local session owner routing
  • mcpgateway/middleware/validation_middleware.py - Skip compressed response sanitization (lines 273-276)

Test Files Added:

  • tests/unit/mcpgateway/transports/test_streamablehttp_compression_contentlength.py (395 lines)
  • tests/unit/mcpgateway/middleware/test_validation_middleware_contentlength.py (224 lines)

✅ Checklist

  • Code formatted (make black isort pre-commit)
  • Tests added/updated for changes
  • Documentation updated (if applicable)
  • No secrets or credentials committed

📓 Notes

Known Limitation

One low-priority location still has manual Content-Length:

  • mcpgateway/transports/mcp_ingress_mount.py:195 - 503 error responses

This is unlikely to trigger compression (error messages typically < 500 bytes) but should be addressed for consistency in a future PR.

Related Issues

This fix addresses the primary bug (Content-Length mismatch). A secondary bug exists where MCP sessions are not cleaned up on connection errors, causing cascading failures after the initial error. This is tracked separately and documented in Issue #5460.

Architecture Context

ASGI Middleware Execution Order (response path is reverse of registration):

Request  → ValidationMiddleware → CompressionMiddleware → App
Response ← ValidationMiddleware ← CompressionMiddleware ← App

On the response path:

  1. App generates uncompressed response
  2. CompressionMiddleware compresses body and sets Content-Length
  3. ValidationMiddleware must NOT modify compressed responses or headers

Testing Strategy

Unit tests verify:

  • Transport layer doesn't set Content-Length manually
  • Compression middleware correctly sets Content-Length after compression
  • ValidationMiddleware skips compressed responses
  • Static analysis confirms no manual Content-Length in source code

Integration testing (recommended post-deployment):

  • Full request/response cycle through middleware stack
  • Multi-worker environment with Redis forwarding
  • Session affinity routing with large responses
  • Different compression algorithms and response sizes

…ng compression errors

Fixes #5457

When compression is enabled (COMPRESSION_ENABLED=true), manually setting
Content-Length headers before the compression middleware runs causes
"Too much data for declared Content-Length" errors because the declared
size is uncompressed but the actual body is compressed.

Changes:
- Remove manual Content-Length from streamablehttp transport (4 locations)
- Skip sanitization of compressed responses in ValidationMiddleware
- Add 16 regression tests to prevent future occurrences

The compression middleware now correctly sets Content-Length after
compressing the response body, ensuring the header matches the actual
transmitted byte count.

Signed-off-by: Bogdan-Marius-Catanus <bogdan-marius.catanus@ibm.com>
@bogdanmariusc10 bogdanmariusc10 added the bug Something isn't working label Jul 1, 2026
@bogdanmariusc10 bogdanmariusc10 added the ica ICA related issues label Jul 1, 2026
@bogdanmariusc10 bogdanmariusc10 linked an issue Jul 1, 2026 that may be closed by this pull request
7 tasks
@bogdanmariusc10 bogdanmariusc10 added MUST P1: Non-negotiable, critical requirements without which the product is non-functional or unsafe api REST API Related item labels Jul 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api REST API Related item bug Something isn't working ica ICA related issues MUST P1: Non-negotiable, critical requirements without which the product is non-functional or unsafe

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG]: Content-Length mismatch when compression enabled

1 participant