refactor: deprecate tool spec's arguments-only handler in favor of CallToolRequest (#375)
Tool handlers now receive CallToolRequest instead of raw arguments to enable access to additional metadata parameters like progressToken - Deprecate handlers that take only Map<String, Object> arguments - Introduce new handlers that receive the full CallToolRequest object - Maintain backward compatibility with deprecated call handlers - Enhance API to provide access to complete tool request context beyond just arguments - Add builder pattern for AsyncToolSpecification and SyncToolSpecification - Add duplicate tool name validation during server building and runtime registration - Update all test files to use new callTool handlers and builder pattern - Add test coverage for new builder functionality and CallToolRequest handling Signed-off-by: Christian Tzolov <christian.tzolov@broadcom.com>
This commit is contained in:
parent
95df67ec05
commit
901175e14f
6 changed files with 1687 additions and 692 deletions
|
|
@ -30,10 +30,11 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
|||
|
||||
/**
|
||||
* Test suite for the {@link McpAsyncServer} that can be used with different
|
||||
* {@link McpTransportProvider} implementations.
|
||||
* {@link io.modelcontextprotocol.spec.McpServerTransportProvider} implementations.
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
*/
|
||||
// KEEP IN SYNC with the class in mcp-test module
|
||||
public abstract class AbstractMcpAsyncServerTests {
|
||||
|
||||
private static final String TEST_TOOL_NAME = "test-tool";
|
||||
|
|
@ -101,6 +102,7 @@ public abstract class AbstractMcpAsyncServerTests {
|
|||
""";
|
||||
|
||||
@Test
|
||||
@Deprecated
|
||||
void testAddTool() {
|
||||
Tool newTool = new McpSchema.Tool("new-tool", "New test tool", emptyJsonSchema);
|
||||
var mcpAsyncServer = McpServer.async(createMcpTransportProvider())
|
||||
|
|
@ -116,6 +118,23 @@ 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")
|
||||
.capabilities(ServerCapabilities.builder().tools(true).build())
|
||||
.build();
|
||||
|
||||
StepVerifier.create(mcpAsyncServer.addTool(McpServerFeatures.AsyncToolSpecification.builder()
|
||||
.tool(newTool)
|
||||
.callHandler((exchange, request) -> Mono.just(new CallToolResult(List.of(), false)))
|
||||
.build())).verifyComplete();
|
||||
|
||||
assertThatCode(() -> mcpAsyncServer.closeGracefully().block(Duration.ofSeconds(10))).doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Deprecated
|
||||
void testAddDuplicateTool() {
|
||||
Tool duplicateTool = new McpSchema.Tool(TEST_TOOL_NAME, "Duplicate tool", emptyJsonSchema);
|
||||
|
||||
|
|
@ -136,6 +155,83 @@ public abstract class AbstractMcpAsyncServerTests {
|
|||
assertThatCode(() -> mcpAsyncServer.closeGracefully().block(Duration.ofSeconds(10))).doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddDuplicateToolCall() {
|
||||
Tool duplicateTool = new McpSchema.Tool(TEST_TOOL_NAME, "Duplicate tool", emptyJsonSchema);
|
||||
|
||||
var mcpAsyncServer = McpServer.async(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
.capabilities(ServerCapabilities.builder().tools(true).build())
|
||||
.toolCall(duplicateTool, (exchange, request) -> Mono.just(new CallToolResult(List.of(), false)))
|
||||
.build();
|
||||
|
||||
StepVerifier.create(mcpAsyncServer.addTool(McpServerFeatures.AsyncToolSpecification.builder()
|
||||
.tool(duplicateTool)
|
||||
.callHandler((exchange, request) -> Mono.just(new CallToolResult(List.of(), false)))
|
||||
.build())).verifyErrorSatisfies(error -> {
|
||||
assertThat(error).isInstanceOf(McpError.class)
|
||||
.hasMessage("Tool with name '" + TEST_TOOL_NAME + "' already exists");
|
||||
});
|
||||
|
||||
assertThatCode(() -> mcpAsyncServer.closeGracefully().block(Duration.ofSeconds(10))).doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDuplicateToolCallDuringBuilding() {
|
||||
Tool duplicateTool = new Tool("duplicate-build-toolcall", "Duplicate toolcall during building",
|
||||
emptyJsonSchema);
|
||||
|
||||
assertThatThrownBy(() -> McpServer.async(createMcpTransportProvider())
|
||||
.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!
|
||||
.build()).isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("Tool with name 'duplicate-build-toolcall' is already registered.");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDuplicateToolsInBatchListRegistration() {
|
||||
Tool duplicateTool = new Tool("batch-list-tool", "Duplicate tool in batch list", emptyJsonSchema);
|
||||
List<McpServerFeatures.AsyncToolSpecification> specs = List.of(
|
||||
McpServerFeatures.AsyncToolSpecification.builder()
|
||||
.tool(duplicateTool)
|
||||
.callHandler((exchange, request) -> Mono.just(new CallToolResult(List.of(), false)))
|
||||
.build(),
|
||||
McpServerFeatures.AsyncToolSpecification.builder()
|
||||
.tool(duplicateTool)
|
||||
.callHandler((exchange, request) -> Mono.just(new CallToolResult(List.of(), false)))
|
||||
.build() // Duplicate!
|
||||
);
|
||||
|
||||
assertThatThrownBy(() -> McpServer.async(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
.capabilities(ServerCapabilities.builder().tools(true).build())
|
||||
.tools(specs)
|
||||
.build()).isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("Tool with name 'batch-list-tool' is already registered.");
|
||||
}
|
||||
|
||||
@Test
|
||||
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")
|
||||
.capabilities(ServerCapabilities.builder().tools(true).build())
|
||||
.tools(McpServerFeatures.AsyncToolSpecification.builder()
|
||||
.tool(duplicateTool)
|
||||
.callHandler((exchange, request) -> Mono.just(new CallToolResult(List.of(), false)))
|
||||
.build(),
|
||||
McpServerFeatures.AsyncToolSpecification.builder()
|
||||
.tool(duplicateTool)
|
||||
.callHandler((exchange, request) -> Mono.just(new CallToolResult(List.of(), false)))
|
||||
.build() // Duplicate!
|
||||
)
|
||||
.build()).isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("Tool with name 'batch-varargs-tool' is already registered.");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRemoveTool() {
|
||||
Tool too = new McpSchema.Tool(TEST_TOOL_NAME, "Duplicate tool", emptyJsonSchema);
|
||||
|
|
@ -143,7 +239,7 @@ public abstract class AbstractMcpAsyncServerTests {
|
|||
var mcpAsyncServer = McpServer.async(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
.capabilities(ServerCapabilities.builder().tools(true).build())
|
||||
.tool(too, (exchange, args) -> Mono.just(new CallToolResult(List.of(), false)))
|
||||
.toolCall(too, (exchange, request) -> Mono.just(new CallToolResult(List.of(), false)))
|
||||
.build();
|
||||
|
||||
StepVerifier.create(mcpAsyncServer.removeTool(TEST_TOOL_NAME)).verifyComplete();
|
||||
|
|
@ -172,7 +268,7 @@ public abstract class AbstractMcpAsyncServerTests {
|
|||
var mcpAsyncServer = McpServer.async(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
.capabilities(ServerCapabilities.builder().tools(true).build())
|
||||
.tool(too, (exchange, args) -> Mono.just(new CallToolResult(List.of(), false)))
|
||||
.toolCall(too, (exchange, args) -> Mono.just(new CallToolResult(List.of(), false)))
|
||||
.build();
|
||||
|
||||
StepVerifier.create(mcpAsyncServer.notifyToolsListChanged()).verifyComplete();
|
||||
|
|
|
|||
|
|
@ -4,8 +4,16 @@
|
|||
|
||||
package io.modelcontextprotocol.server;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import io.modelcontextprotocol.spec.McpError;
|
||||
import io.modelcontextprotocol.spec.McpSchema;
|
||||
import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
|
||||
|
|
@ -17,13 +25,6 @@ import io.modelcontextprotocol.spec.McpSchema.Resource;
|
|||
import io.modelcontextprotocol.spec.McpSchema.ServerCapabilities;
|
||||
import io.modelcontextprotocol.spec.McpSchema.Tool;
|
||||
import io.modelcontextprotocol.spec.McpServerTransportProvider;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
/**
|
||||
* Test suite for the {@link McpSyncServer} that can be used with different
|
||||
|
|
@ -31,6 +32,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
|||
*
|
||||
* @author Christian Tzolov
|
||||
*/
|
||||
// KEEP IN SYNC with the class in mcp-test module
|
||||
public abstract class AbstractMcpSyncServerTests {
|
||||
|
||||
private static final String TEST_TOOL_NAME = "test-tool";
|
||||
|
|
@ -108,6 +110,7 @@ public abstract class AbstractMcpSyncServerTests {
|
|||
""";
|
||||
|
||||
@Test
|
||||
@Deprecated
|
||||
void testAddTool() {
|
||||
var mcpSyncServer = McpServer.sync(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
|
|
@ -123,6 +126,23 @@ public abstract class AbstractMcpSyncServerTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testAddToolCall() {
|
||||
var mcpSyncServer = McpServer.sync(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
.capabilities(ServerCapabilities.builder().tools(true).build())
|
||||
.build();
|
||||
|
||||
Tool newTool = new McpSchema.Tool("new-tool", "New test tool", emptyJsonSchema);
|
||||
assertThatCode(() -> mcpSyncServer.addTool(McpServerFeatures.SyncToolSpecification.builder()
|
||||
.tool(newTool)
|
||||
.callHandler((exchange, request) -> new CallToolResult(List.of(), false))
|
||||
.build())).doesNotThrowAnyException();
|
||||
|
||||
assertThatCode(() -> mcpSyncServer.closeGracefully()).doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Deprecated
|
||||
void testAddDuplicateTool() {
|
||||
Tool duplicateTool = new McpSchema.Tool(TEST_TOOL_NAME, "Duplicate tool", emptyJsonSchema);
|
||||
|
||||
|
|
@ -140,6 +160,81 @@ public abstract class AbstractMcpSyncServerTests {
|
|||
assertThatCode(() -> mcpSyncServer.closeGracefully()).doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddDuplicateToolCall() {
|
||||
Tool duplicateTool = new McpSchema.Tool(TEST_TOOL_NAME, "Duplicate tool", emptyJsonSchema);
|
||||
|
||||
var mcpSyncServer = McpServer.sync(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
.capabilities(ServerCapabilities.builder().tools(true).build())
|
||||
.toolCall(duplicateTool, (exchange, request) -> new CallToolResult(List.of(), false))
|
||||
.build();
|
||||
|
||||
assertThatThrownBy(() -> mcpSyncServer.addTool(McpServerFeatures.SyncToolSpecification.builder()
|
||||
.tool(duplicateTool)
|
||||
.callHandler((exchange, request) -> new CallToolResult(List.of(), false))
|
||||
.build())).isInstanceOf(McpError.class)
|
||||
.hasMessage("Tool with name '" + TEST_TOOL_NAME + "' already exists");
|
||||
|
||||
assertThatCode(() -> mcpSyncServer.closeGracefully()).doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDuplicateToolCallDuringBuilding() {
|
||||
Tool duplicateTool = new Tool("duplicate-build-toolcall", "Duplicate toolcall during building",
|
||||
emptyJsonSchema);
|
||||
|
||||
assertThatThrownBy(() -> McpServer.sync(createMcpTransportProvider())
|
||||
.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!
|
||||
.build()).isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("Tool with name 'duplicate-build-toolcall' is already registered.");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDuplicateToolsInBatchListRegistration() {
|
||||
Tool duplicateTool = new Tool("batch-list-tool", "Duplicate tool in batch list", emptyJsonSchema);
|
||||
List<McpServerFeatures.SyncToolSpecification> specs = List.of(
|
||||
McpServerFeatures.SyncToolSpecification.builder()
|
||||
.tool(duplicateTool)
|
||||
.callHandler((exchange, request) -> new CallToolResult(List.of(), false))
|
||||
.build(),
|
||||
McpServerFeatures.SyncToolSpecification.builder()
|
||||
.tool(duplicateTool)
|
||||
.callHandler((exchange, request) -> new CallToolResult(List.of(), false))
|
||||
.build() // Duplicate!
|
||||
);
|
||||
|
||||
assertThatThrownBy(() -> McpServer.sync(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
.capabilities(ServerCapabilities.builder().tools(true).build())
|
||||
.tools(specs)
|
||||
.build()).isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("Tool with name 'batch-list-tool' is already registered.");
|
||||
}
|
||||
|
||||
@Test
|
||||
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")
|
||||
.capabilities(ServerCapabilities.builder().tools(true).build())
|
||||
.tools(McpServerFeatures.SyncToolSpecification.builder()
|
||||
.tool(duplicateTool)
|
||||
.callHandler((exchange, request) -> new CallToolResult(List.of(), false))
|
||||
.build(),
|
||||
McpServerFeatures.SyncToolSpecification.builder()
|
||||
.tool(duplicateTool)
|
||||
.callHandler((exchange, request) -> new CallToolResult(List.of(), false))
|
||||
.build() // Duplicate!
|
||||
)
|
||||
.build()).isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("Tool with name 'batch-varargs-tool' is already registered.");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRemoveTool() {
|
||||
Tool tool = new McpSchema.Tool(TEST_TOOL_NAME, "Test tool", emptyJsonSchema);
|
||||
|
|
@ -147,7 +242,7 @@ public abstract class AbstractMcpSyncServerTests {
|
|||
var mcpSyncServer = McpServer.sync(createMcpTransportProvider())
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
.capabilities(ServerCapabilities.builder().tools(true).build())
|
||||
.tool(tool, (exchange, args) -> new CallToolResult(List.of(), false))
|
||||
.toolCall(tool, (exchange, args) -> new CallToolResult(List.of(), false))
|
||||
.build();
|
||||
|
||||
assertThatCode(() -> mcpSyncServer.removeTool(TEST_TOOL_NAME)).doesNotThrowAnyException();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,229 @@
|
|||
/*
|
||||
* Copyright 2024-2024 the original author or authors.
|
||||
*/
|
||||
|
||||
package io.modelcontextprotocol.server;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import io.modelcontextprotocol.spec.McpSchema.CallToolRequest;
|
||||
import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
|
||||
import io.modelcontextprotocol.spec.McpSchema.TextContent;
|
||||
import io.modelcontextprotocol.spec.McpSchema.Tool;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
/**
|
||||
* Tests for {@link McpServerFeatures.AsyncToolSpecification.Builder}.
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
*/
|
||||
class AsyncToolSpecificationBuilderTest {
|
||||
|
||||
String emptyJsonSchema = """
|
||||
{
|
||||
"type": "object"
|
||||
}
|
||||
""";
|
||||
|
||||
@Test
|
||||
void builderShouldCreateValidAsyncToolSpecification() {
|
||||
|
||||
Tool tool = new Tool("test-tool", "A test tool", emptyJsonSchema);
|
||||
|
||||
McpServerFeatures.AsyncToolSpecification specification = McpServerFeatures.AsyncToolSpecification.builder()
|
||||
.tool(tool)
|
||||
.callHandler((exchange, request) -> Mono
|
||||
.just(new CallToolResult(List.of(new TextContent("Test result")), false)))
|
||||
.build();
|
||||
|
||||
assertThat(specification).isNotNull();
|
||||
assertThat(specification.tool()).isEqualTo(tool);
|
||||
assertThat(specification.callHandler()).isNotNull();
|
||||
assertThat(specification.call()).isNull(); // deprecated field should be null
|
||||
}
|
||||
|
||||
@Test
|
||||
void builderShouldThrowExceptionWhenToolIsNull() {
|
||||
assertThatThrownBy(() -> McpServerFeatures.AsyncToolSpecification.builder()
|
||||
.callHandler((exchange, request) -> Mono.just(new CallToolResult(List.of(), false)))
|
||||
.build()).isInstanceOf(IllegalArgumentException.class).hasMessage("Tool must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void builderShouldThrowExceptionWhenCallToolIsNull() {
|
||||
Tool tool = new Tool("test-tool", "A test tool", emptyJsonSchema);
|
||||
|
||||
assertThatThrownBy(() -> McpServerFeatures.AsyncToolSpecification.builder().tool(tool).build())
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("Call handler function must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void builderShouldAllowMethodChaining() {
|
||||
Tool tool = new Tool("test-tool", "A test tool", emptyJsonSchema);
|
||||
McpServerFeatures.AsyncToolSpecification.Builder builder = McpServerFeatures.AsyncToolSpecification.builder();
|
||||
|
||||
// Then - verify method chaining returns the same builder instance
|
||||
assertThat(builder.tool(tool)).isSameAs(builder);
|
||||
assertThat(builder.callHandler((exchange, request) -> Mono.just(new CallToolResult(List.of(), false))))
|
||||
.isSameAs(builder);
|
||||
}
|
||||
|
||||
@Test
|
||||
void builtSpecificationShouldExecuteCallToolCorrectly() {
|
||||
Tool tool = new Tool("calculator", "Simple calculator", emptyJsonSchema);
|
||||
String expectedResult = "42";
|
||||
|
||||
McpServerFeatures.AsyncToolSpecification specification = McpServerFeatures.AsyncToolSpecification.builder()
|
||||
.tool(tool)
|
||||
.callHandler((exchange, request) -> {
|
||||
return Mono.just(new CallToolResult(List.of(new TextContent(expectedResult)), false));
|
||||
})
|
||||
.build();
|
||||
|
||||
CallToolRequest request = new CallToolRequest("calculator", Map.of());
|
||||
Mono<CallToolResult> resultMono = specification.callHandler().apply(null, request);
|
||||
|
||||
StepVerifier.create(resultMono).assertNext(result -> {
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.content()).hasSize(1);
|
||||
assertThat(result.content().get(0)).isInstanceOf(TextContent.class);
|
||||
assertThat(((TextContent) result.content().get(0)).text()).isEqualTo(expectedResult);
|
||||
assertThat(result.isError()).isFalse();
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
void deprecatedConstructorShouldWorkCorrectly() {
|
||||
Tool tool = new Tool("deprecated-tool", "A deprecated tool", emptyJsonSchema);
|
||||
String expectedResult = "deprecated result";
|
||||
|
||||
// Test the deprecated constructor that takes a 'call' function
|
||||
McpServerFeatures.AsyncToolSpecification specification = new McpServerFeatures.AsyncToolSpecification(tool,
|
||||
(exchange, arguments) -> Mono
|
||||
.just(new CallToolResult(List.of(new TextContent(expectedResult)), false)));
|
||||
|
||||
assertThat(specification).isNotNull();
|
||||
assertThat(specification.tool()).isEqualTo(tool);
|
||||
assertThat(specification.call()).isNotNull(); // deprecated field should be set
|
||||
assertThat(specification.callHandler()).isNotNull(); // should be automatically
|
||||
// created
|
||||
|
||||
// Test that the callTool function works (it should delegate to the call function)
|
||||
CallToolRequest request = new CallToolRequest("deprecated-tool", Map.of("arg1", "value1"));
|
||||
Mono<CallToolResult> resultMono = specification.callHandler().apply(null, request);
|
||||
|
||||
StepVerifier.create(resultMono).assertNext(result -> {
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.content()).hasSize(1);
|
||||
assertThat(result.content().get(0)).isInstanceOf(TextContent.class);
|
||||
assertThat(((TextContent) result.content().get(0)).text()).isEqualTo(expectedResult);
|
||||
assertThat(result.isError()).isFalse();
|
||||
}).verifyComplete();
|
||||
|
||||
// Test that the deprecated call function also works directly
|
||||
Mono<CallToolResult> callResultMono = specification.call().apply(null, request.arguments());
|
||||
|
||||
StepVerifier.create(callResultMono).assertNext(result -> {
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.content()).hasSize(1);
|
||||
assertThat(result.content().get(0)).isInstanceOf(TextContent.class);
|
||||
assertThat(((TextContent) result.content().get(0)).text()).isEqualTo(expectedResult);
|
||||
assertThat(result.isError()).isFalse();
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromSyncShouldConvertSyncToolSpecificationCorrectly() {
|
||||
Tool tool = new Tool("sync-tool", "A sync tool", emptyJsonSchema);
|
||||
String expectedResult = "sync result";
|
||||
|
||||
// Create a sync tool specification
|
||||
McpServerFeatures.SyncToolSpecification syncSpec = McpServerFeatures.SyncToolSpecification.builder()
|
||||
.tool(tool)
|
||||
.callHandler((exchange, request) -> new CallToolResult(List.of(new TextContent(expectedResult)), false))
|
||||
.build();
|
||||
|
||||
// Convert to async using fromSync
|
||||
McpServerFeatures.AsyncToolSpecification asyncSpec = McpServerFeatures.AsyncToolSpecification
|
||||
.fromSync(syncSpec);
|
||||
|
||||
assertThat(asyncSpec).isNotNull();
|
||||
assertThat(asyncSpec.tool()).isEqualTo(tool);
|
||||
assertThat(asyncSpec.callHandler()).isNotNull();
|
||||
assertThat(asyncSpec.call()).isNull(); // should be null since sync spec doesn't
|
||||
// have deprecated call
|
||||
|
||||
// Test that the converted async specification works correctly
|
||||
CallToolRequest request = new CallToolRequest("sync-tool", Map.of("param", "value"));
|
||||
Mono<CallToolResult> resultMono = asyncSpec.callHandler().apply(null, request);
|
||||
|
||||
StepVerifier.create(resultMono).assertNext(result -> {
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.content()).hasSize(1);
|
||||
assertThat(result.content().get(0)).isInstanceOf(TextContent.class);
|
||||
assertThat(((TextContent) result.content().get(0)).text()).isEqualTo(expectedResult);
|
||||
assertThat(result.isError()).isFalse();
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
void fromSyncShouldConvertSyncToolSpecificationWithDeprecatedCallCorrectly() {
|
||||
Tool tool = new Tool("sync-deprecated-tool", "A sync tool with deprecated call", emptyJsonSchema);
|
||||
String expectedResult = "sync deprecated result";
|
||||
McpAsyncServerExchange nullExchange = null; // Mock or create a suitable exchange
|
||||
// if needed
|
||||
|
||||
// Create a sync tool specification using the deprecated constructor
|
||||
McpServerFeatures.SyncToolSpecification syncSpec = new McpServerFeatures.SyncToolSpecification(tool,
|
||||
(exchange, arguments) -> new CallToolResult(List.of(new TextContent(expectedResult)), false));
|
||||
|
||||
// Convert to async using fromSync
|
||||
McpServerFeatures.AsyncToolSpecification asyncSpec = McpServerFeatures.AsyncToolSpecification
|
||||
.fromSync(syncSpec);
|
||||
|
||||
assertThat(asyncSpec).isNotNull();
|
||||
assertThat(asyncSpec.tool()).isEqualTo(tool);
|
||||
assertThat(asyncSpec.callHandler()).isNotNull();
|
||||
assertThat(asyncSpec.call()).isNotNull(); // should be set since sync spec has
|
||||
// deprecated call
|
||||
|
||||
// Test that the converted async specification works correctly via callTool
|
||||
CallToolRequest request = new CallToolRequest("sync-deprecated-tool", Map.of("param", "value"));
|
||||
Mono<CallToolResult> resultMono = asyncSpec.callHandler().apply(nullExchange, request);
|
||||
|
||||
StepVerifier.create(resultMono).assertNext(result -> {
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.content()).hasSize(1);
|
||||
assertThat(result.content().get(0)).isInstanceOf(TextContent.class);
|
||||
assertThat(((TextContent) result.content().get(0)).text()).isEqualTo(expectedResult);
|
||||
assertThat(result.isError()).isFalse();
|
||||
}).verifyComplete();
|
||||
|
||||
// Test that the deprecated call function also works
|
||||
Mono<CallToolResult> callResultMono = asyncSpec.call().apply(nullExchange, request.arguments());
|
||||
|
||||
StepVerifier.create(callResultMono).assertNext(result -> {
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.content()).hasSize(1);
|
||||
assertThat(result.content().get(0)).isInstanceOf(TextContent.class);
|
||||
assertThat(((TextContent) result.content().get(0)).text()).isEqualTo(expectedResult);
|
||||
assertThat(result.isError()).isFalse();
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromSyncShouldReturnNullWhenSyncSpecIsNull() {
|
||||
assertThat(McpServerFeatures.AsyncToolSpecification.fromSync(null)).isNull();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
package io.modelcontextprotocol.server;
|
||||
|
||||
public abstract class BaseMcpAsyncServerTests {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* Copyright 2024-2024 the original author or authors.
|
||||
*/
|
||||
|
||||
package io.modelcontextprotocol.server;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import io.modelcontextprotocol.spec.McpSchema.CallToolRequest;
|
||||
import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
|
||||
import io.modelcontextprotocol.spec.McpSchema.TextContent;
|
||||
import io.modelcontextprotocol.spec.McpSchema.Tool;
|
||||
|
||||
/**
|
||||
* Tests for {@link McpServerFeatures.SyncToolSpecification.Builder}.
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
*/
|
||||
class SyncToolSpecificationBuilderTest {
|
||||
|
||||
String emptyJsonSchema = """
|
||||
{
|
||||
"type": "object"
|
||||
}
|
||||
""";
|
||||
|
||||
@Test
|
||||
void builderShouldCreateValidSyncToolSpecification() {
|
||||
|
||||
Tool tool = new Tool("test-tool", "A test tool", emptyJsonSchema);
|
||||
|
||||
McpServerFeatures.SyncToolSpecification specification = McpServerFeatures.SyncToolSpecification.builder()
|
||||
.tool(tool)
|
||||
.callHandler((exchange, request) -> new CallToolResult(List.of(new TextContent("Test result")), false))
|
||||
.build();
|
||||
|
||||
assertThat(specification).isNotNull();
|
||||
assertThat(specification.tool()).isEqualTo(tool);
|
||||
assertThat(specification.callHandler()).isNotNull();
|
||||
assertThat(specification.call()).isNull(); // deprecated field should be null
|
||||
}
|
||||
|
||||
@Test
|
||||
void builderShouldThrowExceptionWhenToolIsNull() {
|
||||
assertThatThrownBy(() -> McpServerFeatures.SyncToolSpecification.builder()
|
||||
.callHandler((exchange, request) -> new CallToolResult(List.of(), false))
|
||||
.build()).isInstanceOf(IllegalArgumentException.class).hasMessage("Tool must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void builderShouldThrowExceptionWhenCallToolIsNull() {
|
||||
Tool tool = new Tool("test-tool", "A test tool", emptyJsonSchema);
|
||||
|
||||
assertThatThrownBy(() -> McpServerFeatures.SyncToolSpecification.builder().tool(tool).build())
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("CallTool function must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void builderShouldAllowMethodChaining() {
|
||||
Tool tool = new Tool("test-tool", "A test tool", emptyJsonSchema);
|
||||
McpServerFeatures.SyncToolSpecification.Builder builder = McpServerFeatures.SyncToolSpecification.builder();
|
||||
|
||||
// Then - verify method chaining returns the same builder instance
|
||||
assertThat(builder.tool(tool)).isSameAs(builder);
|
||||
assertThat(builder.callHandler((exchange, request) -> new CallToolResult(List.of(), false))).isSameAs(builder);
|
||||
}
|
||||
|
||||
@Test
|
||||
void builtSpecificationShouldExecuteCallToolCorrectly() {
|
||||
Tool tool = new Tool("calculator", "Simple calculator", emptyJsonSchema);
|
||||
String expectedResult = "42";
|
||||
|
||||
McpServerFeatures.SyncToolSpecification specification = McpServerFeatures.SyncToolSpecification.builder()
|
||||
.tool(tool)
|
||||
.callHandler((exchange, request) -> {
|
||||
// Simple test implementation
|
||||
return new CallToolResult(List.of(new TextContent(expectedResult)), false);
|
||||
})
|
||||
.build();
|
||||
|
||||
CallToolRequest request = new CallToolRequest("calculator", Map.of());
|
||||
CallToolResult result = specification.callHandler().apply(null, request);
|
||||
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.content()).hasSize(1);
|
||||
assertThat(result.content().get(0)).isInstanceOf(TextContent.class);
|
||||
assertThat(((TextContent) result.content().get(0)).text()).isEqualTo(expectedResult);
|
||||
assertThat(result.isError()).isFalse();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -109,13 +109,15 @@ class HttpServletSseServerTransportProviderIntegrationTests {
|
|||
// @Disabled
|
||||
void testCreateMessageWithoutSamplingCapabilities() {
|
||||
|
||||
McpServerFeatures.AsyncToolSpecification tool = new McpServerFeatures.AsyncToolSpecification(
|
||||
new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema), (exchange, request) -> {
|
||||
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
|
||||
.tool(new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema))
|
||||
.callHandler((exchange, request) -> {
|
||||
|
||||
exchange.createMessage(mock(McpSchema.CreateMessageRequest.class)).block();
|
||||
exchange.createMessage(mock(McpSchema.CreateMessageRequest.class)).block();
|
||||
|
||||
return Mono.just(mock(CallToolResult.class));
|
||||
});
|
||||
return Mono.just(mock(CallToolResult.class));
|
||||
})
|
||||
.build();
|
||||
|
||||
var server = McpServer.async(mcpServerTransportProvider).serverInfo("test-server", "1.0.0").tools(tool).build();
|
||||
|
||||
|
|
@ -151,31 +153,33 @@ class HttpServletSseServerTransportProviderIntegrationTests {
|
|||
CallToolResult callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")),
|
||||
null);
|
||||
|
||||
McpServerFeatures.AsyncToolSpecification tool = new McpServerFeatures.AsyncToolSpecification(
|
||||
new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema), (exchange, request) -> {
|
||||
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
|
||||
.tool(new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema))
|
||||
.callHandler((exchange, request) -> {
|
||||
|
||||
var createMessageRequest = McpSchema.CreateMessageRequest.builder()
|
||||
.messages(List.of(new McpSchema.SamplingMessage(McpSchema.Role.USER,
|
||||
new McpSchema.TextContent("Test message"))))
|
||||
.modelPreferences(ModelPreferences.builder()
|
||||
.hints(List.of())
|
||||
.costPriority(1.0)
|
||||
.speedPriority(1.0)
|
||||
.intelligencePriority(1.0)
|
||||
.build())
|
||||
.build();
|
||||
var createMessageRequest = McpSchema.CreateMessageRequest.builder()
|
||||
.messages(List.of(new McpSchema.SamplingMessage(McpSchema.Role.USER,
|
||||
new McpSchema.TextContent("Test message"))))
|
||||
.modelPreferences(ModelPreferences.builder()
|
||||
.hints(List.of())
|
||||
.costPriority(1.0)
|
||||
.speedPriority(1.0)
|
||||
.intelligencePriority(1.0)
|
||||
.build())
|
||||
.build();
|
||||
|
||||
StepVerifier.create(exchange.createMessage(createMessageRequest)).consumeNextWith(result -> {
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.role()).isEqualTo(Role.USER);
|
||||
assertThat(result.content()).isInstanceOf(McpSchema.TextContent.class);
|
||||
assertThat(((McpSchema.TextContent) result.content()).text()).isEqualTo("Test message");
|
||||
assertThat(result.model()).isEqualTo("MockModelName");
|
||||
assertThat(result.stopReason()).isEqualTo(CreateMessageResult.StopReason.STOP_SEQUENCE);
|
||||
}).verifyComplete();
|
||||
StepVerifier.create(exchange.createMessage(createMessageRequest)).consumeNextWith(result -> {
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.role()).isEqualTo(Role.USER);
|
||||
assertThat(result.content()).isInstanceOf(McpSchema.TextContent.class);
|
||||
assertThat(((McpSchema.TextContent) result.content()).text()).isEqualTo("Test message");
|
||||
assertThat(result.model()).isEqualTo("MockModelName");
|
||||
assertThat(result.stopReason()).isEqualTo(CreateMessageResult.StopReason.STOP_SEQUENCE);
|
||||
}).verifyComplete();
|
||||
|
||||
return Mono.just(callResponse);
|
||||
});
|
||||
return Mono.just(callResponse);
|
||||
})
|
||||
.build();
|
||||
|
||||
var mcpServer = McpServer.async(mcpServerTransportProvider)
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
|
|
@ -226,31 +230,33 @@ class HttpServletSseServerTransportProviderIntegrationTests {
|
|||
CallToolResult callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")),
|
||||
null);
|
||||
|
||||
McpServerFeatures.AsyncToolSpecification tool = new McpServerFeatures.AsyncToolSpecification(
|
||||
new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema), (exchange, request) -> {
|
||||
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
|
||||
.tool(new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema))
|
||||
.callHandler((exchange, request) -> {
|
||||
|
||||
var craeteMessageRequest = McpSchema.CreateMessageRequest.builder()
|
||||
.messages(List.of(new McpSchema.SamplingMessage(McpSchema.Role.USER,
|
||||
new McpSchema.TextContent("Test message"))))
|
||||
.modelPreferences(ModelPreferences.builder()
|
||||
.hints(List.of())
|
||||
.costPriority(1.0)
|
||||
.speedPriority(1.0)
|
||||
.intelligencePriority(1.0)
|
||||
.build())
|
||||
.build();
|
||||
var craeteMessageRequest = McpSchema.CreateMessageRequest.builder()
|
||||
.messages(List.of(new McpSchema.SamplingMessage(McpSchema.Role.USER,
|
||||
new McpSchema.TextContent("Test message"))))
|
||||
.modelPreferences(ModelPreferences.builder()
|
||||
.hints(List.of())
|
||||
.costPriority(1.0)
|
||||
.speedPriority(1.0)
|
||||
.intelligencePriority(1.0)
|
||||
.build())
|
||||
.build();
|
||||
|
||||
StepVerifier.create(exchange.createMessage(craeteMessageRequest)).consumeNextWith(result -> {
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.role()).isEqualTo(Role.USER);
|
||||
assertThat(result.content()).isInstanceOf(McpSchema.TextContent.class);
|
||||
assertThat(((McpSchema.TextContent) result.content()).text()).isEqualTo("Test message");
|
||||
assertThat(result.model()).isEqualTo("MockModelName");
|
||||
assertThat(result.stopReason()).isEqualTo(CreateMessageResult.StopReason.STOP_SEQUENCE);
|
||||
}).verifyComplete();
|
||||
StepVerifier.create(exchange.createMessage(craeteMessageRequest)).consumeNextWith(result -> {
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.role()).isEqualTo(Role.USER);
|
||||
assertThat(result.content()).isInstanceOf(McpSchema.TextContent.class);
|
||||
assertThat(((McpSchema.TextContent) result.content()).text()).isEqualTo("Test message");
|
||||
assertThat(result.model()).isEqualTo("MockModelName");
|
||||
assertThat(result.stopReason()).isEqualTo(CreateMessageResult.StopReason.STOP_SEQUENCE);
|
||||
}).verifyComplete();
|
||||
|
||||
return Mono.just(callResponse);
|
||||
});
|
||||
return Mono.just(callResponse);
|
||||
})
|
||||
.build();
|
||||
|
||||
var mcpServer = McpServer.async(mcpServerTransportProvider)
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
|
|
@ -298,31 +304,33 @@ class HttpServletSseServerTransportProviderIntegrationTests {
|
|||
CallToolResult callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")),
|
||||
null);
|
||||
|
||||
McpServerFeatures.AsyncToolSpecification tool = new McpServerFeatures.AsyncToolSpecification(
|
||||
new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema), (exchange, request) -> {
|
||||
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
|
||||
.tool(new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema))
|
||||
.callHandler((exchange, request) -> {
|
||||
|
||||
var craeteMessageRequest = McpSchema.CreateMessageRequest.builder()
|
||||
.messages(List.of(new McpSchema.SamplingMessage(McpSchema.Role.USER,
|
||||
new McpSchema.TextContent("Test message"))))
|
||||
.modelPreferences(ModelPreferences.builder()
|
||||
.hints(List.of())
|
||||
.costPriority(1.0)
|
||||
.speedPriority(1.0)
|
||||
.intelligencePriority(1.0)
|
||||
.build())
|
||||
.build();
|
||||
var craeteMessageRequest = McpSchema.CreateMessageRequest.builder()
|
||||
.messages(List.of(new McpSchema.SamplingMessage(McpSchema.Role.USER,
|
||||
new McpSchema.TextContent("Test message"))))
|
||||
.modelPreferences(ModelPreferences.builder()
|
||||
.hints(List.of())
|
||||
.costPriority(1.0)
|
||||
.speedPriority(1.0)
|
||||
.intelligencePriority(1.0)
|
||||
.build())
|
||||
.build();
|
||||
|
||||
StepVerifier.create(exchange.createMessage(craeteMessageRequest)).consumeNextWith(result -> {
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.role()).isEqualTo(Role.USER);
|
||||
assertThat(result.content()).isInstanceOf(McpSchema.TextContent.class);
|
||||
assertThat(((McpSchema.TextContent) result.content()).text()).isEqualTo("Test message");
|
||||
assertThat(result.model()).isEqualTo("MockModelName");
|
||||
assertThat(result.stopReason()).isEqualTo(CreateMessageResult.StopReason.STOP_SEQUENCE);
|
||||
}).verifyComplete();
|
||||
StepVerifier.create(exchange.createMessage(craeteMessageRequest)).consumeNextWith(result -> {
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.role()).isEqualTo(Role.USER);
|
||||
assertThat(result.content()).isInstanceOf(McpSchema.TextContent.class);
|
||||
assertThat(((McpSchema.TextContent) result.content()).text()).isEqualTo("Test message");
|
||||
assertThat(result.model()).isEqualTo("MockModelName");
|
||||
assertThat(result.stopReason()).isEqualTo(CreateMessageResult.StopReason.STOP_SEQUENCE);
|
||||
}).verifyComplete();
|
||||
|
||||
return Mono.just(callResponse);
|
||||
});
|
||||
return Mono.just(callResponse);
|
||||
})
|
||||
.build();
|
||||
|
||||
var mcpServer = McpServer.async(mcpServerTransportProvider)
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
|
|
@ -348,13 +356,15 @@ class HttpServletSseServerTransportProviderIntegrationTests {
|
|||
// @Disabled
|
||||
void testCreateElicitationWithoutElicitationCapabilities() {
|
||||
|
||||
McpServerFeatures.AsyncToolSpecification tool = new McpServerFeatures.AsyncToolSpecification(
|
||||
new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema), (exchange, request) -> {
|
||||
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
|
||||
.tool(new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema))
|
||||
.callHandler((exchange, request) -> {
|
||||
|
||||
exchange.createElicitation(mock(ElicitRequest.class)).block();
|
||||
exchange.createElicitation(mock(ElicitRequest.class)).block();
|
||||
|
||||
return Mono.just(mock(CallToolResult.class));
|
||||
});
|
||||
return Mono.just(mock(CallToolResult.class));
|
||||
})
|
||||
.build();
|
||||
|
||||
var server = McpServer.async(mcpServerTransportProvider).serverInfo("test-server", "1.0.0").tools(tool).build();
|
||||
|
||||
|
|
@ -388,23 +398,25 @@ class HttpServletSseServerTransportProviderIntegrationTests {
|
|||
CallToolResult callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")),
|
||||
null);
|
||||
|
||||
McpServerFeatures.AsyncToolSpecification tool = new McpServerFeatures.AsyncToolSpecification(
|
||||
new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema), (exchange, request) -> {
|
||||
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
|
||||
.tool(new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema))
|
||||
.callHandler((exchange, request) -> {
|
||||
|
||||
var elicitationRequest = ElicitRequest.builder()
|
||||
.message("Test message")
|
||||
.requestedSchema(
|
||||
Map.of("type", "object", "properties", Map.of("message", Map.of("type", "string"))))
|
||||
.build();
|
||||
var elicitationRequest = ElicitRequest.builder()
|
||||
.message("Test message")
|
||||
.requestedSchema(
|
||||
Map.of("type", "object", "properties", Map.of("message", Map.of("type", "string"))))
|
||||
.build();
|
||||
|
||||
StepVerifier.create(exchange.createElicitation(elicitationRequest)).consumeNextWith(result -> {
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.action()).isEqualTo(ElicitResult.Action.ACCEPT);
|
||||
assertThat(result.content().get("message")).isEqualTo("Test message");
|
||||
}).verifyComplete();
|
||||
StepVerifier.create(exchange.createElicitation(elicitationRequest)).consumeNextWith(result -> {
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.action()).isEqualTo(ElicitResult.Action.ACCEPT);
|
||||
assertThat(result.content().get("message")).isEqualTo("Test message");
|
||||
}).verifyComplete();
|
||||
|
||||
return Mono.just(callResponse);
|
||||
});
|
||||
return Mono.just(callResponse);
|
||||
})
|
||||
.build();
|
||||
|
||||
var mcpServer = McpServer.async(mcpServerTransportProvider)
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
|
|
@ -454,23 +466,25 @@ class HttpServletSseServerTransportProviderIntegrationTests {
|
|||
CallToolResult callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")),
|
||||
null);
|
||||
|
||||
McpServerFeatures.AsyncToolSpecification tool = new McpServerFeatures.AsyncToolSpecification(
|
||||
new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema), (exchange, request) -> {
|
||||
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
|
||||
.tool(new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema))
|
||||
.callHandler((exchange, request) -> {
|
||||
|
||||
var elicitationRequest = ElicitRequest.builder()
|
||||
.message("Test message")
|
||||
.requestedSchema(
|
||||
Map.of("type", "object", "properties", Map.of("message", Map.of("type", "string"))))
|
||||
.build();
|
||||
var elicitationRequest = ElicitRequest.builder()
|
||||
.message("Test message")
|
||||
.requestedSchema(
|
||||
Map.of("type", "object", "properties", Map.of("message", Map.of("type", "string"))))
|
||||
.build();
|
||||
|
||||
StepVerifier.create(exchange.createElicitation(elicitationRequest)).consumeNextWith(result -> {
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.action()).isEqualTo(ElicitResult.Action.ACCEPT);
|
||||
assertThat(result.content().get("message")).isEqualTo("Test message");
|
||||
}).verifyComplete();
|
||||
StepVerifier.create(exchange.createElicitation(elicitationRequest)).consumeNextWith(result -> {
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.action()).isEqualTo(ElicitResult.Action.ACCEPT);
|
||||
assertThat(result.content().get("message")).isEqualTo("Test message");
|
||||
}).verifyComplete();
|
||||
|
||||
return Mono.just(callResponse);
|
||||
});
|
||||
return Mono.just(callResponse);
|
||||
})
|
||||
.build();
|
||||
|
||||
var mcpServer = McpServer.async(mcpServerTransportProvider)
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
|
|
@ -517,23 +531,25 @@ class HttpServletSseServerTransportProviderIntegrationTests {
|
|||
CallToolResult callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")),
|
||||
null);
|
||||
|
||||
McpServerFeatures.AsyncToolSpecification tool = new McpServerFeatures.AsyncToolSpecification(
|
||||
new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema), (exchange, request) -> {
|
||||
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
|
||||
.tool(new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema))
|
||||
.callHandler((exchange, request) -> {
|
||||
|
||||
var elicitationRequest = ElicitRequest.builder()
|
||||
.message("Test message")
|
||||
.requestedSchema(
|
||||
Map.of("type", "object", "properties", Map.of("message", Map.of("type", "string"))))
|
||||
.build();
|
||||
var elicitationRequest = ElicitRequest.builder()
|
||||
.message("Test message")
|
||||
.requestedSchema(
|
||||
Map.of("type", "object", "properties", Map.of("message", Map.of("type", "string"))))
|
||||
.build();
|
||||
|
||||
StepVerifier.create(exchange.createElicitation(elicitationRequest)).consumeNextWith(result -> {
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.action()).isEqualTo(ElicitResult.Action.ACCEPT);
|
||||
assertThat(result.content().get("message")).isEqualTo("Test message");
|
||||
}).verifyComplete();
|
||||
StepVerifier.create(exchange.createElicitation(elicitationRequest)).consumeNextWith(result -> {
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.action()).isEqualTo(ElicitResult.Action.ACCEPT);
|
||||
assertThat(result.content().get("message")).isEqualTo("Test message");
|
||||
}).verifyComplete();
|
||||
|
||||
return Mono.just(callResponse);
|
||||
});
|
||||
return Mono.just(callResponse);
|
||||
})
|
||||
.build();
|
||||
|
||||
var mcpServer = McpServer.async(mcpServerTransportProvider)
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
|
|
@ -602,13 +618,15 @@ class HttpServletSseServerTransportProviderIntegrationTests {
|
|||
@Test
|
||||
void testRootsWithoutCapability() {
|
||||
|
||||
McpServerFeatures.SyncToolSpecification tool = new McpServerFeatures.SyncToolSpecification(
|
||||
new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema), (exchange, request) -> {
|
||||
McpServerFeatures.SyncToolSpecification tool = McpServerFeatures.SyncToolSpecification.builder()
|
||||
.tool(new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema))
|
||||
.callHandler((exchange, request) -> {
|
||||
|
||||
exchange.listRoots(); // try to list roots
|
||||
exchange.listRoots(); // try to list roots
|
||||
|
||||
return mock(CallToolResult.class);
|
||||
});
|
||||
return mock(CallToolResult.class);
|
||||
})
|
||||
.build();
|
||||
|
||||
var mcpServer = McpServer.sync(mcpServerTransportProvider).rootsChangeHandler((exchange, rootsUpdate) -> {
|
||||
}).tools(tool).build();
|
||||
|
|
@ -726,20 +744,22 @@ class HttpServletSseServerTransportProviderIntegrationTests {
|
|||
void testToolCallSuccess() {
|
||||
|
||||
var callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")), null);
|
||||
McpServerFeatures.SyncToolSpecification tool1 = new McpServerFeatures.SyncToolSpecification(
|
||||
new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema), (exchange, request) -> {
|
||||
assertThat(McpTestServletFilter.getThreadLocalValue())
|
||||
.as("blocking code exectuion should be offloaded")
|
||||
.isNull();
|
||||
// perform a blocking call to a remote service
|
||||
String response = RestClient.create()
|
||||
.get()
|
||||
.uri("https://raw.githubusercontent.com/modelcontextprotocol/java-sdk/refs/heads/main/README.md")
|
||||
.retrieve()
|
||||
.body(String.class);
|
||||
assertThat(response).isNotBlank();
|
||||
return callResponse;
|
||||
});
|
||||
|
||||
McpServerFeatures.SyncToolSpecification tool1 = McpServerFeatures.SyncToolSpecification.builder()
|
||||
.tool(new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema))
|
||||
.callHandler((exchange, request) -> {
|
||||
assertThat(McpTestServletFilter.getThreadLocalValue()).as("blocking code exectuion should be offloaded")
|
||||
.isNull();
|
||||
// perform a blocking call to a remote service
|
||||
String response = RestClient.create()
|
||||
.get()
|
||||
.uri("https://raw.githubusercontent.com/modelcontextprotocol/java-sdk/refs/heads/main/README.md")
|
||||
.retrieve()
|
||||
.body(String.class);
|
||||
assertThat(response).isNotBlank();
|
||||
return callResponse;
|
||||
})
|
||||
.build();
|
||||
|
||||
var mcpServer = McpServer.sync(mcpServerTransportProvider)
|
||||
.capabilities(ServerCapabilities.builder().tools(true).build())
|
||||
|
|
@ -796,17 +816,19 @@ class HttpServletSseServerTransportProviderIntegrationTests {
|
|||
void testToolListChangeHandlingSuccess() {
|
||||
|
||||
var callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")), null);
|
||||
McpServerFeatures.SyncToolSpecification tool1 = new McpServerFeatures.SyncToolSpecification(
|
||||
new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema), (exchange, request) -> {
|
||||
// perform a blocking call to a remote service
|
||||
String response = RestClient.create()
|
||||
.get()
|
||||
.uri("https://raw.githubusercontent.com/modelcontextprotocol/java-sdk/refs/heads/main/README.md")
|
||||
.retrieve()
|
||||
.body(String.class);
|
||||
assertThat(response).isNotBlank();
|
||||
return callResponse;
|
||||
});
|
||||
McpServerFeatures.SyncToolSpecification tool1 = McpServerFeatures.SyncToolSpecification.builder()
|
||||
.tool(new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema))
|
||||
.callHandler((exchange, request) -> {
|
||||
// perform a blocking call to a remote service
|
||||
String response = RestClient.create()
|
||||
.get()
|
||||
.uri("https://raw.githubusercontent.com/modelcontextprotocol/java-sdk/refs/heads/main/README.md")
|
||||
.retrieve()
|
||||
.body(String.class);
|
||||
assertThat(response).isNotBlank();
|
||||
return callResponse;
|
||||
})
|
||||
.build();
|
||||
|
||||
AtomicReference<List<Tool>> rootsRef = new AtomicReference<>();
|
||||
|
||||
|
|
@ -847,9 +869,10 @@ class HttpServletSseServerTransportProviderIntegrationTests {
|
|||
});
|
||||
|
||||
// Add a new tool
|
||||
McpServerFeatures.SyncToolSpecification tool2 = new McpServerFeatures.SyncToolSpecification(
|
||||
new McpSchema.Tool("tool2", "tool2 description", emptyJsonSchema),
|
||||
(exchange, request) -> callResponse);
|
||||
McpServerFeatures.SyncToolSpecification tool2 = McpServerFeatures.SyncToolSpecification.builder()
|
||||
.tool(new McpSchema.Tool("tool2", "tool2 description", emptyJsonSchema))
|
||||
.callHandler((exchange, request) -> callResponse)
|
||||
.build();
|
||||
|
||||
mcpServer.addTool(tool2);
|
||||
|
||||
|
|
@ -883,59 +906,60 @@ class HttpServletSseServerTransportProviderIntegrationTests {
|
|||
List<McpSchema.LoggingMessageNotification> receivedNotifications = new ArrayList<>();
|
||||
|
||||
// Create server with a tool that sends logging notifications
|
||||
McpServerFeatures.AsyncToolSpecification tool = new McpServerFeatures.AsyncToolSpecification(
|
||||
new McpSchema.Tool("logging-test", "Test logging notifications", emptyJsonSchema),
|
||||
(exchange, request) -> {
|
||||
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
|
||||
.tool(new McpSchema.Tool("logging-test", "Test logging notifications", emptyJsonSchema))
|
||||
.callHandler((exchange, request) -> {
|
||||
|
||||
// Create and send notifications with different levels
|
||||
// Create and send notifications with different levels
|
||||
|
||||
// This should be filtered out (DEBUG < NOTICE)
|
||||
exchange
|
||||
.loggingNotification(McpSchema.LoggingMessageNotification.builder()
|
||||
.level(McpSchema.LoggingLevel.DEBUG)
|
||||
.logger("test-logger")
|
||||
.data("Debug message")
|
||||
.build())
|
||||
.block();
|
||||
// This should be filtered out (DEBUG < NOTICE)
|
||||
exchange
|
||||
.loggingNotification(McpSchema.LoggingMessageNotification.builder()
|
||||
.level(McpSchema.LoggingLevel.DEBUG)
|
||||
.logger("test-logger")
|
||||
.data("Debug message")
|
||||
.build())
|
||||
.block();
|
||||
|
||||
// This should be sent (NOTICE >= NOTICE)
|
||||
exchange
|
||||
.loggingNotification(McpSchema.LoggingMessageNotification.builder()
|
||||
.level(McpSchema.LoggingLevel.NOTICE)
|
||||
.logger("test-logger")
|
||||
.data("Notice message")
|
||||
.build())
|
||||
.block();
|
||||
// This should be sent (NOTICE >= NOTICE)
|
||||
exchange
|
||||
.loggingNotification(McpSchema.LoggingMessageNotification.builder()
|
||||
.level(McpSchema.LoggingLevel.NOTICE)
|
||||
.logger("test-logger")
|
||||
.data("Notice message")
|
||||
.build())
|
||||
.block();
|
||||
|
||||
// This should be sent (ERROR > NOTICE)
|
||||
exchange
|
||||
.loggingNotification(McpSchema.LoggingMessageNotification.builder()
|
||||
.level(McpSchema.LoggingLevel.ERROR)
|
||||
.logger("test-logger")
|
||||
.data("Error message")
|
||||
.build())
|
||||
.block();
|
||||
// This should be sent (ERROR > NOTICE)
|
||||
exchange
|
||||
.loggingNotification(McpSchema.LoggingMessageNotification.builder()
|
||||
.level(McpSchema.LoggingLevel.ERROR)
|
||||
.logger("test-logger")
|
||||
.data("Error message")
|
||||
.build())
|
||||
.block();
|
||||
|
||||
// This should be filtered out (INFO < NOTICE)
|
||||
exchange
|
||||
.loggingNotification(McpSchema.LoggingMessageNotification.builder()
|
||||
.level(McpSchema.LoggingLevel.INFO)
|
||||
.logger("test-logger")
|
||||
.data("Another info message")
|
||||
.build())
|
||||
.block();
|
||||
// This should be filtered out (INFO < NOTICE)
|
||||
exchange
|
||||
.loggingNotification(McpSchema.LoggingMessageNotification.builder()
|
||||
.level(McpSchema.LoggingLevel.INFO)
|
||||
.logger("test-logger")
|
||||
.data("Another info message")
|
||||
.build())
|
||||
.block();
|
||||
|
||||
// This should be sent (ERROR >= NOTICE)
|
||||
exchange
|
||||
.loggingNotification(McpSchema.LoggingMessageNotification.builder()
|
||||
.level(McpSchema.LoggingLevel.ERROR)
|
||||
.logger("test-logger")
|
||||
.data("Another error message")
|
||||
.build())
|
||||
.block();
|
||||
// This should be sent (ERROR >= NOTICE)
|
||||
exchange
|
||||
.loggingNotification(McpSchema.LoggingMessageNotification.builder()
|
||||
.level(McpSchema.LoggingLevel.ERROR)
|
||||
.logger("test-logger")
|
||||
.data("Another error message")
|
||||
.build())
|
||||
.block();
|
||||
|
||||
return Mono.just(new CallToolResult("Logging test completed", false));
|
||||
});
|
||||
return Mono.just(new CallToolResult("Logging test completed", false));
|
||||
})
|
||||
.build();
|
||||
|
||||
var mcpServer = McpServer.async(mcpServerTransportProvider)
|
||||
.serverInfo("test-server", "1.0.0")
|
||||
|
|
|
|||
Loading…
Reference in a new issue