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
14 changed files with 1687 additions and 692 deletions
|
|
@ -3,6 +3,12 @@
|
||||||
*/
|
*/
|
||||||
package io.modelcontextprotocol;
|
package io.modelcontextprotocol;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||||
|
import static org.assertj.core.api.Assertions.assertWith;
|
||||||
|
import static org.awaitility.Awaitility.await;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
@ -15,38 +21,49 @@ import java.util.function.BiFunction;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import io.modelcontextprotocol.client.McpClient;
|
|
||||||
import io.modelcontextprotocol.client.transport.HttpClientSseClientTransport;
|
|
||||||
import io.modelcontextprotocol.client.transport.WebFluxSseClientTransport;
|
|
||||||
import io.modelcontextprotocol.server.McpServer;
|
|
||||||
import io.modelcontextprotocol.server.McpServerFeatures;
|
|
||||||
import io.modelcontextprotocol.server.TestUtil;
|
|
||||||
import io.modelcontextprotocol.server.McpSyncServerExchange;
|
|
||||||
import io.modelcontextprotocol.server.transport.WebFluxSseServerTransportProvider;
|
|
||||||
import io.modelcontextprotocol.spec.McpError;
|
|
||||||
import io.modelcontextprotocol.spec.McpSchema;
|
|
||||||
import io.modelcontextprotocol.spec.McpSchema.*;
|
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
import org.junit.jupiter.params.provider.ValueSource;
|
import org.junit.jupiter.params.provider.ValueSource;
|
||||||
import reactor.core.publisher.Mono;
|
|
||||||
import reactor.netty.DisposableServer;
|
|
||||||
import reactor.netty.http.server.HttpServer;
|
|
||||||
|
|
||||||
import org.springframework.http.server.reactive.HttpHandler;
|
import org.springframework.http.server.reactive.HttpHandler;
|
||||||
import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
|
import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
|
||||||
import org.springframework.web.client.RestClient;
|
import org.springframework.web.client.RestClient;
|
||||||
import org.springframework.web.reactive.function.client.WebClient;
|
import org.springframework.web.reactive.function.client.WebClient;
|
||||||
import org.springframework.web.reactive.function.server.RouterFunctions;
|
import org.springframework.web.reactive.function.server.RouterFunctions;
|
||||||
import reactor.test.StepVerifier;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
|
||||||
import static org.assertj.core.api.Assertions.assertWith;
|
import io.modelcontextprotocol.client.McpClient;
|
||||||
import static org.awaitility.Awaitility.await;
|
import io.modelcontextprotocol.client.transport.HttpClientSseClientTransport;
|
||||||
import static org.mockito.Mockito.mock;
|
import io.modelcontextprotocol.client.transport.WebFluxSseClientTransport;
|
||||||
|
import io.modelcontextprotocol.server.McpServer;
|
||||||
|
import io.modelcontextprotocol.server.McpServerFeatures;
|
||||||
|
import io.modelcontextprotocol.server.McpSyncServerExchange;
|
||||||
|
import io.modelcontextprotocol.server.TestUtil;
|
||||||
|
import io.modelcontextprotocol.server.transport.WebFluxSseServerTransportProvider;
|
||||||
|
import io.modelcontextprotocol.spec.McpError;
|
||||||
|
import io.modelcontextprotocol.spec.McpSchema;
|
||||||
|
import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
|
||||||
|
import io.modelcontextprotocol.spec.McpSchema.ClientCapabilities;
|
||||||
|
import io.modelcontextprotocol.spec.McpSchema.CompleteRequest;
|
||||||
|
import io.modelcontextprotocol.spec.McpSchema.CompleteResult;
|
||||||
|
import io.modelcontextprotocol.spec.McpSchema.CreateMessageRequest;
|
||||||
|
import io.modelcontextprotocol.spec.McpSchema.CreateMessageResult;
|
||||||
|
import io.modelcontextprotocol.spec.McpSchema.ElicitRequest;
|
||||||
|
import io.modelcontextprotocol.spec.McpSchema.ElicitResult;
|
||||||
|
import io.modelcontextprotocol.spec.McpSchema.InitializeResult;
|
||||||
|
import io.modelcontextprotocol.spec.McpSchema.ModelPreferences;
|
||||||
|
import io.modelcontextprotocol.spec.McpSchema.Prompt;
|
||||||
|
import io.modelcontextprotocol.spec.McpSchema.PromptArgument;
|
||||||
|
import io.modelcontextprotocol.spec.McpSchema.PromptReference;
|
||||||
|
import io.modelcontextprotocol.spec.McpSchema.Role;
|
||||||
|
import io.modelcontextprotocol.spec.McpSchema.Root;
|
||||||
|
import io.modelcontextprotocol.spec.McpSchema.ServerCapabilities;
|
||||||
|
import io.modelcontextprotocol.spec.McpSchema.Tool;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
import reactor.netty.DisposableServer;
|
||||||
|
import reactor.netty.http.server.HttpServer;
|
||||||
|
import reactor.test.StepVerifier;
|
||||||
|
|
||||||
class WebFluxSseIntegrationTests {
|
class WebFluxSseIntegrationTests {
|
||||||
|
|
||||||
|
|
@ -103,10 +120,11 @@ class WebFluxSseIntegrationTests {
|
||||||
|
|
||||||
var clientBuilder = clientBuilders.get(clientType);
|
var clientBuilder = clientBuilders.get(clientType);
|
||||||
|
|
||||||
McpServerFeatures.AsyncToolSpecification tool = new McpServerFeatures.AsyncToolSpecification(
|
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
|
||||||
new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema),
|
.tool(new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema))
|
||||||
(exchange, request) -> exchange.createMessage(mock(CreateMessageRequest.class))
|
.callHandler((exchange, request) -> exchange.createMessage(mock(CreateMessageRequest.class))
|
||||||
.thenReturn(mock(CallToolResult.class)));
|
.thenReturn(mock(CallToolResult.class)))
|
||||||
|
.build();
|
||||||
|
|
||||||
var server = McpServer.async(mcpServerTransportProvider).serverInfo("test-server", "1.0.0").tools(tool).build();
|
var server = McpServer.async(mcpServerTransportProvider).serverInfo("test-server", "1.0.0").tools(tool).build();
|
||||||
|
|
||||||
|
|
@ -145,24 +163,26 @@ class WebFluxSseIntegrationTests {
|
||||||
|
|
||||||
AtomicReference<CreateMessageResult> samplingResult = new AtomicReference<>();
|
AtomicReference<CreateMessageResult> samplingResult = new AtomicReference<>();
|
||||||
|
|
||||||
McpServerFeatures.AsyncToolSpecification tool = new McpServerFeatures.AsyncToolSpecification(
|
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
|
||||||
new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema), (exchange, request) -> {
|
.tool(new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema))
|
||||||
|
.callHandler((exchange, request) -> {
|
||||||
|
|
||||||
var createMessageRequest = McpSchema.CreateMessageRequest.builder()
|
var createMessageRequest = McpSchema.CreateMessageRequest.builder()
|
||||||
.messages(List.of(new McpSchema.SamplingMessage(McpSchema.Role.USER,
|
.messages(List.of(new McpSchema.SamplingMessage(McpSchema.Role.USER,
|
||||||
new McpSchema.TextContent("Test message"))))
|
new McpSchema.TextContent("Test message"))))
|
||||||
.modelPreferences(ModelPreferences.builder()
|
.modelPreferences(ModelPreferences.builder()
|
||||||
.hints(List.of())
|
.hints(List.of())
|
||||||
.costPriority(1.0)
|
.costPriority(1.0)
|
||||||
.speedPriority(1.0)
|
.speedPriority(1.0)
|
||||||
.intelligencePriority(1.0)
|
.intelligencePriority(1.0)
|
||||||
.build())
|
.build())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
return exchange.createMessage(createMessageRequest)
|
return exchange.createMessage(createMessageRequest)
|
||||||
.doOnNext(samplingResult::set)
|
.doOnNext(samplingResult::set)
|
||||||
.thenReturn(callResponse);
|
.thenReturn(callResponse);
|
||||||
});
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
var mcpServer = McpServer.async(mcpServerTransportProvider)
|
var mcpServer = McpServer.async(mcpServerTransportProvider)
|
||||||
.serverInfo("test-server", "1.0.0")
|
.serverInfo("test-server", "1.0.0")
|
||||||
|
|
@ -221,24 +241,26 @@ class WebFluxSseIntegrationTests {
|
||||||
|
|
||||||
AtomicReference<CreateMessageResult> samplingResult = new AtomicReference<>();
|
AtomicReference<CreateMessageResult> samplingResult = new AtomicReference<>();
|
||||||
|
|
||||||
McpServerFeatures.AsyncToolSpecification tool = new McpServerFeatures.AsyncToolSpecification(
|
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
|
||||||
new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema), (exchange, request) -> {
|
.tool(new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema))
|
||||||
|
.callHandler((exchange, request) -> {
|
||||||
|
|
||||||
var craeteMessageRequest = McpSchema.CreateMessageRequest.builder()
|
var craeteMessageRequest = McpSchema.CreateMessageRequest.builder()
|
||||||
.messages(List.of(new McpSchema.SamplingMessage(McpSchema.Role.USER,
|
.messages(List.of(new McpSchema.SamplingMessage(McpSchema.Role.USER,
|
||||||
new McpSchema.TextContent("Test message"))))
|
new McpSchema.TextContent("Test message"))))
|
||||||
.modelPreferences(ModelPreferences.builder()
|
.modelPreferences(ModelPreferences.builder()
|
||||||
.hints(List.of())
|
.hints(List.of())
|
||||||
.costPriority(1.0)
|
.costPriority(1.0)
|
||||||
.speedPriority(1.0)
|
.speedPriority(1.0)
|
||||||
.intelligencePriority(1.0)
|
.intelligencePriority(1.0)
|
||||||
.build())
|
.build())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
return exchange.createMessage(craeteMessageRequest)
|
return exchange.createMessage(craeteMessageRequest)
|
||||||
.doOnNext(samplingResult::set)
|
.doOnNext(samplingResult::set)
|
||||||
.thenReturn(callResponse);
|
.thenReturn(callResponse);
|
||||||
});
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
var mcpServer = McpServer.async(mcpServerTransportProvider)
|
var mcpServer = McpServer.async(mcpServerTransportProvider)
|
||||||
.requestTimeout(Duration.ofSeconds(4))
|
.requestTimeout(Duration.ofSeconds(4))
|
||||||
|
|
@ -297,16 +319,18 @@ class WebFluxSseIntegrationTests {
|
||||||
CallToolResult callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")),
|
CallToolResult callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")),
|
||||||
null);
|
null);
|
||||||
|
|
||||||
McpServerFeatures.AsyncToolSpecification tool = new McpServerFeatures.AsyncToolSpecification(
|
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
|
||||||
new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema), (exchange, request) -> {
|
.tool(new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema))
|
||||||
|
.callHandler((exchange, request) -> {
|
||||||
|
|
||||||
var craeteMessageRequest = McpSchema.CreateMessageRequest.builder()
|
var craeteMessageRequest = McpSchema.CreateMessageRequest.builder()
|
||||||
.messages(List.of(new McpSchema.SamplingMessage(McpSchema.Role.USER,
|
.messages(List.of(new McpSchema.SamplingMessage(McpSchema.Role.USER,
|
||||||
new McpSchema.TextContent("Test message"))))
|
new McpSchema.TextContent("Test message"))))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
return exchange.createMessage(craeteMessageRequest).thenReturn(callResponse);
|
return exchange.createMessage(craeteMessageRequest).thenReturn(callResponse);
|
||||||
});
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
var mcpServer = McpServer.async(mcpServerTransportProvider)
|
var mcpServer = McpServer.async(mcpServerTransportProvider)
|
||||||
.requestTimeout(Duration.ofSeconds(1))
|
.requestTimeout(Duration.ofSeconds(1))
|
||||||
|
|
@ -340,13 +364,15 @@ class WebFluxSseIntegrationTests {
|
||||||
|
|
||||||
var clientBuilder = clientBuilders.get(clientType);
|
var clientBuilder = clientBuilders.get(clientType);
|
||||||
|
|
||||||
McpServerFeatures.AsyncToolSpecification tool = new McpServerFeatures.AsyncToolSpecification(
|
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
|
||||||
new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema), (exchange, request) -> {
|
.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();
|
var server = McpServer.async(mcpServerTransportProvider).serverInfo("test-server", "1.0.0").tools(tool).build();
|
||||||
|
|
||||||
|
|
@ -383,23 +409,25 @@ class WebFluxSseIntegrationTests {
|
||||||
CallToolResult callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")),
|
CallToolResult callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")),
|
||||||
null);
|
null);
|
||||||
|
|
||||||
McpServerFeatures.AsyncToolSpecification tool = new McpServerFeatures.AsyncToolSpecification(
|
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
|
||||||
new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema), (exchange, request) -> {
|
.tool(new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema))
|
||||||
|
.callHandler((exchange, request) -> {
|
||||||
|
|
||||||
var elicitationRequest = ElicitRequest.builder()
|
var elicitationRequest = ElicitRequest.builder()
|
||||||
.message("Test message")
|
.message("Test message")
|
||||||
.requestedSchema(
|
.requestedSchema(
|
||||||
Map.of("type", "object", "properties", Map.of("message", Map.of("type", "string"))))
|
Map.of("type", "object", "properties", Map.of("message", Map.of("type", "string"))))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
StepVerifier.create(exchange.createElicitation(elicitationRequest)).consumeNextWith(result -> {
|
StepVerifier.create(exchange.createElicitation(elicitationRequest)).consumeNextWith(result -> {
|
||||||
assertThat(result).isNotNull();
|
assertThat(result).isNotNull();
|
||||||
assertThat(result.action()).isEqualTo(ElicitResult.Action.ACCEPT);
|
assertThat(result.action()).isEqualTo(ElicitResult.Action.ACCEPT);
|
||||||
assertThat(result.content().get("message")).isEqualTo("Test message");
|
assertThat(result.content().get("message")).isEqualTo("Test message");
|
||||||
}).verifyComplete();
|
}).verifyComplete();
|
||||||
|
|
||||||
return Mono.just(callResponse);
|
return Mono.just(callResponse);
|
||||||
});
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
var mcpServer = McpServer.async(mcpServerTransportProvider)
|
var mcpServer = McpServer.async(mcpServerTransportProvider)
|
||||||
.serverInfo("test-server", "1.0.0")
|
.serverInfo("test-server", "1.0.0")
|
||||||
|
|
@ -445,23 +473,25 @@ class WebFluxSseIntegrationTests {
|
||||||
CallToolResult callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")),
|
CallToolResult callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")),
|
||||||
null);
|
null);
|
||||||
|
|
||||||
McpServerFeatures.AsyncToolSpecification tool = new McpServerFeatures.AsyncToolSpecification(
|
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
|
||||||
new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema), (exchange, request) -> {
|
.tool(new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema))
|
||||||
|
.callHandler((exchange, request) -> {
|
||||||
|
|
||||||
var elicitationRequest = ElicitRequest.builder()
|
var elicitationRequest = ElicitRequest.builder()
|
||||||
.message("Test message")
|
.message("Test message")
|
||||||
.requestedSchema(
|
.requestedSchema(
|
||||||
Map.of("type", "object", "properties", Map.of("message", Map.of("type", "string"))))
|
Map.of("type", "object", "properties", Map.of("message", Map.of("type", "string"))))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
StepVerifier.create(exchange.createElicitation(elicitationRequest)).consumeNextWith(result -> {
|
StepVerifier.create(exchange.createElicitation(elicitationRequest)).consumeNextWith(result -> {
|
||||||
assertThat(result).isNotNull();
|
assertThat(result).isNotNull();
|
||||||
assertThat(result.action()).isEqualTo(ElicitResult.Action.ACCEPT);
|
assertThat(result.action()).isEqualTo(ElicitResult.Action.ACCEPT);
|
||||||
assertThat(result.content().get("message")).isEqualTo("Test message");
|
assertThat(result.content().get("message")).isEqualTo("Test message");
|
||||||
}).verifyComplete();
|
}).verifyComplete();
|
||||||
|
|
||||||
return Mono.just(callResponse);
|
return Mono.just(callResponse);
|
||||||
});
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
var mcpServer = McpServer.async(mcpServerTransportProvider)
|
var mcpServer = McpServer.async(mcpServerTransportProvider)
|
||||||
.serverInfo("test-server", "1.0.0")
|
.serverInfo("test-server", "1.0.0")
|
||||||
|
|
@ -514,23 +544,25 @@ class WebFluxSseIntegrationTests {
|
||||||
CallToolResult callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")),
|
CallToolResult callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")),
|
||||||
null);
|
null);
|
||||||
|
|
||||||
McpServerFeatures.AsyncToolSpecification tool = new McpServerFeatures.AsyncToolSpecification(
|
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
|
||||||
new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema), (exchange, request) -> {
|
.tool(new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema))
|
||||||
|
.callHandler((exchange, request) -> {
|
||||||
|
|
||||||
var elicitationRequest = ElicitRequest.builder()
|
var elicitationRequest = ElicitRequest.builder()
|
||||||
.message("Test message")
|
.message("Test message")
|
||||||
.requestedSchema(
|
.requestedSchema(
|
||||||
Map.of("type", "object", "properties", Map.of("message", Map.of("type", "string"))))
|
Map.of("type", "object", "properties", Map.of("message", Map.of("type", "string"))))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
StepVerifier.create(exchange.createElicitation(elicitationRequest)).consumeNextWith(result -> {
|
StepVerifier.create(exchange.createElicitation(elicitationRequest)).consumeNextWith(result -> {
|
||||||
assertThat(result).isNotNull();
|
assertThat(result).isNotNull();
|
||||||
assertThat(result.action()).isEqualTo(ElicitResult.Action.ACCEPT);
|
assertThat(result.action()).isEqualTo(ElicitResult.Action.ACCEPT);
|
||||||
assertThat(result.content().get("message")).isEqualTo("Test message");
|
assertThat(result.content().get("message")).isEqualTo("Test message");
|
||||||
}).verifyComplete();
|
}).verifyComplete();
|
||||||
|
|
||||||
return Mono.just(callResponse);
|
return Mono.just(callResponse);
|
||||||
});
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
var mcpServer = McpServer.async(mcpServerTransportProvider)
|
var mcpServer = McpServer.async(mcpServerTransportProvider)
|
||||||
.serverInfo("test-server", "1.0.0")
|
.serverInfo("test-server", "1.0.0")
|
||||||
|
|
@ -605,13 +637,15 @@ class WebFluxSseIntegrationTests {
|
||||||
|
|
||||||
var clientBuilder = clientBuilders.get(clientType);
|
var clientBuilder = clientBuilders.get(clientType);
|
||||||
|
|
||||||
McpServerFeatures.SyncToolSpecification tool = new McpServerFeatures.SyncToolSpecification(
|
McpServerFeatures.SyncToolSpecification tool = McpServerFeatures.SyncToolSpecification.builder()
|
||||||
new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema), (exchange, request) -> {
|
.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) -> {
|
var mcpServer = McpServer.sync(mcpServerTransportProvider).rootsChangeHandler((exchange, rootsUpdate) -> {
|
||||||
}).tools(tool).build();
|
}).tools(tool).build();
|
||||||
|
|
@ -744,17 +778,19 @@ class WebFluxSseIntegrationTests {
|
||||||
var clientBuilder = clientBuilders.get(clientType);
|
var clientBuilder = clientBuilders.get(clientType);
|
||||||
|
|
||||||
var callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")), null);
|
var callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")), null);
|
||||||
McpServerFeatures.SyncToolSpecification tool1 = new McpServerFeatures.SyncToolSpecification(
|
McpServerFeatures.SyncToolSpecification tool1 = McpServerFeatures.SyncToolSpecification.builder()
|
||||||
new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema), (exchange, request) -> {
|
.tool(new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema))
|
||||||
// perform a blocking call to a remote service
|
.callHandler((exchange, request) -> {
|
||||||
String response = RestClient.create()
|
// perform a blocking call to a remote service
|
||||||
.get()
|
String response = RestClient.create()
|
||||||
.uri("https://raw.githubusercontent.com/modelcontextprotocol/java-sdk/refs/heads/main/README.md")
|
.get()
|
||||||
.retrieve()
|
.uri("https://raw.githubusercontent.com/modelcontextprotocol/java-sdk/refs/heads/main/README.md")
|
||||||
.body(String.class);
|
.retrieve()
|
||||||
assertThat(response).isNotBlank();
|
.body(String.class);
|
||||||
return callResponse;
|
assertThat(response).isNotBlank();
|
||||||
});
|
return callResponse;
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
var mcpServer = McpServer.sync(mcpServerTransportProvider)
|
var mcpServer = McpServer.sync(mcpServerTransportProvider)
|
||||||
.capabilities(ServerCapabilities.builder().tools(true).build())
|
.capabilities(ServerCapabilities.builder().tools(true).build())
|
||||||
|
|
@ -784,17 +820,19 @@ class WebFluxSseIntegrationTests {
|
||||||
var clientBuilder = clientBuilders.get(clientType);
|
var clientBuilder = clientBuilders.get(clientType);
|
||||||
|
|
||||||
var callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")), null);
|
var callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")), null);
|
||||||
McpServerFeatures.SyncToolSpecification tool1 = new McpServerFeatures.SyncToolSpecification(
|
McpServerFeatures.SyncToolSpecification tool1 = McpServerFeatures.SyncToolSpecification.builder()
|
||||||
new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema), (exchange, request) -> {
|
.tool(new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema))
|
||||||
// perform a blocking call to a remote service
|
.callHandler((exchange, request) -> {
|
||||||
String response = RestClient.create()
|
// perform a blocking call to a remote service
|
||||||
.get()
|
String response = RestClient.create()
|
||||||
.uri("https://raw.githubusercontent.com/modelcontextprotocol/java-sdk/refs/heads/main/README.md")
|
.get()
|
||||||
.retrieve()
|
.uri("https://raw.githubusercontent.com/modelcontextprotocol/java-sdk/refs/heads/main/README.md")
|
||||||
.body(String.class);
|
.retrieve()
|
||||||
assertThat(response).isNotBlank();
|
.body(String.class);
|
||||||
return callResponse;
|
assertThat(response).isNotBlank();
|
||||||
});
|
return callResponse;
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
AtomicReference<List<Tool>> rootsRef = new AtomicReference<>();
|
AtomicReference<List<Tool>> rootsRef = new AtomicReference<>();
|
||||||
|
|
||||||
|
|
@ -835,9 +873,10 @@ class WebFluxSseIntegrationTests {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add a new tool
|
// Add a new tool
|
||||||
McpServerFeatures.SyncToolSpecification tool2 = new McpServerFeatures.SyncToolSpecification(
|
McpServerFeatures.SyncToolSpecification tool2 = McpServerFeatures.SyncToolSpecification.builder()
|
||||||
new McpSchema.Tool("tool2", "tool2 description", emptyJsonSchema),
|
.tool(new McpSchema.Tool("tool2", "tool2 description", emptyJsonSchema))
|
||||||
(exchange, request) -> callResponse);
|
.callHandler((exchange, request) -> callResponse)
|
||||||
|
.build();
|
||||||
|
|
||||||
mcpServer.addTool(tool2);
|
mcpServer.addTool(tool2);
|
||||||
|
|
||||||
|
|
@ -880,13 +919,13 @@ class WebFluxSseIntegrationTests {
|
||||||
var clientBuilder = clientBuilders.get(clientType);
|
var clientBuilder = clientBuilders.get(clientType);
|
||||||
|
|
||||||
// Create server with a tool that sends logging notifications
|
// Create server with a tool that sends logging notifications
|
||||||
McpServerFeatures.AsyncToolSpecification tool = new McpServerFeatures.AsyncToolSpecification(
|
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
|
||||||
new McpSchema.Tool("logging-test", "Test logging notifications", emptyJsonSchema),
|
.tool(new McpSchema.Tool("logging-test", "Test logging notifications", emptyJsonSchema))
|
||||||
(exchange, request) -> {
|
.callHandler((exchange, request) -> {
|
||||||
|
|
||||||
// Create and send notifications with different levels
|
// Create and send notifications with different levels
|
||||||
|
|
||||||
//@formatter:off
|
//@formatter:off
|
||||||
return exchange // This should be filtered out (DEBUG < NOTICE)
|
return exchange // This should be filtered out (DEBUG < NOTICE)
|
||||||
.loggingNotification(McpSchema.LoggingMessageNotification.builder()
|
.loggingNotification(McpSchema.LoggingMessageNotification.builder()
|
||||||
.level(McpSchema.LoggingLevel.DEBUG)
|
.level(McpSchema.LoggingLevel.DEBUG)
|
||||||
|
|
@ -919,7 +958,8 @@ class WebFluxSseIntegrationTests {
|
||||||
.build()))
|
.build()))
|
||||||
.thenReturn(new CallToolResult("Logging test completed", false));
|
.thenReturn(new CallToolResult("Logging test completed", false));
|
||||||
//@formatter:on
|
//@formatter:on
|
||||||
});
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
var mcpServer = McpServer.async(mcpServerTransportProvider)
|
var mcpServer = McpServer.async(mcpServerTransportProvider)
|
||||||
.serverInfo("test-server", "1.0.0")
|
.serverInfo("test-server", "1.0.0")
|
||||||
|
|
@ -1035,26 +1075,27 @@ class WebFluxSseIntegrationTests {
|
||||||
// Create server with a tool that uses ping functionality
|
// Create server with a tool that uses ping functionality
|
||||||
AtomicReference<String> executionOrder = new AtomicReference<>("");
|
AtomicReference<String> executionOrder = new AtomicReference<>("");
|
||||||
|
|
||||||
McpServerFeatures.AsyncToolSpecification tool = new McpServerFeatures.AsyncToolSpecification(
|
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
|
||||||
new McpSchema.Tool("ping-async-test", "Test ping async behavior", emptyJsonSchema),
|
.tool(new McpSchema.Tool("ping-async-test", "Test ping async behavior", emptyJsonSchema))
|
||||||
(exchange, request) -> {
|
.callHandler((exchange, request) -> {
|
||||||
|
|
||||||
executionOrder.set(executionOrder.get() + "1");
|
executionOrder.set(executionOrder.get() + "1");
|
||||||
|
|
||||||
// Test async ping behavior
|
// Test async ping behavior
|
||||||
return exchange.ping().doOnNext(result -> {
|
return exchange.ping().doOnNext(result -> {
|
||||||
|
|
||||||
assertThat(result).isNotNull();
|
assertThat(result).isNotNull();
|
||||||
// Ping should return an empty object or map
|
// Ping should return an empty object or map
|
||||||
assertThat(result).isInstanceOf(Map.class);
|
assertThat(result).isInstanceOf(Map.class);
|
||||||
|
|
||||||
executionOrder.set(executionOrder.get() + "2");
|
executionOrder.set(executionOrder.get() + "2");
|
||||||
assertThat(result).isNotNull();
|
assertThat(result).isNotNull();
|
||||||
}).then(Mono.fromCallable(() -> {
|
}).then(Mono.fromCallable(() -> {
|
||||||
executionOrder.set(executionOrder.get() + "3");
|
executionOrder.set(executionOrder.get() + "3");
|
||||||
return new CallToolResult("Async ping test completed", false);
|
return new CallToolResult("Async ping test completed", false);
|
||||||
}));
|
}));
|
||||||
});
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
var mcpServer = McpServer.async(mcpServerTransportProvider)
|
var mcpServer = McpServer.async(mcpServerTransportProvider)
|
||||||
.serverInfo("test-server", "1.0.0")
|
.serverInfo("test-server", "1.0.0")
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,11 @@
|
||||||
*/
|
*/
|
||||||
package io.modelcontextprotocol.server;
|
package io.modelcontextprotocol.server;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||||
|
import static org.awaitility.Awaitility.await;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
@ -10,7 +15,20 @@ import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import org.apache.catalina.LifecycleException;
|
||||||
|
import org.apache.catalina.LifecycleState;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.web.client.RestClient;
|
||||||
|
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||||
|
import org.springframework.web.servlet.function.RouterFunction;
|
||||||
|
import org.springframework.web.servlet.function.ServerResponse;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
import io.modelcontextprotocol.client.McpClient;
|
import io.modelcontextprotocol.client.McpClient;
|
||||||
import io.modelcontextprotocol.client.transport.HttpClientSseClientTransport;
|
import io.modelcontextprotocol.client.transport.HttpClientSseClientTransport;
|
||||||
import io.modelcontextprotocol.server.transport.WebMvcSseServerTransportProvider;
|
import io.modelcontextprotocol.server.transport.WebMvcSseServerTransportProvider;
|
||||||
|
|
@ -26,27 +44,10 @@ import io.modelcontextprotocol.spec.McpSchema.Role;
|
||||||
import io.modelcontextprotocol.spec.McpSchema.Root;
|
import io.modelcontextprotocol.spec.McpSchema.Root;
|
||||||
import io.modelcontextprotocol.spec.McpSchema.ServerCapabilities;
|
import io.modelcontextprotocol.spec.McpSchema.ServerCapabilities;
|
||||||
import io.modelcontextprotocol.spec.McpSchema.Tool;
|
import io.modelcontextprotocol.spec.McpSchema.Tool;
|
||||||
import org.apache.catalina.LifecycleException;
|
|
||||||
import org.apache.catalina.LifecycleState;
|
|
||||||
import org.junit.jupiter.api.AfterEach;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import reactor.core.scheduler.Schedulers;
|
import reactor.core.scheduler.Schedulers;
|
||||||
import reactor.test.StepVerifier;
|
import reactor.test.StepVerifier;
|
||||||
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.web.client.RestClient;
|
|
||||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
|
||||||
import org.springframework.web.servlet.function.RouterFunction;
|
|
||||||
import org.springframework.web.servlet.function.ServerResponse;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
|
||||||
import static org.awaitility.Awaitility.await;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
|
|
||||||
class WebMvcSseIntegrationTests {
|
class WebMvcSseIntegrationTests {
|
||||||
|
|
||||||
private static final int PORT = TestUtil.findAvailablePort();
|
private static final int PORT = TestUtil.findAvailablePort();
|
||||||
|
|
@ -122,13 +123,13 @@ class WebMvcSseIntegrationTests {
|
||||||
@Test
|
@Test
|
||||||
void testCreateMessageWithoutSamplingCapabilities() {
|
void testCreateMessageWithoutSamplingCapabilities() {
|
||||||
|
|
||||||
McpServerFeatures.AsyncToolSpecification tool = new McpServerFeatures.AsyncToolSpecification(
|
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
|
||||||
new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema), (exchange, request) -> {
|
.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();
|
||||||
|
|
||||||
//@formatter:off
|
//@formatter:off
|
||||||
var server = McpServer.async(mcpServerTransportProvider)
|
var server = McpServer.async(mcpServerTransportProvider)
|
||||||
|
|
@ -169,31 +170,33 @@ class WebMvcSseIntegrationTests {
|
||||||
CallToolResult callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")),
|
CallToolResult callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")),
|
||||||
null);
|
null);
|
||||||
|
|
||||||
McpServerFeatures.AsyncToolSpecification tool = new McpServerFeatures.AsyncToolSpecification(
|
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
|
||||||
new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema), (exchange, request) -> {
|
.tool(new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema))
|
||||||
|
.callHandler((exchange, request) -> {
|
||||||
|
|
||||||
var createMessageRequest = McpSchema.CreateMessageRequest.builder()
|
var createMessageRequest = McpSchema.CreateMessageRequest.builder()
|
||||||
.messages(List.of(new McpSchema.SamplingMessage(McpSchema.Role.USER,
|
.messages(List.of(new McpSchema.SamplingMessage(McpSchema.Role.USER,
|
||||||
new McpSchema.TextContent("Test message"))))
|
new McpSchema.TextContent("Test message"))))
|
||||||
.modelPreferences(ModelPreferences.builder()
|
.modelPreferences(ModelPreferences.builder()
|
||||||
.hints(List.of())
|
.hints(List.of())
|
||||||
.costPriority(1.0)
|
.costPriority(1.0)
|
||||||
.speedPriority(1.0)
|
.speedPriority(1.0)
|
||||||
.intelligencePriority(1.0)
|
.intelligencePriority(1.0)
|
||||||
.build())
|
.build())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
StepVerifier.create(exchange.createMessage(createMessageRequest)).consumeNextWith(result -> {
|
StepVerifier.create(exchange.createMessage(createMessageRequest)).consumeNextWith(result -> {
|
||||||
assertThat(result).isNotNull();
|
assertThat(result).isNotNull();
|
||||||
assertThat(result.role()).isEqualTo(Role.USER);
|
assertThat(result.role()).isEqualTo(Role.USER);
|
||||||
assertThat(result.content()).isInstanceOf(McpSchema.TextContent.class);
|
assertThat(result.content()).isInstanceOf(McpSchema.TextContent.class);
|
||||||
assertThat(((McpSchema.TextContent) result.content()).text()).isEqualTo("Test message");
|
assertThat(((McpSchema.TextContent) result.content()).text()).isEqualTo("Test message");
|
||||||
assertThat(result.model()).isEqualTo("MockModelName");
|
assertThat(result.model()).isEqualTo("MockModelName");
|
||||||
assertThat(result.stopReason()).isEqualTo(CreateMessageResult.StopReason.STOP_SEQUENCE);
|
assertThat(result.stopReason()).isEqualTo(CreateMessageResult.StopReason.STOP_SEQUENCE);
|
||||||
}).verifyComplete();
|
}).verifyComplete();
|
||||||
|
|
||||||
return Mono.just(callResponse);
|
return Mono.just(callResponse);
|
||||||
});
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
//@formatter:off
|
//@formatter:off
|
||||||
var mcpServer = McpServer.async(mcpServerTransportProvider)
|
var mcpServer = McpServer.async(mcpServerTransportProvider)
|
||||||
|
|
@ -245,31 +248,33 @@ class WebMvcSseIntegrationTests {
|
||||||
CallToolResult callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")),
|
CallToolResult callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")),
|
||||||
null);
|
null);
|
||||||
|
|
||||||
McpServerFeatures.AsyncToolSpecification tool = new McpServerFeatures.AsyncToolSpecification(
|
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
|
||||||
new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema), (exchange, request) -> {
|
.tool(new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema))
|
||||||
|
.callHandler((exchange, request) -> {
|
||||||
|
|
||||||
var craeteMessageRequest = McpSchema.CreateMessageRequest.builder()
|
var craeteMessageRequest = McpSchema.CreateMessageRequest.builder()
|
||||||
.messages(List.of(new McpSchema.SamplingMessage(McpSchema.Role.USER,
|
.messages(List.of(new McpSchema.SamplingMessage(McpSchema.Role.USER,
|
||||||
new McpSchema.TextContent("Test message"))))
|
new McpSchema.TextContent("Test message"))))
|
||||||
.modelPreferences(ModelPreferences.builder()
|
.modelPreferences(ModelPreferences.builder()
|
||||||
.hints(List.of())
|
.hints(List.of())
|
||||||
.costPriority(1.0)
|
.costPriority(1.0)
|
||||||
.speedPriority(1.0)
|
.speedPriority(1.0)
|
||||||
.intelligencePriority(1.0)
|
.intelligencePriority(1.0)
|
||||||
.build())
|
.build())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
StepVerifier.create(exchange.createMessage(craeteMessageRequest)).consumeNextWith(result -> {
|
StepVerifier.create(exchange.createMessage(craeteMessageRequest)).consumeNextWith(result -> {
|
||||||
assertThat(result).isNotNull();
|
assertThat(result).isNotNull();
|
||||||
assertThat(result.role()).isEqualTo(Role.USER);
|
assertThat(result.role()).isEqualTo(Role.USER);
|
||||||
assertThat(result.content()).isInstanceOf(McpSchema.TextContent.class);
|
assertThat(result.content()).isInstanceOf(McpSchema.TextContent.class);
|
||||||
assertThat(((McpSchema.TextContent) result.content()).text()).isEqualTo("Test message");
|
assertThat(((McpSchema.TextContent) result.content()).text()).isEqualTo("Test message");
|
||||||
assertThat(result.model()).isEqualTo("MockModelName");
|
assertThat(result.model()).isEqualTo("MockModelName");
|
||||||
assertThat(result.stopReason()).isEqualTo(CreateMessageResult.StopReason.STOP_SEQUENCE);
|
assertThat(result.stopReason()).isEqualTo(CreateMessageResult.StopReason.STOP_SEQUENCE);
|
||||||
}).verifyComplete();
|
}).verifyComplete();
|
||||||
|
|
||||||
return Mono.just(callResponse);
|
return Mono.just(callResponse);
|
||||||
});
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
var mcpServer = McpServer.async(mcpServerTransportProvider)
|
var mcpServer = McpServer.async(mcpServerTransportProvider)
|
||||||
.serverInfo("test-server", "1.0.0")
|
.serverInfo("test-server", "1.0.0")
|
||||||
|
|
@ -317,31 +322,33 @@ class WebMvcSseIntegrationTests {
|
||||||
CallToolResult callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")),
|
CallToolResult callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")),
|
||||||
null);
|
null);
|
||||||
|
|
||||||
McpServerFeatures.AsyncToolSpecification tool = new McpServerFeatures.AsyncToolSpecification(
|
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
|
||||||
new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema), (exchange, request) -> {
|
.tool(new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema))
|
||||||
|
.callHandler((exchange, request) -> {
|
||||||
|
|
||||||
var craeteMessageRequest = McpSchema.CreateMessageRequest.builder()
|
var craeteMessageRequest = McpSchema.CreateMessageRequest.builder()
|
||||||
.messages(List.of(new McpSchema.SamplingMessage(McpSchema.Role.USER,
|
.messages(List.of(new McpSchema.SamplingMessage(McpSchema.Role.USER,
|
||||||
new McpSchema.TextContent("Test message"))))
|
new McpSchema.TextContent("Test message"))))
|
||||||
.modelPreferences(ModelPreferences.builder()
|
.modelPreferences(ModelPreferences.builder()
|
||||||
.hints(List.of())
|
.hints(List.of())
|
||||||
.costPriority(1.0)
|
.costPriority(1.0)
|
||||||
.speedPriority(1.0)
|
.speedPriority(1.0)
|
||||||
.intelligencePriority(1.0)
|
.intelligencePriority(1.0)
|
||||||
.build())
|
.build())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
StepVerifier.create(exchange.createMessage(craeteMessageRequest)).consumeNextWith(result -> {
|
StepVerifier.create(exchange.createMessage(craeteMessageRequest)).consumeNextWith(result -> {
|
||||||
assertThat(result).isNotNull();
|
assertThat(result).isNotNull();
|
||||||
assertThat(result.role()).isEqualTo(Role.USER);
|
assertThat(result.role()).isEqualTo(Role.USER);
|
||||||
assertThat(result.content()).isInstanceOf(McpSchema.TextContent.class);
|
assertThat(result.content()).isInstanceOf(McpSchema.TextContent.class);
|
||||||
assertThat(((McpSchema.TextContent) result.content()).text()).isEqualTo("Test message");
|
assertThat(((McpSchema.TextContent) result.content()).text()).isEqualTo("Test message");
|
||||||
assertThat(result.model()).isEqualTo("MockModelName");
|
assertThat(result.model()).isEqualTo("MockModelName");
|
||||||
assertThat(result.stopReason()).isEqualTo(CreateMessageResult.StopReason.STOP_SEQUENCE);
|
assertThat(result.stopReason()).isEqualTo(CreateMessageResult.StopReason.STOP_SEQUENCE);
|
||||||
}).verifyComplete();
|
}).verifyComplete();
|
||||||
|
|
||||||
return Mono.just(callResponse);
|
return Mono.just(callResponse);
|
||||||
});
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
var mcpServer = McpServer.async(mcpServerTransportProvider)
|
var mcpServer = McpServer.async(mcpServerTransportProvider)
|
||||||
.serverInfo("test-server", "1.0.0")
|
.serverInfo("test-server", "1.0.0")
|
||||||
|
|
@ -366,13 +373,15 @@ class WebMvcSseIntegrationTests {
|
||||||
@Test
|
@Test
|
||||||
void testCreateElicitationWithoutElicitationCapabilities() {
|
void testCreateElicitationWithoutElicitationCapabilities() {
|
||||||
|
|
||||||
McpServerFeatures.AsyncToolSpecification tool = new McpServerFeatures.AsyncToolSpecification(
|
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
|
||||||
new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema), (exchange, request) -> {
|
.tool(new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema))
|
||||||
|
.callHandler((exchange, request) -> {
|
||||||
|
|
||||||
exchange.createElicitation(mock(McpSchema.ElicitRequest.class)).block();
|
exchange.createElicitation(mock(McpSchema.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();
|
var server = McpServer.async(mcpServerTransportProvider).serverInfo("test-server", "1.0.0").tools(tool).build();
|
||||||
|
|
||||||
|
|
@ -407,23 +416,25 @@ class WebMvcSseIntegrationTests {
|
||||||
CallToolResult callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")),
|
CallToolResult callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")),
|
||||||
null);
|
null);
|
||||||
|
|
||||||
McpServerFeatures.AsyncToolSpecification tool = new McpServerFeatures.AsyncToolSpecification(
|
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
|
||||||
new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema), (exchange, request) -> {
|
.tool(new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema))
|
||||||
|
.callHandler((exchange, request) -> {
|
||||||
|
|
||||||
var elicitationRequest = McpSchema.ElicitRequest.builder()
|
var elicitationRequest = McpSchema.ElicitRequest.builder()
|
||||||
.message("Test message")
|
.message("Test message")
|
||||||
.requestedSchema(
|
.requestedSchema(
|
||||||
Map.of("type", "object", "properties", Map.of("message", Map.of("type", "string"))))
|
Map.of("type", "object", "properties", Map.of("message", Map.of("type", "string"))))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
StepVerifier.create(exchange.createElicitation(elicitationRequest)).consumeNextWith(result -> {
|
StepVerifier.create(exchange.createElicitation(elicitationRequest)).consumeNextWith(result -> {
|
||||||
assertThat(result).isNotNull();
|
assertThat(result).isNotNull();
|
||||||
assertThat(result.action()).isEqualTo(McpSchema.ElicitResult.Action.ACCEPT);
|
assertThat(result.action()).isEqualTo(McpSchema.ElicitResult.Action.ACCEPT);
|
||||||
assertThat(result.content().get("message")).isEqualTo("Test message");
|
assertThat(result.content().get("message")).isEqualTo("Test message");
|
||||||
}).verifyComplete();
|
}).verifyComplete();
|
||||||
|
|
||||||
return Mono.just(callResponse);
|
return Mono.just(callResponse);
|
||||||
});
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
var mcpServer = McpServer.async(mcpServerTransportProvider)
|
var mcpServer = McpServer.async(mcpServerTransportProvider)
|
||||||
.serverInfo("test-server", "1.0.0")
|
.serverInfo("test-server", "1.0.0")
|
||||||
|
|
@ -474,23 +485,25 @@ class WebMvcSseIntegrationTests {
|
||||||
CallToolResult callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")),
|
CallToolResult callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")),
|
||||||
null);
|
null);
|
||||||
|
|
||||||
McpServerFeatures.AsyncToolSpecification tool = new McpServerFeatures.AsyncToolSpecification(
|
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
|
||||||
new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema), (exchange, request) -> {
|
.tool(new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema))
|
||||||
|
.callHandler((exchange, request) -> {
|
||||||
|
|
||||||
var elicitationRequest = McpSchema.ElicitRequest.builder()
|
var elicitationRequest = McpSchema.ElicitRequest.builder()
|
||||||
.message("Test message")
|
.message("Test message")
|
||||||
.requestedSchema(
|
.requestedSchema(
|
||||||
Map.of("type", "object", "properties", Map.of("message", Map.of("type", "string"))))
|
Map.of("type", "object", "properties", Map.of("message", Map.of("type", "string"))))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
StepVerifier.create(exchange.createElicitation(elicitationRequest)).consumeNextWith(result -> {
|
StepVerifier.create(exchange.createElicitation(elicitationRequest)).consumeNextWith(result -> {
|
||||||
assertThat(result).isNotNull();
|
assertThat(result).isNotNull();
|
||||||
assertThat(result.action()).isEqualTo(McpSchema.ElicitResult.Action.ACCEPT);
|
assertThat(result.action()).isEqualTo(McpSchema.ElicitResult.Action.ACCEPT);
|
||||||
assertThat(result.content().get("message")).isEqualTo("Test message");
|
assertThat(result.content().get("message")).isEqualTo("Test message");
|
||||||
}).verifyComplete();
|
}).verifyComplete();
|
||||||
|
|
||||||
return Mono.just(callResponse);
|
return Mono.just(callResponse);
|
||||||
});
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
var mcpServer = McpServer.async(mcpServerTransportProvider)
|
var mcpServer = McpServer.async(mcpServerTransportProvider)
|
||||||
.serverInfo("test-server", "1.0.0")
|
.serverInfo("test-server", "1.0.0")
|
||||||
|
|
@ -538,23 +551,25 @@ class WebMvcSseIntegrationTests {
|
||||||
CallToolResult callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")),
|
CallToolResult callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")),
|
||||||
null);
|
null);
|
||||||
|
|
||||||
McpServerFeatures.AsyncToolSpecification tool = new McpServerFeatures.AsyncToolSpecification(
|
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
|
||||||
new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema), (exchange, request) -> {
|
.tool(new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema))
|
||||||
|
.callHandler((exchange, request) -> {
|
||||||
|
|
||||||
var elicitationRequest = McpSchema.ElicitRequest.builder()
|
var elicitationRequest = McpSchema.ElicitRequest.builder()
|
||||||
.message("Test message")
|
.message("Test message")
|
||||||
.requestedSchema(
|
.requestedSchema(
|
||||||
Map.of("type", "object", "properties", Map.of("message", Map.of("type", "string"))))
|
Map.of("type", "object", "properties", Map.of("message", Map.of("type", "string"))))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
StepVerifier.create(exchange.createElicitation(elicitationRequest)).consumeNextWith(result -> {
|
StepVerifier.create(exchange.createElicitation(elicitationRequest)).consumeNextWith(result -> {
|
||||||
assertThat(result).isNotNull();
|
assertThat(result).isNotNull();
|
||||||
assertThat(result.action()).isEqualTo(McpSchema.ElicitResult.Action.ACCEPT);
|
assertThat(result.action()).isEqualTo(McpSchema.ElicitResult.Action.ACCEPT);
|
||||||
assertThat(result.content().get("message")).isEqualTo("Test message");
|
assertThat(result.content().get("message")).isEqualTo("Test message");
|
||||||
}).verifyComplete();
|
}).verifyComplete();
|
||||||
|
|
||||||
return Mono.just(callResponse);
|
return Mono.just(callResponse);
|
||||||
});
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
var mcpServer = McpServer.async(mcpServerTransportProvider)
|
var mcpServer = McpServer.async(mcpServerTransportProvider)
|
||||||
.serverInfo("test-server", "1.0.0")
|
.serverInfo("test-server", "1.0.0")
|
||||||
|
|
@ -623,13 +638,15 @@ class WebMvcSseIntegrationTests {
|
||||||
@Test
|
@Test
|
||||||
void testRootsWithoutCapability() {
|
void testRootsWithoutCapability() {
|
||||||
|
|
||||||
McpServerFeatures.SyncToolSpecification tool = new McpServerFeatures.SyncToolSpecification(
|
McpServerFeatures.SyncToolSpecification tool = McpServerFeatures.SyncToolSpecification.builder()
|
||||||
new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema), (exchange, request) -> {
|
.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) -> {
|
var mcpServer = McpServer.sync(mcpServerTransportProvider).rootsChangeHandler((exchange, rootsUpdate) -> {
|
||||||
}).tools(tool).build();
|
}).tools(tool).build();
|
||||||
|
|
@ -750,17 +767,19 @@ class WebMvcSseIntegrationTests {
|
||||||
void testToolCallSuccess() {
|
void testToolCallSuccess() {
|
||||||
|
|
||||||
var callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")), null);
|
var callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")), null);
|
||||||
McpServerFeatures.SyncToolSpecification tool1 = new McpServerFeatures.SyncToolSpecification(
|
McpServerFeatures.SyncToolSpecification tool1 = McpServerFeatures.SyncToolSpecification.builder()
|
||||||
new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema), (exchange, request) -> {
|
.tool(new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema))
|
||||||
// perform a blocking call to a remote service
|
.callHandler((exchange, request) -> {
|
||||||
String response = RestClient.create()
|
// perform a blocking call to a remote service
|
||||||
.get()
|
String response = RestClient.create()
|
||||||
.uri("https://raw.githubusercontent.com/modelcontextprotocol/java-sdk/refs/heads/main/README.md")
|
.get()
|
||||||
.retrieve()
|
.uri("https://raw.githubusercontent.com/modelcontextprotocol/java-sdk/refs/heads/main/README.md")
|
||||||
.body(String.class);
|
.retrieve()
|
||||||
assertThat(response).isNotBlank();
|
.body(String.class);
|
||||||
return callResponse;
|
assertThat(response).isNotBlank();
|
||||||
});
|
return callResponse;
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
var mcpServer = McpServer.sync(mcpServerTransportProvider)
|
var mcpServer = McpServer.sync(mcpServerTransportProvider)
|
||||||
.capabilities(ServerCapabilities.builder().tools(true).build())
|
.capabilities(ServerCapabilities.builder().tools(true).build())
|
||||||
|
|
@ -813,17 +832,19 @@ class WebMvcSseIntegrationTests {
|
||||||
void testToolListChangeHandlingSuccess() {
|
void testToolListChangeHandlingSuccess() {
|
||||||
|
|
||||||
var callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")), null);
|
var callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")), null);
|
||||||
McpServerFeatures.SyncToolSpecification tool1 = new McpServerFeatures.SyncToolSpecification(
|
McpServerFeatures.SyncToolSpecification tool1 = McpServerFeatures.SyncToolSpecification.builder()
|
||||||
new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema), (exchange, request) -> {
|
.tool(new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema))
|
||||||
// perform a blocking call to a remote service
|
.callHandler((exchange, request) -> {
|
||||||
String response = RestClient.create()
|
// perform a blocking call to a remote service
|
||||||
.get()
|
String response = RestClient.create()
|
||||||
.uri("https://raw.githubusercontent.com/modelcontextprotocol/java-sdk/refs/heads/main/README.md")
|
.get()
|
||||||
.retrieve()
|
.uri("https://raw.githubusercontent.com/modelcontextprotocol/java-sdk/refs/heads/main/README.md")
|
||||||
.body(String.class);
|
.retrieve()
|
||||||
assertThat(response).isNotBlank();
|
.body(String.class);
|
||||||
return callResponse;
|
assertThat(response).isNotBlank();
|
||||||
});
|
return callResponse;
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
AtomicReference<List<Tool>> rootsRef = new AtomicReference<>();
|
AtomicReference<List<Tool>> rootsRef = new AtomicReference<>();
|
||||||
|
|
||||||
|
|
@ -864,9 +885,10 @@ class WebMvcSseIntegrationTests {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add a new tool
|
// Add a new tool
|
||||||
McpServerFeatures.SyncToolSpecification tool2 = new McpServerFeatures.SyncToolSpecification(
|
McpServerFeatures.SyncToolSpecification tool2 = McpServerFeatures.SyncToolSpecification.builder()
|
||||||
new McpSchema.Tool("tool2", "tool2 description", emptyJsonSchema),
|
.tool(new McpSchema.Tool("tool2", "tool2 description", emptyJsonSchema))
|
||||||
(exchange, request) -> callResponse);
|
.callHandler((exchange, request) -> callResponse)
|
||||||
|
.build();
|
||||||
|
|
||||||
mcpServer.addTool(tool2);
|
mcpServer.addTool(tool2);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
*
|
*
|
||||||
* @author Christian Tzolov
|
* @author Christian Tzolov
|
||||||
*/
|
*/
|
||||||
// KEEP IN SYNC with the class in mcp-test module
|
|
||||||
public abstract class AbstractMcpAsyncServerTests {
|
public abstract class AbstractMcpAsyncServerTests {
|
||||||
|
|
||||||
private static final String TEST_TOOL_NAME = "test-tool";
|
private static final String TEST_TOOL_NAME = "test-tool";
|
||||||
|
|
@ -102,6 +101,7 @@ public abstract class AbstractMcpAsyncServerTests {
|
||||||
""";
|
""";
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@Deprecated
|
||||||
void testAddTool() {
|
void testAddTool() {
|
||||||
Tool newTool = new McpSchema.Tool("new-tool", "New test tool", emptyJsonSchema);
|
Tool newTool = new McpSchema.Tool("new-tool", "New test tool", emptyJsonSchema);
|
||||||
var mcpAsyncServer = McpServer.async(createMcpTransportProvider())
|
var mcpAsyncServer = McpServer.async(createMcpTransportProvider())
|
||||||
|
|
@ -117,6 +117,23 @@ public abstract class AbstractMcpAsyncServerTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@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() {
|
void testAddDuplicateTool() {
|
||||||
Tool duplicateTool = new McpSchema.Tool(TEST_TOOL_NAME, "Duplicate tool", emptyJsonSchema);
|
Tool duplicateTool = new McpSchema.Tool(TEST_TOOL_NAME, "Duplicate tool", emptyJsonSchema);
|
||||||
|
|
||||||
|
|
@ -137,6 +154,83 @@ public abstract class AbstractMcpAsyncServerTests {
|
||||||
assertThatCode(() -> mcpAsyncServer.closeGracefully().block(Duration.ofSeconds(10))).doesNotThrowAnyException();
|
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
|
@Test
|
||||||
void testRemoveTool() {
|
void testRemoveTool() {
|
||||||
Tool too = new McpSchema.Tool(TEST_TOOL_NAME, "Duplicate tool", emptyJsonSchema);
|
Tool too = new McpSchema.Tool(TEST_TOOL_NAME, "Duplicate tool", emptyJsonSchema);
|
||||||
|
|
@ -144,7 +238,7 @@ public abstract class AbstractMcpAsyncServerTests {
|
||||||
var mcpAsyncServer = McpServer.async(createMcpTransportProvider())
|
var mcpAsyncServer = McpServer.async(createMcpTransportProvider())
|
||||||
.serverInfo("test-server", "1.0.0")
|
.serverInfo("test-server", "1.0.0")
|
||||||
.capabilities(ServerCapabilities.builder().tools(true).build())
|
.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();
|
.build();
|
||||||
|
|
||||||
StepVerifier.create(mcpAsyncServer.removeTool(TEST_TOOL_NAME)).verifyComplete();
|
StepVerifier.create(mcpAsyncServer.removeTool(TEST_TOOL_NAME)).verifyComplete();
|
||||||
|
|
@ -173,7 +267,7 @@ public abstract class AbstractMcpAsyncServerTests {
|
||||||
var mcpAsyncServer = McpServer.async(createMcpTransportProvider())
|
var mcpAsyncServer = McpServer.async(createMcpTransportProvider())
|
||||||
.serverInfo("test-server", "1.0.0")
|
.serverInfo("test-server", "1.0.0")
|
||||||
.capabilities(ServerCapabilities.builder().tools(true).build())
|
.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();
|
.build();
|
||||||
|
|
||||||
StepVerifier.create(mcpAsyncServer.notifyToolsListChanged()).verifyComplete();
|
StepVerifier.create(mcpAsyncServer.notifyToolsListChanged()).verifyComplete();
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,16 @@
|
||||||
|
|
||||||
package io.modelcontextprotocol.server;
|
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 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.McpError;
|
||||||
import io.modelcontextprotocol.spec.McpSchema;
|
import io.modelcontextprotocol.spec.McpSchema;
|
||||||
import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
|
import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
|
||||||
|
|
@ -17,21 +25,13 @@ import io.modelcontextprotocol.spec.McpSchema.Resource;
|
||||||
import io.modelcontextprotocol.spec.McpSchema.ServerCapabilities;
|
import io.modelcontextprotocol.spec.McpSchema.ServerCapabilities;
|
||||||
import io.modelcontextprotocol.spec.McpSchema.Tool;
|
import io.modelcontextprotocol.spec.McpSchema.Tool;
|
||||||
import io.modelcontextprotocol.spec.McpServerTransportProvider;
|
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
|
* Test suite for the {@link McpSyncServer} that can be used with different
|
||||||
* {@link io.modelcontextprotocol.spec.McpServerTransportProvider} implementations.
|
* {@link McpTransportProvider} implementations.
|
||||||
*
|
*
|
||||||
* @author Christian Tzolov
|
* @author Christian Tzolov
|
||||||
*/
|
*/
|
||||||
// KEEP IN SYNC with the class in mcp-test module
|
|
||||||
public abstract class AbstractMcpSyncServerTests {
|
public abstract class AbstractMcpSyncServerTests {
|
||||||
|
|
||||||
private static final String TEST_TOOL_NAME = "test-tool";
|
private static final String TEST_TOOL_NAME = "test-tool";
|
||||||
|
|
@ -109,6 +109,7 @@ public abstract class AbstractMcpSyncServerTests {
|
||||||
""";
|
""";
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@Deprecated
|
||||||
void testAddTool() {
|
void testAddTool() {
|
||||||
var mcpSyncServer = McpServer.sync(createMcpTransportProvider())
|
var mcpSyncServer = McpServer.sync(createMcpTransportProvider())
|
||||||
.serverInfo("test-server", "1.0.0")
|
.serverInfo("test-server", "1.0.0")
|
||||||
|
|
@ -124,6 +125,23 @@ public abstract class AbstractMcpSyncServerTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@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() {
|
void testAddDuplicateTool() {
|
||||||
Tool duplicateTool = new McpSchema.Tool(TEST_TOOL_NAME, "Duplicate tool", emptyJsonSchema);
|
Tool duplicateTool = new McpSchema.Tool(TEST_TOOL_NAME, "Duplicate tool", emptyJsonSchema);
|
||||||
|
|
||||||
|
|
@ -141,6 +159,81 @@ public abstract class AbstractMcpSyncServerTests {
|
||||||
assertThatCode(() -> mcpSyncServer.closeGracefully()).doesNotThrowAnyException();
|
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
|
@Test
|
||||||
void testRemoveTool() {
|
void testRemoveTool() {
|
||||||
Tool tool = new McpSchema.Tool(TEST_TOOL_NAME, "Test tool", emptyJsonSchema);
|
Tool tool = new McpSchema.Tool(TEST_TOOL_NAME, "Test tool", emptyJsonSchema);
|
||||||
|
|
@ -148,7 +241,7 @@ public abstract class AbstractMcpSyncServerTests {
|
||||||
var mcpSyncServer = McpServer.sync(createMcpTransportProvider())
|
var mcpSyncServer = McpServer.sync(createMcpTransportProvider())
|
||||||
.serverInfo("test-server", "1.0.0")
|
.serverInfo("test-server", "1.0.0")
|
||||||
.capabilities(ServerCapabilities.builder().tools(true).build())
|
.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();
|
.build();
|
||||||
|
|
||||||
assertThatCode(() -> mcpSyncServer.removeTool(TEST_TOOL_NAME)).doesNotThrowAnyException();
|
assertThatCode(() -> mcpSyncServer.removeTool(TEST_TOOL_NAME)).doesNotThrowAnyException();
|
||||||
|
|
|
||||||
|
|
@ -268,7 +268,7 @@ public class McpAsyncServer {
|
||||||
// ---------------------------------------
|
// ---------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a new tool specification at runtime.
|
* Add a new tool call specification at runtime.
|
||||||
* @param toolSpecification The tool specification to add
|
* @param toolSpecification The tool specification to add
|
||||||
* @return Mono that completes when clients have been notified of the change
|
* @return Mono that completes when clients have been notified of the change
|
||||||
*/
|
*/
|
||||||
|
|
@ -279,7 +279,7 @@ public class McpAsyncServer {
|
||||||
if (toolSpecification.tool() == null) {
|
if (toolSpecification.tool() == null) {
|
||||||
return Mono.error(new McpError("Tool must not be null"));
|
return Mono.error(new McpError("Tool must not be null"));
|
||||||
}
|
}
|
||||||
if (toolSpecification.call() == null) {
|
if (toolSpecification.call() == null && toolSpecification.callHandler() == null) {
|
||||||
return Mono.error(new McpError("Tool call handler must not be null"));
|
return Mono.error(new McpError("Tool call handler must not be null"));
|
||||||
}
|
}
|
||||||
if (this.serverCapabilities.tools() == null) {
|
if (this.serverCapabilities.tools() == null) {
|
||||||
|
|
@ -360,7 +360,7 @@ public class McpAsyncServer {
|
||||||
return Mono.error(new McpError("Tool not found: " + callToolRequest.name()));
|
return Mono.error(new McpError("Tool not found: " + callToolRequest.name()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return toolSpecification.map(tool -> tool.call().apply(exchange, callToolRequest.arguments()))
|
return toolSpecification.map(tool -> tool.callHandler().apply(exchange, callToolRequest))
|
||||||
.orElse(Mono.error(new McpError("Tool not found: " + callToolRequest.name())));
|
.orElse(Mono.error(new McpError("Tool not found: " + callToolRequest.name())));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -82,12 +82,16 @@ import reactor.core.publisher.Mono;
|
||||||
* .capabilities(new ServerCapabilities(...))
|
* .capabilities(new ServerCapabilities(...))
|
||||||
* // Register tools
|
* // Register tools
|
||||||
* .tools(
|
* .tools(
|
||||||
* new McpServerFeatures.AsyncToolSpecification(calculatorTool,
|
* McpServerFeatures.AsyncToolSpecification.builder()
|
||||||
* (exchange, args) -> Mono.fromSupplier(() -> calculate(args))
|
* .tool(calculatorTool)
|
||||||
* .map(result -> new CallToolResult("Result: " + result))),
|
* .callTool((exchange, args) -> Mono.fromSupplier(() -> calculate(args.arguments()))
|
||||||
* new McpServerFeatures.AsyncToolSpecification(weatherTool,
|
* .map(result -> new CallToolResult("Result: " + result))))
|
||||||
* (exchange, args) -> Mono.fromSupplier(() -> getWeather(args))
|
*. .build(),
|
||||||
* .map(result -> new CallToolResult("Weather: " + result)))
|
* McpServerFeatures.AsyncToolSpecification.builder()
|
||||||
|
* .tool((weatherTool)
|
||||||
|
* .callTool((exchange, args) -> Mono.fromSupplier(() -> getWeather(args.arguments()))
|
||||||
|
* .map(result -> new CallToolResult("Weather: " + result))))
|
||||||
|
* .build()
|
||||||
* )
|
* )
|
||||||
* // Register resources
|
* // Register resources
|
||||||
* .resources(
|
* .resources(
|
||||||
|
|
@ -321,17 +325,47 @@ public interface McpServer {
|
||||||
* map of arguments passed to the tool.
|
* map of arguments passed to the tool.
|
||||||
* @return This builder instance for method chaining
|
* @return This builder instance for method chaining
|
||||||
* @throws IllegalArgumentException if tool or handler is null
|
* @throws IllegalArgumentException if tool or handler is null
|
||||||
|
* @deprecated Use {@link #toolCall(McpSchema.Tool, BiFunction)} instead for tool
|
||||||
|
* calls that require a request object.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public AsyncSpecification tool(McpSchema.Tool tool,
|
public AsyncSpecification tool(McpSchema.Tool tool,
|
||||||
BiFunction<McpAsyncServerExchange, Map<String, Object>, Mono<CallToolResult>> handler) {
|
BiFunction<McpAsyncServerExchange, Map<String, Object>, Mono<CallToolResult>> handler) {
|
||||||
Assert.notNull(tool, "Tool must not be null");
|
Assert.notNull(tool, "Tool must not be null");
|
||||||
Assert.notNull(handler, "Handler must not be null");
|
Assert.notNull(handler, "Handler must not be null");
|
||||||
|
assertNoDuplicateTool(tool.name());
|
||||||
|
|
||||||
this.tools.add(new McpServerFeatures.AsyncToolSpecification(tool, handler));
|
this.tools.add(new McpServerFeatures.AsyncToolSpecification(tool, handler));
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a single tool with its implementation handler to the server. This is a
|
||||||
|
* convenience method for registering individual tools without creating a
|
||||||
|
* {@link McpServerFeatures.AsyncToolSpecification} explicitly.
|
||||||
|
* @param tool The tool definition including name, description, and schema. Must
|
||||||
|
* not be null.
|
||||||
|
* @param callHandler The function that implements the tool's logic. Must not be
|
||||||
|
* null. The function's first argument is an {@link McpAsyncServerExchange} upon
|
||||||
|
* which the server can interact with the connected client. The second argument is
|
||||||
|
* the {@link McpSchema.CallToolRequest} object containing the tool call
|
||||||
|
* @return This builder instance for method chaining
|
||||||
|
* @throws IllegalArgumentException if tool or handler is null
|
||||||
|
*/
|
||||||
|
public AsyncSpecification toolCall(McpSchema.Tool tool,
|
||||||
|
BiFunction<McpAsyncServerExchange, McpSchema.CallToolRequest, Mono<CallToolResult>> callHandler) {
|
||||||
|
|
||||||
|
Assert.notNull(tool, "Tool must not be null");
|
||||||
|
Assert.notNull(callHandler, "Handler must not be null");
|
||||||
|
assertNoDuplicateTool(tool.name());
|
||||||
|
|
||||||
|
this.tools
|
||||||
|
.add(McpServerFeatures.AsyncToolSpecification.builder().tool(tool).callHandler(callHandler).build());
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds multiple tools with their handlers to the server using a List. This method
|
* Adds multiple tools with their handlers to the server using a List. This method
|
||||||
* is useful when tools are dynamically generated or loaded from a configuration
|
* is useful when tools are dynamically generated or loaded from a configuration
|
||||||
|
|
@ -344,7 +378,12 @@ public interface McpServer {
|
||||||
*/
|
*/
|
||||||
public AsyncSpecification tools(List<McpServerFeatures.AsyncToolSpecification> toolSpecifications) {
|
public AsyncSpecification tools(List<McpServerFeatures.AsyncToolSpecification> toolSpecifications) {
|
||||||
Assert.notNull(toolSpecifications, "Tool handlers list must not be null");
|
Assert.notNull(toolSpecifications, "Tool handlers list must not be null");
|
||||||
this.tools.addAll(toolSpecifications);
|
|
||||||
|
for (var tool : toolSpecifications) {
|
||||||
|
assertNoDuplicateTool(tool.tool().name());
|
||||||
|
this.tools.add(tool);
|
||||||
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -355,24 +394,31 @@ public interface McpServer {
|
||||||
* <p>
|
* <p>
|
||||||
* Example usage: <pre>{@code
|
* Example usage: <pre>{@code
|
||||||
* .tools(
|
* .tools(
|
||||||
* new McpServerFeatures.AsyncToolSpecification(calculatorTool, calculatorHandler),
|
* McpServerFeatures.AsyncToolSpecification.builder().tool(calculatorTool).callTool(calculatorHandler).build(),
|
||||||
* new McpServerFeatures.AsyncToolSpecification(weatherTool, weatherHandler),
|
* McpServerFeatures.AsyncToolSpecification.builder().tool(weatherTool).callTool(weatherHandler).build(),
|
||||||
* new McpServerFeatures.AsyncToolSpecification(fileManagerTool, fileManagerHandler)
|
* McpServerFeatures.AsyncToolSpecification.builder().tool(fileManagerTool).callTool(fileManagerHandler).build()
|
||||||
* )
|
* )
|
||||||
* }</pre>
|
* }</pre>
|
||||||
* @param toolSpecifications The tool specifications to add. Must not be null.
|
* @param toolSpecifications The tool specifications to add. Must not be null.
|
||||||
* @return This builder instance for method chaining
|
* @return This builder instance for method chaining
|
||||||
* @throws IllegalArgumentException if toolSpecifications is null
|
* @throws IllegalArgumentException if toolSpecifications is null
|
||||||
* @see #tools(List)
|
|
||||||
*/
|
*/
|
||||||
public AsyncSpecification tools(McpServerFeatures.AsyncToolSpecification... toolSpecifications) {
|
public AsyncSpecification tools(McpServerFeatures.AsyncToolSpecification... toolSpecifications) {
|
||||||
Assert.notNull(toolSpecifications, "Tool handlers list must not be null");
|
Assert.notNull(toolSpecifications, "Tool handlers list must not be null");
|
||||||
|
|
||||||
for (McpServerFeatures.AsyncToolSpecification tool : toolSpecifications) {
|
for (McpServerFeatures.AsyncToolSpecification tool : toolSpecifications) {
|
||||||
|
assertNoDuplicateTool(tool.tool().name());
|
||||||
this.tools.add(tool);
|
this.tools.add(tool);
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void assertNoDuplicateTool(String toolName) {
|
||||||
|
if (this.tools.stream().anyMatch(toolSpec -> toolSpec.tool().name().equals(toolName))) {
|
||||||
|
throw new IllegalArgumentException("Tool with name '" + toolName + "' is already registered.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers multiple resources with their handlers using a Map. This method is
|
* Registers multiple resources with their handlers using a Map. This method is
|
||||||
* useful when resources are dynamically generated or loaded from a configuration
|
* useful when resources are dynamically generated or loaded from a configuration
|
||||||
|
|
@ -814,17 +860,45 @@ public interface McpServer {
|
||||||
* list of arguments passed to the tool.
|
* list of arguments passed to the tool.
|
||||||
* @return This builder instance for method chaining
|
* @return This builder instance for method chaining
|
||||||
* @throws IllegalArgumentException if tool or handler is null
|
* @throws IllegalArgumentException if tool or handler is null
|
||||||
|
* @deprecated Use {@link #toolCall(McpSchema.Tool, BiFunction)} instead for tool
|
||||||
|
* calls that require a request object.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public SyncSpecification tool(McpSchema.Tool tool,
|
public SyncSpecification tool(McpSchema.Tool tool,
|
||||||
BiFunction<McpSyncServerExchange, Map<String, Object>, McpSchema.CallToolResult> handler) {
|
BiFunction<McpSyncServerExchange, Map<String, Object>, McpSchema.CallToolResult> handler) {
|
||||||
Assert.notNull(tool, "Tool must not be null");
|
Assert.notNull(tool, "Tool must not be null");
|
||||||
Assert.notNull(handler, "Handler must not be null");
|
Assert.notNull(handler, "Handler must not be null");
|
||||||
|
assertNoDuplicateTool(tool.name());
|
||||||
|
|
||||||
this.tools.add(new McpServerFeatures.SyncToolSpecification(tool, handler));
|
this.tools.add(new McpServerFeatures.SyncToolSpecification(tool, handler));
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a single tool with its implementation handler to the server. This is a
|
||||||
|
* convenience method for registering individual tools without creating a
|
||||||
|
* {@link McpServerFeatures.SyncToolSpecification} explicitly.
|
||||||
|
* @param tool The tool definition including name, description, and schema. Must
|
||||||
|
* not be null.
|
||||||
|
* @param handler The function that implements the tool's logic. Must not be null.
|
||||||
|
* The function's first argument is an {@link McpSyncServerExchange} upon which
|
||||||
|
* the server can interact with the connected client. The second argument is the
|
||||||
|
* list of arguments passed to the tool.
|
||||||
|
* @return This builder instance for method chaining
|
||||||
|
* @throws IllegalArgumentException if tool or handler is null
|
||||||
|
*/
|
||||||
|
public SyncSpecification toolCall(McpSchema.Tool tool,
|
||||||
|
BiFunction<McpSyncServerExchange, McpSchema.CallToolRequest, McpSchema.CallToolResult> handler) {
|
||||||
|
Assert.notNull(tool, "Tool must not be null");
|
||||||
|
Assert.notNull(handler, "Handler must not be null");
|
||||||
|
assertNoDuplicateTool(tool.name());
|
||||||
|
|
||||||
|
this.tools.add(new McpServerFeatures.SyncToolSpecification(tool, null, handler));
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds multiple tools with their handlers to the server using a List. This method
|
* Adds multiple tools with their handlers to the server using a List. This method
|
||||||
* is useful when tools are dynamically generated or loaded from a configuration
|
* is useful when tools are dynamically generated or loaded from a configuration
|
||||||
|
|
@ -837,7 +911,13 @@ public interface McpServer {
|
||||||
*/
|
*/
|
||||||
public SyncSpecification tools(List<McpServerFeatures.SyncToolSpecification> toolSpecifications) {
|
public SyncSpecification tools(List<McpServerFeatures.SyncToolSpecification> toolSpecifications) {
|
||||||
Assert.notNull(toolSpecifications, "Tool handlers list must not be null");
|
Assert.notNull(toolSpecifications, "Tool handlers list must not be null");
|
||||||
this.tools.addAll(toolSpecifications);
|
|
||||||
|
for (var tool : toolSpecifications) {
|
||||||
|
String toolName = tool.tool().name();
|
||||||
|
assertNoDuplicateTool(toolName); // Check against existing tools
|
||||||
|
this.tools.add(tool);
|
||||||
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -860,12 +940,20 @@ public interface McpServer {
|
||||||
*/
|
*/
|
||||||
public SyncSpecification tools(McpServerFeatures.SyncToolSpecification... toolSpecifications) {
|
public SyncSpecification tools(McpServerFeatures.SyncToolSpecification... toolSpecifications) {
|
||||||
Assert.notNull(toolSpecifications, "Tool handlers list must not be null");
|
Assert.notNull(toolSpecifications, "Tool handlers list must not be null");
|
||||||
|
|
||||||
for (McpServerFeatures.SyncToolSpecification tool : toolSpecifications) {
|
for (McpServerFeatures.SyncToolSpecification tool : toolSpecifications) {
|
||||||
|
assertNoDuplicateTool(tool.tool().name());
|
||||||
this.tools.add(tool);
|
this.tools.add(tool);
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void assertNoDuplicateTool(String toolName) {
|
||||||
|
if (this.tools.stream().anyMatch(toolSpec -> toolSpec.tool().name().equals(toolName))) {
|
||||||
|
throw new IllegalArgumentException("Tool with name '" + toolName + "' is already registered.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers multiple resources with their handlers using a Map. This method is
|
* Registers multiple resources with their handlers using a Map. This method is
|
||||||
* useful when resources are dynamically generated or loaded from a configuration
|
* useful when resources are dynamically generated or loaded from a configuration
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import java.util.function.BiConsumer;
|
||||||
import java.util.function.BiFunction;
|
import java.util.function.BiFunction;
|
||||||
|
|
||||||
import io.modelcontextprotocol.spec.McpSchema;
|
import io.modelcontextprotocol.spec.McpSchema;
|
||||||
|
import io.modelcontextprotocol.spec.McpSchema.CallToolRequest;
|
||||||
import io.modelcontextprotocol.util.Assert;
|
import io.modelcontextprotocol.util.Assert;
|
||||||
import io.modelcontextprotocol.util.Utils;
|
import io.modelcontextprotocol.util.Utils;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
@ -205,51 +206,110 @@ public class McpServerFeatures {
|
||||||
/**
|
/**
|
||||||
* Specification of a tool with its asynchronous handler function. Tools are the
|
* Specification of a tool with its asynchronous handler function. Tools are the
|
||||||
* primary way for MCP servers to expose functionality to AI models. Each tool
|
* primary way for MCP servers to expose functionality to AI models. Each tool
|
||||||
* represents a specific capability, such as:
|
* represents a specific capability.
|
||||||
* <ul>
|
|
||||||
* <li>Performing calculations
|
|
||||||
* <li>Accessing external APIs
|
|
||||||
* <li>Querying databases
|
|
||||||
* <li>Manipulating files
|
|
||||||
* <li>Executing system commands
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* Example tool specification: <pre>{@code
|
|
||||||
* new McpServerFeatures.AsyncToolSpecification(
|
|
||||||
* new Tool(
|
|
||||||
* "calculator",
|
|
||||||
* "Performs mathematical calculations",
|
|
||||||
* new JsonSchemaObject()
|
|
||||||
* .required("expression")
|
|
||||||
* .property("expression", JsonSchemaType.STRING)
|
|
||||||
* ),
|
|
||||||
* (exchange, args) -> {
|
|
||||||
* String expr = (String) args.get("expression");
|
|
||||||
* return Mono.fromSupplier(() -> evaluate(expr))
|
|
||||||
* .map(result -> new CallToolResult("Result: " + result));
|
|
||||||
* }
|
|
||||||
* )
|
|
||||||
* }</pre>
|
|
||||||
*
|
*
|
||||||
* @param tool The tool definition including name, description, and parameter schema
|
* @param tool The tool definition including name, description, and parameter schema
|
||||||
* @param call The function that implements the tool's logic, receiving arguments and
|
* @param call Deprecated. Uset he {@link AsyncToolSpecification#callHandler} instead.
|
||||||
* returning results. The function's first argument is an
|
* @param callHandler The function that implements the tool's logic, receiving a
|
||||||
* {@link McpAsyncServerExchange} upon which the server can interact with the
|
* {@link McpAsyncServerExchange} and a
|
||||||
* connected client. The second arguments is a map of tool arguments.
|
* {@link io.modelcontextprotocol.spec.McpSchema.CallToolRequest} and returning
|
||||||
|
* results. The function's first argument is an {@link McpAsyncServerExchange} upon
|
||||||
|
* which the server can interact with the connected client. The second arguments is a
|
||||||
|
* map of tool arguments.
|
||||||
*/
|
*/
|
||||||
public record AsyncToolSpecification(McpSchema.Tool tool,
|
public record AsyncToolSpecification(McpSchema.Tool tool,
|
||||||
BiFunction<McpAsyncServerExchange, Map<String, Object>, Mono<McpSchema.CallToolResult>> call) {
|
@Deprecated BiFunction<McpAsyncServerExchange, Map<String, Object>, Mono<McpSchema.CallToolResult>> call,
|
||||||
|
BiFunction<McpAsyncServerExchange, McpSchema.CallToolRequest, Mono<McpSchema.CallToolResult>> callHandler) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link AsyncToolSpecification(McpSchema.Tool, null,
|
||||||
|
* BiFunction)} instead.
|
||||||
|
**/
|
||||||
|
@Deprecated
|
||||||
|
public AsyncToolSpecification(McpSchema.Tool tool,
|
||||||
|
BiFunction<McpAsyncServerExchange, Map<String, Object>, Mono<McpSchema.CallToolResult>> call) {
|
||||||
|
this(tool, call, (exchange, toolReq) -> call.apply(exchange, toolReq.arguments()));
|
||||||
|
}
|
||||||
|
|
||||||
|
static AsyncToolSpecification fromSync(SyncToolSpecification syncToolSpec) {
|
||||||
|
return fromSync(syncToolSpec, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static AsyncToolSpecification fromSync(SyncToolSpecification syncToolSpec, boolean immediate) {
|
||||||
|
|
||||||
static AsyncToolSpecification fromSync(SyncToolSpecification tool, boolean immediate) {
|
|
||||||
// FIXME: This is temporary, proper validation should be implemented
|
// FIXME: This is temporary, proper validation should be implemented
|
||||||
if (tool == null) {
|
if (syncToolSpec == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return new AsyncToolSpecification(tool.tool(), (exchange, map) -> {
|
|
||||||
var toolResult = Mono.fromCallable(() -> tool.call().apply(new McpSyncServerExchange(exchange), map));
|
BiFunction<McpAsyncServerExchange, Map<String, Object>, Mono<McpSchema.CallToolResult>> deprecatedCall = (syncToolSpec
|
||||||
|
.call() != null) ? (exchange, map) -> {
|
||||||
|
var toolResult = Mono
|
||||||
|
.fromCallable(() -> syncToolSpec.call().apply(new McpSyncServerExchange(exchange), map));
|
||||||
|
return immediate ? toolResult : toolResult.subscribeOn(Schedulers.boundedElastic());
|
||||||
|
} : null;
|
||||||
|
|
||||||
|
BiFunction<McpAsyncServerExchange, McpSchema.CallToolRequest, Mono<McpSchema.CallToolResult>> callHandler = (
|
||||||
|
exchange, req) -> {
|
||||||
|
var toolResult = Mono
|
||||||
|
.fromCallable(() -> syncToolSpec.callHandler().apply(new McpSyncServerExchange(exchange), req));
|
||||||
return immediate ? toolResult : toolResult.subscribeOn(Schedulers.boundedElastic());
|
return immediate ? toolResult : toolResult.subscribeOn(Schedulers.boundedElastic());
|
||||||
});
|
};
|
||||||
|
|
||||||
|
return new AsyncToolSpecification(syncToolSpec.tool(), deprecatedCall, callHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder for creating AsyncToolSpecification instances.
|
||||||
|
*/
|
||||||
|
public static class Builder {
|
||||||
|
|
||||||
|
private McpSchema.Tool tool;
|
||||||
|
|
||||||
|
private BiFunction<McpAsyncServerExchange, McpSchema.CallToolRequest, Mono<McpSchema.CallToolResult>> callHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the tool definition.
|
||||||
|
* @param tool The tool definition including name, description, and parameter
|
||||||
|
* schema
|
||||||
|
* @return this builder instance
|
||||||
|
*/
|
||||||
|
public Builder tool(McpSchema.Tool tool) {
|
||||||
|
this.tool = tool;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the call tool handler function.
|
||||||
|
* @param callHandler The function that implements the tool's logic
|
||||||
|
* @return this builder instance
|
||||||
|
*/
|
||||||
|
public Builder callHandler(
|
||||||
|
BiFunction<McpAsyncServerExchange, McpSchema.CallToolRequest, Mono<McpSchema.CallToolResult>> callHandler) {
|
||||||
|
this.callHandler = callHandler;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the AsyncToolSpecification instance.
|
||||||
|
* @return a new AsyncToolSpecification instance
|
||||||
|
* @throws IllegalArgumentException if required fields are not set
|
||||||
|
*/
|
||||||
|
public AsyncToolSpecification build() {
|
||||||
|
Assert.notNull(tool, "Tool must not be null");
|
||||||
|
Assert.notNull(callHandler, "Call handler function must not be null");
|
||||||
|
|
||||||
|
return new AsyncToolSpecification(tool, null, callHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new builder instance.
|
||||||
|
* @return a new Builder instance
|
||||||
|
*/
|
||||||
|
public static Builder builder() {
|
||||||
|
return new Builder();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -265,13 +325,13 @@ public class McpServerFeatures {
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* Example resource specification: <pre>{@code
|
* Example resource specification:
|
||||||
|
*
|
||||||
|
* <pre>{@code
|
||||||
* new McpServerFeatures.AsyncResourceSpecification(
|
* new McpServerFeatures.AsyncResourceSpecification(
|
||||||
* new Resource("docs", "Documentation files", "text/markdown"),
|
* new Resource("docs", "Documentation files", "text/markdown"),
|
||||||
* (exchange, request) ->
|
* (exchange, request) -> Mono.fromSupplier(() -> readFile(request.getPath()))
|
||||||
* Mono.fromSupplier(() -> readFile(request.getPath()))
|
* .map(ReadResourceResult::new))
|
||||||
* .map(ReadResourceResult::new)
|
|
||||||
* )
|
|
||||||
* }</pre>
|
* }</pre>
|
||||||
*
|
*
|
||||||
* @param resource The resource definition including name, description, and MIME type
|
* @param resource The resource definition including name, description, and MIME type
|
||||||
|
|
@ -308,16 +368,16 @@ public class McpServerFeatures {
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* Example prompt specification: <pre>{@code
|
* Example prompt specification:
|
||||||
|
*
|
||||||
|
* <pre>{@code
|
||||||
* new McpServerFeatures.AsyncPromptSpecification(
|
* new McpServerFeatures.AsyncPromptSpecification(
|
||||||
* new Prompt("analyze", "Code analysis template"),
|
* new Prompt("analyze", "Code analysis template"),
|
||||||
* (exchange, request) -> {
|
* (exchange, request) -> {
|
||||||
* String code = request.getArguments().get("code");
|
* String code = request.getArguments().get("code");
|
||||||
* return Mono.just(new GetPromptResult(
|
* return Mono.just(new GetPromptResult(
|
||||||
* "Analyze this code:\n\n" + code + "\n\nProvide feedback on:"
|
* "Analyze this code:\n\n" + code + "\n\nProvide feedback on:"));
|
||||||
* ));
|
* })
|
||||||
* }
|
|
||||||
* )
|
|
||||||
* }</pre>
|
* }</pre>
|
||||||
*
|
*
|
||||||
* @param prompt The prompt definition including name and description
|
* @param prompt The prompt definition including name and description
|
||||||
|
|
@ -386,41 +446,99 @@ public class McpServerFeatures {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specification of a tool with its synchronous handler function. Tools are the
|
* Specification of a tool with its synchronous handler function. Tools are the
|
||||||
* primary way for MCP servers to expose functionality to AI models. Each tool
|
* primary way for MCP servers to expose functionality to AI models.
|
||||||
* represents a specific capability, such as:
|
|
||||||
* <ul>
|
|
||||||
* <li>Performing calculations
|
|
||||||
* <li>Accessing external APIs
|
|
||||||
* <li>Querying databases
|
|
||||||
* <li>Manipulating files
|
|
||||||
* <li>Executing system commands
|
|
||||||
* </ul>
|
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* Example tool specification: <pre>{@code
|
* Example tool specification:
|
||||||
* new McpServerFeatures.SyncToolSpecification(
|
*
|
||||||
* new Tool(
|
* <pre>{@code
|
||||||
* "calculator",
|
* McpServerFeatures.SyncToolSpecification.builder()
|
||||||
* "Performs mathematical calculations",
|
* .tool(new Tool(
|
||||||
* new JsonSchemaObject()
|
* "calculator",
|
||||||
* .required("expression")
|
* "Performs mathematical calculations",
|
||||||
* .property("expression", JsonSchemaType.STRING)
|
* new JsonSchemaObject()
|
||||||
* ),
|
* .required("expression")
|
||||||
* (exchange, args) -> {
|
* .property("expression", JsonSchemaType.STRING)))
|
||||||
* String expr = (String) args.get("expression");
|
* .toolHandler((exchange, req) -> {
|
||||||
* return new CallToolResult("Result: " + evaluate(expr));
|
* String expr = (String) req.arguments().get("expression");
|
||||||
* }
|
* return new CallToolResult("Result: " + evaluate(expr));
|
||||||
* )
|
* }))
|
||||||
|
* .build();
|
||||||
* }</pre>
|
* }</pre>
|
||||||
*
|
*
|
||||||
* @param tool The tool definition including name, description, and parameter schema
|
* @param tool The tool definition including name, description, and parameter schema
|
||||||
* @param call The function that implements the tool's logic, receiving arguments and
|
* @param call (Deprected) The function that implements the tool's logic, receiving
|
||||||
* returning results. The function's first argument is an
|
* arguments and returning results. The function's first argument is an
|
||||||
* {@link McpSyncServerExchange} upon which the server can interact with the connected
|
* {@link McpSyncServerExchange} upon which the server can interact with the connected
|
||||||
* client. The second arguments is a map of arguments passed to the tool.
|
* @param callHandler The function that implements the tool's logic, receiving a
|
||||||
|
* {@link McpSyncServerExchange} and a
|
||||||
|
* {@link io.modelcontextprotocol.spec.McpSchema.CallToolRequest} and returning
|
||||||
|
* results. The function's first argument is an {@link McpSyncServerExchange} upon
|
||||||
|
* which the server can interact with the client. The second arguments is a map of
|
||||||
|
* arguments passed to the tool.
|
||||||
*/
|
*/
|
||||||
public record SyncToolSpecification(McpSchema.Tool tool,
|
public record SyncToolSpecification(McpSchema.Tool tool,
|
||||||
BiFunction<McpSyncServerExchange, Map<String, Object>, McpSchema.CallToolResult> call) {
|
@Deprecated BiFunction<McpSyncServerExchange, Map<String, Object>, McpSchema.CallToolResult> call,
|
||||||
|
BiFunction<McpSyncServerExchange, CallToolRequest, McpSchema.CallToolResult> callHandler) {
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
public SyncToolSpecification(McpSchema.Tool tool,
|
||||||
|
BiFunction<McpSyncServerExchange, Map<String, Object>, McpSchema.CallToolResult> call) {
|
||||||
|
this(tool, call, (exchange, toolReq) -> call.apply(exchange, toolReq.arguments()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder for creating SyncToolSpecification instances.
|
||||||
|
*/
|
||||||
|
public static class Builder {
|
||||||
|
|
||||||
|
private McpSchema.Tool tool;
|
||||||
|
|
||||||
|
private BiFunction<McpSyncServerExchange, CallToolRequest, McpSchema.CallToolResult> callHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the tool definition.
|
||||||
|
* @param tool The tool definition including name, description, and parameter
|
||||||
|
* schema
|
||||||
|
* @return this builder instance
|
||||||
|
*/
|
||||||
|
public Builder tool(McpSchema.Tool tool) {
|
||||||
|
this.tool = tool;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the call tool handler function.
|
||||||
|
* @param callHandler The function that implements the tool's logic
|
||||||
|
* @return this builder instance
|
||||||
|
*/
|
||||||
|
public Builder callHandler(
|
||||||
|
BiFunction<McpSyncServerExchange, CallToolRequest, McpSchema.CallToolResult> callHandler) {
|
||||||
|
this.callHandler = callHandler;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the SyncToolSpecification instance.
|
||||||
|
* @return a new SyncToolSpecification instance
|
||||||
|
* @throws IllegalArgumentException if required fields are not set
|
||||||
|
*/
|
||||||
|
public SyncToolSpecification build() {
|
||||||
|
Assert.notNull(tool, "Tool must not be null");
|
||||||
|
Assert.notNull(callHandler, "CallTool function must not be null");
|
||||||
|
|
||||||
|
return new SyncToolSpecification(tool, null, callHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new builder instance.
|
||||||
|
* @return a new Builder instance
|
||||||
|
*/
|
||||||
|
public static Builder builder() {
|
||||||
|
return new Builder();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -435,14 +553,15 @@ public class McpServerFeatures {
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* Example resource specification: <pre>{@code
|
* Example resource specification:
|
||||||
|
*
|
||||||
|
* <pre>{@code
|
||||||
* new McpServerFeatures.SyncResourceSpecification(
|
* new McpServerFeatures.SyncResourceSpecification(
|
||||||
* new Resource("docs", "Documentation files", "text/markdown"),
|
* new Resource("docs", "Documentation files", "text/markdown"),
|
||||||
* (exchange, request) -> {
|
* (exchange, request) -> {
|
||||||
* String content = readFile(request.getPath());
|
* String content = readFile(request.getPath());
|
||||||
* return new ReadResourceResult(content);
|
* return new ReadResourceResult(content);
|
||||||
* }
|
* })
|
||||||
* )
|
|
||||||
* }</pre>
|
* }</pre>
|
||||||
*
|
*
|
||||||
* @param resource The resource definition including name, description, and MIME type
|
* @param resource The resource definition including name, description, and MIME type
|
||||||
|
|
@ -467,16 +586,16 @@ public class McpServerFeatures {
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* Example prompt specification: <pre>{@code
|
* Example prompt specification:
|
||||||
|
*
|
||||||
|
* <pre>{@code
|
||||||
* new McpServerFeatures.SyncPromptSpecification(
|
* new McpServerFeatures.SyncPromptSpecification(
|
||||||
* new Prompt("analyze", "Code analysis template"),
|
* new Prompt("analyze", "Code analysis template"),
|
||||||
* (exchange, request) -> {
|
* (exchange, request) -> {
|
||||||
* String code = request.getArguments().get("code");
|
* String code = request.getArguments().get("code");
|
||||||
* return new GetPromptResult(
|
* return new GetPromptResult(
|
||||||
* "Analyze this code:\n\n" + code + "\n\nProvide feedback on:"
|
* "Analyze this code:\n\n" + code + "\n\nProvide feedback on:");
|
||||||
* );
|
* })
|
||||||
* }
|
|
||||||
* )
|
|
||||||
* }</pre>
|
* }</pre>
|
||||||
*
|
*
|
||||||
* @param prompt The prompt definition including name and description
|
* @param prompt The prompt definition including name and description
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
package io.modelcontextprotocol.server;
|
package io.modelcontextprotocol.server;
|
||||||
|
|
||||||
|
import io.modelcontextprotocol.server.McpServerFeatures.AsyncToolSpecification;
|
||||||
import io.modelcontextprotocol.spec.McpSchema;
|
import io.modelcontextprotocol.spec.McpSchema;
|
||||||
import io.modelcontextprotocol.spec.McpSchema.LoggingMessageNotification;
|
import io.modelcontextprotocol.spec.McpSchema.LoggingMessageNotification;
|
||||||
import io.modelcontextprotocol.util.Assert;
|
import io.modelcontextprotocol.util.Assert;
|
||||||
|
|
|
||||||
|
|
@ -30,10 +30,11 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test suite for the {@link McpAsyncServer} that can be used with different
|
* Test suite for the {@link McpAsyncServer} that can be used with different
|
||||||
* {@link McpTransportProvider} implementations.
|
* {@link io.modelcontextprotocol.spec.McpServerTransportProvider} implementations.
|
||||||
*
|
*
|
||||||
* @author Christian Tzolov
|
* @author Christian Tzolov
|
||||||
*/
|
*/
|
||||||
|
// KEEP IN SYNC with the class in mcp-test module
|
||||||
public abstract class AbstractMcpAsyncServerTests {
|
public abstract class AbstractMcpAsyncServerTests {
|
||||||
|
|
||||||
private static final String TEST_TOOL_NAME = "test-tool";
|
private static final String TEST_TOOL_NAME = "test-tool";
|
||||||
|
|
@ -101,6 +102,7 @@ public abstract class AbstractMcpAsyncServerTests {
|
||||||
""";
|
""";
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@Deprecated
|
||||||
void testAddTool() {
|
void testAddTool() {
|
||||||
Tool newTool = new McpSchema.Tool("new-tool", "New test tool", emptyJsonSchema);
|
Tool newTool = new McpSchema.Tool("new-tool", "New test tool", emptyJsonSchema);
|
||||||
var mcpAsyncServer = McpServer.async(createMcpTransportProvider())
|
var mcpAsyncServer = McpServer.async(createMcpTransportProvider())
|
||||||
|
|
@ -116,6 +118,23 @@ public abstract class AbstractMcpAsyncServerTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@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() {
|
void testAddDuplicateTool() {
|
||||||
Tool duplicateTool = new McpSchema.Tool(TEST_TOOL_NAME, "Duplicate tool", emptyJsonSchema);
|
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();
|
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
|
@Test
|
||||||
void testRemoveTool() {
|
void testRemoveTool() {
|
||||||
Tool too = new McpSchema.Tool(TEST_TOOL_NAME, "Duplicate tool", emptyJsonSchema);
|
Tool too = new McpSchema.Tool(TEST_TOOL_NAME, "Duplicate tool", emptyJsonSchema);
|
||||||
|
|
@ -143,7 +239,7 @@ public abstract class AbstractMcpAsyncServerTests {
|
||||||
var mcpAsyncServer = McpServer.async(createMcpTransportProvider())
|
var mcpAsyncServer = McpServer.async(createMcpTransportProvider())
|
||||||
.serverInfo("test-server", "1.0.0")
|
.serverInfo("test-server", "1.0.0")
|
||||||
.capabilities(ServerCapabilities.builder().tools(true).build())
|
.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();
|
.build();
|
||||||
|
|
||||||
StepVerifier.create(mcpAsyncServer.removeTool(TEST_TOOL_NAME)).verifyComplete();
|
StepVerifier.create(mcpAsyncServer.removeTool(TEST_TOOL_NAME)).verifyComplete();
|
||||||
|
|
@ -172,7 +268,7 @@ public abstract class AbstractMcpAsyncServerTests {
|
||||||
var mcpAsyncServer = McpServer.async(createMcpTransportProvider())
|
var mcpAsyncServer = McpServer.async(createMcpTransportProvider())
|
||||||
.serverInfo("test-server", "1.0.0")
|
.serverInfo("test-server", "1.0.0")
|
||||||
.capabilities(ServerCapabilities.builder().tools(true).build())
|
.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();
|
.build();
|
||||||
|
|
||||||
StepVerifier.create(mcpAsyncServer.notifyToolsListChanged()).verifyComplete();
|
StepVerifier.create(mcpAsyncServer.notifyToolsListChanged()).verifyComplete();
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,16 @@
|
||||||
|
|
||||||
package io.modelcontextprotocol.server;
|
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 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.McpError;
|
||||||
import io.modelcontextprotocol.spec.McpSchema;
|
import io.modelcontextprotocol.spec.McpSchema;
|
||||||
import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
|
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.ServerCapabilities;
|
||||||
import io.modelcontextprotocol.spec.McpSchema.Tool;
|
import io.modelcontextprotocol.spec.McpSchema.Tool;
|
||||||
import io.modelcontextprotocol.spec.McpServerTransportProvider;
|
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
|
* 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
|
* @author Christian Tzolov
|
||||||
*/
|
*/
|
||||||
|
// KEEP IN SYNC with the class in mcp-test module
|
||||||
public abstract class AbstractMcpSyncServerTests {
|
public abstract class AbstractMcpSyncServerTests {
|
||||||
|
|
||||||
private static final String TEST_TOOL_NAME = "test-tool";
|
private static final String TEST_TOOL_NAME = "test-tool";
|
||||||
|
|
@ -108,6 +110,7 @@ public abstract class AbstractMcpSyncServerTests {
|
||||||
""";
|
""";
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@Deprecated
|
||||||
void testAddTool() {
|
void testAddTool() {
|
||||||
var mcpSyncServer = McpServer.sync(createMcpTransportProvider())
|
var mcpSyncServer = McpServer.sync(createMcpTransportProvider())
|
||||||
.serverInfo("test-server", "1.0.0")
|
.serverInfo("test-server", "1.0.0")
|
||||||
|
|
@ -123,6 +126,23 @@ public abstract class AbstractMcpSyncServerTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@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() {
|
void testAddDuplicateTool() {
|
||||||
Tool duplicateTool = new McpSchema.Tool(TEST_TOOL_NAME, "Duplicate tool", emptyJsonSchema);
|
Tool duplicateTool = new McpSchema.Tool(TEST_TOOL_NAME, "Duplicate tool", emptyJsonSchema);
|
||||||
|
|
||||||
|
|
@ -140,6 +160,81 @@ public abstract class AbstractMcpSyncServerTests {
|
||||||
assertThatCode(() -> mcpSyncServer.closeGracefully()).doesNotThrowAnyException();
|
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
|
@Test
|
||||||
void testRemoveTool() {
|
void testRemoveTool() {
|
||||||
Tool tool = new McpSchema.Tool(TEST_TOOL_NAME, "Test tool", emptyJsonSchema);
|
Tool tool = new McpSchema.Tool(TEST_TOOL_NAME, "Test tool", emptyJsonSchema);
|
||||||
|
|
@ -147,7 +242,7 @@ public abstract class AbstractMcpSyncServerTests {
|
||||||
var mcpSyncServer = McpServer.sync(createMcpTransportProvider())
|
var mcpSyncServer = McpServer.sync(createMcpTransportProvider())
|
||||||
.serverInfo("test-server", "1.0.0")
|
.serverInfo("test-server", "1.0.0")
|
||||||
.capabilities(ServerCapabilities.builder().tools(true).build())
|
.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();
|
.build();
|
||||||
|
|
||||||
assertThatCode(() -> mcpSyncServer.removeTool(TEST_TOOL_NAME)).doesNotThrowAnyException();
|
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
|
// @Disabled
|
||||||
void testCreateMessageWithoutSamplingCapabilities() {
|
void testCreateMessageWithoutSamplingCapabilities() {
|
||||||
|
|
||||||
McpServerFeatures.AsyncToolSpecification tool = new McpServerFeatures.AsyncToolSpecification(
|
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
|
||||||
new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema), (exchange, request) -> {
|
.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();
|
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")),
|
CallToolResult callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")),
|
||||||
null);
|
null);
|
||||||
|
|
||||||
McpServerFeatures.AsyncToolSpecification tool = new McpServerFeatures.AsyncToolSpecification(
|
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
|
||||||
new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema), (exchange, request) -> {
|
.tool(new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema))
|
||||||
|
.callHandler((exchange, request) -> {
|
||||||
|
|
||||||
var createMessageRequest = McpSchema.CreateMessageRequest.builder()
|
var createMessageRequest = McpSchema.CreateMessageRequest.builder()
|
||||||
.messages(List.of(new McpSchema.SamplingMessage(McpSchema.Role.USER,
|
.messages(List.of(new McpSchema.SamplingMessage(McpSchema.Role.USER,
|
||||||
new McpSchema.TextContent("Test message"))))
|
new McpSchema.TextContent("Test message"))))
|
||||||
.modelPreferences(ModelPreferences.builder()
|
.modelPreferences(ModelPreferences.builder()
|
||||||
.hints(List.of())
|
.hints(List.of())
|
||||||
.costPriority(1.0)
|
.costPriority(1.0)
|
||||||
.speedPriority(1.0)
|
.speedPriority(1.0)
|
||||||
.intelligencePriority(1.0)
|
.intelligencePriority(1.0)
|
||||||
.build())
|
.build())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
StepVerifier.create(exchange.createMessage(createMessageRequest)).consumeNextWith(result -> {
|
StepVerifier.create(exchange.createMessage(createMessageRequest)).consumeNextWith(result -> {
|
||||||
assertThat(result).isNotNull();
|
assertThat(result).isNotNull();
|
||||||
assertThat(result.role()).isEqualTo(Role.USER);
|
assertThat(result.role()).isEqualTo(Role.USER);
|
||||||
assertThat(result.content()).isInstanceOf(McpSchema.TextContent.class);
|
assertThat(result.content()).isInstanceOf(McpSchema.TextContent.class);
|
||||||
assertThat(((McpSchema.TextContent) result.content()).text()).isEqualTo("Test message");
|
assertThat(((McpSchema.TextContent) result.content()).text()).isEqualTo("Test message");
|
||||||
assertThat(result.model()).isEqualTo("MockModelName");
|
assertThat(result.model()).isEqualTo("MockModelName");
|
||||||
assertThat(result.stopReason()).isEqualTo(CreateMessageResult.StopReason.STOP_SEQUENCE);
|
assertThat(result.stopReason()).isEqualTo(CreateMessageResult.StopReason.STOP_SEQUENCE);
|
||||||
}).verifyComplete();
|
}).verifyComplete();
|
||||||
|
|
||||||
return Mono.just(callResponse);
|
return Mono.just(callResponse);
|
||||||
});
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
var mcpServer = McpServer.async(mcpServerTransportProvider)
|
var mcpServer = McpServer.async(mcpServerTransportProvider)
|
||||||
.serverInfo("test-server", "1.0.0")
|
.serverInfo("test-server", "1.0.0")
|
||||||
|
|
@ -226,31 +230,33 @@ class HttpServletSseServerTransportProviderIntegrationTests {
|
||||||
CallToolResult callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")),
|
CallToolResult callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")),
|
||||||
null);
|
null);
|
||||||
|
|
||||||
McpServerFeatures.AsyncToolSpecification tool = new McpServerFeatures.AsyncToolSpecification(
|
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
|
||||||
new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema), (exchange, request) -> {
|
.tool(new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema))
|
||||||
|
.callHandler((exchange, request) -> {
|
||||||
|
|
||||||
var craeteMessageRequest = McpSchema.CreateMessageRequest.builder()
|
var craeteMessageRequest = McpSchema.CreateMessageRequest.builder()
|
||||||
.messages(List.of(new McpSchema.SamplingMessage(McpSchema.Role.USER,
|
.messages(List.of(new McpSchema.SamplingMessage(McpSchema.Role.USER,
|
||||||
new McpSchema.TextContent("Test message"))))
|
new McpSchema.TextContent("Test message"))))
|
||||||
.modelPreferences(ModelPreferences.builder()
|
.modelPreferences(ModelPreferences.builder()
|
||||||
.hints(List.of())
|
.hints(List.of())
|
||||||
.costPriority(1.0)
|
.costPriority(1.0)
|
||||||
.speedPriority(1.0)
|
.speedPriority(1.0)
|
||||||
.intelligencePriority(1.0)
|
.intelligencePriority(1.0)
|
||||||
.build())
|
.build())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
StepVerifier.create(exchange.createMessage(craeteMessageRequest)).consumeNextWith(result -> {
|
StepVerifier.create(exchange.createMessage(craeteMessageRequest)).consumeNextWith(result -> {
|
||||||
assertThat(result).isNotNull();
|
assertThat(result).isNotNull();
|
||||||
assertThat(result.role()).isEqualTo(Role.USER);
|
assertThat(result.role()).isEqualTo(Role.USER);
|
||||||
assertThat(result.content()).isInstanceOf(McpSchema.TextContent.class);
|
assertThat(result.content()).isInstanceOf(McpSchema.TextContent.class);
|
||||||
assertThat(((McpSchema.TextContent) result.content()).text()).isEqualTo("Test message");
|
assertThat(((McpSchema.TextContent) result.content()).text()).isEqualTo("Test message");
|
||||||
assertThat(result.model()).isEqualTo("MockModelName");
|
assertThat(result.model()).isEqualTo("MockModelName");
|
||||||
assertThat(result.stopReason()).isEqualTo(CreateMessageResult.StopReason.STOP_SEQUENCE);
|
assertThat(result.stopReason()).isEqualTo(CreateMessageResult.StopReason.STOP_SEQUENCE);
|
||||||
}).verifyComplete();
|
}).verifyComplete();
|
||||||
|
|
||||||
return Mono.just(callResponse);
|
return Mono.just(callResponse);
|
||||||
});
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
var mcpServer = McpServer.async(mcpServerTransportProvider)
|
var mcpServer = McpServer.async(mcpServerTransportProvider)
|
||||||
.serverInfo("test-server", "1.0.0")
|
.serverInfo("test-server", "1.0.0")
|
||||||
|
|
@ -298,31 +304,33 @@ class HttpServletSseServerTransportProviderIntegrationTests {
|
||||||
CallToolResult callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")),
|
CallToolResult callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")),
|
||||||
null);
|
null);
|
||||||
|
|
||||||
McpServerFeatures.AsyncToolSpecification tool = new McpServerFeatures.AsyncToolSpecification(
|
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
|
||||||
new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema), (exchange, request) -> {
|
.tool(new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema))
|
||||||
|
.callHandler((exchange, request) -> {
|
||||||
|
|
||||||
var craeteMessageRequest = McpSchema.CreateMessageRequest.builder()
|
var craeteMessageRequest = McpSchema.CreateMessageRequest.builder()
|
||||||
.messages(List.of(new McpSchema.SamplingMessage(McpSchema.Role.USER,
|
.messages(List.of(new McpSchema.SamplingMessage(McpSchema.Role.USER,
|
||||||
new McpSchema.TextContent("Test message"))))
|
new McpSchema.TextContent("Test message"))))
|
||||||
.modelPreferences(ModelPreferences.builder()
|
.modelPreferences(ModelPreferences.builder()
|
||||||
.hints(List.of())
|
.hints(List.of())
|
||||||
.costPriority(1.0)
|
.costPriority(1.0)
|
||||||
.speedPriority(1.0)
|
.speedPriority(1.0)
|
||||||
.intelligencePriority(1.0)
|
.intelligencePriority(1.0)
|
||||||
.build())
|
.build())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
StepVerifier.create(exchange.createMessage(craeteMessageRequest)).consumeNextWith(result -> {
|
StepVerifier.create(exchange.createMessage(craeteMessageRequest)).consumeNextWith(result -> {
|
||||||
assertThat(result).isNotNull();
|
assertThat(result).isNotNull();
|
||||||
assertThat(result.role()).isEqualTo(Role.USER);
|
assertThat(result.role()).isEqualTo(Role.USER);
|
||||||
assertThat(result.content()).isInstanceOf(McpSchema.TextContent.class);
|
assertThat(result.content()).isInstanceOf(McpSchema.TextContent.class);
|
||||||
assertThat(((McpSchema.TextContent) result.content()).text()).isEqualTo("Test message");
|
assertThat(((McpSchema.TextContent) result.content()).text()).isEqualTo("Test message");
|
||||||
assertThat(result.model()).isEqualTo("MockModelName");
|
assertThat(result.model()).isEqualTo("MockModelName");
|
||||||
assertThat(result.stopReason()).isEqualTo(CreateMessageResult.StopReason.STOP_SEQUENCE);
|
assertThat(result.stopReason()).isEqualTo(CreateMessageResult.StopReason.STOP_SEQUENCE);
|
||||||
}).verifyComplete();
|
}).verifyComplete();
|
||||||
|
|
||||||
return Mono.just(callResponse);
|
return Mono.just(callResponse);
|
||||||
});
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
var mcpServer = McpServer.async(mcpServerTransportProvider)
|
var mcpServer = McpServer.async(mcpServerTransportProvider)
|
||||||
.serverInfo("test-server", "1.0.0")
|
.serverInfo("test-server", "1.0.0")
|
||||||
|
|
@ -348,13 +356,15 @@ class HttpServletSseServerTransportProviderIntegrationTests {
|
||||||
// @Disabled
|
// @Disabled
|
||||||
void testCreateElicitationWithoutElicitationCapabilities() {
|
void testCreateElicitationWithoutElicitationCapabilities() {
|
||||||
|
|
||||||
McpServerFeatures.AsyncToolSpecification tool = new McpServerFeatures.AsyncToolSpecification(
|
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
|
||||||
new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema), (exchange, request) -> {
|
.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();
|
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")),
|
CallToolResult callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")),
|
||||||
null);
|
null);
|
||||||
|
|
||||||
McpServerFeatures.AsyncToolSpecification tool = new McpServerFeatures.AsyncToolSpecification(
|
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
|
||||||
new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema), (exchange, request) -> {
|
.tool(new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema))
|
||||||
|
.callHandler((exchange, request) -> {
|
||||||
|
|
||||||
var elicitationRequest = ElicitRequest.builder()
|
var elicitationRequest = ElicitRequest.builder()
|
||||||
.message("Test message")
|
.message("Test message")
|
||||||
.requestedSchema(
|
.requestedSchema(
|
||||||
Map.of("type", "object", "properties", Map.of("message", Map.of("type", "string"))))
|
Map.of("type", "object", "properties", Map.of("message", Map.of("type", "string"))))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
StepVerifier.create(exchange.createElicitation(elicitationRequest)).consumeNextWith(result -> {
|
StepVerifier.create(exchange.createElicitation(elicitationRequest)).consumeNextWith(result -> {
|
||||||
assertThat(result).isNotNull();
|
assertThat(result).isNotNull();
|
||||||
assertThat(result.action()).isEqualTo(ElicitResult.Action.ACCEPT);
|
assertThat(result.action()).isEqualTo(ElicitResult.Action.ACCEPT);
|
||||||
assertThat(result.content().get("message")).isEqualTo("Test message");
|
assertThat(result.content().get("message")).isEqualTo("Test message");
|
||||||
}).verifyComplete();
|
}).verifyComplete();
|
||||||
|
|
||||||
return Mono.just(callResponse);
|
return Mono.just(callResponse);
|
||||||
});
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
var mcpServer = McpServer.async(mcpServerTransportProvider)
|
var mcpServer = McpServer.async(mcpServerTransportProvider)
|
||||||
.serverInfo("test-server", "1.0.0")
|
.serverInfo("test-server", "1.0.0")
|
||||||
|
|
@ -454,23 +466,25 @@ class HttpServletSseServerTransportProviderIntegrationTests {
|
||||||
CallToolResult callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")),
|
CallToolResult callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")),
|
||||||
null);
|
null);
|
||||||
|
|
||||||
McpServerFeatures.AsyncToolSpecification tool = new McpServerFeatures.AsyncToolSpecification(
|
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
|
||||||
new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema), (exchange, request) -> {
|
.tool(new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema))
|
||||||
|
.callHandler((exchange, request) -> {
|
||||||
|
|
||||||
var elicitationRequest = ElicitRequest.builder()
|
var elicitationRequest = ElicitRequest.builder()
|
||||||
.message("Test message")
|
.message("Test message")
|
||||||
.requestedSchema(
|
.requestedSchema(
|
||||||
Map.of("type", "object", "properties", Map.of("message", Map.of("type", "string"))))
|
Map.of("type", "object", "properties", Map.of("message", Map.of("type", "string"))))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
StepVerifier.create(exchange.createElicitation(elicitationRequest)).consumeNextWith(result -> {
|
StepVerifier.create(exchange.createElicitation(elicitationRequest)).consumeNextWith(result -> {
|
||||||
assertThat(result).isNotNull();
|
assertThat(result).isNotNull();
|
||||||
assertThat(result.action()).isEqualTo(ElicitResult.Action.ACCEPT);
|
assertThat(result.action()).isEqualTo(ElicitResult.Action.ACCEPT);
|
||||||
assertThat(result.content().get("message")).isEqualTo("Test message");
|
assertThat(result.content().get("message")).isEqualTo("Test message");
|
||||||
}).verifyComplete();
|
}).verifyComplete();
|
||||||
|
|
||||||
return Mono.just(callResponse);
|
return Mono.just(callResponse);
|
||||||
});
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
var mcpServer = McpServer.async(mcpServerTransportProvider)
|
var mcpServer = McpServer.async(mcpServerTransportProvider)
|
||||||
.serverInfo("test-server", "1.0.0")
|
.serverInfo("test-server", "1.0.0")
|
||||||
|
|
@ -517,23 +531,25 @@ class HttpServletSseServerTransportProviderIntegrationTests {
|
||||||
CallToolResult callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")),
|
CallToolResult callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")),
|
||||||
null);
|
null);
|
||||||
|
|
||||||
McpServerFeatures.AsyncToolSpecification tool = new McpServerFeatures.AsyncToolSpecification(
|
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
|
||||||
new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema), (exchange, request) -> {
|
.tool(new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema))
|
||||||
|
.callHandler((exchange, request) -> {
|
||||||
|
|
||||||
var elicitationRequest = ElicitRequest.builder()
|
var elicitationRequest = ElicitRequest.builder()
|
||||||
.message("Test message")
|
.message("Test message")
|
||||||
.requestedSchema(
|
.requestedSchema(
|
||||||
Map.of("type", "object", "properties", Map.of("message", Map.of("type", "string"))))
|
Map.of("type", "object", "properties", Map.of("message", Map.of("type", "string"))))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
StepVerifier.create(exchange.createElicitation(elicitationRequest)).consumeNextWith(result -> {
|
StepVerifier.create(exchange.createElicitation(elicitationRequest)).consumeNextWith(result -> {
|
||||||
assertThat(result).isNotNull();
|
assertThat(result).isNotNull();
|
||||||
assertThat(result.action()).isEqualTo(ElicitResult.Action.ACCEPT);
|
assertThat(result.action()).isEqualTo(ElicitResult.Action.ACCEPT);
|
||||||
assertThat(result.content().get("message")).isEqualTo("Test message");
|
assertThat(result.content().get("message")).isEqualTo("Test message");
|
||||||
}).verifyComplete();
|
}).verifyComplete();
|
||||||
|
|
||||||
return Mono.just(callResponse);
|
return Mono.just(callResponse);
|
||||||
});
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
var mcpServer = McpServer.async(mcpServerTransportProvider)
|
var mcpServer = McpServer.async(mcpServerTransportProvider)
|
||||||
.serverInfo("test-server", "1.0.0")
|
.serverInfo("test-server", "1.0.0")
|
||||||
|
|
@ -602,13 +618,15 @@ class HttpServletSseServerTransportProviderIntegrationTests {
|
||||||
@Test
|
@Test
|
||||||
void testRootsWithoutCapability() {
|
void testRootsWithoutCapability() {
|
||||||
|
|
||||||
McpServerFeatures.SyncToolSpecification tool = new McpServerFeatures.SyncToolSpecification(
|
McpServerFeatures.SyncToolSpecification tool = McpServerFeatures.SyncToolSpecification.builder()
|
||||||
new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema), (exchange, request) -> {
|
.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) -> {
|
var mcpServer = McpServer.sync(mcpServerTransportProvider).rootsChangeHandler((exchange, rootsUpdate) -> {
|
||||||
}).tools(tool).build();
|
}).tools(tool).build();
|
||||||
|
|
@ -726,20 +744,22 @@ class HttpServletSseServerTransportProviderIntegrationTests {
|
||||||
void testToolCallSuccess() {
|
void testToolCallSuccess() {
|
||||||
|
|
||||||
var callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")), null);
|
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) -> {
|
McpServerFeatures.SyncToolSpecification tool1 = McpServerFeatures.SyncToolSpecification.builder()
|
||||||
assertThat(McpTestServletFilter.getThreadLocalValue())
|
.tool(new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema))
|
||||||
.as("blocking code exectuion should be offloaded")
|
.callHandler((exchange, request) -> {
|
||||||
.isNull();
|
assertThat(McpTestServletFilter.getThreadLocalValue()).as("blocking code exectuion should be offloaded")
|
||||||
// perform a blocking call to a remote service
|
.isNull();
|
||||||
String response = RestClient.create()
|
// perform a blocking call to a remote service
|
||||||
.get()
|
String response = RestClient.create()
|
||||||
.uri("https://raw.githubusercontent.com/modelcontextprotocol/java-sdk/refs/heads/main/README.md")
|
.get()
|
||||||
.retrieve()
|
.uri("https://raw.githubusercontent.com/modelcontextprotocol/java-sdk/refs/heads/main/README.md")
|
||||||
.body(String.class);
|
.retrieve()
|
||||||
assertThat(response).isNotBlank();
|
.body(String.class);
|
||||||
return callResponse;
|
assertThat(response).isNotBlank();
|
||||||
});
|
return callResponse;
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
var mcpServer = McpServer.sync(mcpServerTransportProvider)
|
var mcpServer = McpServer.sync(mcpServerTransportProvider)
|
||||||
.capabilities(ServerCapabilities.builder().tools(true).build())
|
.capabilities(ServerCapabilities.builder().tools(true).build())
|
||||||
|
|
@ -796,17 +816,19 @@ class HttpServletSseServerTransportProviderIntegrationTests {
|
||||||
void testToolListChangeHandlingSuccess() {
|
void testToolListChangeHandlingSuccess() {
|
||||||
|
|
||||||
var callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")), null);
|
var callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")), null);
|
||||||
McpServerFeatures.SyncToolSpecification tool1 = new McpServerFeatures.SyncToolSpecification(
|
McpServerFeatures.SyncToolSpecification tool1 = McpServerFeatures.SyncToolSpecification.builder()
|
||||||
new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema), (exchange, request) -> {
|
.tool(new McpSchema.Tool("tool1", "tool1 description", emptyJsonSchema))
|
||||||
// perform a blocking call to a remote service
|
.callHandler((exchange, request) -> {
|
||||||
String response = RestClient.create()
|
// perform a blocking call to a remote service
|
||||||
.get()
|
String response = RestClient.create()
|
||||||
.uri("https://raw.githubusercontent.com/modelcontextprotocol/java-sdk/refs/heads/main/README.md")
|
.get()
|
||||||
.retrieve()
|
.uri("https://raw.githubusercontent.com/modelcontextprotocol/java-sdk/refs/heads/main/README.md")
|
||||||
.body(String.class);
|
.retrieve()
|
||||||
assertThat(response).isNotBlank();
|
.body(String.class);
|
||||||
return callResponse;
|
assertThat(response).isNotBlank();
|
||||||
});
|
return callResponse;
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
AtomicReference<List<Tool>> rootsRef = new AtomicReference<>();
|
AtomicReference<List<Tool>> rootsRef = new AtomicReference<>();
|
||||||
|
|
||||||
|
|
@ -847,9 +869,10 @@ class HttpServletSseServerTransportProviderIntegrationTests {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add a new tool
|
// Add a new tool
|
||||||
McpServerFeatures.SyncToolSpecification tool2 = new McpServerFeatures.SyncToolSpecification(
|
McpServerFeatures.SyncToolSpecification tool2 = McpServerFeatures.SyncToolSpecification.builder()
|
||||||
new McpSchema.Tool("tool2", "tool2 description", emptyJsonSchema),
|
.tool(new McpSchema.Tool("tool2", "tool2 description", emptyJsonSchema))
|
||||||
(exchange, request) -> callResponse);
|
.callHandler((exchange, request) -> callResponse)
|
||||||
|
.build();
|
||||||
|
|
||||||
mcpServer.addTool(tool2);
|
mcpServer.addTool(tool2);
|
||||||
|
|
||||||
|
|
@ -883,59 +906,60 @@ class HttpServletSseServerTransportProviderIntegrationTests {
|
||||||
List<McpSchema.LoggingMessageNotification> receivedNotifications = new ArrayList<>();
|
List<McpSchema.LoggingMessageNotification> receivedNotifications = new ArrayList<>();
|
||||||
|
|
||||||
// Create server with a tool that sends logging notifications
|
// Create server with a tool that sends logging notifications
|
||||||
McpServerFeatures.AsyncToolSpecification tool = new McpServerFeatures.AsyncToolSpecification(
|
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
|
||||||
new McpSchema.Tool("logging-test", "Test logging notifications", emptyJsonSchema),
|
.tool(new McpSchema.Tool("logging-test", "Test logging notifications", emptyJsonSchema))
|
||||||
(exchange, request) -> {
|
.callHandler((exchange, request) -> {
|
||||||
|
|
||||||
// Create and send notifications with different levels
|
// Create and send notifications with different levels
|
||||||
|
|
||||||
// This should be filtered out (DEBUG < NOTICE)
|
// This should be filtered out (DEBUG < NOTICE)
|
||||||
exchange
|
exchange
|
||||||
.loggingNotification(McpSchema.LoggingMessageNotification.builder()
|
.loggingNotification(McpSchema.LoggingMessageNotification.builder()
|
||||||
.level(McpSchema.LoggingLevel.DEBUG)
|
.level(McpSchema.LoggingLevel.DEBUG)
|
||||||
.logger("test-logger")
|
.logger("test-logger")
|
||||||
.data("Debug message")
|
.data("Debug message")
|
||||||
.build())
|
.build())
|
||||||
.block();
|
.block();
|
||||||
|
|
||||||
// This should be sent (NOTICE >= NOTICE)
|
// This should be sent (NOTICE >= NOTICE)
|
||||||
exchange
|
exchange
|
||||||
.loggingNotification(McpSchema.LoggingMessageNotification.builder()
|
.loggingNotification(McpSchema.LoggingMessageNotification.builder()
|
||||||
.level(McpSchema.LoggingLevel.NOTICE)
|
.level(McpSchema.LoggingLevel.NOTICE)
|
||||||
.logger("test-logger")
|
.logger("test-logger")
|
||||||
.data("Notice message")
|
.data("Notice message")
|
||||||
.build())
|
.build())
|
||||||
.block();
|
.block();
|
||||||
|
|
||||||
// This should be sent (ERROR > NOTICE)
|
// This should be sent (ERROR > NOTICE)
|
||||||
exchange
|
exchange
|
||||||
.loggingNotification(McpSchema.LoggingMessageNotification.builder()
|
.loggingNotification(McpSchema.LoggingMessageNotification.builder()
|
||||||
.level(McpSchema.LoggingLevel.ERROR)
|
.level(McpSchema.LoggingLevel.ERROR)
|
||||||
.logger("test-logger")
|
.logger("test-logger")
|
||||||
.data("Error message")
|
.data("Error message")
|
||||||
.build())
|
.build())
|
||||||
.block();
|
.block();
|
||||||
|
|
||||||
// This should be filtered out (INFO < NOTICE)
|
// This should be filtered out (INFO < NOTICE)
|
||||||
exchange
|
exchange
|
||||||
.loggingNotification(McpSchema.LoggingMessageNotification.builder()
|
.loggingNotification(McpSchema.LoggingMessageNotification.builder()
|
||||||
.level(McpSchema.LoggingLevel.INFO)
|
.level(McpSchema.LoggingLevel.INFO)
|
||||||
.logger("test-logger")
|
.logger("test-logger")
|
||||||
.data("Another info message")
|
.data("Another info message")
|
||||||
.build())
|
.build())
|
||||||
.block();
|
.block();
|
||||||
|
|
||||||
// This should be sent (ERROR >= NOTICE)
|
// This should be sent (ERROR >= NOTICE)
|
||||||
exchange
|
exchange
|
||||||
.loggingNotification(McpSchema.LoggingMessageNotification.builder()
|
.loggingNotification(McpSchema.LoggingMessageNotification.builder()
|
||||||
.level(McpSchema.LoggingLevel.ERROR)
|
.level(McpSchema.LoggingLevel.ERROR)
|
||||||
.logger("test-logger")
|
.logger("test-logger")
|
||||||
.data("Another error message")
|
.data("Another error message")
|
||||||
.build())
|
.build())
|
||||||
.block();
|
.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)
|
var mcpServer = McpServer.async(mcpServerTransportProvider)
|
||||||
.serverInfo("test-server", "1.0.0")
|
.serverInfo("test-server", "1.0.0")
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue