Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,12 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response)

response.setContentType(APPLICATION_JSON);
response.setCharacterEncoding(UTF_8);
response.setStatus(HttpServletResponse.SC_OK);

int httpStatus = HttpServletResponse.SC_OK;
if (jsonrpcResponse.error() != null) {
httpStatus = mapJsonRpcErrorToHttpStatus(jsonrpcResponse.error().code());
}
response.setStatus(httpStatus);

String jsonResponseText = jsonMapper.writeValueAsString(jsonrpcResponse);
PrintWriter writer = response.getWriter();
Expand Down Expand Up @@ -249,6 +254,21 @@ else if (message instanceof McpSchema.JSONRPCNotification jsonrpcNotification) {
}
}

/**
* Maps a JSON-RPC error code to an appropriate HTTP status code per the MCP
* Streamable HTTP specification (2026-07-28). Only METHOD_NOT_FOUND is mapped to a
* non-200 status (HTTP 404) as specified by the protocol. All other JSON-RPC errors
* are returned with HTTP 200 per standard JSON-RPC conventions.
* @param jsonRpcErrorCode The JSON-RPC error code
* @return The corresponding HTTP status code
*/
private static int mapJsonRpcErrorToHttpStatus(int jsonRpcErrorCode) {
if (jsonRpcErrorCode == McpSchema.ErrorCodes.METHOD_NOT_FOUND) {
return HttpServletResponse.SC_NOT_FOUND;
}
return HttpServletResponse.SC_OK;
}

/**
* Sends an error response to the client.
* @param response The HTTP servlet response
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -915,6 +915,77 @@ public void cancel() {
assertThat(response.statusCode()).isEqualTo(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE);
}

@Test
void testMissingHandlerReturnsHttp404WithMethodNotFoundError() throws Exception {
var mcpServer = McpServer.sync(mcpStatelessServerTransport)
.serverInfo("test-server", "1.0.0")
.capabilities(ServerCapabilities.builder().build())
.build();

// Use MockHttpServletRequest/Response to directly verify the HTTP 404 status
// and JSON-RPC METHOD_NOT_FOUND error, since a real HTTP client treats 404
// as a transport-level error per the spec.
McpSchema.JSONRPCRequest jsonrpcRequest = new McpSchema.JSONRPCRequest("foo/bar", "test-request-123");

MockHttpServletRequest request = new MockHttpServletRequest("POST", CUSTOM_MESSAGE_ENDPOINT);
MockHttpServletResponse response = new MockHttpServletResponse();

byte[] content = JSON_MAPPER.writeValueAsBytes(jsonrpcRequest);
request.setContent(content);
request.addHeader("Content-Type", APPLICATION_JSON);
request.addHeader("Content-Length", Integer.toString(content.length));
request.addHeader("Accept", APPLICATION_JSON + ", " + TEXT_EVENT_STREAM);
request.addHeader(HttpHeaders.PROTOCOL_VERSION, ProtocolVersions.MCP_2025_03_26);

mcpStatelessServerTransport.service(request, response);

assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_NOT_FOUND);

McpSchema.JSONRPCResponse jsonrpcResponse = JSON_MAPPER.readValue(response.getContentAsByteArray(),
McpSchema.JSONRPCResponse.class);

assertThat(jsonrpcResponse).isNotNull();
assertThat(jsonrpcResponse.error()).isNotNull();
assertThat(jsonrpcResponse.error().code()).isEqualTo(ErrorCodes.METHOD_NOT_FOUND);
assertThat(jsonrpcResponse.error().message()).isEqualTo("Method not found: foo/bar");

mcpServer.closeGracefully();
}

@Test
void testUnknownMethodReturnsHttp404WithMethodNotFoundError() throws Exception {
var mcpServer = McpServer.sync(mcpStatelessServerTransport)
.serverInfo("test-server", "1.0.0")
.capabilities(ServerCapabilities.builder().build())
.build();

McpSchema.JSONRPCRequest jsonrpcRequest = new McpSchema.JSONRPCRequest("server/discover", "discover-1");

MockHttpServletRequest request = new MockHttpServletRequest("POST", CUSTOM_MESSAGE_ENDPOINT);
MockHttpServletResponse response = new MockHttpServletResponse();

byte[] content = JSON_MAPPER.writeValueAsBytes(jsonrpcRequest);
request.setContent(content);
request.addHeader("Content-Type", APPLICATION_JSON);
request.addHeader("Content-Length", Integer.toString(content.length));
request.addHeader("Accept", APPLICATION_JSON + ", " + TEXT_EVENT_STREAM);
request.addHeader(HttpHeaders.PROTOCOL_VERSION, ProtocolVersions.MCP_2025_03_26);

mcpStatelessServerTransport.service(request, response);

assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_NOT_FOUND);

McpSchema.JSONRPCResponse jsonrpcResponse = JSON_MAPPER.readValue(response.getContentAsByteArray(),
McpSchema.JSONRPCResponse.class);

assertThat(jsonrpcResponse).isNotNull();
assertThat(jsonrpcResponse.error()).isNotNull();
assertThat(jsonrpcResponse.error().code()).isEqualTo(ErrorCodes.METHOD_NOT_FOUND);
assertThat(jsonrpcResponse.error().message()).isEqualTo("Method not found: server/discover");

mcpServer.close();
}

private double evaluateExpression(String expression) {
// Simple expression evaluator for testing
return switch (expression) {
Expand Down