refactor: add notification handler to stateless servers. (#835)

Signed-off-by: Daniel Garnier-Moiroux <git@garnier.wf>
Co-authored-by: Daniel Garnier-Moiroux <git@garnier.wf>
This commit is contained in:
Minsung Son 2026-07-11 00:51:38 +09:00 • committed by GitHub
commit fd004989b9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 81 additions and 4 deletions

View file

@ -133,10 +133,26 @@ public class McpStatelessAsyncServer {
this.protocolVersions = new ArrayList<>(mcpTransport.protocolVersions());
McpStatelessServerHandler handler = new DefaultMcpStatelessServerHandler(requestHandlers, Map.of());
Map<String, McpStatelessNotificationHandler> notificationHandlers = prepareNotificationHandlers();
McpStatelessServerHandler handler = new DefaultMcpStatelessServerHandler(requestHandlers, notificationHandlers);
mcpTransport.setMcpHandler(handler);
}
private Map<String, McpStatelessNotificationHandler> prepareNotificationHandlers() {
Map<String, McpStatelessNotificationHandler> notificationHandlers = new HashMap<>();
notificationHandlers.put(McpSchema.METHOD_NOTIFICATION_INITIALIZED, (exchange, params) -> {
logger.debug("Received {}", McpSchema.METHOD_NOTIFICATION_INITIALIZED);
return Mono.empty();
});
notificationHandlers.put(McpSchema.METHOD_NOTIFICATION_ROOTS_LIST_CHANGED, (exchange, params) -> {
logger.debug("Received {}", McpSchema.METHOD_NOTIFICATION_ROOTS_LIST_CHANGED);
return Mono.empty();
});
return notificationHandlers;
}
// ---------------------------------------
// Lifecycle Management
// ---------------------------------------

View file

@ -11,6 +11,10 @@ import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiFunction;
import java.util.function.Function;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.read.ListAppender;
import io.modelcontextprotocol.client.McpClient;
import io.modelcontextprotocol.client.transport.HttpClientStreamableHttpTransport;
import io.modelcontextprotocol.common.McpTransportContext;
@ -42,13 +46,13 @@ 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.slf4j.LoggerFactory;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
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;
import static io.modelcontextprotocol.util.McpJsonMapperUtils.JSON_MAPPER;
@ -810,6 +814,63 @@ class HttpServletStatelessIntegrationTests {
}
}
@Test
void testInitializedNotificationDoesNotLogWarn() {
Logger handlerLogger = (Logger) LoggerFactory.getLogger(DefaultMcpStatelessServerHandler.class);
ListAppender<ILoggingEvent> logAppender = new ListAppender<>();
logAppender.start();
handlerLogger.addAppender(logAppender);
try {
var mcpServer = McpServer.sync(mcpStatelessServerTransport)
.serverInfo("test-server", "1.0.0")
.capabilities(ServerCapabilities.builder().build())
.build();
try (var mcpClient = clientBuilder.build()) {
mcpClient.initialize(); // automatically sends notifications/initialized
}
finally {
mcpServer.close();
}
}
finally {
handlerLogger.detachAppender(logAppender);
logAppender.stop();
}
assertThat(logAppender.list).noneMatch(event -> event.getLevel() == Level.WARN);
}
@Test
void testRootsListChangedNotificationDoesNotLogWarn() {
Logger handlerLogger = (Logger) LoggerFactory.getLogger(DefaultMcpStatelessServerHandler.class);
ListAppender<ILoggingEvent> logAppender = new ListAppender<>();
logAppender.start();
handlerLogger.addAppender(logAppender);
try {
var mcpServer = McpServer.sync(mcpStatelessServerTransport)
.serverInfo("test-server", "1.0.0")
.capabilities(ServerCapabilities.builder().build())
.build();
try (var mcpClient = clientBuilder.build()) {
mcpClient.initialize();
mcpClient.rootsListChangedNotification();
}
finally {
mcpServer.close();
}
}
finally {
handlerLogger.detachAppender(logAppender);
logAppender.stop();
}
assertThat(logAppender.list).noneMatch(event -> event.getLevel() == Level.WARN);
}
private double evaluateExpression(String expression) {
// Simple expression evaluator for testing
return switch (expression) {