Skip to content

Commit b188f99

Browse files
committed
fix: honor q=0 in streamable HTTP Accept negotiation
1 parent 1ab51df commit b188f99

File tree

2 files changed

+33
-4
lines changed

2 files changed

+33
-4
lines changed

src/mcp/server/streamable_http.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -397,9 +397,27 @@ def _check_accept_headers(self, request: Request) -> tuple[bool, bool]:
397397
- */* matches any media type
398398
- application/* matches any application/ subtype
399399
- text/* matches any text/ subtype
400+
- media types with q=0 are treated as unacceptable
400401
"""
401402
accept_header = request.headers.get("accept", "")
402-
accept_types = [media_type.strip().split(";")[0].strip().lower() for media_type in accept_header.split(",")]
403+
accept_types: list[str] = []
404+
for media_range in accept_header.split(","):
405+
parts = [part.strip().lower() for part in media_range.split(";")]
406+
media_type = parts[0]
407+
if not media_type:
408+
continue
409+
410+
quality = 1.0
411+
for param in parts[1:]:
412+
if param.startswith("q="):
413+
try:
414+
quality = float(param[2:])
415+
except ValueError:
416+
pass
417+
break
418+
419+
if quality > 0:
420+
accept_types.append(media_type)
403421

404422
has_wildcard = "*/*" in accept_types
405423
has_json = has_wildcard or any(t in (CONTENT_TYPE_JSON, "application/*") for t in accept_types)

tests/shared/test_streamable_http.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,8 @@ def test_accept_header_wildcard(basic_server: None, basic_server_url: str, accep
613613
[
614614
("application/json", "application/json"),
615615
("text/event-stream", "text/event-stream"),
616+
("application/json;q=0.9, text/event-stream;q=0", "application/json"),
617+
("text/event-stream;q=0.9, application/json;q=0", "text/event-stream"),
616618
],
617619
)
618620
def test_accept_header_single_media_type_negotiates_response(
@@ -659,6 +661,7 @@ def test_accept_header_single_media_type_negotiates_response(
659661
"text/html",
660662
"text/plain",
661663
"application/xml",
664+
"application/json;q=0, text/event-stream;q=0",
662665
],
663666
)
664667
def test_accept_header_incompatible(basic_server: None, basic_server_url: str, accept_header: str):
@@ -933,14 +936,22 @@ def test_json_response_missing_accept_header(json_response_server: None, json_se
933936
assert "Not Acceptable" in response.text
934937

935938

936-
def test_json_response_incorrect_accept_header(json_response_server: None, json_server_url: str):
939+
@pytest.mark.parametrize(
940+
"accept_header",
941+
[
942+
"text/event-stream",
943+
"application/json;q=0, text/event-stream;q=1",
944+
],
945+
)
946+
def test_json_response_incorrect_accept_header(
947+
json_response_server: None, json_server_url: str, accept_header: str
948+
):
937949
"""Test that json_response servers reject requests with incorrect Accept header."""
938950
mcp_url = f"{json_server_url}/mcp"
939-
# Test with only text/event-stream (wrong for JSON server)
940951
response = requests.post(
941952
mcp_url,
942953
headers={
943-
"Accept": "text/event-stream",
954+
"Accept": accept_header,
944955
"Content-Type": "application/json",
945956
},
946957
json=INIT_REQUEST,

0 commit comments

Comments
 (0)