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:
parent
0557b87603
commit
fd004989b9
2 changed files with 81 additions and 4 deletions
|
|
@ -133,10 +133,26 @@ public class McpStatelessAsyncServer {
|
||||||
|
|
||||||
this.protocolVersions = new ArrayList<>(mcpTransport.protocolVersions());
|
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);
|
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
|
// Lifecycle Management
|
||||||
// ---------------------------------------
|
// ---------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,10 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.function.BiFunction;
|
import java.util.function.BiFunction;
|
||||||
import java.util.function.Function;
|
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.McpClient;
|
||||||
import io.modelcontextprotocol.client.transport.HttpClientStreamableHttpTransport;
|
import io.modelcontextprotocol.client.transport.HttpClientStreamableHttpTransport;
|
||||||
import io.modelcontextprotocol.common.McpTransportContext;
|
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.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.Timeout;
|
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.MockHttpServletRequest;
|
||||||
import org.springframework.mock.web.MockHttpServletResponse;
|
import org.springframework.mock.web.MockHttpServletResponse;
|
||||||
import org.springframework.web.client.RestClient;
|
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.APPLICATION_JSON;
|
||||||
import static io.modelcontextprotocol.server.transport.HttpServletStatelessServerTransport.TEXT_EVENT_STREAM;
|
import static io.modelcontextprotocol.server.transport.HttpServletStatelessServerTransport.TEXT_EVENT_STREAM;
|
||||||
import static io.modelcontextprotocol.util.McpJsonMapperUtils.JSON_MAPPER;
|
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) {
|
private double evaluateExpression(String expression) {
|
||||||
// Simple expression evaluator for testing
|
// Simple expression evaluator for testing
|
||||||
return switch (expression) {
|
return switch (expression) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue