From e0a470fe2daabf14e4671dc0cd63aa66884ab916 Mon Sep 17 00:00:00 2001 From: RJ Lopez Date: Tue, 14 Apr 2026 02:23:35 -0500 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20accept=20wildcard=20patterns=20in=20?= =?UTF-8?q?Accept=20header=20per=20RFC=207231=20=C2=A75.3.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mcp/server/streamable_http.py | 10 +++++++++- tests/shared/test_streamable_http.py | 14 ++++++-------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/mcp/server/streamable_http.py b/src/mcp/server/streamable_http.py index f14201857..1989df10b 100644 --- a/src/mcp/server/streamable_http.py +++ b/src/mcp/server/streamable_http.py @@ -396,11 +396,19 @@ def _check_accept_headers(self, request: Request) -> tuple[bool, bool]: """Check if the request accepts the required media types. Supports wildcard media types per RFC 7231, section 5.3.2: + - Missing Accept header matches any media type - */* matches any media type - application/* matches any application/ subtype - text/* matches any text/ subtype """ - accept_header = request.headers.get("accept", "") + accept_header = request.headers.get("accept") + + # RFC 7231, Section 5.3.2: + # A request without any Accept header field implies that the user agent + # will accept any media type in response. + if not accept_header: + return True, True + accept_types = [media_type.strip().split(";")[0].strip().lower() for media_type in accept_header.split(",")] has_wildcard = "*/*" in accept_types diff --git a/tests/shared/test_streamable_http.py b/tests/shared/test_streamable_http.py index 3d5770fb6..eacec34a1 100644 --- a/tests/shared/test_streamable_http.py +++ b/tests/shared/test_streamable_http.py @@ -581,8 +581,7 @@ def test_accept_header_validation(basic_server: None, basic_server_url: str): headers={"Content-Type": "application/json"}, json={"jsonrpc": "2.0", "method": "initialize", "id": 1}, ) - assert response.status_code == 406 - assert "Not Acceptable" in response.text + assert response.status_code == 200 @pytest.mark.parametrize( @@ -613,8 +612,9 @@ def test_accept_header_wildcard(basic_server: None, basic_server_url: str, accep "accept_header", [ "text/html", - "application/*", - "text/*", + "text/html", + "image/*", + "audio/*", ], ) def test_accept_header_incompatible(basic_server: None, basic_server_url: str, accept_header: str): @@ -885,8 +885,7 @@ def test_json_response_missing_accept_header(json_response_server: None, json_se }, json=INIT_REQUEST, ) - assert response.status_code == 406 - assert "Not Acceptable" in response.text + assert response.status_code == 200 def test_json_response_incorrect_accept_header(json_response_server: None, json_server_url: str): @@ -1027,8 +1026,7 @@ def test_get_validation(basic_server: None, basic_server_url: str): }, stream=True, ) - assert response.status_code == 406 - assert "Not Acceptable" in response.text + assert response.status_code == 200 # Test with wrong Accept header response = requests.get( From 2a90fecc44376e4c38be4a7606450a45780e113a Mon Sep 17 00:00:00 2001 From: RJ Lopez Date: Fri, 17 Apr 2026 01:01:48 -0500 Subject: [PATCH 2/2] chore: remove unnecessary pragma: no cover from mock_receive functions --- tests/server/test_streamable_http_manager.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/server/test_streamable_http_manager.py b/tests/server/test_streamable_http_manager.py index 47cfbf14a..c7426c087 100644 --- a/tests/server/test_streamable_http_manager.py +++ b/tests/server/test_streamable_http_manager.py @@ -119,7 +119,7 @@ async def mock_send(message: Message): "headers": [(b"content-type", b"application/json")], } - async def mock_receive(): # pragma: no cover + async def mock_receive(): return {"type": "http.request", "body": b"", "more_body": False} # Trigger session creation @@ -178,7 +178,7 @@ async def mock_send(message: Message): "headers": [(b"content-type", b"application/json")], } - async def mock_receive(): # pragma: no cover + async def mock_receive(): return {"type": "http.request", "body": b"", "more_body": False} # Trigger session creation @@ -357,7 +357,7 @@ async def mock_send(message: Message): "headers": [(b"content-type", b"application/json")], } - async def mock_receive(): # pragma: no cover + async def mock_receive(): return {"type": "http.request", "body": b"", "more_body": False} await manager.handle_request(scope, mock_receive, mock_send)