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:
Dariusz Jędrzejczyk 2025-07-30 13:41:18 +02:00 • committed by GitHub
commit a8f5a3fb4f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 6578 additions and 704 deletions

View file

@ -37,7 +37,7 @@ class StdioMcpAsyncClientTests extends AbstractMcpAsyncClientTests {
}
protected Duration getInitializationTimeout() {
return Duration.ofSeconds(10);
return Duration.ofSeconds(20);
}
}

View file

@ -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)))

View file

@ -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();

View file

@ -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
// ---------------------------------------

View file

@ -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

View file

@ -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
// ---------------------------------------

View file

@ -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());
}
}

View file

@ -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());
}
}

View file

@ -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());
}
}

View file

@ -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());
}
}