From f9bf1247e5c8e30f6890c1bdea258504f7abec04 Mon Sep 17 00:00:00 2001 From: Adam Jones Date: Mon, 20 Oct 2025 12:01:21 -0700 Subject: [PATCH 1/2] 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. --- src/mcp/server/streamable_http.py | 32 +++++++++++++++++++++------- tests/shared/test_streamable_http.py | 15 +++++++++++++ 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/src/mcp/server/streamable_http.py b/src/mcp/server/streamable_http.py index b45d742b0..de22279f5 100644 --- a/src/mcp/server/streamable_http.py +++ b/src/mcp/server/streamable_http.py @@ -306,20 +306,36 @@ def _check_content_type(self, request: Request) -> bool: return any(part == CONTENT_TYPE_JSON for part in content_type_parts) + async def _validate_accept_header(self, request: Request, scope: Scope, send: Send) -> bool: + """Validate Accept header based on response mode. Returns True if valid.""" + has_json, has_sse = self._check_accept_headers(request) + if self.is_json_response_enabled: + # For JSON-only responses, only require application/json + if not has_json: + response = self._create_error_response( + "Not Acceptable: Client must accept application/json", + HTTPStatus.NOT_ACCEPTABLE, + ) + await response(scope, request.receive, send) + return False + # For SSE responses, require both content types + elif not (has_json and has_sse): + response = self._create_error_response( + "Not Acceptable: Client must accept both application/json and text/event-stream", + HTTPStatus.NOT_ACCEPTABLE, + ) + await response(scope, request.receive, send) + return False + return True + async def _handle_post_request(self, scope: Scope, request: Request, receive: Receive, send: Send) -> None: """Handle POST requests containing JSON-RPC messages.""" writer = self._read_stream_writer if writer is None: raise ValueError("No read stream writer available. Ensure connect() is called first.") try: - # Check Accept headers - has_json, has_sse = self._check_accept_headers(request) - if not (has_json and has_sse): - response = self._create_error_response( - ("Not Acceptable: Client must accept both application/json and text/event-stream"), - HTTPStatus.NOT_ACCEPTABLE, - ) - await response(scope, receive, send) + # Validate Accept header + if not await self._validate_accept_header(request, scope, send): return # Validate Content-Type diff --git a/tests/shared/test_streamable_http.py b/tests/shared/test_streamable_http.py index 55800da33..b10aacf70 100644 --- a/tests/shared/test_streamable_http.py +++ b/tests/shared/test_streamable_http.py @@ -693,6 +693,21 @@ def test_json_response(json_response_server: None, json_server_url: str): assert response.headers.get("Content-Type") == "application/json" +def test_json_response_accept_json_only(json_response_server: None, json_server_url: str): + """Test that json_response servers only require application/json in Accept header.""" + mcp_url = f"{json_server_url}/mcp" + response = requests.post( + mcp_url, + headers={ + "Accept": "application/json", + "Content-Type": "application/json", + }, + json=INIT_REQUEST, + ) + assert response.status_code == 200 + assert response.headers.get("Content-Type") == "application/json" + + def test_get_sse_stream(basic_server: None, basic_server_url: str): """Test establishing an SSE stream via GET request.""" # First, we need to initialize a session From cda12b945d10c37c0d66af4e955362b3fcae6817 Mon Sep 17 00:00:00 2001 From: Adam Jones Date: Mon, 27 Oct 2025 16:20:09 +0000 Subject: [PATCH 2/2] test: add failure case tests for JSON response Accept header validation Add tests to verify that JSON-only servers properly reject requests with: - Missing Accept header (returns 406) - Incorrect Accept header like text/event-stream only (returns 406) These tests address the validation failure cases requested in the PR review to ensure proper error handling for JSON-only server configurations. --- tests/shared/test_streamable_http.py | 30 ++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/shared/test_streamable_http.py b/tests/shared/test_streamable_http.py index b10aacf70..34e929168 100644 --- a/tests/shared/test_streamable_http.py +++ b/tests/shared/test_streamable_http.py @@ -708,6 +708,36 @@ def test_json_response_accept_json_only(json_response_server: None, json_server_ assert response.headers.get("Content-Type") == "application/json" +def test_json_response_missing_accept_header(json_response_server: None, json_server_url: str): + """Test that json_response servers reject requests without Accept header.""" + mcp_url = f"{json_server_url}/mcp" + response = requests.post( + mcp_url, + headers={ + "Content-Type": "application/json", + }, + json=INIT_REQUEST, + ) + assert response.status_code == 406 + assert "Not Acceptable" in response.text + + +def test_json_response_incorrect_accept_header(json_response_server: None, json_server_url: str): + """Test that json_response servers reject requests with incorrect Accept header.""" + mcp_url = f"{json_server_url}/mcp" + # Test with only text/event-stream (wrong for JSON server) + response = requests.post( + mcp_url, + headers={ + "Accept": "text/event-stream", + "Content-Type": "application/json", + }, + json=INIT_REQUEST, + ) + assert response.status_code == 406 + assert "Not Acceptable" in response.text + + def test_get_sse_stream(basic_server: None, basic_server_url: str): """Test establishing an SSE stream via GET request.""" # First, we need to initialize a session