feat: add Streamable HTTP Server abstractions and implement WebFlux transport provider (#420)
- Add WebFluxStreamableServerTransportProvider for streamable HTTP sessions - Add WebFluxStatelessServerTransport for stateless HTTP MCP servers - Add McpStatelessServerFeatures, McpStatelessServerHandler, McpStatelessRequestHandler, McpStatelessNotificationHandler - Refactor server architecture to support multiple transport categories: streamable, single-session, stateless - Introduce McpTransportContext for transport-level metadata extraction - Add session management capabilities for streamable HTTP connections - Update MCP protocol version to 2025-03-26 - Add test coverage for new transport implementations - Implement integration tests for both stateless and streamable transports Signed-off-by: Dariusz Jędrzejczyk <dariusz.jedrzejczyk@broadcom.com> Signed-off-by: Christian Tzolov <christian.tzolov@broadcom.com> Co-authored-by: Christian Tzolov <christian.tzolov@broadcom.com>
This commit is contained in:
parent
c3a0b1855a
commit
a8f5a3fb4f
10 changed files with 6578 additions and 704 deletions
|
|
@ -37,7 +37,7 @@ class StdioMcpAsyncClientTests extends AbstractMcpAsyncClientTests {
|
|||
}
|
||||
|
||||
protected Duration getInitializationTimeout() {
|
||||
return Duration.ofSeconds(10);
|
||||
return Duration.ofSeconds(20);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ import io.modelcontextprotocol.spec.McpServerTransportProvider;
|
|||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
|
|
@ -43,7 +45,7 @@ public abstract class AbstractMcpAsyncServerTests {
|
|||
|
||||
private static final String TEST_PROMPT_NAME = "test-prompt";
|
||||
|
||||
abstract protected McpServerTransportProvider createMcpTransportProvider();
|
||||
abstract protected McpServer.AsyncSpecification<?> prepareAsyncServerBuilder();
|
||||
|
||||
protected void onStart() {
|
||||
}
|
||||
|
|
@ -64,28 +66,29 @@ public abstract class AbstractMcpAsyncServerTests {
|
|||
// Server Lifecycle Tests
|
||||
// ---------------------------------------
|
||||
|
||||
@Test
|
||||
void testConstructorWithInvalidArguments() {
|
||||
@ParameterizedTest(name = "{0} : {displayName} ")
|
||||
@ValueSource(strings = { "sse", "streamable" })
|
||||
void testConstructorWithInvalidArguments(String serverType) {
|
||||
assertThatThrownBy(() -> McpServer.async((McpServerTransportProvider) null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("Transport provider must not be null");
|
||||
|
||||
assertThatThrownBy(
|
||||
() -> McpServer.async(createMcpTransportProvider()).serverInfo((McpSchema.Implementation) null))
|
||||
assertThatThrownBy(() -> prepareAsyncServerBuilder().serverInfo((McpSchema.Implementation) null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("Server info must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGracefulShutdown() {
|
||||
var mcpAsyncServer = McpServer.async(createMcpTransportProvider()).serverInfo("test-server", "1.0.0").build();
|
||||
McpServer.AsyncSpecification<?> builder = prepareAsyncServerBuilder();
|
||||
var mcpAsyncServer = builder.serverInfo("test-server", "1.0.0").build();
|
||||
|
||||
StepVerifier.create(mcpAsyncServer.closeGracefully()).verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testImmediateClose() {
|
||||
var mcpAsyncServer = McpServer.async(createMcpTransportProvider()).serverInfo("test-server", "1.0.0").build();
|
||||
var mcpAsyncServer = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0").build();
|
||||
|
||||
assertThatCode(() -> mcpAsyncServer.close()).doesNotThrowAnyException();
|
||||
}
|
||||
|
|
@ -105,8 +108,7 @@ public abstract class AbstractMcpAsyncServerTests {
|
|||
@Deprecated
|
||||
void testAddTool() {
|
||||
Tool newTool = new McpSchema.Tool("new-tool", "New test tool", emptyJsonSchema);
|
||||
var mcpAsyncServer = McpServer.async(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
var mcpAsyncServer = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0")
|
||||
.capabilities(ServerCapabilities.builder().tools(true).build())
|
||||
.build();
|
||||
|
||||
|
|
@ -120,8 +122,7 @@ public abstract class AbstractMcpAsyncServerTests {
|
|||
@Test
|
||||
void testAddToolCall() {
|
||||
Tool newTool = new McpSchema.Tool("new-tool", "New test tool", emptyJsonSchema);
|
||||
var mcpAsyncServer = McpServer.async(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
var mcpAsyncServer = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0")
|
||||
.capabilities(ServerCapabilities.builder().tools(true).build())
|
||||
.build();
|
||||
|
||||
|
|
@ -138,8 +139,7 @@ public abstract class AbstractMcpAsyncServerTests {
|
|||
void testAddDuplicateTool() {
|
||||
Tool duplicateTool = new McpSchema.Tool(TEST_TOOL_NAME, "Duplicate tool", emptyJsonSchema);
|
||||
|
||||
var mcpAsyncServer = McpServer.async(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
var mcpAsyncServer = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0")
|
||||
.capabilities(ServerCapabilities.builder().tools(true).build())
|
||||
.tool(duplicateTool, (exchange, args) -> Mono.just(new CallToolResult(List.of(), false)))
|
||||
.build();
|
||||
|
|
@ -159,8 +159,7 @@ public abstract class AbstractMcpAsyncServerTests {
|
|||
void testAddDuplicateToolCall() {
|
||||
Tool duplicateTool = new McpSchema.Tool(TEST_TOOL_NAME, "Duplicate tool", emptyJsonSchema);
|
||||
|
||||
var mcpAsyncServer = McpServer.async(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
var mcpAsyncServer = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0")
|
||||
.capabilities(ServerCapabilities.builder().tools(true).build())
|
||||
.toolCall(duplicateTool, (exchange, request) -> Mono.just(new CallToolResult(List.of(), false)))
|
||||
.build();
|
||||
|
|
@ -181,8 +180,7 @@ public abstract class AbstractMcpAsyncServerTests {
|
|||
Tool duplicateTool = new Tool("duplicate-build-toolcall", "Duplicate toolcall during building",
|
||||
emptyJsonSchema);
|
||||
|
||||
assertThatThrownBy(() -> McpServer.async(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
assertThatThrownBy(() -> prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0")
|
||||
.capabilities(ServerCapabilities.builder().tools(true).build())
|
||||
.toolCall(duplicateTool, (exchange, request) -> Mono.just(new CallToolResult(List.of(), false)))
|
||||
.toolCall(duplicateTool, (exchange, request) -> Mono.just(new CallToolResult(List.of(), false))) // Duplicate!
|
||||
|
|
@ -204,8 +202,7 @@ public abstract class AbstractMcpAsyncServerTests {
|
|||
.build() // Duplicate!
|
||||
);
|
||||
|
||||
assertThatThrownBy(() -> McpServer.async(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
assertThatThrownBy(() -> prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0")
|
||||
.capabilities(ServerCapabilities.builder().tools(true).build())
|
||||
.tools(specs)
|
||||
.build()).isInstanceOf(IllegalArgumentException.class)
|
||||
|
|
@ -216,8 +213,7 @@ public abstract class AbstractMcpAsyncServerTests {
|
|||
void testDuplicateToolsInBatchVarargsRegistration() {
|
||||
Tool duplicateTool = new Tool("batch-varargs-tool", "Duplicate tool in batch varargs", emptyJsonSchema);
|
||||
|
||||
assertThatThrownBy(() -> McpServer.async(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
assertThatThrownBy(() -> prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0")
|
||||
.capabilities(ServerCapabilities.builder().tools(true).build())
|
||||
.tools(McpServerFeatures.AsyncToolSpecification.builder()
|
||||
.tool(duplicateTool)
|
||||
|
|
@ -236,8 +232,7 @@ public abstract class AbstractMcpAsyncServerTests {
|
|||
void testRemoveTool() {
|
||||
Tool too = new McpSchema.Tool(TEST_TOOL_NAME, "Duplicate tool", emptyJsonSchema);
|
||||
|
||||
var mcpAsyncServer = McpServer.async(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
var mcpAsyncServer = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0")
|
||||
.capabilities(ServerCapabilities.builder().tools(true).build())
|
||||
.toolCall(too, (exchange, request) -> Mono.just(new CallToolResult(List.of(), false)))
|
||||
.build();
|
||||
|
|
@ -249,8 +244,7 @@ public abstract class AbstractMcpAsyncServerTests {
|
|||
|
||||
@Test
|
||||
void testRemoveNonexistentTool() {
|
||||
var mcpAsyncServer = McpServer.async(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
var mcpAsyncServer = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0")
|
||||
.capabilities(ServerCapabilities.builder().tools(true).build())
|
||||
.build();
|
||||
|
||||
|
|
@ -265,8 +259,7 @@ public abstract class AbstractMcpAsyncServerTests {
|
|||
void testNotifyToolsListChanged() {
|
||||
Tool too = new McpSchema.Tool(TEST_TOOL_NAME, "Duplicate tool", emptyJsonSchema);
|
||||
|
||||
var mcpAsyncServer = McpServer.async(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
var mcpAsyncServer = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0")
|
||||
.capabilities(ServerCapabilities.builder().tools(true).build())
|
||||
.toolCall(too, (exchange, args) -> Mono.just(new CallToolResult(List.of(), false)))
|
||||
.build();
|
||||
|
|
@ -282,7 +275,7 @@ public abstract class AbstractMcpAsyncServerTests {
|
|||
|
||||
@Test
|
||||
void testNotifyResourcesListChanged() {
|
||||
var mcpAsyncServer = McpServer.async(createMcpTransportProvider()).serverInfo("test-server", "1.0.0").build();
|
||||
var mcpAsyncServer = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0").build();
|
||||
|
||||
StepVerifier.create(mcpAsyncServer.notifyResourcesListChanged()).verifyComplete();
|
||||
|
||||
|
|
@ -291,7 +284,7 @@ public abstract class AbstractMcpAsyncServerTests {
|
|||
|
||||
@Test
|
||||
void testNotifyResourcesUpdated() {
|
||||
var mcpAsyncServer = McpServer.async(createMcpTransportProvider()).serverInfo("test-server", "1.0.0").build();
|
||||
var mcpAsyncServer = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0").build();
|
||||
|
||||
StepVerifier
|
||||
.create(mcpAsyncServer
|
||||
|
|
@ -303,8 +296,7 @@ public abstract class AbstractMcpAsyncServerTests {
|
|||
|
||||
@Test
|
||||
void testAddResource() {
|
||||
var mcpAsyncServer = McpServer.async(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
var mcpAsyncServer = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0")
|
||||
.capabilities(ServerCapabilities.builder().resources(true, false).build())
|
||||
.build();
|
||||
|
||||
|
|
@ -320,8 +312,7 @@ public abstract class AbstractMcpAsyncServerTests {
|
|||
|
||||
@Test
|
||||
void testAddResourceWithNullSpecification() {
|
||||
var mcpAsyncServer = McpServer.async(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
var mcpAsyncServer = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0")
|
||||
.capabilities(ServerCapabilities.builder().resources(true, false).build())
|
||||
.build();
|
||||
|
||||
|
|
@ -336,9 +327,7 @@ public abstract class AbstractMcpAsyncServerTests {
|
|||
@Test
|
||||
void testAddResourceWithoutCapability() {
|
||||
// Create a server without resource capabilities
|
||||
McpAsyncServer serverWithoutResources = McpServer.async(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
.build();
|
||||
McpAsyncServer serverWithoutResources = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0").build();
|
||||
|
||||
Resource resource = new Resource(TEST_RESOURCE_URI, "Test Resource", "text/plain", "Test resource description",
|
||||
null);
|
||||
|
|
@ -354,9 +343,7 @@ public abstract class AbstractMcpAsyncServerTests {
|
|||
@Test
|
||||
void testRemoveResourceWithoutCapability() {
|
||||
// Create a server without resource capabilities
|
||||
McpAsyncServer serverWithoutResources = McpServer.async(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
.build();
|
||||
McpAsyncServer serverWithoutResources = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0").build();
|
||||
|
||||
StepVerifier.create(serverWithoutResources.removeResource(TEST_RESOURCE_URI)).verifyErrorSatisfies(error -> {
|
||||
assertThat(error).isInstanceOf(McpError.class)
|
||||
|
|
@ -370,7 +357,7 @@ public abstract class AbstractMcpAsyncServerTests {
|
|||
|
||||
@Test
|
||||
void testNotifyPromptsListChanged() {
|
||||
var mcpAsyncServer = McpServer.async(createMcpTransportProvider()).serverInfo("test-server", "1.0.0").build();
|
||||
var mcpAsyncServer = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0").build();
|
||||
|
||||
StepVerifier.create(mcpAsyncServer.notifyPromptsListChanged()).verifyComplete();
|
||||
|
||||
|
|
@ -379,8 +366,7 @@ public abstract class AbstractMcpAsyncServerTests {
|
|||
|
||||
@Test
|
||||
void testAddPromptWithNullSpecification() {
|
||||
var mcpAsyncServer = McpServer.async(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
var mcpAsyncServer = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0")
|
||||
.capabilities(ServerCapabilities.builder().prompts(false).build())
|
||||
.build();
|
||||
|
||||
|
|
@ -393,9 +379,7 @@ public abstract class AbstractMcpAsyncServerTests {
|
|||
@Test
|
||||
void testAddPromptWithoutCapability() {
|
||||
// Create a server without prompt capabilities
|
||||
McpAsyncServer serverWithoutPrompts = McpServer.async(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
.build();
|
||||
McpAsyncServer serverWithoutPrompts = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0").build();
|
||||
|
||||
Prompt prompt = new Prompt(TEST_PROMPT_NAME, "Test Prompt", "Test Prompt", List.of());
|
||||
McpServerFeatures.AsyncPromptSpecification specification = new McpServerFeatures.AsyncPromptSpecification(
|
||||
|
|
@ -411,9 +395,7 @@ public abstract class AbstractMcpAsyncServerTests {
|
|||
@Test
|
||||
void testRemovePromptWithoutCapability() {
|
||||
// Create a server without prompt capabilities
|
||||
McpAsyncServer serverWithoutPrompts = McpServer.async(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
.build();
|
||||
McpAsyncServer serverWithoutPrompts = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0").build();
|
||||
|
||||
StepVerifier.create(serverWithoutPrompts.removePrompt(TEST_PROMPT_NAME)).verifyErrorSatisfies(error -> {
|
||||
assertThat(error).isInstanceOf(McpError.class)
|
||||
|
|
@ -430,8 +412,7 @@ public abstract class AbstractMcpAsyncServerTests {
|
|||
prompt, (exchange, req) -> Mono.just(new GetPromptResult("Test prompt description", List
|
||||
.of(new PromptMessage(McpSchema.Role.ASSISTANT, new McpSchema.TextContent("Test content"))))));
|
||||
|
||||
var mcpAsyncServer = McpServer.async(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
var mcpAsyncServer = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0")
|
||||
.capabilities(ServerCapabilities.builder().prompts(true).build())
|
||||
.prompts(specification)
|
||||
.build();
|
||||
|
|
@ -443,8 +424,7 @@ public abstract class AbstractMcpAsyncServerTests {
|
|||
|
||||
@Test
|
||||
void testRemoveNonexistentPrompt() {
|
||||
var mcpAsyncServer2 = McpServer.async(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
var mcpAsyncServer2 = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0")
|
||||
.capabilities(ServerCapabilities.builder().prompts(true).build())
|
||||
.build();
|
||||
|
||||
|
|
@ -467,8 +447,7 @@ public abstract class AbstractMcpAsyncServerTests {
|
|||
var rootsReceived = new McpSchema.Root[1];
|
||||
var consumerCalled = new boolean[1];
|
||||
|
||||
var singleConsumerServer = McpServer.async(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
var singleConsumerServer = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0")
|
||||
.rootsChangeHandlers(List.of((exchange, roots) -> Mono.fromRunnable(() -> {
|
||||
consumerCalled[0] = true;
|
||||
if (!roots.isEmpty()) {
|
||||
|
|
@ -487,8 +466,7 @@ public abstract class AbstractMcpAsyncServerTests {
|
|||
var consumer2Called = new boolean[1];
|
||||
var rootsContent = new List[1];
|
||||
|
||||
var multipleConsumersServer = McpServer.async(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
var multipleConsumersServer = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0")
|
||||
.rootsChangeHandlers(List.of((exchange, roots) -> Mono.fromRunnable(() -> {
|
||||
consumer1Called[0] = true;
|
||||
rootsContent[0] = roots;
|
||||
|
|
@ -501,8 +479,7 @@ public abstract class AbstractMcpAsyncServerTests {
|
|||
onClose();
|
||||
|
||||
// Test error handling
|
||||
var errorHandlingServer = McpServer.async(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
var errorHandlingServer = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0")
|
||||
.rootsChangeHandlers(List.of((exchange, roots) -> {
|
||||
throw new RuntimeException("Test error");
|
||||
}))
|
||||
|
|
@ -514,9 +491,7 @@ public abstract class AbstractMcpAsyncServerTests {
|
|||
onClose();
|
||||
|
||||
// Test without consumers
|
||||
var noConsumersServer = McpServer.async(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
.build();
|
||||
var noConsumersServer = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0").build();
|
||||
|
||||
assertThat(noConsumersServer).isNotNull();
|
||||
assertThatCode(() -> noConsumersServer.closeGracefully().block(Duration.ofSeconds(10)))
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ import io.modelcontextprotocol.spec.McpServerTransportProvider;
|
|||
|
||||
/**
|
||||
* Test suite for the {@link McpSyncServer} that can be used with different
|
||||
* {@link McpTransportProvider} implementations.
|
||||
* {@link McpServerTransportProvider} implementations.
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
*/
|
||||
|
|
@ -41,7 +41,7 @@ public abstract class AbstractMcpSyncServerTests {
|
|||
|
||||
private static final String TEST_PROMPT_NAME = "test-prompt";
|
||||
|
||||
abstract protected McpServerTransportProvider createMcpTransportProvider();
|
||||
abstract protected McpServer.SyncSpecification<?> prepareSyncServerBuilder();
|
||||
|
||||
protected void onStart() {
|
||||
}
|
||||
|
|
@ -69,28 +69,28 @@ public abstract class AbstractMcpSyncServerTests {
|
|||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("Transport provider must not be null");
|
||||
|
||||
assertThatThrownBy(() -> McpServer.sync(createMcpTransportProvider()).serverInfo(null))
|
||||
assertThatThrownBy(() -> prepareSyncServerBuilder().serverInfo(null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("Server info must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGracefulShutdown() {
|
||||
var mcpSyncServer = McpServer.sync(createMcpTransportProvider()).serverInfo("test-server", "1.0.0").build();
|
||||
var mcpSyncServer = prepareSyncServerBuilder().serverInfo("test-server", "1.0.0").build();
|
||||
|
||||
assertThatCode(() -> mcpSyncServer.closeGracefully()).doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testImmediateClose() {
|
||||
var mcpSyncServer = McpServer.sync(createMcpTransportProvider()).serverInfo("test-server", "1.0.0").build();
|
||||
var mcpSyncServer = prepareSyncServerBuilder().serverInfo("test-server", "1.0.0").build();
|
||||
|
||||
assertThatCode(() -> mcpSyncServer.close()).doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetAsyncServer() {
|
||||
var mcpSyncServer = McpServer.sync(createMcpTransportProvider()).serverInfo("test-server", "1.0.0").build();
|
||||
var mcpSyncServer = prepareSyncServerBuilder().serverInfo("test-server", "1.0.0").build();
|
||||
|
||||
assertThat(mcpSyncServer.getAsyncServer()).isNotNull();
|
||||
|
||||
|
|
@ -112,8 +112,7 @@ public abstract class AbstractMcpSyncServerTests {
|
|||
@Test
|
||||
@Deprecated
|
||||
void testAddTool() {
|
||||
var mcpSyncServer = McpServer.sync(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
var mcpSyncServer = prepareSyncServerBuilder().serverInfo("test-server", "1.0.0")
|
||||
.capabilities(ServerCapabilities.builder().tools(true).build())
|
||||
.build();
|
||||
|
||||
|
|
@ -127,8 +126,7 @@ public abstract class AbstractMcpSyncServerTests {
|
|||
|
||||
@Test
|
||||
void testAddToolCall() {
|
||||
var mcpSyncServer = McpServer.sync(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
var mcpSyncServer = prepareSyncServerBuilder().serverInfo("test-server", "1.0.0")
|
||||
.capabilities(ServerCapabilities.builder().tools(true).build())
|
||||
.build();
|
||||
|
||||
|
|
@ -146,8 +144,7 @@ public abstract class AbstractMcpSyncServerTests {
|
|||
void testAddDuplicateTool() {
|
||||
Tool duplicateTool = new McpSchema.Tool(TEST_TOOL_NAME, "Duplicate tool", emptyJsonSchema);
|
||||
|
||||
var mcpSyncServer = McpServer.sync(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
var mcpSyncServer = prepareSyncServerBuilder().serverInfo("test-server", "1.0.0")
|
||||
.capabilities(ServerCapabilities.builder().tools(true).build())
|
||||
.tool(duplicateTool, (exchange, args) -> new CallToolResult(List.of(), false))
|
||||
.build();
|
||||
|
|
@ -164,8 +161,7 @@ public abstract class AbstractMcpSyncServerTests {
|
|||
void testAddDuplicateToolCall() {
|
||||
Tool duplicateTool = new McpSchema.Tool(TEST_TOOL_NAME, "Duplicate tool", emptyJsonSchema);
|
||||
|
||||
var mcpSyncServer = McpServer.sync(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
var mcpSyncServer = prepareSyncServerBuilder().serverInfo("test-server", "1.0.0")
|
||||
.capabilities(ServerCapabilities.builder().tools(true).build())
|
||||
.toolCall(duplicateTool, (exchange, request) -> new CallToolResult(List.of(), false))
|
||||
.build();
|
||||
|
|
@ -184,8 +180,7 @@ public abstract class AbstractMcpSyncServerTests {
|
|||
Tool duplicateTool = new Tool("duplicate-build-toolcall", "Duplicate toolcall during building",
|
||||
emptyJsonSchema);
|
||||
|
||||
assertThatThrownBy(() -> McpServer.sync(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
assertThatThrownBy(() -> prepareSyncServerBuilder().serverInfo("test-server", "1.0.0")
|
||||
.capabilities(ServerCapabilities.builder().tools(true).build())
|
||||
.toolCall(duplicateTool, (exchange, request) -> new CallToolResult(List.of(), false))
|
||||
.toolCall(duplicateTool, (exchange, request) -> new CallToolResult(List.of(), false)) // Duplicate!
|
||||
|
|
@ -207,8 +202,7 @@ public abstract class AbstractMcpSyncServerTests {
|
|||
.build() // Duplicate!
|
||||
);
|
||||
|
||||
assertThatThrownBy(() -> McpServer.sync(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
assertThatThrownBy(() -> prepareSyncServerBuilder().serverInfo("test-server", "1.0.0")
|
||||
.capabilities(ServerCapabilities.builder().tools(true).build())
|
||||
.tools(specs)
|
||||
.build()).isInstanceOf(IllegalArgumentException.class)
|
||||
|
|
@ -219,8 +213,7 @@ public abstract class AbstractMcpSyncServerTests {
|
|||
void testDuplicateToolsInBatchVarargsRegistration() {
|
||||
Tool duplicateTool = new Tool("batch-varargs-tool", "Duplicate tool in batch varargs", emptyJsonSchema);
|
||||
|
||||
assertThatThrownBy(() -> McpServer.sync(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
assertThatThrownBy(() -> prepareSyncServerBuilder().serverInfo("test-server", "1.0.0")
|
||||
.capabilities(ServerCapabilities.builder().tools(true).build())
|
||||
.tools(McpServerFeatures.SyncToolSpecification.builder()
|
||||
.tool(duplicateTool)
|
||||
|
|
@ -239,8 +232,7 @@ public abstract class AbstractMcpSyncServerTests {
|
|||
void testRemoveTool() {
|
||||
Tool tool = new McpSchema.Tool(TEST_TOOL_NAME, "Test tool", emptyJsonSchema);
|
||||
|
||||
var mcpSyncServer = McpServer.sync(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
var mcpSyncServer = prepareSyncServerBuilder().serverInfo("test-server", "1.0.0")
|
||||
.capabilities(ServerCapabilities.builder().tools(true).build())
|
||||
.toolCall(tool, (exchange, args) -> new CallToolResult(List.of(), false))
|
||||
.build();
|
||||
|
|
@ -252,8 +244,7 @@ public abstract class AbstractMcpSyncServerTests {
|
|||
|
||||
@Test
|
||||
void testRemoveNonexistentTool() {
|
||||
var mcpSyncServer = McpServer.sync(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
var mcpSyncServer = prepareSyncServerBuilder().serverInfo("test-server", "1.0.0")
|
||||
.capabilities(ServerCapabilities.builder().tools(true).build())
|
||||
.build();
|
||||
|
||||
|
|
@ -265,7 +256,7 @@ public abstract class AbstractMcpSyncServerTests {
|
|||
|
||||
@Test
|
||||
void testNotifyToolsListChanged() {
|
||||
var mcpSyncServer = McpServer.sync(createMcpTransportProvider()).serverInfo("test-server", "1.0.0").build();
|
||||
var mcpSyncServer = prepareSyncServerBuilder().serverInfo("test-server", "1.0.0").build();
|
||||
|
||||
assertThatCode(() -> mcpSyncServer.notifyToolsListChanged()).doesNotThrowAnyException();
|
||||
|
||||
|
|
@ -278,7 +269,7 @@ public abstract class AbstractMcpSyncServerTests {
|
|||
|
||||
@Test
|
||||
void testNotifyResourcesListChanged() {
|
||||
var mcpSyncServer = McpServer.sync(createMcpTransportProvider()).serverInfo("test-server", "1.0.0").build();
|
||||
var mcpSyncServer = prepareSyncServerBuilder().serverInfo("test-server", "1.0.0").build();
|
||||
|
||||
assertThatCode(() -> mcpSyncServer.notifyResourcesListChanged()).doesNotThrowAnyException();
|
||||
|
||||
|
|
@ -287,7 +278,7 @@ public abstract class AbstractMcpSyncServerTests {
|
|||
|
||||
@Test
|
||||
void testNotifyResourcesUpdated() {
|
||||
var mcpSyncServer = McpServer.sync(createMcpTransportProvider()).serverInfo("test-server", "1.0.0").build();
|
||||
var mcpSyncServer = prepareSyncServerBuilder().serverInfo("test-server", "1.0.0").build();
|
||||
|
||||
assertThatCode(() -> mcpSyncServer
|
||||
.notifyResourcesUpdated(new McpSchema.ResourcesUpdatedNotification(TEST_RESOURCE_URI)))
|
||||
|
|
@ -298,8 +289,7 @@ public abstract class AbstractMcpSyncServerTests {
|
|||
|
||||
@Test
|
||||
void testAddResource() {
|
||||
var mcpSyncServer = McpServer.sync(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
var mcpSyncServer = prepareSyncServerBuilder().serverInfo("test-server", "1.0.0")
|
||||
.capabilities(ServerCapabilities.builder().resources(true, false).build())
|
||||
.build();
|
||||
|
||||
|
|
@ -315,8 +305,7 @@ public abstract class AbstractMcpSyncServerTests {
|
|||
|
||||
@Test
|
||||
void testAddResourceWithNullSpecification() {
|
||||
var mcpSyncServer = McpServer.sync(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
var mcpSyncServer = prepareSyncServerBuilder().serverInfo("test-server", "1.0.0")
|
||||
.capabilities(ServerCapabilities.builder().resources(true, false).build())
|
||||
.build();
|
||||
|
||||
|
|
@ -329,9 +318,7 @@ public abstract class AbstractMcpSyncServerTests {
|
|||
|
||||
@Test
|
||||
void testAddResourceWithoutCapability() {
|
||||
var serverWithoutResources = McpServer.sync(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
.build();
|
||||
var serverWithoutResources = prepareSyncServerBuilder().serverInfo("test-server", "1.0.0").build();
|
||||
|
||||
Resource resource = new Resource(TEST_RESOURCE_URI, "Test Resource", "text/plain", "Test resource description",
|
||||
null);
|
||||
|
|
@ -344,9 +331,7 @@ public abstract class AbstractMcpSyncServerTests {
|
|||
|
||||
@Test
|
||||
void testRemoveResourceWithoutCapability() {
|
||||
var serverWithoutResources = McpServer.sync(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
.build();
|
||||
var serverWithoutResources = prepareSyncServerBuilder().serverInfo("test-server", "1.0.0").build();
|
||||
|
||||
assertThatThrownBy(() -> serverWithoutResources.removeResource(TEST_RESOURCE_URI)).isInstanceOf(McpError.class)
|
||||
.hasMessage("Server must be configured with resource capabilities");
|
||||
|
|
@ -358,7 +343,7 @@ public abstract class AbstractMcpSyncServerTests {
|
|||
|
||||
@Test
|
||||
void testNotifyPromptsListChanged() {
|
||||
var mcpSyncServer = McpServer.sync(createMcpTransportProvider()).serverInfo("test-server", "1.0.0").build();
|
||||
var mcpSyncServer = prepareSyncServerBuilder().serverInfo("test-server", "1.0.0").build();
|
||||
|
||||
assertThatCode(() -> mcpSyncServer.notifyPromptsListChanged()).doesNotThrowAnyException();
|
||||
|
||||
|
|
@ -367,8 +352,7 @@ public abstract class AbstractMcpSyncServerTests {
|
|||
|
||||
@Test
|
||||
void testAddPromptWithNullSpecification() {
|
||||
var mcpSyncServer = McpServer.sync(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
var mcpSyncServer = prepareSyncServerBuilder().serverInfo("test-server", "1.0.0")
|
||||
.capabilities(ServerCapabilities.builder().prompts(false).build())
|
||||
.build();
|
||||
|
||||
|
|
@ -379,9 +363,7 @@ public abstract class AbstractMcpSyncServerTests {
|
|||
|
||||
@Test
|
||||
void testAddPromptWithoutCapability() {
|
||||
var serverWithoutPrompts = McpServer.sync(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
.build();
|
||||
var serverWithoutPrompts = prepareSyncServerBuilder().serverInfo("test-server", "1.0.0").build();
|
||||
|
||||
Prompt prompt = new Prompt(TEST_PROMPT_NAME, "Test Prompt", "Test Prompt", List.of());
|
||||
McpServerFeatures.SyncPromptSpecification specification = new McpServerFeatures.SyncPromptSpecification(prompt,
|
||||
|
|
@ -394,9 +376,7 @@ public abstract class AbstractMcpSyncServerTests {
|
|||
|
||||
@Test
|
||||
void testRemovePromptWithoutCapability() {
|
||||
var serverWithoutPrompts = McpServer.sync(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
.build();
|
||||
var serverWithoutPrompts = prepareSyncServerBuilder().serverInfo("test-server", "1.0.0").build();
|
||||
|
||||
assertThatThrownBy(() -> serverWithoutPrompts.removePrompt(TEST_PROMPT_NAME)).isInstanceOf(McpError.class)
|
||||
.hasMessage("Server must be configured with prompt capabilities");
|
||||
|
|
@ -409,8 +389,7 @@ public abstract class AbstractMcpSyncServerTests {
|
|||
(exchange, req) -> new GetPromptResult("Test prompt description", List
|
||||
.of(new PromptMessage(McpSchema.Role.ASSISTANT, new McpSchema.TextContent("Test content")))));
|
||||
|
||||
var mcpSyncServer = McpServer.sync(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
var mcpSyncServer = prepareSyncServerBuilder().serverInfo("test-server", "1.0.0")
|
||||
.capabilities(ServerCapabilities.builder().prompts(true).build())
|
||||
.prompts(specification)
|
||||
.build();
|
||||
|
|
@ -422,8 +401,7 @@ public abstract class AbstractMcpSyncServerTests {
|
|||
|
||||
@Test
|
||||
void testRemoveNonexistentPrompt() {
|
||||
var mcpSyncServer = McpServer.sync(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
var mcpSyncServer = prepareSyncServerBuilder().serverInfo("test-server", "1.0.0")
|
||||
.capabilities(ServerCapabilities.builder().prompts(true).build())
|
||||
.build();
|
||||
|
||||
|
|
@ -443,8 +421,7 @@ public abstract class AbstractMcpSyncServerTests {
|
|||
var rootsReceived = new McpSchema.Root[1];
|
||||
var consumerCalled = new boolean[1];
|
||||
|
||||
var singleConsumerServer = McpServer.sync(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
var singleConsumerServer = prepareSyncServerBuilder().serverInfo("test-server", "1.0.0")
|
||||
.rootsChangeHandlers(List.of((exchange, roots) -> {
|
||||
consumerCalled[0] = true;
|
||||
if (!roots.isEmpty()) {
|
||||
|
|
@ -462,8 +439,7 @@ public abstract class AbstractMcpSyncServerTests {
|
|||
var consumer2Called = new boolean[1];
|
||||
var rootsContent = new List[1];
|
||||
|
||||
var multipleConsumersServer = McpServer.sync(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
var multipleConsumersServer = prepareSyncServerBuilder().serverInfo("test-server", "1.0.0")
|
||||
.rootsChangeHandlers(List.of((exchange, roots) -> {
|
||||
consumer1Called[0] = true;
|
||||
rootsContent[0] = roots;
|
||||
|
|
@ -475,8 +451,7 @@ public abstract class AbstractMcpSyncServerTests {
|
|||
onClose();
|
||||
|
||||
// Test error handling
|
||||
var errorHandlingServer = McpServer.sync(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
var errorHandlingServer = prepareSyncServerBuilder().serverInfo("test-server", "1.0.0")
|
||||
.rootsChangeHandlers(List.of((exchange, roots) -> {
|
||||
throw new RuntimeException("Test error");
|
||||
}))
|
||||
|
|
@ -487,7 +462,7 @@ public abstract class AbstractMcpSyncServerTests {
|
|||
onClose();
|
||||
|
||||
// Test without consumers
|
||||
var noConsumersServer = McpServer.sync(createMcpTransportProvider()).serverInfo("test-server", "1.0.0").build();
|
||||
var noConsumersServer = prepareSyncServerBuilder().serverInfo("test-server", "1.0.0").build();
|
||||
|
||||
assertThat(noConsumersServer).isNotNull();
|
||||
assertThatCode(() -> noConsumersServer.closeGracefully()).doesNotThrowAnyException();
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
|||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
|
@ -54,7 +53,8 @@ class McpAsyncServerExchangeTests {
|
|||
|
||||
clientInfo = new McpSchema.Implementation("test-client", "1.0.0");
|
||||
|
||||
exchange = new McpAsyncServerExchange(mockSession, clientCapabilities, clientInfo);
|
||||
exchange = new McpAsyncServerExchange("testSessionId", mockSession, clientCapabilities, clientInfo,
|
||||
new DefaultMcpTransportContext());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -219,27 +219,33 @@ class McpAsyncServerExchangeTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testLoggingNotificationWithAllowedLevel() {
|
||||
void testSetMinLoggingLevelWithNullValue() {
|
||||
assertThatThrownBy(() -> exchange.setMinLoggingLevel(null)).isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("minLoggingLevel must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoggingNotificationWithAllowedLevel() {
|
||||
McpSchema.LoggingMessageNotification notification = McpSchema.LoggingMessageNotification.builder()
|
||||
.level(McpSchema.LoggingLevel.ERROR)
|
||||
.logger("test-logger")
|
||||
.data("Test error message")
|
||||
.build();
|
||||
|
||||
when(mockSession.isNotificationForLevelAllowed(any())).thenReturn(Boolean.TRUE);
|
||||
when(mockSession.sendNotification(eq(McpSchema.METHOD_NOTIFICATION_MESSAGE), eq(notification)))
|
||||
.thenReturn(Mono.empty());
|
||||
|
||||
StepVerifier.create(exchange.loggingNotification(notification)).verifyComplete();
|
||||
|
||||
// Verify that sendNotification was called exactly once
|
||||
verify(mockSession, times(1)).isNotificationForLevelAllowed(eq(McpSchema.LoggingLevel.ERROR));
|
||||
verify(mockSession, times(1)).sendNotification(eq(McpSchema.METHOD_NOTIFICATION_MESSAGE), eq(notification));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoggingNotificationWithFilteredLevel() {
|
||||
// Given - Set minimum level to WARNING, send DEBUG message
|
||||
exchange.setMinLoggingLevel(McpSchema.LoggingLevel.WARNING);
|
||||
exchange.setMinLoggingLevel(McpSchema.LoggingLevel.DEBUG);
|
||||
verify(mockSession, times(1)).setMinLoggingLevel(eq(McpSchema.LoggingLevel.DEBUG));
|
||||
|
||||
McpSchema.LoggingMessageNotification debugNotification = McpSchema.LoggingMessageNotification.builder()
|
||||
.level(McpSchema.LoggingLevel.DEBUG)
|
||||
|
|
@ -247,104 +253,38 @@ class McpAsyncServerExchangeTests {
|
|||
.data("Debug message that should be filtered")
|
||||
.build();
|
||||
|
||||
// When & Then - Should complete without sending notification
|
||||
StepVerifier.create(exchange.loggingNotification(debugNotification)).verifyComplete();
|
||||
|
||||
// Verify that sendNotification was never called for filtered DEBUG level
|
||||
verify(mockSession, never()).sendNotification(eq(McpSchema.METHOD_NOTIFICATION_MESSAGE), eq(debugNotification));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoggingNotificationLevelFiltering() {
|
||||
// Given - Set minimum level to WARNING
|
||||
exchange.setMinLoggingLevel(McpSchema.LoggingLevel.WARNING);
|
||||
|
||||
// Test DEBUG (should be filtered)
|
||||
McpSchema.LoggingMessageNotification debugNotification = McpSchema.LoggingMessageNotification.builder()
|
||||
.level(McpSchema.LoggingLevel.DEBUG)
|
||||
.logger("test-logger")
|
||||
.data("Debug message")
|
||||
.build();
|
||||
when(mockSession.isNotificationForLevelAllowed(eq(McpSchema.LoggingLevel.DEBUG))).thenReturn(Boolean.TRUE);
|
||||
when(mockSession.sendNotification(eq(McpSchema.METHOD_NOTIFICATION_MESSAGE), eq(debugNotification)))
|
||||
.thenReturn(Mono.empty());
|
||||
|
||||
StepVerifier.create(exchange.loggingNotification(debugNotification)).verifyComplete();
|
||||
|
||||
// Verify that sendNotification was never called for DEBUG level
|
||||
verify(mockSession, never()).sendNotification(eq(McpSchema.METHOD_NOTIFICATION_MESSAGE), eq(debugNotification));
|
||||
verify(mockSession, times(1)).isNotificationForLevelAllowed(eq(McpSchema.LoggingLevel.DEBUG));
|
||||
verify(mockSession, times(1)).sendNotification(eq(McpSchema.METHOD_NOTIFICATION_MESSAGE),
|
||||
eq(debugNotification));
|
||||
|
||||
// Test INFO (should be filtered)
|
||||
McpSchema.LoggingMessageNotification infoNotification = McpSchema.LoggingMessageNotification.builder()
|
||||
.level(McpSchema.LoggingLevel.INFO)
|
||||
.logger("test-logger")
|
||||
.data("Info message")
|
||||
.build();
|
||||
|
||||
StepVerifier.create(exchange.loggingNotification(infoNotification)).verifyComplete();
|
||||
|
||||
// Verify that sendNotification was never called for INFO level
|
||||
verify(mockSession, never()).sendNotification(eq(McpSchema.METHOD_NOTIFICATION_MESSAGE), eq(infoNotification));
|
||||
|
||||
reset(mockSession);
|
||||
|
||||
// Test WARNING (should be sent)
|
||||
McpSchema.LoggingMessageNotification warningNotification = McpSchema.LoggingMessageNotification.builder()
|
||||
.level(McpSchema.LoggingLevel.WARNING)
|
||||
.logger("test-logger")
|
||||
.data("Warning message")
|
||||
.data("Debug message that should be filtered")
|
||||
.build();
|
||||
|
||||
when(mockSession.sendNotification(eq(McpSchema.METHOD_NOTIFICATION_MESSAGE), eq(warningNotification)))
|
||||
.thenReturn(Mono.empty());
|
||||
|
||||
StepVerifier.create(exchange.loggingNotification(warningNotification)).verifyComplete();
|
||||
|
||||
// Verify that sendNotification was called exactly once for WARNING level
|
||||
verify(mockSession, times(1)).sendNotification(eq(McpSchema.METHOD_NOTIFICATION_MESSAGE),
|
||||
verify(mockSession, times(1)).isNotificationForLevelAllowed(eq(McpSchema.LoggingLevel.WARNING));
|
||||
verify(mockSession, never()).sendNotification(eq(McpSchema.METHOD_NOTIFICATION_MESSAGE),
|
||||
eq(warningNotification));
|
||||
|
||||
// Test ERROR (should be sent)
|
||||
McpSchema.LoggingMessageNotification errorNotification = McpSchema.LoggingMessageNotification.builder()
|
||||
.level(McpSchema.LoggingLevel.ERROR)
|
||||
.logger("test-logger")
|
||||
.data("Error message")
|
||||
.build();
|
||||
|
||||
when(mockSession.sendNotification(eq(McpSchema.METHOD_NOTIFICATION_MESSAGE), eq(errorNotification)))
|
||||
.thenReturn(Mono.empty());
|
||||
|
||||
StepVerifier.create(exchange.loggingNotification(errorNotification)).verifyComplete();
|
||||
|
||||
// Verify that sendNotification was called exactly once for ERROR level
|
||||
verify(mockSession, times(1)).sendNotification(eq(McpSchema.METHOD_NOTIFICATION_MESSAGE),
|
||||
eq(errorNotification));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoggingNotificationWithDefaultLevel() {
|
||||
|
||||
McpSchema.LoggingMessageNotification infoNotification = McpSchema.LoggingMessageNotification.builder()
|
||||
.level(McpSchema.LoggingLevel.INFO)
|
||||
.logger("test-logger")
|
||||
.data("Info message")
|
||||
.build();
|
||||
|
||||
when(mockSession.sendNotification(eq(McpSchema.METHOD_NOTIFICATION_MESSAGE), eq(infoNotification)))
|
||||
.thenReturn(Mono.empty());
|
||||
|
||||
StepVerifier.create(exchange.loggingNotification(infoNotification)).verifyComplete();
|
||||
|
||||
// Verify that sendNotification was called exactly once for default level
|
||||
verify(mockSession, times(1)).sendNotification(eq(McpSchema.METHOD_NOTIFICATION_MESSAGE), eq(infoNotification));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoggingNotificationWithSessionError() {
|
||||
|
||||
McpSchema.LoggingMessageNotification notification = McpSchema.LoggingMessageNotification.builder()
|
||||
.level(McpSchema.LoggingLevel.ERROR)
|
||||
.logger("test-logger")
|
||||
.data("Test error message")
|
||||
.build();
|
||||
|
||||
when(mockSession.isNotificationForLevelAllowed(any())).thenReturn(Boolean.TRUE);
|
||||
when(mockSession.sendNotification(eq(McpSchema.METHOD_NOTIFICATION_MESSAGE), eq(notification)))
|
||||
.thenReturn(Mono.error(new RuntimeException("Session error")));
|
||||
|
||||
|
|
@ -353,44 +293,6 @@ class McpAsyncServerExchangeTests {
|
|||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetMinLoggingLevelWithNullValue() {
|
||||
// When & Then
|
||||
assertThatThrownBy(() -> exchange.setMinLoggingLevel(null)).isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("minLoggingLevel must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoggingLevelHierarchy() {
|
||||
// Test all logging levels to ensure proper hierarchy
|
||||
McpSchema.LoggingLevel[] levels = { McpSchema.LoggingLevel.DEBUG, McpSchema.LoggingLevel.INFO,
|
||||
McpSchema.LoggingLevel.NOTICE, McpSchema.LoggingLevel.WARNING, McpSchema.LoggingLevel.ERROR,
|
||||
McpSchema.LoggingLevel.CRITICAL, McpSchema.LoggingLevel.ALERT, McpSchema.LoggingLevel.EMERGENCY };
|
||||
|
||||
// Set minimum level to WARNING
|
||||
exchange.setMinLoggingLevel(McpSchema.LoggingLevel.WARNING);
|
||||
|
||||
for (McpSchema.LoggingLevel level : levels) {
|
||||
McpSchema.LoggingMessageNotification notification = McpSchema.LoggingMessageNotification.builder()
|
||||
.level(level)
|
||||
.logger("test-logger")
|
||||
.data("Test message for " + level)
|
||||
.build();
|
||||
|
||||
if (level.level() >= McpSchema.LoggingLevel.WARNING.level()) {
|
||||
// Should be sent
|
||||
when(mockSession.sendNotification(eq(McpSchema.METHOD_NOTIFICATION_MESSAGE), eq(notification)))
|
||||
.thenReturn(Mono.empty());
|
||||
|
||||
StepVerifier.create(exchange.loggingNotification(notification)).verifyComplete();
|
||||
}
|
||||
else {
|
||||
// Should be filtered (completes without sending)
|
||||
StepVerifier.create(exchange.loggingNotification(notification)).verifyComplete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------
|
||||
// Create Elicitation Tests
|
||||
// ---------------------------------------
|
||||
|
|
|
|||
|
|
@ -41,6 +41,8 @@ class McpCompletionTests {
|
|||
|
||||
private HttpServletSseServerTransportProvider mcpServerTransportProvider;
|
||||
|
||||
private static final int PORT = TomcatTestUtil.findAvailablePort();
|
||||
|
||||
private static final String CUSTOM_MESSAGE_ENDPOINT = "/otherPath/mcp/message";
|
||||
|
||||
McpClient.SyncSpec clientBuilder;
|
||||
|
|
@ -55,7 +57,7 @@ class McpCompletionTests {
|
|||
.messageEndpoint(CUSTOM_MESSAGE_ENDPOINT)
|
||||
.build();
|
||||
|
||||
tomcat = TomcatTestUtil.createTomcatServer("", 3400, mcpServerTransportProvider);
|
||||
tomcat = TomcatTestUtil.createTomcatServer("", PORT, mcpServerTransportProvider);
|
||||
try {
|
||||
tomcat.start();
|
||||
assertThat(tomcat.getServer().getState()).isEqualTo(LifecycleState.STARTED);
|
||||
|
|
@ -64,7 +66,7 @@ class McpCompletionTests {
|
|||
throw new RuntimeException("Failed to start Tomcat", e);
|
||||
}
|
||||
|
||||
this.clientBuilder = McpClient.sync(HttpClientSseClientTransport.builder("http://localhost:" + 3400).build());
|
||||
this.clientBuilder = McpClient.sync(HttpClientSseClientTransport.builder("http://localhost:" + PORT).build());
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
|
|
|
|||
|
|
@ -226,19 +226,21 @@ class McpSyncServerExchangeTests {
|
|||
.data("Test error message")
|
||||
.build();
|
||||
|
||||
when(mockSession.isNotificationForLevelAllowed(any())).thenReturn(Boolean.TRUE);
|
||||
when(mockSession.sendNotification(eq(McpSchema.METHOD_NOTIFICATION_MESSAGE), eq(notification)))
|
||||
.thenReturn(Mono.empty());
|
||||
|
||||
exchange.loggingNotification(notification);
|
||||
|
||||
// Verify that sendNotification was called exactly once
|
||||
verify(mockSession, times(1)).isNotificationForLevelAllowed(eq(McpSchema.LoggingLevel.ERROR));
|
||||
verify(mockSession, times(1)).sendNotification(eq(McpSchema.METHOD_NOTIFICATION_MESSAGE), eq(notification));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoggingNotificationWithFilteredLevel() {
|
||||
// Given - Set minimum level to WARNING, send DEBUG message
|
||||
asyncExchange.setMinLoggingLevel(McpSchema.LoggingLevel.WARNING);
|
||||
asyncExchange.setMinLoggingLevel(McpSchema.LoggingLevel.DEBUG);
|
||||
verify(mockSession, times(1)).setMinLoggingLevel(McpSchema.LoggingLevel.DEBUG);
|
||||
|
||||
McpSchema.LoggingMessageNotification debugNotification = McpSchema.LoggingMessageNotification.builder()
|
||||
.level(McpSchema.LoggingLevel.DEBUG)
|
||||
|
|
@ -246,93 +248,27 @@ class McpSyncServerExchangeTests {
|
|||
.data("Debug message that should be filtered")
|
||||
.build();
|
||||
|
||||
// When & Then - Should complete without sending notification
|
||||
exchange.loggingNotification(debugNotification);
|
||||
|
||||
// Verify that sendNotification was never called for filtered DEBUG level
|
||||
verify(mockSession, never()).sendNotification(eq(McpSchema.METHOD_NOTIFICATION_MESSAGE), eq(debugNotification));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoggingNotificationLevelFiltering() {
|
||||
// Given - Set minimum level to WARNING
|
||||
asyncExchange.setMinLoggingLevel(McpSchema.LoggingLevel.WARNING);
|
||||
|
||||
// Test DEBUG (should be filtered)
|
||||
McpSchema.LoggingMessageNotification debugNotification = McpSchema.LoggingMessageNotification.builder()
|
||||
.level(McpSchema.LoggingLevel.DEBUG)
|
||||
.logger("test-logger")
|
||||
.data("Debug message")
|
||||
.build();
|
||||
when(mockSession.isNotificationForLevelAllowed(eq(McpSchema.LoggingLevel.DEBUG))).thenReturn(Boolean.TRUE);
|
||||
when(mockSession.sendNotification(eq(McpSchema.METHOD_NOTIFICATION_MESSAGE), eq(debugNotification)))
|
||||
.thenReturn(Mono.empty());
|
||||
|
||||
exchange.loggingNotification(debugNotification);
|
||||
|
||||
// Verify that sendNotification was never called for DEBUG level
|
||||
verify(mockSession, never()).sendNotification(eq(McpSchema.METHOD_NOTIFICATION_MESSAGE), eq(debugNotification));
|
||||
verify(mockSession, times(1)).isNotificationForLevelAllowed(McpSchema.LoggingLevel.DEBUG);
|
||||
verify(mockSession, times(1)).sendNotification(eq(McpSchema.METHOD_NOTIFICATION_MESSAGE),
|
||||
eq(debugNotification));
|
||||
|
||||
// Test INFO (should be filtered)
|
||||
McpSchema.LoggingMessageNotification infoNotification = McpSchema.LoggingMessageNotification.builder()
|
||||
.level(McpSchema.LoggingLevel.INFO)
|
||||
.logger("test-logger")
|
||||
.data("Info message")
|
||||
.build();
|
||||
|
||||
exchange.loggingNotification(infoNotification);
|
||||
|
||||
// Verify that sendNotification was never called for INFO level
|
||||
verify(mockSession, never()).sendNotification(eq(McpSchema.METHOD_NOTIFICATION_MESSAGE), eq(infoNotification));
|
||||
|
||||
reset(mockSession);
|
||||
|
||||
// Test WARNING (should be sent)
|
||||
McpSchema.LoggingMessageNotification warningNotification = McpSchema.LoggingMessageNotification.builder()
|
||||
.level(McpSchema.LoggingLevel.WARNING)
|
||||
.logger("test-logger")
|
||||
.data("Warning message")
|
||||
.data("Debug message that should be filtered")
|
||||
.build();
|
||||
|
||||
when(mockSession.sendNotification(eq(McpSchema.METHOD_NOTIFICATION_MESSAGE), eq(warningNotification)))
|
||||
.thenReturn(Mono.empty());
|
||||
|
||||
exchange.loggingNotification(warningNotification);
|
||||
|
||||
// Verify that sendNotification was called exactly once for WARNING level
|
||||
verify(mockSession, times(1)).sendNotification(eq(McpSchema.METHOD_NOTIFICATION_MESSAGE),
|
||||
verify(mockSession, times(1)).isNotificationForLevelAllowed(McpSchema.LoggingLevel.WARNING);
|
||||
verify(mockSession, never()).sendNotification(eq(McpSchema.METHOD_NOTIFICATION_MESSAGE),
|
||||
eq(warningNotification));
|
||||
|
||||
// Test ERROR (should be sent)
|
||||
McpSchema.LoggingMessageNotification errorNotification = McpSchema.LoggingMessageNotification.builder()
|
||||
.level(McpSchema.LoggingLevel.ERROR)
|
||||
.logger("test-logger")
|
||||
.data("Error message")
|
||||
.build();
|
||||
|
||||
when(mockSession.sendNotification(eq(McpSchema.METHOD_NOTIFICATION_MESSAGE), eq(errorNotification)))
|
||||
.thenReturn(Mono.empty());
|
||||
|
||||
exchange.loggingNotification(errorNotification);
|
||||
|
||||
// Verify that sendNotification was called exactly once for ERROR level
|
||||
verify(mockSession, times(1)).sendNotification(eq(McpSchema.METHOD_NOTIFICATION_MESSAGE),
|
||||
eq(errorNotification));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoggingNotificationWithDefaultLevel() {
|
||||
|
||||
McpSchema.LoggingMessageNotification infoNotification = McpSchema.LoggingMessageNotification.builder()
|
||||
.level(McpSchema.LoggingLevel.INFO)
|
||||
.logger("test-logger")
|
||||
.data("Info message")
|
||||
.build();
|
||||
|
||||
when(mockSession.sendNotification(eq(McpSchema.METHOD_NOTIFICATION_MESSAGE), eq(infoNotification)))
|
||||
.thenReturn(Mono.empty());
|
||||
|
||||
exchange.loggingNotification(infoNotification);
|
||||
|
||||
// Verify that sendNotification was called exactly once for default level
|
||||
verify(mockSession, times(1)).sendNotification(eq(McpSchema.METHOD_NOTIFICATION_MESSAGE), eq(infoNotification));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -344,6 +280,7 @@ class McpSyncServerExchangeTests {
|
|||
.data("Test error message")
|
||||
.build();
|
||||
|
||||
when(mockSession.isNotificationForLevelAllowed(any())).thenReturn(Boolean.TRUE);
|
||||
when(mockSession.sendNotification(eq(McpSchema.METHOD_NOTIFICATION_MESSAGE), eq(notification)))
|
||||
.thenReturn(Mono.error(new RuntimeException("Session error")));
|
||||
|
||||
|
|
@ -351,37 +288,6 @@ class McpSyncServerExchangeTests {
|
|||
.hasMessage("Session error");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoggingLevelHierarchy() {
|
||||
// Test all logging levels to ensure proper hierarchy
|
||||
McpSchema.LoggingLevel[] levels = { McpSchema.LoggingLevel.DEBUG, McpSchema.LoggingLevel.INFO,
|
||||
McpSchema.LoggingLevel.NOTICE, McpSchema.LoggingLevel.WARNING, McpSchema.LoggingLevel.ERROR,
|
||||
McpSchema.LoggingLevel.CRITICAL, McpSchema.LoggingLevel.ALERT, McpSchema.LoggingLevel.EMERGENCY };
|
||||
|
||||
// Set minimum level to WARNING
|
||||
asyncExchange.setMinLoggingLevel(McpSchema.LoggingLevel.WARNING);
|
||||
|
||||
for (McpSchema.LoggingLevel level : levels) {
|
||||
McpSchema.LoggingMessageNotification notification = McpSchema.LoggingMessageNotification.builder()
|
||||
.level(level)
|
||||
.logger("test-logger")
|
||||
.data("Test message for " + level)
|
||||
.build();
|
||||
|
||||
if (level.level() >= McpSchema.LoggingLevel.WARNING.level()) {
|
||||
// Should be sent
|
||||
when(mockSession.sendNotification(eq(McpSchema.METHOD_NOTIFICATION_MESSAGE), eq(notification)))
|
||||
.thenReturn(Mono.empty());
|
||||
|
||||
exchange.loggingNotification(notification);
|
||||
}
|
||||
else {
|
||||
// Should be filtered (completes without sending)
|
||||
exchange.loggingNotification(notification);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------
|
||||
// Create Elicitation Tests
|
||||
// ---------------------------------------
|
||||
|
|
|
|||
|
|
@ -16,9 +16,13 @@ import org.junit.jupiter.api.Timeout;
|
|||
@Timeout(15) // Giving extra time beyond the client timeout
|
||||
class ServletSseMcpAsyncServerTests extends AbstractMcpAsyncServerTests {
|
||||
|
||||
@Override
|
||||
protected McpServerTransportProvider createMcpTransportProvider() {
|
||||
return HttpServletSseServerTransportProvider.builder().messageEndpoint("/mcp/message").build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected McpServer.AsyncSpecification<?> prepareAsyncServerBuilder() {
|
||||
return McpServer.async(createMcpTransportProvider());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,9 +16,13 @@ import org.junit.jupiter.api.Timeout;
|
|||
@Timeout(15) // Giving extra time beyond the client timeout
|
||||
class ServletSseMcpSyncServerTests extends AbstractMcpSyncServerTests {
|
||||
|
||||
@Override
|
||||
protected McpServerTransportProvider createMcpTransportProvider() {
|
||||
return HttpServletSseServerTransportProvider.builder().messageEndpoint("/mcp/message").build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected McpServer.SyncSpecification<?> prepareSyncServerBuilder() {
|
||||
return McpServer.sync(createMcpTransportProvider());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,9 +16,13 @@ import org.junit.jupiter.api.Timeout;
|
|||
@Timeout(15) // Giving extra time beyond the client timeout
|
||||
class StdioMcpAsyncServerTests extends AbstractMcpAsyncServerTests {
|
||||
|
||||
@Override
|
||||
protected McpServerTransportProvider createMcpTransportProvider() {
|
||||
return new StdioServerTransportProvider();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected McpServer.AsyncSpecification<?> prepareAsyncServerBuilder() {
|
||||
return McpServer.async(createMcpTransportProvider());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,9 +16,13 @@ import org.junit.jupiter.api.Timeout;
|
|||
@Timeout(15) // Giving extra time beyond the client timeout
|
||||
class StdioMcpSyncServerTests extends AbstractMcpSyncServerTests {
|
||||
|
||||
@Override
|
||||
protected McpServerTransportProvider createMcpTransportProvider() {
|
||||
return new StdioServerTransportProvider();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected McpServer.SyncSpecification<?> prepareSyncServerBuilder() {
|
||||
return McpServer.sync(createMcpTransportProvider());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue