Skip to content

Commit 06f1c91

Browse files
authored
Merge branch 'main' into feature/streamable_http_extensions
2 parents fb04e15 + 31ae5f4 commit 06f1c91

File tree

2 files changed

+69
-8
lines changed

2 files changed

+69
-8
lines changed

src/mcp/server/streamable_http.py

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -306,20 +306,36 @@ def _check_content_type(self, request: Request) -> bool:
306306

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

309+
async def _validate_accept_header(self, request: Request, scope: Scope, send: Send) -> bool:
310+
"""Validate Accept header based on response mode. Returns True if valid."""
311+
has_json, has_sse = self._check_accept_headers(request)
312+
if self.is_json_response_enabled:
313+
# For JSON-only responses, only require application/json
314+
if not has_json:
315+
response = self._create_error_response(
316+
"Not Acceptable: Client must accept application/json",
317+
HTTPStatus.NOT_ACCEPTABLE,
318+
)
319+
await response(scope, request.receive, send)
320+
return False
321+
# For SSE responses, require both content types
322+
elif not (has_json and has_sse):
323+
response = self._create_error_response(
324+
"Not Acceptable: Client must accept both application/json and text/event-stream",
325+
HTTPStatus.NOT_ACCEPTABLE,
326+
)
327+
await response(scope, request.receive, send)
328+
return False
329+
return True
330+
309331
async def _handle_post_request(self, scope: Scope, request: Request, receive: Receive, send: Send) -> None:
310332
"""Handle POST requests containing JSON-RPC messages."""
311333
writer = self._read_stream_writer
312334
if writer is None:
313335
raise ValueError("No read stream writer available. Ensure connect() is called first.")
314336
try:
315-
# Check Accept headers
316-
has_json, has_sse = self._check_accept_headers(request)
317-
if not (has_json and has_sse):
318-
response = self._create_error_response(
319-
("Not Acceptable: Client must accept both application/json and text/event-stream"),
320-
HTTPStatus.NOT_ACCEPTABLE,
321-
)
322-
await response(scope, receive, send)
337+
# Validate Accept header
338+
if not await self._validate_accept_header(request, scope, send):
323339
return
324340

325341
# Validate Content-Type

tests/shared/test_streamable_http.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,51 @@ def test_json_response(json_response_server: None, json_server_url: str):
694694
assert response.headers.get("Content-Type") == "application/json"
695695

696696

697+
def test_json_response_accept_json_only(json_response_server: None, json_server_url: str):
698+
"""Test that json_response servers only require application/json in Accept header."""
699+
mcp_url = f"{json_server_url}/mcp"
700+
response = requests.post(
701+
mcp_url,
702+
headers={
703+
"Accept": "application/json",
704+
"Content-Type": "application/json",
705+
},
706+
json=INIT_REQUEST,
707+
)
708+
assert response.status_code == 200
709+
assert response.headers.get("Content-Type") == "application/json"
710+
711+
712+
def test_json_response_missing_accept_header(json_response_server: None, json_server_url: str):
713+
"""Test that json_response servers reject requests without Accept header."""
714+
mcp_url = f"{json_server_url}/mcp"
715+
response = requests.post(
716+
mcp_url,
717+
headers={
718+
"Content-Type": "application/json",
719+
},
720+
json=INIT_REQUEST,
721+
)
722+
assert response.status_code == 406
723+
assert "Not Acceptable" in response.text
724+
725+
726+
def test_json_response_incorrect_accept_header(json_response_server: None, json_server_url: str):
727+
"""Test that json_response servers reject requests with incorrect Accept header."""
728+
mcp_url = f"{json_server_url}/mcp"
729+
# Test with only text/event-stream (wrong for JSON server)
730+
response = requests.post(
731+
mcp_url,
732+
headers={
733+
"Accept": "text/event-stream",
734+
"Content-Type": "application/json",
735+
},
736+
json=INIT_REQUEST,
737+
)
738+
assert response.status_code == 406
739+
assert "Not Acceptable" in response.text
740+
741+
697742
def test_get_sse_stream(basic_server: None, basic_server_url: str):
698743
"""Test establishing an SSE stream via GET request."""
699744
# First, we need to initialize a session

0 commit comments

Comments
 (0)