Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/mcp/server/streamable_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
16 changes: 8 additions & 8 deletions tests/issues/test_1363_race_condition_streamable_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,35 +137,35 @@ 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:
response = await client.post(
"/",
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:
response = await client.post(
"/",
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(
Expand Down
25 changes: 23 additions & 2 deletions tests/shared/test_streamable_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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
Expand Down
Loading