Repository navigation
Streamable HTTP server rejects a mixed-case Content-Type with 415 #3670
Description
Activity
- addedbugSomething isn't workingSomething isn't workingv2Affects the v2 line (2.x on main)Affects the v2 line (2.x on main)v1Affects the v1.x maintenance lineAffects the v1.x maintenance line
on Oct 10, 2026 Confirmed against v2
mainat91941ed. The case-fold fixes the mixed-case initialize handshake. One useful conformance detail:Text/Plainis still the middleware 400, whileapplication/json-patch+jsonpasses that prefix check and reaches the transport's 415. So the 415 path remains reachable after the casing fix.Here are the tested patch and regression cases. The patched owning test path passes 6,101 tests (8 skipped, 1 expected failure), with 100% coverage and a clean strict-no-cover check. An independent replay passed the 17 hosting tests; a fresh installed-wheel consumer accepted five valid case/parameter variants and retained the 400 and 415 refusals.
Since you already have a fix ready and filed the issue as requested, this is supporting test material, not a competing PR. The patch leaves the modern request-handler path and
v1.xunchanged.I have the one-line fix ready (lowercase the parsed parts in
_check_content_type, mirroring the two siblings) plus a regression test intest_hosting_http.py(mixed-case variants incl. charset → 200,text/plain→ 400, suffix types still 415). Verified locally: RED on pristine (Application/JSON→ 415), GREEN after (owning file 12/12, server+transports 1456/1456, ruff clean). Could you assign me so I can open the PR per the contributing template?Confirming this on
mainat 4b5fe5a, since the report cites an older commit. I built the extension and called_check_content_typedirectly with a bareRequest, and the behaviour is as described'application/json' -> True 'application/json; charset=utf-8' -> True 'Application/JSON' -> False 'APPLICATION/JSON' -> False 'application/JSON; charset=utf-8' -> FalseSo every mixed-case spelling fails, and the failure is not limited to the bare media type.
Content-Typefield names and media types are both case-insensitive per RFC 9110 5.1 and 8.3.1.The fix is one
.lower()on the split parts, which matches what the two call sites in the same request path already do, and I checked that a non-JSON media type still returns False afterwards. I followed the policy in CONTRIBUTING and did not open a pull request, but the branch is on my fork atJingHao-Leon/python-sdk:fix/content-type-case-insensitivein case that is useful.One correction to the report, offered as a data point rather than a point. The divergence note in
tests/interaction/_requirements.pyforhosting:http:content-type-415says the transport's own 415 path is unreachable because the transport-security middleware answers 400 first. That still holds, and the fix does not change it. What the case sensitivity breaks is any request that reaches the transport without the middleware in front, and the conformance entry cannot see that because the entry is blocked by the same 400Hi — I'd like to be assigned to #3670 if possible. I have the one-line fix ready (lowercase the parsed parts in
_check_content_type, mirroring the sibling checks) plus tests: an interaction test with mixed-case variants incl. charset (200) and atext/plainnegative control (400), and direct unit tests of_check_content_type. My PR #3675 was auto-closed by the linked-issue rule; it should reopen on assignment. Thanks!
Release line
v2 (
main, verified at 91941ed).Description
StreamableHTTPServerTransport._check_content_typecompares the request's Content-Type media type case-sensitively, so a POST withContent-Type: Application/JSONis answered 415 and the handshake never completes. Media types are case-insensitive per RFC 9110 §8.3.1.Two things in the same request path already get this right, which is what makes the behaviour inconsistent rather than merely strict.
check_accept_headers(src/mcp/server/streamable_http.py:96) lowercases every Accept media type:and
TransportSecurityMiddleware._validate_content_type(src/mcp/server/transport_security.py:96) lowercases too:The middleware runs first and is the lenient one, so a mixed-case header passes it and then hits the strict comparison in the transport (
src/mcp/server/streamable_http.py:534):This also makes a note in your own conformance table wrong.
tests/interaction/_requirements.py:3136records a divergence forhosting:http:content-type-415sayingand the call site at line 569 carries
# pragma: no coveron that belief. A mixed-case Content-Type is the public entry point that reaches it.Example Code
Against the repo's own in-process harness on
mainat 91941ed, afteruv sync --frozen:Output:
The fix is to lowercase the parsed parts before comparing, the way the two siblings do.
Why it matters to me
Being straight about this, since you said that is what you use to prioritise: I do not have a client of my own that sends mixed-case, and I found this while checking the divergence notes in
_requirements.pyagainst the code. So the practical weight is not "my deployment is broken", it is that the server rejects a spec-conformant client at the handshake for a header-casing difference, and that your own conformance record currently states the opposite.A one-line fix and a test are ready if you want a pull request. #2916 was the same fix against v1 and was closed in the backlog sweep, so I am filing the issue first as you asked there rather than opening another pull request unprompted.
Python & MCP Python SDK
Python 3.12 on macOS 27 arm64, repository
mainat 91941ed withuv sync --frozen.