Skip to content

Commit 3ac4484

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 a1199b1 commit 3ac4484

File tree

3 files changed

+91
-9
lines changed

3 files changed

+91
-9
lines changed

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +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(new McpError("Missing handler for request type: " + request.method()));
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)));
3638
}
3739
return requestHandler.handle(transportContext, request.params())
3840
.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-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcStatelessIntegrationTests.java

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,32 +3,39 @@
33
*/
44
package io.modelcontextprotocol.server;
55

6+
import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
67
import static org.assertj.core.api.Assertions.assertThat;
78

9+
import java.net.URI;
10+
import java.net.http.HttpClient;
11+
import java.net.http.HttpRequest;
12+
import java.net.http.HttpResponse;
813
import java.time.Duration;
914
import java.util.stream.Stream;
1015

16+
import io.modelcontextprotocol.AbstractStatelessIntegrationTests;
17+
import io.modelcontextprotocol.client.McpClient;
18+
import io.modelcontextprotocol.client.transport.HttpClientStreamableHttpTransport;
19+
import io.modelcontextprotocol.client.transport.WebClientStreamableHttpTransport;
20+
import io.modelcontextprotocol.server.McpServer.StatelessAsyncSpecification;
21+
import io.modelcontextprotocol.server.McpServer.StatelessSyncSpecification;
22+
import io.modelcontextprotocol.server.transport.WebMvcStatelessServerTransport;
23+
import io.modelcontextprotocol.spec.McpSchema;
1124
import org.apache.catalina.LifecycleException;
1225
import org.apache.catalina.LifecycleState;
1326
import org.junit.jupiter.api.AfterEach;
1427
import org.junit.jupiter.api.BeforeEach;
1528
import org.junit.jupiter.api.Timeout;
29+
import org.junit.jupiter.params.ParameterizedTest;
1630
import org.junit.jupiter.params.provider.Arguments;
1731

32+
import org.junit.jupiter.params.provider.ValueSource;
1833
import org.springframework.context.annotation.Bean;
1934
import org.springframework.context.annotation.Configuration;
2035
import org.springframework.web.reactive.function.client.WebClient;
2136
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
2237
import org.springframework.web.servlet.function.RouterFunction;
2338
import org.springframework.web.servlet.function.ServerResponse;
24-
25-
import io.modelcontextprotocol.AbstractStatelessIntegrationTests;
26-
import io.modelcontextprotocol.client.McpClient;
27-
import io.modelcontextprotocol.client.transport.HttpClientStreamableHttpTransport;
28-
import io.modelcontextprotocol.client.transport.WebClientStreamableHttpTransport;
29-
import io.modelcontextprotocol.server.McpServer.StatelessAsyncSpecification;
30-
import io.modelcontextprotocol.server.McpServer.StatelessSyncSpecification;
31-
import io.modelcontextprotocol.server.transport.WebMvcStatelessServerTransport;
3239
import reactor.core.scheduler.Schedulers;
3340

3441
@Timeout(15)
@@ -131,4 +138,37 @@ public void after() {
131138
}
132139
}
133140

141+
@ParameterizedTest
142+
@ValueSource(strings = { "tools/list", "resources/list", "prompts/list" })
143+
void testMissingHandlerReturnsMethodNotFoundError(String method) throws Exception {
144+
var mcpServer = prepareSyncServerBuilder().build();
145+
146+
HttpResponse<String> response;
147+
148+
try {
149+
HttpRequest request = HttpRequest.newBuilder()
150+
.uri(URI.create("http://localhost:" + PORT + MESSAGE_ENDPOINT))
151+
.header("Content-Type", "application/json")
152+
.header("Accept", "application/json, text/event-stream")
153+
.POST(HttpRequest.BodyPublishers.ofString("""
154+
{
155+
"jsonrpc": "2.0",
156+
"method": "%s",
157+
"id": "test-request-123",
158+
"params": {}
159+
}
160+
""".formatted(method)))
161+
.build();
162+
163+
response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
164+
}
165+
finally {
166+
mcpServer.closeGracefully();
167+
}
168+
169+
final var responseBody = response.body();
170+
assertThatJson(responseBody).inPath("error.code").isEqualTo(McpSchema.ErrorCodes.METHOD_NOT_FOUND);
171+
assertThatJson(responseBody).inPath("error.message").isEqualTo("Method not found: " + method);
172+
}
173+
134174
}

0 commit comments

Comments
 (0)