diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpStreamableServerSession.java b/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpStreamableServerSession.java index 5bb5c3812..e7fac7b0d 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpStreamableServerSession.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpStreamableServerSession.java @@ -214,9 +214,13 @@ public Mono responseStream(McpSchema.JSONRPCRequest jsonrpcRequest, McpStr // (sink) if (requestHandler == null) { MethodNotFoundError error = getMethodNotFoundError(jsonrpcRequest.method()); - return transport.sendMessage( - McpSchema.JSONRPCResponse.error(jsonrpcRequest.id(), new McpSchema.JSONRPCResponse.JSONRPCError( - McpSchema.ErrorCodes.METHOD_NOT_FOUND, error.message(), error.data()))); + return transport + .sendMessage( + McpSchema.JSONRPCResponse + .error(jsonrpcRequest.id(), + new McpSchema.JSONRPCResponse.JSONRPCError( + McpSchema.ErrorCodes.METHOD_NOT_FOUND, error.message(), error.data()))) + .then(transport.closeGracefully()); } return requestHandler .handle(new McpAsyncServerExchange(this.id, stream, clientCapabilities.get(), clientInfo.get(), diff --git a/mcp-test/src/test/java/io/modelcontextprotocol/server/HttpServletStreamableIntegrationTests.java b/mcp-test/src/test/java/io/modelcontextprotocol/server/HttpServletStreamableIntegrationTests.java index 83779d2e2..2c9d14030 100644 --- a/mcp-test/src/test/java/io/modelcontextprotocol/server/HttpServletStreamableIntegrationTests.java +++ b/mcp-test/src/test/java/io/modelcontextprotocol/server/HttpServletStreamableIntegrationTests.java @@ -6,6 +6,8 @@ import java.time.Duration; import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; import java.util.stream.Stream; import io.modelcontextprotocol.AbstractMcpClientServerIntegrationTests; @@ -16,14 +18,19 @@ import io.modelcontextprotocol.server.McpServer.SyncSpecification; import io.modelcontextprotocol.server.transport.HttpServletStreamableServerTransportProvider; import io.modelcontextprotocol.server.transport.TomcatTestUtil; +import io.modelcontextprotocol.spec.McpSchema; import jakarta.servlet.http.HttpServletRequest; import org.apache.catalina.LifecycleException; import org.apache.catalina.LifecycleState; import org.apache.catalina.startup.Tomcat; +import org.awaitility.Awaitility; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; import org.junit.jupiter.params.provider.Arguments; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; import static org.assertj.core.api.Assertions.assertThat; @@ -96,6 +103,47 @@ public void after() { } } + @Test + void testMissingHandlerReturnsMethodNotFoundError() { + var mcpServer = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0") + .capabilities(McpSchema.ServerCapabilities.builder().tools(true).build()) + .build(); + var clientTransport = HttpClientStreamableHttpTransport.builder("http://localhost:" + PORT) + .endpoint(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 response = new AtomicReference<>(); + var handler = (Function, Mono>) ( + message) -> message.doOnNext(r -> { + if (r instanceof McpSchema.JSONRPCResponse resp) { + response.set(resp); + } + }); + StepVerifier.create(clientTransport.connect(handler)).verifyComplete(); + + // Send an incorrect request through the transport + StepVerifier + .create(clientTransport.sendMessage(new McpSchema.JSONRPCRequest("foo/bar", "test-request-123"))) + .verifyComplete(); + + // Wait until we've received the response + Awaitility.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"); + } + finally { + mcpServer.close(); + } + + } + static McpTransportContextExtractor TEST_CONTEXT_EXTRACTOR = (r) -> McpTransportContext .create(Map.of("important", "value"));