Skip to content
Merged
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 @@ -32,9 +32,9 @@ public Mono<McpSchema.JSONRPCResponse> handleRequest(McpTransportContext transpo
McpSchema.JSONRPCRequest request) {
McpStatelessRequestHandler<?> requestHandler = this.requestHandlers.get(request.method());
if (requestHandler == null) {
return Mono.error(McpError.builder(McpSchema.ErrorCodes.METHOD_NOT_FOUND)
.message("Missing handler for request type: " + request.method())
.build());
return Mono.just(new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(), null,
new McpSchema.JSONRPCResponse.JSONRPCError(McpSchema.ErrorCodes.METHOD_NOT_FOUND,
"Method not found: " + request.method(), null)));
}
return requestHandler.handle(transportContext, request.params())
.map(result -> McpSchema.JSONRPCResponse.result(request.id(), result))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright 2026-2026 the original author or authors.
*/

package io.modelcontextprotocol.server;

import io.modelcontextprotocol.common.McpTransportContext;
import io.modelcontextprotocol.spec.McpSchema;
import org.junit.jupiter.api.Test;
import reactor.test.StepVerifier;

import java.util.Collections;

import static org.assertj.core.api.Assertions.assertThat;

class DefaultMcpStatelessServerHandlerTests {

@Test
void testHandleRequestWithUnregisteredMethod() {
// no request/initialization handlers
DefaultMcpStatelessServerHandler handler = new DefaultMcpStatelessServerHandler(Collections.emptyMap(),
Collections.emptyMap());

// unregistered method
McpSchema.JSONRPCRequest request = new McpSchema.JSONRPCRequest(McpSchema.JSONRPC_VERSION, "resources/list",
"test-id-123", null);

StepVerifier.create(handler.handleRequest(McpTransportContext.EMPTY, request)).assertNext(response -> {
assertThat(response).isNotNull();
assertThat(response.jsonrpc()).isEqualTo(McpSchema.JSONRPC_VERSION);
assertThat(response.id()).isEqualTo("test-id-123");
assertThat(response.result()).isNull();

assertThat(response.error()).isNotNull();
assertThat(response.error().code()).isEqualTo(McpSchema.ErrorCodes.METHOD_NOT_FOUND);
assertThat(response.error().message()).isEqualTo("Method not found: resources/list");
}).verifyComplete();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiFunction;
import java.util.function.Function;

import io.modelcontextprotocol.client.McpClient;
import io.modelcontextprotocol.client.transport.HttpClientStreamableHttpTransport;
Expand Down Expand Up @@ -45,6 +46,8 @@
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.client.RestClient;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;

import static io.modelcontextprotocol.server.transport.HttpServletStatelessServerTransport.APPLICATION_JSON;
import static io.modelcontextprotocol.server.transport.HttpServletStatelessServerTransport.TEXT_EVENT_STREAM;
Expand Down Expand Up @@ -448,7 +451,7 @@ void testStructuredOutputOfObjectArrayValidationSuccess() {
"type", "object",
"properties", Map.of(
"name", Map.of("type", "string"),
"age", Map.of("type", "number")),
"age", Map.of("type", "number")),
"required", List.of("name", "age"))); // @formatter:on

Tool calculatorTool = Tool.builder("getMembers")
Expand Down Expand Up @@ -765,6 +768,48 @@ void testThrownMcpErrorAndJsonRpcError() throws Exception {
mcpServer.close();
}

@Test
void testMissingHandlerReturnsMethodNotFoundError() {
var mcpServer = McpServer.sync(mcpStatelessServerTransport)
.serverInfo("test-server", "1.0.0")
.capabilities(ServerCapabilities.builder().build())
.build();
var clientTransport = HttpClientStreamableHttpTransport.builder("http://localhost:" + PORT)
.endpoint(CUSTOM_MESSAGE_ENDPOINT)
.build();

try (var mcpClient = McpClient.sync(clientTransport).build()) {
// Create a session using an MCP client
McpSchema.InitializeResult initResult = mcpClient.initialize();
assertThat(initResult).isNotNull();

// Override the response handler in the client to capture responses
AtomicReference<McpSchema.JSONRPCResponse> response = new AtomicReference<>();
var handler = (Function<Mono<McpSchema.JSONRPCMessage>, Mono<McpSchema.JSONRPCMessage>>) (
message) -> message.doOnNext(r -> {
if (r instanceof McpSchema.JSONRPCResponse resp) {
response.set(resp);
}
});
StepVerifier.create(clientTransport.connect(handler)).verifyComplete();

// Send a request for a non-existent method through the transport, bypassing
// the client's capability checks
StepVerifier
.create(clientTransport.sendMessage(new McpSchema.JSONRPCRequest("foo/bar", "test-request-123")))
.verifyComplete();

// Wait until we've received the response
await().atMost(Duration.ofSeconds(1)).until(() -> response.get() != null);

assertThat(response.get().error().code()).isEqualTo(McpSchema.ErrorCodes.METHOD_NOT_FOUND);
assertThat(response.get().error().message()).isEqualTo("Method not found: foo/bar");
}
Comment thread
maff marked this conversation as resolved.
finally {
mcpServer.closeGracefully();
}
}

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