Skip to content

Commit 2d76d16

Browse files
committed
Fix error behavior on unregistered handlers in stateless server handler
Instead of throwing an McpError, it now returns with a JSON-RPC method not found error (-32601), aligned with the stateful implementation.
1 parent 46bacda commit 2d76d16

File tree

3 files changed

+84
-4
lines changed

3 files changed

+84
-4
lines changed

mcp-core/src/main/java/io/modelcontextprotocol/server/DefaultMcpStatelessServerHandler.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@ public Mono<McpSchema.JSONRPCResponse> handleRequest(McpTransportContext transpo
3232
McpSchema.JSONRPCRequest request) {
3333
McpStatelessRequestHandler<?> requestHandler = this.requestHandlers.get(request.method());
3434
if (requestHandler == null) {
35-
return Mono.error(McpError.builder(McpSchema.ErrorCodes.METHOD_NOT_FOUND)
36-
.message("Missing handler for request type: " + request.method())
37-
.build());
35+
return Mono.just(new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(), null,
36+
new McpSchema.JSONRPCResponse.JSONRPCError(McpSchema.ErrorCodes.METHOD_NOT_FOUND,
37+
"Method not found: " + request.method(), null)));
3838
}
3939
return requestHandler.handle(transportContext, request.params())
4040
.map(result -> new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(), result, null))
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2024-2025 the original author or authors.
3+
*/
4+
5+
package io.modelcontextprotocol.server;
6+
7+
import io.modelcontextprotocol.common.McpTransportContext;
8+
import io.modelcontextprotocol.spec.McpSchema;
9+
import org.junit.jupiter.api.Test;
10+
import reactor.test.StepVerifier;
11+
12+
import java.util.Collections;
13+
14+
import static org.assertj.core.api.Assertions.assertThat;
15+
16+
class DefaultMcpStatelessServerHandlerTests {
17+
18+
@Test
19+
void testHandleRequestWithUnregisteredMethod() {
20+
// no request/initialization handlers
21+
DefaultMcpStatelessServerHandler handler = new DefaultMcpStatelessServerHandler(Collections.emptyMap(),
22+
Collections.emptyMap());
23+
24+
// unregistered method
25+
McpSchema.JSONRPCRequest request = new McpSchema.JSONRPCRequest(McpSchema.JSONRPC_VERSION, "resources/list",
26+
"test-id-123", null);
27+
28+
StepVerifier.create(handler.handleRequest(McpTransportContext.EMPTY, request)).assertNext(response -> {
29+
assertThat(response).isNotNull();
30+
assertThat(response.jsonrpc()).isEqualTo(McpSchema.JSONRPC_VERSION);
31+
assertThat(response.id()).isEqualTo("test-id-123");
32+
assertThat(response.result()).isNull();
33+
34+
assertThat(response.error()).isNotNull();
35+
assertThat(response.error().code()).isEqualTo(McpSchema.ErrorCodes.METHOD_NOT_FOUND);
36+
assertThat(response.error().message()).isEqualTo("Method not found: resources/list");
37+
}).verifyComplete();
38+
}
39+
40+
}

mcp-test/src/test/java/io/modelcontextprotocol/server/HttpServletStatelessIntegrationTests.java

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44

55
package io.modelcontextprotocol.server;
66

7+
import java.net.URI;
8+
import java.net.http.HttpClient;
9+
import java.net.http.HttpRequest;
10+
import java.net.http.HttpResponse;
711
import java.time.Duration;
812
import java.util.List;
913
import java.util.Map;
@@ -308,7 +312,7 @@ void testStructuredOutputOfObjectArrayValidationSuccess(String clientType) {
308312
"type", "object",
309313
"properties", Map.of(
310314
"name", Map.of("type", "string"),
311-
"age", Map.of("type", "number")),
315+
"age", Map.of("type", "number")),
312316
"required", List.of("name", "age"))); // @formatter:on
313317

314318
Tool calculatorTool = Tool.builder()
@@ -639,6 +643,42 @@ void testThrownMcpErrorAndJsonRpcError() throws Exception {
639643
mcpServer.close();
640644
}
641645

646+
@ParameterizedTest
647+
@ValueSource(strings = { "tools/list", "resources/list", "prompts/list" })
648+
void testMissingHandlerReturnsMethodNotFoundError(String method) throws Exception {
649+
var mcpServer = McpServer.sync(mcpStatelessServerTransport)
650+
.serverInfo("test-server", "1.0.0")
651+
.capabilities(ServerCapabilities.builder().build())
652+
.build();
653+
654+
HttpResponse<String> response;
655+
656+
try {
657+
HttpRequest request = HttpRequest.newBuilder()
658+
.uri(URI.create("http://localhost:" + PORT + CUSTOM_MESSAGE_ENDPOINT))
659+
.header("Content-Type", "application/json")
660+
.header("Accept", "application/json, text/event-stream")
661+
.POST(HttpRequest.BodyPublishers.ofString("""
662+
{
663+
"jsonrpc": "2.0",
664+
"method": "%s",
665+
"id": "test-request-123",
666+
"params": {}
667+
}
668+
""".formatted(method)))
669+
.build();
670+
671+
response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
672+
}
673+
finally {
674+
mcpServer.closeGracefully();
675+
}
676+
677+
final var responseBody = response.body();
678+
assertThatJson(responseBody).inPath("error.code").isEqualTo(McpSchema.ErrorCodes.METHOD_NOT_FOUND);
679+
assertThatJson(responseBody).inPath("error.message").isEqualTo("Method not found: " + method);
680+
}
681+
642682
private double evaluateExpression(String expression) {
643683
// Simple expression evaluator for testing
644684
return switch (expression) {

0 commit comments

Comments
 (0)