Repository navigation
Description
Bug description
The modern HttpServletMcpTransport (#1170) never checks the request Content-Type. A POST with a valid JSON-RPC body is processed when Content-Type is missing, text/plain or application/x-www-form-urlencoded, and the server returns 200. The legacy transports return 415 for this since #1164 (fixes #961). The Go, TypeScript and Python SDKs also return 415.
Environment
java-sdk main @ bb87ddf, Java 25.0.3, embedded Tomcat.
Steps to reproduce
curl -i http://localhost:8099/mcp \
-H 'Content-Type: text/plain' \
-H 'Accept: application/json, text/event-stream' \
-H 'Mcp-Method: tools/call' -H 'Mcp-Name: echo' \
-H 'MCP-Protocol-Version: 2026-07-28' \
--data-binary '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"echo","_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}}}}'Returns HTTP/1.1 200 and the tool executes. The same happens with no Content-Type and with application/x-www-form-urlencoded.
Expected behavior
415 Unsupported Media Type when the media type isn't application/json, matching the legacy transports. Parameters and case are ignored, so application/json; charset=utf-8 is accepted.
Minimal Complete Reproducible example
Added to HttpServletMcpTransportIntegrationTests:
@Test
void postWithNonJsonContentTypeIsRejected() throws Exception {
Map<String, Object> params = new HashMap<>();
params.put("_meta", meta());
params.put("name", "echo");
HttpRequest request = post("tools/call", params).header("Mcp-Name", "echo")
.setHeader("Content-Type", "text/plain")
.build();
HttpResponse<String> response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
assertThat(response.statusCode()).isEqualTo(415);
}Fails with expected: 415 but was: 200.
Proposed fix
Check Content-Type in doPost before reading the body. HttpServletRequestUtils.isJsonContentType already does the matching but is package-private. Should I make it public, or add a private helper in the modern transport? Happy to send a PR.
Activity