Hacktoberfest 2026: những issue maintainer đã đánh dấu cho tháng Mười, đang mở và phù hợp người mới. Xem issue Hacktoberfest

Streamable HTTP server rejects a mixed-case Content-Type with 415

Đang mở Phù hợp với người mới
#3,670 4 bình luận 0 reaction 0 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 1 ngày

@CRYPTONIKAV đang làm issue này rồi.

Từ ngày 10/10/2026.

  • #3675 của @CRYPTONIKAV — đã đóng, không merge

Đánh giá

Độ khó
2/5
Thời gian dự kiến
1-3 giờ
Mức phù hợp với người mới
75/100
Loại issue
Lỗi
Độ rõ ràng
Đặc tả rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
python
Lĩnh vực
backend

Hướng nghiên cứu

Bắt đầu với _check_content_type trong src/mcp/server/streamable_http.py, khoảng dòng 534, nơi kiểu media được so sánh phân biệt chữ hoa chữ thường, sau đó so sánh với check_accept_headers ở dòng 96 trong cùng tệp. Tái hiện bằng harness trong tiến trình tại tests/interaction/_connect.py với một Content-Type có chữ hoa chữ thường lẫn lộn. Hoàn thành khi Application/JSON nhận được phản hồi 200 và ghi chú chênh lệch hosting:http:content-type-415 trong tests/interaction/_requirements.py được cập nhật cho khớp.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

bug v1 v2
Release line

v2 (main, verified at 91941ed).

Description

StreamableHTTPServerTransport._check_content_type compares the request's Content-Type media type case-sensitively, so a POST with Content-Type: Application/JSON is 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:

    accept_types = [media_type.strip().split(";")[0].strip().lower() for media_type in accept_header.split(",")]

and TransportSecurityMiddleware._validate_content_type (src/mcp/server/transport_security.py:96) lowercases too:

        return content_type is not None and content_type.lower().startswith("application/json")

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):

        return any(part == CONTENT_TYPE_JSON for part in content_type_parts)

This also makes a note in your own conformance table wrong. tests/interaction/_requirements.py:3136 records a divergence for hosting:http:content-type-415 saying

The transport-security middleware rejects a non-JSON Content-Type with 400 'Invalid Content-Type header' before the request reaches the transport, so the transport's own 415 path is unreachable through any public entry point.

and the call site at line 569 carries # pragma: no cover on 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 main at 91941ed, after uv sync --frozen:

import pytest
from mcp_types import CallToolRequestParams, CallToolResult, ListToolsResult, PaginatedRequestParams, TextContent

from mcp.server import Server, ServerRequestContext
from tests.interaction._connect import base_headers, initialize_body, mounted_app

pytestmark = pytest.mark.anyio


def _server() -> Server:
    async def list_tools(ctx: ServerRequestContext, params: PaginatedRequestParams | None) -> ListToolsResult:
        return ListToolsResult(tools=[])

    async def call_tool(ctx: ServerRequestContext, params: CallToolRequestParams) -> CallToolResult:
        return CallToolResult(content=[TextContent(text="done")])

    return Server("hosted", on_list_tools=list_tools, on_call_tool=call_tool)


async def test_content_type_casing() -> None:
    async with mounted_app(_server()) as (http, _):
        for value in ("application/json", "Application/JSON", "APPLICATION/JSON", "application/json; charset=utf-8"):
            r = await http.post("/mcp", json=initialize_body(), headers=base_headers() | {"content-type": value})
            print(f"{value!r:45} -> {r.status_code} {r.text[:60]!r}")

Output:

'application/json'                            -> 200 'event: message\r\ndata: {"jsonrpc":"2.0","id":1,"result":{"ca'
'Application/JSON'                            -> 415 '{"jsonrpc":"2.0","id":null,"error":{"code":-32600,"message":'
'APPLICATION/JSON'                            -> 415 '{"jsonrpc":"2.0","id":null,"error":{"code":-32600,"message":'
'application/json; charset=utf-8'             -> 200 'event: message\r\ndata: {"jsonrpc":"2.0","id":1,"result":{"ca'

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.py against 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 main at 91941ed with uv sync --frozen.

Ngôn ngữ chính
Python
Star
24.5k
Fork
4k
Merge trung bình
18 giờ 31 phút
Pull request đã merge (30 ngày)
38

Chuẩn bị môi trường

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của modelcontextprotocol/python-sdk

Tất cả issue của modelcontextprotocol/python-sdk

Issue tương tự

Thêm issue về Python

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.