Skip to content

Commit f9bf124

Browse files
committed
Relax Accept header requirement for JSON-only responses
When is_json_response_enabled is True, servers only return application/json responses and never use SSE. This change relaxes the Accept header validation to only require application/json in this mode, rather than requiring both application/json and text/event-stream. This makes it easier to test JSON-only MCP servers with tools like curl, which is useful when developing and debugging MCP servers. For servers with is_json_response_enabled=False (SSE mode), the existing requirement for both content types is maintained.
1 parent 40acbc5 commit f9bf124

File tree

2 files changed

+39
-8
lines changed

2 files changed

+39
-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: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -693,6 +693,21 @@ def test_json_response(json_response_server: None, json_server_url: str):
693693
assert response.headers.get("Content-Type") == "application/json"
694694

695695

696+
def test_json_response_accept_json_only(json_response_server: None, json_server_url: str):
697+
"""Test that json_response servers only require application/json in Accept header."""
698+
mcp_url = f"{json_server_url}/mcp"
699+
response = requests.post(
700+
mcp_url,
701+
headers={
702+
"Accept": "application/json",
703+
"Content-Type": "application/json",
704+
},
705+
json=INIT_REQUEST,
706+
)
707+
assert response.status_code == 200
708+
assert response.headers.get("Content-Type") == "application/json"
709+
710+
696711
def test_get_sse_stream(basic_server: None, basic_server_url: str):
697712
"""Test establishing an SSE stream via GET request."""
698713
# First, we need to initialize a session

0 commit comments

Comments
 (0)