diff --git a/src/mcp/server/streamable_http.py b/src/mcp/server/streamable_http.py index aa99e7c88..43ec05fdd 100644 --- a/src/mcp/server/streamable_http.py +++ b/src/mcp/server/streamable_http.py @@ -426,10 +426,10 @@ async def _validate_accept_header(self, request: Request, scope: Scope, send: Se ) await response(scope, request.receive, send) return False - # For SSE responses, require both content types - elif not (has_json and has_sse): + # For SSE responses, require at least one supported content type + elif not (has_json or has_sse): response = self._create_error_response( - "Not Acceptable: Client must accept both application/json and text/event-stream", + "Not Acceptable: Client must accept application/json or text/event-stream", HTTPStatus.NOT_ACCEPTABLE, ) await response(scope, request.receive, send) diff --git a/tests/issues/test_1363_race_condition_streamable_http.py b/tests/issues/test_1363_race_condition_streamable_http.py index db2a82d07..4d2082ac4 100644 --- a/tests/issues/test_1363_race_condition_streamable_http.py +++ b/tests/issues/test_1363_race_condition_streamable_http.py @@ -137,7 +137,7 @@ async def test_race_condition_invalid_accept_headers(caplog: pytest.LogCaptureFi # Suppress WARNING logs (expected validation errors) and capture ERROR logs with caplog.at_level(logging.ERROR): - # Test with missing text/event-stream in Accept header + # Test with only application/json in Accept header (valid — single supported type) async with httpx.AsyncClient( transport=httpx.ASGITransport(app=app), base_url="http://testserver", timeout=5.0 ) as client: @@ -145,14 +145,14 @@ async def test_race_condition_invalid_accept_headers(caplog: pytest.LogCaptureFi "/", json={"jsonrpc": "2.0", "method": "initialize", "id": 1, "params": {}}, headers={ - "Accept": "application/json", # Missing text/event-stream + "Accept": "application/json", "Content-Type": "application/json", }, ) - # Should get 406 Not Acceptable due to missing text/event-stream - assert response.status_code == 406 + # Single supported Accept type is sufficient + assert response.status_code == 200 - # Test with missing application/json in Accept header + # Test with only text/event-stream in Accept header (valid — single supported type) async with httpx.AsyncClient( transport=httpx.ASGITransport(app=app), base_url="http://testserver", timeout=5.0 ) as client: @@ -160,12 +160,12 @@ async def test_race_condition_invalid_accept_headers(caplog: pytest.LogCaptureFi "/", json={"jsonrpc": "2.0", "method": "initialize", "id": 1, "params": {}}, headers={ - "Accept": "text/event-stream", # Missing application/json + "Accept": "text/event-stream", "Content-Type": "application/json", }, ) - # Should get 406 Not Acceptable due to missing application/json - assert response.status_code == 406 + # Single supported Accept type is sufficient + assert response.status_code == 200 # Test with completely invalid Accept header async with httpx.AsyncClient( diff --git a/tests/shared/test_streamable_http.py b/tests/shared/test_streamable_http.py index f8ca30441..a30d262c8 100644 --- a/tests/shared/test_streamable_http.py +++ b/tests/shared/test_streamable_http.py @@ -612,8 +612,7 @@ def test_accept_header_wildcard(basic_server: None, basic_server_url: str, accep "accept_header", [ "text/html", - "application/*", - "text/*", + "image/png", ], ) def test_accept_header_incompatible(basic_server: None, basic_server_url: str, accept_header: str): @@ -630,6 +629,28 @@ def test_accept_header_incompatible(basic_server: None, basic_server_url: str, a assert "Not Acceptable" in response.text +@pytest.mark.parametrize( + "accept_header", + [ + "text/event-stream", + "application/json", + "application/*", + "text/*", + ], +) +def test_accept_header_single_type(basic_server: None, basic_server_url: str, accept_header: str): + """Test that a single supported Accept type is sufficient for SSE mode.""" + response = requests.post( + f"{basic_server_url}/mcp", + headers={ + "Accept": accept_header, + "Content-Type": "application/json", + }, + json=INIT_REQUEST, + ) + assert response.status_code == 200 + + def test_content_type_validation(basic_server: None, basic_server_url: str): """Test that Content-Type header is properly validated.""" # Test with incorrect Content-Type