From d360a0079272b51129a07be906237b4d7244f59f Mon Sep 17 00:00:00 2001 From: Christian Tzolov Date: Fri, 24 Jan 2025 23:25:13 +0100 Subject: [PATCH] Remove redundand code Resolves #4 Signed-off-by: Christian Tzolov --- .../transport/_SseServerTransportTests.java_ | 283 --------- .../client/McpAsyncClient.java | 166 +----- .../client/McpClient.java | 256 -------- .../server/McpAsyncServer.java | 241 +------- .../server/McpServer.java | 561 +----------------- .../server/McpSyncServer.java | 33 -- 6 files changed, 7 insertions(+), 1533 deletions(-) delete mode 100644 mcp-transport/mcp-webflux-sse-transport/src/test/java/org/modelcontextprotocol/server/transport/_SseServerTransportTests.java_ diff --git a/mcp-transport/mcp-webflux-sse-transport/src/test/java/org/modelcontextprotocol/server/transport/_SseServerTransportTests.java_ b/mcp-transport/mcp-webflux-sse-transport/src/test/java/org/modelcontextprotocol/server/transport/_SseServerTransportTests.java_ deleted file mode 100644 index 80f31a227..000000000 --- a/mcp-transport/mcp-webflux-sse-transport/src/test/java/org/modelcontextprotocol/server/transport/_SseServerTransportTests.java_ +++ /dev/null @@ -1,283 +0,0 @@ -package org.springframework.ai.mcp.server.transport; - -import java.time.Duration; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.Timeout; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import reactor.core.Disposable; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; - -import org.springframework.ai.mcp.spec.McpSchema; -import org.springframework.ai.mcp.spec.McpSchema.JSONRPCRequest; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.codec.ServerSentEvent; -import org.springframework.test.web.reactive.server.WebTestClient; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -/** - * Tests for the {@link SseServerTransport} class. - * - * @author Christian Tzolov - */ -@Timeout(15) -class SseServerTransportTests { - - private static final Logger logger = LoggerFactory.getLogger(SseServerTransportTests.class); - - private ObjectMapper objectMapper; - - private String messageEndpoint; - - private SseServerTransport transport; - - private WebTestClient webTestClient; - - @BeforeEach - void setUp() { - objectMapper = new ObjectMapper(); - messageEndpoint = "/message"; - transport = new SseServerTransport(objectMapper, messageEndpoint); - webTestClient = WebTestClient.bindToRouterFunction(transport.getRouterFunction()).build(); - } - - @Test - void constructorValidation() { - assertThatThrownBy(() -> new SseServerTransport(null, "/message")).isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("ObjectMapper must not be null"); - - assertThatThrownBy(() -> new SseServerTransport(new ObjectMapper(), null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("Message endpoint must not be null"); - } - - @Test - void testSseConnectionEstablishment() { - List> events = new ArrayList<>(); - - webTestClient.get() - .uri("/sse") - .accept(MediaType.TEXT_EVENT_STREAM) - .exchange() - .expectStatus() - .isOk() - .expectHeader() - .contentTypeCompatibleWith(MediaType.TEXT_EVENT_STREAM) - .returnResult(String.class) - .getResponseBody() - .map(data -> ServerSentEvent.builder().data(data).build()) - .take(1) // Take only the initial endpoint event - .subscribe(events::add); - - // Wait a bit for the event to be received - StepVerifier.create(Mono.delay(Duration.ofMillis(500))).expectNextCount(1).verifyComplete(); - - assertThat(events).hasSize(1); - assertThat(events.get(0).data()).isEqualTo(messageEndpoint); - } - - @Test - void testMessageHandling() { - AtomicReference receivedMessage = new AtomicReference<>(); - - // Set up message handler - transport.connect(message -> { - message.doOnNext(receivedMessage::set).subscribe(); - return Mono.empty(); - }).block(); - - // Create a test message - JSONRPCRequest testMessage = new JSONRPCRequest(McpSchema.JSONRPC_VERSION, "test-method", "test-id", - Map.of("key", "value")); - - // Send message to endpoint - webTestClient.post() - .uri(messageEndpoint) - .contentType(MediaType.APPLICATION_JSON) - .bodyValue(testMessage) - .exchange() - .expectStatus() - .isOk(); - - // Verify the message was received and processed - assertThat(receivedMessage.get()).isNotNull(); - McpSchema.JSONRPCRequest receivedRequest = (McpSchema.JSONRPCRequest) receivedMessage.get(); - assertThat(receivedRequest.id()).isEqualTo(testMessage.id()); - assertThat(receivedRequest.method()).isEqualTo(testMessage.method()); - } - - @Test - @Disabled("Flaky test") - void testBroadcastMessage() { - // Create test clients - int clientCount = 3; - CountDownLatch connectLatch = new CountDownLatch(clientCount); - CountDownLatch messageLatch = new CountDownLatch(clientCount); - - List>> allReceivedEvents = new ArrayList<>(); - List subscriptions = new ArrayList<>(); - - // Connect clients - for (int i = 0; i < clientCount; i++) { - List> clientEvents = new ArrayList<>(); - allReceivedEvents.add(clientEvents); - - Flux> eventStream = webTestClient.get() - .uri("/sse") - .accept(MediaType.TEXT_EVENT_STREAM) - .exchange() - .expectStatus() - .isOk() - .returnResult(String.class) - .getResponseBody() - .map(data -> ServerSentEvent.builder().data(data).build()); - - Disposable subscription = eventStream.doOnNext(event -> { - clientEvents.add(event); - if (event.event() != null && event.event().equals(SseServerTransport.ENDPOINT_EVENT_TYPE)) { - connectLatch.countDown(); - } - else if (event.event() != null && event.event().equals(SseServerTransport.MESSAGE_EVENT_TYPE)) { - messageLatch.countDown(); - } - }).subscribe(); - - subscriptions.add(subscription); - } - - // Wait for all clients to connect - try { - assertThat(connectLatch.await(5, TimeUnit.SECONDS)).isTrue(); - } - catch (InterruptedException e) { - throw new RuntimeException(e); - } - - // Verify initial connections - for (List> events : allReceivedEvents) { - assertThat(events).hasSize(1); - assertThat(events.get(0).data()).isEqualTo(messageEndpoint); - } - - // Give clients time to fully establish their subscriptions - logger.debug("Waiting for subscriptions to stabilize..."); - try { - Thread.sleep(1000); - } - catch (InterruptedException e) { - throw new RuntimeException(e); - } - logger.debug("Sending broadcast message to {} clients", clientCount); - - // Send broadcast message - JSONRPCRequest broadcastMessage = new JSONRPCRequest(McpSchema.JSONRPC_VERSION, "broadcast", "broadcast-id", - Map.of("message", "Hello all!")); - - // Send the message - transport.sendMessage(broadcastMessage).block(Duration.ofSeconds(5)); - - // Wait for all clients to receive the broadcast - try { - assertThat(messageLatch.await(5, TimeUnit.SECONDS)).isTrue(); - } - catch (InterruptedException e) { - throw new RuntimeException(e); - } - - // Verify each client received both messages - for (List> events : allReceivedEvents) { - assertThat(events).hasSize(2); - assertThat(events.get(0).data()).isEqualTo(messageEndpoint); - assertThat(events.get(1).data()).contains("broadcast-id"); - } - - // Cleanup - subscriptions.forEach(Disposable::dispose); - } - - @Test - void testGracefulShutdown() { - // Connect a client - Flux> eventStream = webTestClient.get() - .uri("/sse") - .accept(MediaType.TEXT_EVENT_STREAM) - .exchange() - .expectStatus() - .isOk() - .returnResult(String.class) - .getResponseBody() - .map(data -> ServerSentEvent.builder().data(data).build()); - - List> receivedEvents = new ArrayList<>(); - eventStream.subscribe(receivedEvents::add); - - // Wait for connection - StepVerifier.create(Mono.delay(Duration.ofMillis(500))).expectNextCount(1).verifyComplete(); - - // Initiate shutdown - transport.closeGracefully().block(Duration.ofSeconds(5)); - - // Verify server rejects new connections with timeout - webTestClient.get() - .uri("/sse") - .accept(MediaType.TEXT_EVENT_STREAM) - .exchange() - .expectStatus() - .isEqualTo(HttpStatus.SERVICE_UNAVAILABLE) - .expectBody(String.class) - .isEqualTo("Server is shutting down"); - - // Verify server rejects new messages with timeout - webTestClient.post() - .uri(messageEndpoint) - .contentType(MediaType.APPLICATION_JSON) - .bodyValue(""" - { - "jsonrpc": "2.0", - "method": "test", - "id": "1", - "params": {} - } - """) - .exchange() - .expectStatus() - .isEqualTo(HttpStatus.SERVICE_UNAVAILABLE) - .expectBody(String.class) - .isEqualTo("Server is shutting down"); - } - - @Test - void testInvalidMessageHandling() { - // Test invalid JSON - webTestClient.post() - .uri(messageEndpoint) - .contentType(MediaType.APPLICATION_JSON) - .bodyValue("invalid json") - .exchange() - .expectStatus() - .isBadRequest(); - - // Test invalid message format - webTestClient.post().uri(messageEndpoint).contentType(MediaType.APPLICATION_JSON).bodyValue(""" - { - "invalid": "message" - } - """).exchange().expectStatus().isBadRequest(); - } - -} diff --git a/mcp/src/main/java/org/modelcontextprotocol/client/McpAsyncClient.java b/mcp/src/main/java/org/modelcontextprotocol/client/McpAsyncClient.java index 4962b37de..875de37a9 100644 --- a/mcp/src/main/java/org/modelcontextprotocol/client/McpAsyncClient.java +++ b/mcp/src/main/java/org/modelcontextprotocol/client/McpAsyncClient.java @@ -15,24 +15,21 @@ import com.fasterxml.jackson.core.type.TypeReference; import org.modelcontextprotocol.spec.ClientMcpTransport; import org.modelcontextprotocol.spec.DefaultMcpSession; -import org.modelcontextprotocol.spec.McpError; -import org.modelcontextprotocol.spec.McpSchema; -import org.modelcontextprotocol.spec.McpTransport; import org.modelcontextprotocol.spec.DefaultMcpSession.NotificationHandler; import org.modelcontextprotocol.spec.DefaultMcpSession.RequestHandler; +import org.modelcontextprotocol.spec.McpError; +import org.modelcontextprotocol.spec.McpSchema; import org.modelcontextprotocol.spec.McpSchema.ClientCapabilities; import org.modelcontextprotocol.spec.McpSchema.CreateMessageRequest; import org.modelcontextprotocol.spec.McpSchema.CreateMessageResult; import org.modelcontextprotocol.spec.McpSchema.GetPromptRequest; import org.modelcontextprotocol.spec.McpSchema.GetPromptResult; -import org.modelcontextprotocol.spec.McpSchema.Implementation; import org.modelcontextprotocol.spec.McpSchema.ListPromptsResult; import org.modelcontextprotocol.spec.McpSchema.LoggingLevel; import org.modelcontextprotocol.spec.McpSchema.LoggingMessageNotification; import org.modelcontextprotocol.spec.McpSchema.PaginatedRequest; import org.modelcontextprotocol.spec.McpSchema.Root; -import org.modelcontextprotocol.spec.McpSchema.ClientCapabilities.RootCapabilities; -import org.modelcontextprotocol.spec.McpSchema.ClientCapabilities.Sampling; +import org.modelcontextprotocol.spec.McpTransport; import org.modelcontextprotocol.util.Assert; import org.modelcontextprotocol.util.Utils; import org.slf4j.Logger; @@ -221,107 +218,6 @@ public class McpAsyncClient { } - /** - * Create a new McpAsyncClient with the given transport and session request-response - * timeout. - * @param transport the transport to use. - * @param requestTimeout the session request-response timeout. - * @param clientInfo the client implementation information. - * @param clientCapabilities the client capabilities. - * @param roots the roots. - * @param toolsChangeConsumers the tools change consumers. - * @param resourcesChangeConsumers the resources change consumers. - * @param promptsChangeConsumers the prompts change consumers. - * @param loggingConsumers the logging consumers. - * @param samplingHandler the sampling handler. - * @deprecated Use {@link McpClient#async(ClientMcpTransport)} to obtain an instance. - */ - @Deprecated - public McpAsyncClient(ClientMcpTransport transport, Duration requestTimeout, Implementation clientInfo, - ClientCapabilities clientCapabilities, Map roots, - List>> toolsChangeConsumers, - List>> resourcesChangeConsumers, - List>> promptsChangeConsumers, - List> loggingConsumers, - Function samplingHandler) { - - Assert.notNull(transport, "Transport must not be null"); - Assert.notNull(requestTimeout, "Request timeout must not be null"); - Assert.notNull(clientInfo, "Client info must not be null"); - - this.protocolVersions = List.of(McpSchema.LATEST_PROTOCOL_VERSION); - - this.clientInfo = clientInfo; - - this.clientCapabilities = (clientCapabilities != null) ? clientCapabilities - : new McpSchema.ClientCapabilities(null, !Utils.isEmpty(roots) ? new RootCapabilities(false) : null, - samplingHandler != null ? new Sampling() : null); - - this.transport = transport; - - this.roots = roots != null ? new ConcurrentHashMap<>(roots) : new ConcurrentHashMap<>(); - - // Request Handlers - Map> requestHandlers = new HashMap<>(); - - // Roots List Request Handler - if (this.clientCapabilities.roots() != null) { - requestHandlers.put(McpSchema.METHOD_ROOTS_LIST, rootsListRequestHandler()); - } - - // Sampling Handler - if (this.clientCapabilities.sampling() != null) { - if (samplingHandler == null) { - throw new McpError("Sampling handler must not be null when client capabilities include sampling"); - } - this.samplingHandler = r -> Mono.fromCallable(() -> samplingHandler.apply(r)) - .subscribeOn(Schedulers.boundedElastic()); - requestHandlers.put(McpSchema.METHOD_SAMPLING_CREATE_MESSAGE, samplingCreateMessageHandler()); - } - - // Notification Handlers - Map notificationHandlers = new HashMap<>(); - - // Tools Change Notification - List>> toolsChangeConsumersFinal = new ArrayList<>(); - toolsChangeConsumersFinal.add((notification) -> logger.info("Tools changed: {}", notification)); - if (!Utils.isEmpty(toolsChangeConsumers)) { - toolsChangeConsumersFinal.addAll(toolsChangeConsumers); - } - notificationHandlers.put(McpSchema.METHOD_NOTIFICATION_TOOLS_LIST_CHANGED, - toolsChangeNotificationHandler(toolsChangeConsumersFinal)); - - // Resources Change Notification - List>> resourcesChangeConsumersFinal = new ArrayList<>(); - resourcesChangeConsumersFinal.add((notification) -> logger.info("Resources changed: {}", notification)); - if (!Utils.isEmpty(resourcesChangeConsumers)) { - resourcesChangeConsumersFinal.addAll(resourcesChangeConsumers); - } - notificationHandlers.put(McpSchema.METHOD_NOTIFICATION_RESOURCES_LIST_CHANGED, - resourcesChangeNotificationHandler(resourcesChangeConsumersFinal)); - - // Prompts Change Notification - List>> promptsChangeConsumersFinal = new ArrayList<>(); - promptsChangeConsumersFinal.add((notification) -> logger.info("Prompts changed: {}", notification)); - if (!Utils.isEmpty(promptsChangeConsumers)) { - promptsChangeConsumersFinal.addAll(promptsChangeConsumers); - } - notificationHandlers.put(McpSchema.METHOD_NOTIFICATION_PROMPTS_LIST_CHANGED, - promptsChangeNotificationHandler(promptsChangeConsumersFinal)); - - // Utility Logging Notification - List> loggingConsumersFinal = new ArrayList<>(); - loggingConsumersFinal.add((notification) -> logger.info("Logging: {}", notification)); - if (!Utils.isEmpty(loggingConsumers)) { - loggingConsumersFinal.addAll(loggingConsumers); - } - notificationHandlers.put(McpSchema.METHOD_NOTIFICATION_MESSAGE, - loggingNotificationHandler(loggingConsumersFinal)); - - this.mcpSession = new DefaultMcpSession(requestTimeout, transport, requestHandlers, notificationHandlers); - - } - // -------------------------- // Lifecycle // -------------------------- @@ -587,32 +483,6 @@ public Mono listTools(String cursor) { LIST_TOOLS_RESULT_TYPE_REF); } - /** - * Creates a notification handler for tools/list_changed notifications from the - * server. When the server's available tools change, it sends a notification to inform - * connected clients. This handler automatically fetches the updated tool list and - * distributes it to all registered consumers. - * @param toolsChangeConsumers List of consumers that will be notified when the tools - * list changes. Each consumer receives the complete updated list of tools. - * @return A NotificationHandler that processes tools/list_changed notifications by: - * 1. Fetching the current list of tools from the server 2. Distributing the updated - * list to all registered consumers 3. Handling any errors that occur during this - * process - */ - @Deprecated - private NotificationHandler toolsChangeNotificationHandler( - List>> toolsChangeConsumers) { - - return params -> listTools().flatMap(listToolsResult -> Mono.fromRunnable(() -> { - for (Consumer> toolsChangeConsumer : toolsChangeConsumers) { - toolsChangeConsumer.accept(listToolsResult.tools()); - } - }).subscribeOn(Schedulers.boundedElastic())).onErrorResume(error -> { - logger.error("Error handling tools list change notification", error); - return Mono.empty(); - }).then(); // Convert to Mono - } - /** * Creates a notification handler for tools/list_changed notifications from the * server. When the server's available tools change, it sends a notification to inform @@ -745,20 +615,6 @@ public Mono unsubscribeResource(McpSchema.UnsubscribeRequest unsubscribeRe VOID_TYPE_REFERENCE); } - @Deprecated - private NotificationHandler resourcesChangeNotificationHandler( - List>> resourcesChangeConsumers) { - - return params -> listResources().flatMap(listResourcesResult -> Mono.fromRunnable(() -> { - for (Consumer> resourceChangeConsumer : resourcesChangeConsumers) { - resourceChangeConsumer.accept(listResourcesResult.resources()); - } - }).subscribeOn(Schedulers.boundedElastic())).onErrorResume(error -> { - logger.error("Error handling resources list change notification", error); - return Mono.empty(); - }).then(); - } - private NotificationHandler asyncResourcesChangeNotificationHandler( List, Mono>> resourcesChangeConsumers) { return params -> listResources().flatMap(listResourcesResult -> Flux.fromIterable(resourcesChangeConsumers) @@ -816,22 +672,6 @@ public Mono promptListChangedNotification() { return this.mcpSession.sendNotification(McpSchema.METHOD_NOTIFICATION_PROMPTS_LIST_CHANGED); } - @Deprecated - private NotificationHandler promptsChangeNotificationHandler( - List>> promptsChangeConsumers) { - - return params -> {// @formatter:off - return listPrompts().flatMap(listPromptsResult -> Mono.fromRunnable(() -> { - for (Consumer> promptChangeConsumer : promptsChangeConsumers) { - promptChangeConsumer.accept(listPromptsResult.prompts()); - } - }).subscribeOn(Schedulers.boundedElastic())).onErrorResume(error -> { - logger.error("Error handling prompts list change notification", error); - return Mono.empty(); - }).then(); // Convert to Mono - }; // @formatter:on - } - private NotificationHandler asyncPromptsChangeNotificationHandler( List, Mono>> promptsChangeConsumers) { return params -> listPrompts().flatMap(listPromptsResult -> Flux.fromIterable(promptsChangeConsumers) diff --git a/mcp/src/main/java/org/modelcontextprotocol/client/McpClient.java b/mcp/src/main/java/org/modelcontextprotocol/client/McpClient.java index 7cf5341d2..2580ab062 100644 --- a/mcp/src/main/java/org/modelcontextprotocol/client/McpClient.java +++ b/mcp/src/main/java/org/modelcontextprotocol/client/McpClient.java @@ -135,262 +135,6 @@ static AsyncSpec async(ClientMcpTransport transport) { return new AsyncSpec(transport); } - /** - * Start building an MCP client with the specified transport layer. The transport - * layer handles the low-level communication between client and server using protocols - * like stdio or Server-Sent Events (SSE). - * @param transport The transport layer implementation for MCP communication. Common - * implementations include {@code StdioClientTransport} for stdio-based communication - * and {@code SseClientTransport} for SSE-based communication. - * @return A new builder instance for configuring the client - * @throws IllegalArgumentException if transport is null - * @deprecated Use {@link #sync(ClientMcpTransport)} or - * {@link #async(ClientMcpTransport)} specification builder to configure the client - * and build an instance. - */ - @Deprecated - public static Builder using(ClientMcpTransport transport) { - return new Builder(transport); - } - - /** - * Builder class for creating and configuring MCP clients. This class follows the - * builder pattern to provide a fluent API for setting up clients with custom - * configurations. - * - *

- * The builder supports configuration of: - *

    - *
  • Transport layer for client-server communication - *
  • Request timeouts for operation boundaries - *
  • Client capabilities for feature negotiation - *
  • Client implementation details for version tracking - *
  • Root URIs for resource access - *
  • Change notification handlers for tools, resources, and prompts - *
  • Custom message sampling logic - *
- * - * @deprecated Use {@link #sync(ClientMcpTransport)} or - * {@link #async(ClientMcpTransport)} specification builder to instantiate an - * instance. - */ - @Deprecated - public static class Builder { - - private final ClientMcpTransport transport; - - private Duration requestTimeout = Duration.ofSeconds(20); // Default timeout - - private ClientCapabilities capabilities; - - private Implementation clientInfo = new Implementation("Spring AI MCP Client", "0.3.1"); - - private Map roots = new HashMap<>(); - - private List>> toolsChangeConsumers = new ArrayList<>(); - - private List>> resourcesChangeConsumers = new ArrayList<>(); - - private List>> promptsChangeConsumers = new ArrayList<>(); - - private List> loggingConsumers = new ArrayList<>(); - - private Function samplingHandler; - - private Builder(ClientMcpTransport transport) { - Assert.notNull(transport, "Transport must not be null"); - this.transport = transport; - } - - /** - * Sets the duration to wait for server responses before timing out requests. This - * timeout applies to all requests made through the client, including tool calls, - * resource access, and prompt operations. - * @param requestTimeout The duration to wait before timing out requests. Must not - * be null. - * @return This builder instance for method chaining - * @throws IllegalArgumentException if requestTimeout is null - */ - public Builder requestTimeout(Duration requestTimeout) { - Assert.notNull(requestTimeout, "Request timeout must not be null"); - this.requestTimeout = requestTimeout; - return this; - } - - /** - * Sets the client capabilities that will be advertised to the server during - * connection initialization. Capabilities define what features the client - * supports, such as tool execution, resource access, and prompt handling. - * @param capabilities The client capabilities configuration. Must not be null. - * @return This builder instance for method chaining - * @throws IllegalArgumentException if capabilities is null - */ - public Builder capabilities(ClientCapabilities capabilities) { - Assert.notNull(capabilities, "Capabilities must not be null"); - this.capabilities = capabilities; - return this; - } - - /** - * Sets the client implementation information that will be shared with the server - * during connection initialization. This helps with version compatibility and - * debugging. - * @param clientInfo The client implementation details including name and version. - * Must not be null. - * @return This builder instance for method chaining - * @throws IllegalArgumentException if clientInfo is null - */ - public Builder clientInfo(Implementation clientInfo) { - Assert.notNull(clientInfo, "Client info must not be null"); - this.clientInfo = clientInfo; - return this; - } - - /** - * Sets the root URIs that this client can access. Roots define the base URIs for - * resources that the client can request from the server. For example, a root - * might be "file://workspace" for accessing workspace files. - * @param roots A list of root definitions. Must not be null. - * @return This builder instance for method chaining - * @throws IllegalArgumentException if roots is null - */ - public Builder roots(List roots) { - Assert.notNull(roots, "Roots must not be null"); - for (Root root : roots) { - this.roots.put(root.uri(), root); - } - return this; - } - - /** - * Sets the root URIs that this client can access, using a varargs parameter for - * convenience. This is an alternative to {@link #roots(List)}. - * @param roots An array of root definitions. Must not be null. - * @return This builder instance for method chaining - * @throws IllegalArgumentException if roots is null - * @see #roots(List) - */ - public Builder roots(Root... roots) { - Assert.notNull(roots, "Roots must not be null"); - for (Root root : roots) { - this.roots.put(root.uri(), root); - } - return this; - } - - /** - * Sets a custom sampling handler for processing message creation requests. The - * sampling handler can modify or validate messages before they are sent to the - * server, enabling custom processing logic. - * @param samplingHandler A function that processes message requests and returns - * results. Must not be null. - * @return This builder instance for method chaining - * @throws IllegalArgumentException if samplingHandler is null - */ - public Builder sampling(Function samplingHandler) { - Assert.notNull(samplingHandler, "Sampling handler must not be null"); - this.samplingHandler = samplingHandler; - return this; - } - - /** - * Adds a consumer to be notified when the available tools change. This allows the - * client to react to changes in the server's tool capabilities, such as tools - * being added or removed. - * @param toolsChangeConsumer A consumer that receives the updated list of - * available tools. Must not be null. - * @return This builder instance for method chaining - * @throws IllegalArgumentException if toolsChangeConsumer is null - */ - public Builder toolsChangeConsumer(Consumer> toolsChangeConsumer) { - Assert.notNull(toolsChangeConsumer, "Tools change consumer must not be null"); - this.toolsChangeConsumers.add(toolsChangeConsumer); - return this; - } - - /** - * Adds a consumer to be notified when the available resources change. This allows - * the client to react to changes in the server's resource availability, such as - * files being added or removed. - * @param resourcesChangeConsumer A consumer that receives the updated list of - * available resources. Must not be null. - * @return This builder instance for method chaining - * @throws IllegalArgumentException if resourcesChangeConsumer is null - */ - public Builder resourcesChangeConsumer(Consumer> resourcesChangeConsumer) { - Assert.notNull(resourcesChangeConsumer, "Resources change consumer must not be null"); - this.resourcesChangeConsumers.add(resourcesChangeConsumer); - return this; - } - - /** - * Adds a consumer to be notified when the available prompts change. This allows - * the client to react to changes in the server's prompt templates, such as new - * templates being added or existing ones being modified. - * @param promptsChangeConsumer A consumer that receives the updated list of - * available prompts. Must not be null. - * @return This builder instance for method chaining - * @throws IllegalArgumentException if promptsChangeConsumer is null - */ - public Builder promptsChangeConsumer(Consumer> promptsChangeConsumer) { - Assert.notNull(promptsChangeConsumer, "Prompts change consumer must not be null"); - this.promptsChangeConsumers.add(promptsChangeConsumer); - return this; - } - - /** - * Adds a consumer to be notified when logging messages are received from the - * server. This allows the client to react to log messages, such as warnings or - * errors, that are sent by the server. - * @param loggingConsumer A consumer that receives logging messages. Must not be - * null. - * @return This builder instance for method chaining - */ - public Builder loggingConsumer(Consumer loggingConsumer) { - Assert.notNull(loggingConsumer, "Logging consumer must not be null"); - this.loggingConsumers.add(loggingConsumer); - return this; - } - - /** - * Adds multiple consumers to be notified when logging messages are received from - * the server. This allows the client to react to log messages, such as warnings - * or errors, that are sent by the server. - * @param loggingConsumers A list of consumers that receive logging messages. Must - * not be null. - * @return This builder instance for method chaining - */ - public Builder loggingConsumers(List> loggingConsumers) { - Assert.notNull(loggingConsumers, "Logging consumers must not be null"); - this.loggingConsumers.addAll(loggingConsumers); - return this; - } - - /** - * Builds a synchronous MCP client that provides blocking operations. Synchronous - * clients wait for each operation to complete before returning, making them - * simpler to use but potentially less performant for concurrent operations. - * @return A new instance of {@link McpSyncClient} configured with this builder's - * settings - */ - public McpSyncClient sync() { - return new McpSyncClient(async()); - } - - /** - * Builds an asynchronous MCP client that provides non-blocking operations. - * Asynchronous clients return CompletableFuture objects immediately, allowing for - * concurrent operations and reactive programming patterns. - * @return A new instance of {@link McpAsyncClient} configured with this builder's - * settings - */ - public McpAsyncClient async() { - return new McpAsyncClient(transport, requestTimeout, clientInfo, capabilities, roots, toolsChangeConsumers, - resourcesChangeConsumers, promptsChangeConsumers, loggingConsumers, samplingHandler); - } - - } - /** * Synchronous client specification. This class follows the builder pattern to provide * a fluent API for setting up clients with custom configurations. diff --git a/mcp/src/main/java/org/modelcontextprotocol/server/McpAsyncServer.java b/mcp/src/main/java/org/modelcontextprotocol/server/McpAsyncServer.java index 04c6a8559..9086714a5 100644 --- a/mcp/src/main/java/org/modelcontextprotocol/server/McpAsyncServer.java +++ b/mcp/src/main/java/org/modelcontextprotocol/server/McpAsyncServer.java @@ -11,29 +11,24 @@ import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; -import java.util.function.Consumer; import java.util.function.Function; import com.fasterxml.jackson.core.type.TypeReference; -import org.modelcontextprotocol.server.McpServer.PromptRegistration; -import org.modelcontextprotocol.server.McpServer.ResourceRegistration; -import org.modelcontextprotocol.server.McpServer.ToolRegistration; import org.modelcontextprotocol.spec.DefaultMcpSession; +import org.modelcontextprotocol.spec.DefaultMcpSession.NotificationHandler; import org.modelcontextprotocol.spec.McpError; import org.modelcontextprotocol.spec.McpSchema; -import org.modelcontextprotocol.spec.ServerMcpTransport; -import org.modelcontextprotocol.spec.DefaultMcpSession.NotificationHandler; import org.modelcontextprotocol.spec.McpSchema.CallToolResult; import org.modelcontextprotocol.spec.McpSchema.ClientCapabilities; import org.modelcontextprotocol.spec.McpSchema.LoggingLevel; import org.modelcontextprotocol.spec.McpSchema.LoggingMessageNotification; import org.modelcontextprotocol.spec.McpSchema.Tool; +import org.modelcontextprotocol.spec.ServerMcpTransport; import org.modelcontextprotocol.util.Utils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import reactor.core.scheduler.Schedulers; /** * The Model Context Protocol (MCP) server implementation that provides asynchronous @@ -183,102 +178,6 @@ public class McpAsyncServer { notificationHandlers); } - /** - * Create a new McpAsyncServer with the given transport and capabilities. - * @param mcpTransport The transport layer implementation for MCP communication - * @param serverInfo The server implementation details - * @param serverCapabilities The server capabilities - * @param tools The list of tool registrations - * @param resources The map of resource registrations - * @param resourceTemplates The list of resource templates - * @param prompts The map of prompt registrations - * @param rootsChangeConsumers The list of consumers that will be notified when the - * roots list changes - * @deprecated Use {@link McpServer#sync(ServerMcpTransport)} or - * {@link McpServer#async(ServerMcpTransport)} to create a new server instance. - */ - @Deprecated - public McpAsyncServer(ServerMcpTransport mcpTransport, McpSchema.Implementation serverInfo, - McpSchema.ServerCapabilities serverCapabilities, List tools, - Map resources, List resourceTemplates, - Map prompts, List>> rootsChangeConsumers) { - - this.protocolVersions = List.of(McpSchema.LATEST_PROTOCOL_VERSION); - this.serverInfo = serverInfo; - if (!Utils.isEmpty(tools)) { - this.tools.addAll(McpServer.mapDeprecatedTools(tools)); - } - if (!Utils.isEmpty(resources)) { - this.resources.putAll(McpServer.mapDeprecatedResources(resources)); - } - if (!Utils.isEmpty(resourceTemplates)) { - this.resourceTemplates.addAll(resourceTemplates); - } - if (!Utils.isEmpty(prompts)) { - this.prompts.putAll(McpServer.mapDeprecatedPrompts(prompts)); - } - - this.serverCapabilities = (serverCapabilities != null) ? serverCapabilities : new McpSchema.ServerCapabilities( - null, // experimental - new McpSchema.ServerCapabilities.LoggingCapabilities(), // Enable logging - // by default - !Utils.isEmpty(this.prompts) ? new McpSchema.ServerCapabilities.PromptCapabilities(false) : null, - !Utils.isEmpty(this.resources) ? new McpSchema.ServerCapabilities.ResourceCapabilities(false, false) - : null, - !Utils.isEmpty(this.tools) ? new McpSchema.ServerCapabilities.ToolCapabilities(false) : null); - - Map> requestHandlers = new HashMap<>(); - - // Initialize request handlers for standard MCP methods - requestHandlers.put(McpSchema.METHOD_INITIALIZE, initializeRequestHandler()); - - // Ping MUST respond with an empty data, but not NULL response. - requestHandlers.put(McpSchema.METHOD_PING, (params) -> Mono.just("")); - - // Add tools API handlers if the tool capability is enabled - if (this.serverCapabilities.tools() != null) { - requestHandlers.put(McpSchema.METHOD_TOOLS_LIST, toolsListRequestHandler()); - requestHandlers.put(McpSchema.METHOD_TOOLS_CALL, toolsCallRequestHandler()); - } - - // Add resources API handlers if provided - if (!Utils.isEmpty(this.resources)) { - requestHandlers.put(McpSchema.METHOD_RESOURCES_LIST, resourcesListRequestHandler()); - requestHandlers.put(McpSchema.METHOD_RESOURCES_READ, resourcesReadRequestHandler()); - } - - // Add resource templates API handlers if provided. - if (!Utils.isEmpty(this.resourceTemplates)) { - requestHandlers.put(McpSchema.METHOD_RESOURCES_TEMPLATES_LIST, resourceTemplateListRequestHandler()); - } - - // Add prompts API handlers if provider exists - if (!Utils.isEmpty(this.prompts)) { - requestHandlers.put(McpSchema.METHOD_PROMPT_LIST, promptsListRequestHandler()); - requestHandlers.put(McpSchema.METHOD_PROMPT_GET, promptsGetRequestHandler()); - } - - // Add logging API handlers if the logging capability is enabled - if (this.serverCapabilities.logging() != null) { - requestHandlers.put(McpSchema.METHOD_LOGGING_SET_LEVEL, setLoggerRequestHandler()); - } - - Map notificationHandlers = new HashMap<>(); - - notificationHandlers.put(McpSchema.METHOD_NOTIFICATION_INITIALIZED, (params) -> Mono.empty()); - - if (Utils.isEmpty(rootsChangeConsumers)) { - rootsChangeConsumers = List.of((roots) -> logger - .warn("Roots list changed notification, but no consumers provided. Roots list changed: {}", roots)); - } - notificationHandlers.put(McpSchema.METHOD_NOTIFICATION_ROOTS_LIST_CHANGED, - rootsListChnagedNotificationHandler(rootsChangeConsumers)); - - this.transport = mcpTransport; - this.mcpSession = new DefaultMcpSession(Duration.ofSeconds(10), mcpTransport, requestHandlers, - notificationHandlers); - } - // --------------------------------------- // Lifecycle Management // --------------------------------------- @@ -309,33 +208,6 @@ private DefaultMcpSession.RequestHandler asyncInitia }; } - @Deprecated - private DefaultMcpSession.RequestHandler initializeRequestHandler() { - return params -> { - McpSchema.InitializeRequest initializeRequest = transport.unmarshalFrom(params, - new TypeReference() { - }); - - this.clientCapabilities = initializeRequest.capabilities(); - this.clientInfo = initializeRequest.clientInfo(); - - logger.info("Client initialize request - Protocol: {}, Capabilities: {}, Info: {}", - initializeRequest.protocolVersion(), initializeRequest.capabilities(), - initializeRequest.clientInfo()); - - if (!McpSchema.LATEST_PROTOCOL_VERSION.equals(initializeRequest.protocolVersion())) { - return Mono.error(new McpError( - "Unsupported protocol version from client: " + initializeRequest.protocolVersion())) - .publishOn(Schedulers.boundedElastic()); - } - - return Mono - .just(new McpSchema.InitializeResult(McpSchema.LATEST_PROTOCOL_VERSION, this.serverCapabilities, - this.serverInfo, null)) - .publishOn(Schedulers.boundedElastic()); - }; - } - /** * Get the server capabilities that define the supported features and functionality. * @return The server capabilities @@ -404,20 +276,6 @@ public Mono listRoots(String cursor) { LIST_ROOTS_RESULT_TYPE_REF); } - @Deprecated - private NotificationHandler rootsListChnagedNotificationHandler( - List>> rootsChangeConsumers) { - - return params -> { - return listRoots().flatMap(listRootsResult -> Mono.fromRunnable(() -> { - rootsChangeConsumers.stream().forEach(consumer -> consumer.accept(listRootsResult.roots())); - }).subscribeOn(Schedulers.boundedElastic())).onErrorResume(error -> { - logger.error("Error handling roots list change notification", error); - return Mono.empty(); - }).then(); - }; - } - private NotificationHandler asyncRootsListChangedNotificationHandler( List, Mono>> rootsChangeConsumers) { return params -> listRoots().flatMap(listRootsResult -> Flux.fromIterable(rootsChangeConsumers) @@ -469,40 +327,6 @@ public Mono addTool(McpServerFeatures.AsyncToolRegistration toolRegistrati }); } - /** - * Add a new tool registration at runtime. - * @param toolRegistration The tool registration to add - * @return Mono that completes when clients have been notified of the change - * @deprecated Use {@link #addTool(McpServerFeatures.AsyncToolRegistration)}. - */ - @Deprecated - public Mono addTool(ToolRegistration toolRegistration) { - if (toolRegistration == null) { - return Mono.error(new McpError("Tool registration must not be null")); - } - if (toolRegistration.tool() == null) { - return Mono.error(new McpError("Tool must not be null")); - } - if (toolRegistration.call() == null) { - return Mono.error(new McpError("Tool call handler must not be null")); - } - if (this.serverCapabilities.tools() == null) { - return Mono.error(new McpError("Server must be configured with tool capabilities")); - } - - // Check for duplicate tool names - if (this.tools.stream().anyMatch(th -> th.tool().name().equals(toolRegistration.tool().name()))) { - return Mono.error(new McpError("Tool with name '" + toolRegistration.tool().name() + "' already exists")); - } - - this.tools.add(McpServer.mapDeprecatedTool(toolRegistration)); - logger.info("Added tool handler: {}", toolRegistration.tool().name()); - if (this.serverCapabilities.tools().listChanged()) { - return notifyToolsListChanged(); - } - return Mono.empty(); - } - /** * Remove a tool handler at runtime. * @param toolName The name of the tool handler to remove @@ -595,35 +419,6 @@ public Mono addResource(McpServerFeatures.AsyncResourceRegistration resour }); } - /** - * Add a new resource handler at runtime. - * @param resourceHandler The resource handler to add - * @return Mono that completes when clients have been notified of the change - * @deprecated Use {@link #addResource(McpServerFeatures.AsyncResourceRegistration)}. - */ - @Deprecated - public Mono addResource(ResourceRegistration resourceHandler) { - if (resourceHandler == null || resourceHandler.resource() == null) { - return Mono.error(new McpError("Resource must not be null")); - } - - if (this.serverCapabilities.resources() == null) { - return Mono.error(new McpError("Server must be configured with resource capabilities")); - } - - if (this.resources.containsKey(resourceHandler.resource().uri())) { - return Mono - .error(new McpError("Resource with URI '" + resourceHandler.resource().uri() + "' already exists")); - } - - this.resources.put(resourceHandler.resource().uri(), McpServer.mapDeprecatedResource(resourceHandler)); - logger.info("Added resource handler: {}", resourceHandler.resource().uri()); - if (this.serverCapabilities.resources().listChanged()) { - return notifyResourcesListChanged(); - } - return Mono.empty(); - } - /** * Remove a resource handler at runtime. * @param resourceUri The URI of the resource handler to remove @@ -724,38 +519,6 @@ public Mono addPrompt(McpServerFeatures.AsyncPromptRegistration promptRegi }); } - /** - * Add a new prompt handler at runtime. - * @param promptRegistration The prompt handler to add - * @return Mono that completes when clients have been notified of the change - * @deprecated Use {@link #addPrompt(McpServerFeatures.AsyncPromptRegistration)}. - */ - @Deprecated - public Mono addPrompt(PromptRegistration promptRegistration) { - if (promptRegistration == null) { - return Mono.error(new McpError("Prompt registration must not be null")); - } - if (this.serverCapabilities.prompts() == null) { - return Mono.error(new McpError("Server must be configured with prompt capabilities")); - } - - if (this.prompts.containsKey(promptRegistration.prompt().name())) { - return Mono - .error(new McpError("Prompt with name '" + promptRegistration.prompt().name() + "' already exists")); - } - - this.prompts.put(promptRegistration.prompt().name(), McpServer.mapDeprecatedPrompt(promptRegistration)); - - logger.info("Added prompt handler: {}", promptRegistration.prompt().name()); - - // Servers that declared the listChanged capability SHOULD send a notification, - // when the list of available prompts changes - if (this.serverCapabilities.prompts().listChanged()) { - return notifyPromptsListChanged(); - } - return Mono.empty(); - } - /** * Remove a prompt handler at runtime. * @param promptName The name of the prompt handler to remove diff --git a/mcp/src/main/java/org/modelcontextprotocol/server/McpServer.java b/mcp/src/main/java/org/modelcontextprotocol/server/McpServer.java index 7564f001d..870282f0a 100644 --- a/mcp/src/main/java/org/modelcontextprotocol/server/McpServer.java +++ b/mcp/src/main/java/org/modelcontextprotocol/server/McpServer.java @@ -10,16 +10,14 @@ import java.util.Map; import java.util.function.Consumer; import java.util.function.Function; -import java.util.stream.Collectors; import org.modelcontextprotocol.spec.McpSchema; -import org.modelcontextprotocol.spec.McpTransport; -import org.modelcontextprotocol.spec.ServerMcpTransport; import org.modelcontextprotocol.spec.McpSchema.CallToolResult; import org.modelcontextprotocol.spec.McpSchema.ResourceTemplate; +import org.modelcontextprotocol.spec.McpTransport; +import org.modelcontextprotocol.spec.ServerMcpTransport; import org.modelcontextprotocol.util.Assert; import reactor.core.publisher.Mono; -import reactor.core.scheduler.Schedulers; /** * Factory class for creating Model Context Protocol (MCP) servers. MCP servers expose @@ -138,18 +136,6 @@ static AsyncSpec async(ServerMcpTransport transport) { return new AsyncSpec(transport); } - /** - * Start building an MCP server with the specified transport. - * @param transport The transport layer implementation for MCP communication - * @return A new builder instance - * @deprecated Use {@link #sync(ServerMcpTransport)} or - * {@link #async(ServerMcpTransport)} to create a server instance. - */ - @Deprecated - public static Builder using(ServerMcpTransport transport) { - return new Builder(transport); - } - /** * Asynchronous server specification. */ @@ -908,547 +894,4 @@ public McpSyncServer build() { } - /** - * Builder class for creating MCP servers with custom configuration. - * - * @deprecated Use {@link #sync(ServerMcpTransport)} or - * {@link #async(ServerMcpTransport)} to create a server. - */ - @Deprecated - public static class Builder { - - private static final McpSchema.Implementation DEFAULT_SERVER_INFO = new McpSchema.Implementation("mcp-server", - "1.0.0"); - - private final ServerMcpTransport transport; - - private McpSchema.Implementation serverInfo = DEFAULT_SERVER_INFO; - - private McpSchema.ServerCapabilities serverCapabilities; - - /** - * The Model Context Protocol (MCP) allows servers to expose tools that can be - * invoked by language models. Tools enable models to interact with external - * systems, such as querying databases, calling APIs, or performing computations. - * Each tool is uniquely identified by a name and includes metadata describing its - * schema. - */ - private final List tools = new ArrayList<>(); - - /** - * The Model Context Protocol (MCP) provides a standardized way for servers to - * expose resources to clients. Resources allow servers to share data that - * provides context to language models, such as files, database schemas, or - * application-specific information. Each resource is uniquely identified by a - * URI. - */ - private Map resources = new HashMap<>(); - - private List resourceTemplates = new ArrayList<>(); - - /** - * The Model Context Protocol (MCP) provides a standardized way for servers to - * expose prompt templates to clients. Prompts allow servers to provide structured - * messages and instructions for interacting with language models. Clients can - * discover available prompts, retrieve their contents, and provide arguments to - * customize them. - */ - private Map prompts = new HashMap<>(); - - private List>> rootsChangeConsumers = new ArrayList<>(); - - private Builder(ServerMcpTransport transport) { - Assert.notNull(transport, "Transport must not be null"); - this.transport = transport; - } - - /** - * Sets the server implementation information that will be shared with clients - * during connection initialization. This helps with version compatibility, - * debugging, and server identification. - * @param serverInfo The server implementation details including name and version. - * Must not be null. - * @return This builder instance for method chaining - * @throws IllegalArgumentException if serverInfo is null - */ - public Builder serverInfo(McpSchema.Implementation serverInfo) { - Assert.notNull(serverInfo, "Server info must not be null"); - this.serverInfo = serverInfo; - return this; - } - - /** - * Sets the server implementation information using name and version strings. This - * is a convenience method alternative to - * {@link #serverInfo(McpSchema.Implementation)}. - * @param name The server name. Must not be null or empty. - * @param version The server version. Must not be null or empty. - * @return This builder instance for method chaining - * @throws IllegalArgumentException if name or version is null or empty - * @see #serverInfo(McpSchema.Implementation) - */ - public Builder serverInfo(String name, String version) { - Assert.hasText(name, "Name must not be null or empty"); - Assert.hasText(version, "Version must not be null or empty"); - this.serverInfo = new McpSchema.Implementation(name, version); - return this; - } - - /** - * Sets the server capabilities that will be advertised to clients during - * connection initialization. Capabilities define what features the server - * supports, such as: - *
    - *
  • Tool execution - *
  • Resource access - *
  • Prompt handling - *
  • Streaming responses - *
  • Batch operations - *
- * @param serverCapabilities The server capabilities configuration. Must not be - * null. - * @return This builder instance for method chaining - * @throws IllegalArgumentException if serverCapabilities is null - */ - public Builder capabilities(McpSchema.ServerCapabilities serverCapabilities) { - this.serverCapabilities = serverCapabilities; - 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 ToolRegistration} explicitly. - * - *

- * Example usage:

{@code
-		 * .tool(
-		 *     new Tool("calculator", "Performs calculations", schema),
-		 *     args -> new CallToolResult("Result: " + calculate(args))
-		 * )
-		 * }
- * @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. - * @return This builder instance for method chaining - * @throws IllegalArgumentException if tool or handler is null - */ - public Builder tool(McpSchema.Tool tool, Function, McpSchema.CallToolResult> handler) { - Assert.notNull(tool, "Tool must not be null"); - Assert.notNull(handler, "Handler must not be null"); - - this.tools.add(new ToolRegistration(tool, handler)); - - return this; - } - - /** - * 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 - * source. - * @param toolRegistrations The list of tool registrations to add. Must not be - * null. - * @return This builder instance for method chaining - * @throws IllegalArgumentException if toolRegistrations is null - * @see #tools(ToolRegistration...) - */ - public Builder tools(List toolRegistrations) { - Assert.notNull(toolRegistrations, "Tool handlers list must not be null"); - this.tools.addAll(toolRegistrations); - return this; - } - - /** - * Adds multiple tools with their handlers to the server using varargs. This - * method provides a convenient way to register multiple tools inline. - * - *

- * Example usage:

{@code
-		 * .tools(
-		 *     new ToolRegistration(calculatorTool, calculatorHandler),
-		 *     new ToolRegistration(weatherTool, weatherHandler),
-		 *     new ToolRegistration(fileManagerTool, fileManagerHandler)
-		 * )
-		 * }
- * @param toolRegistrations The tool registrations to add. Must not be null. - * @return This builder instance for method chaining - * @throws IllegalArgumentException if toolRegistrations is null - * @see #tools(List) - */ - public Builder tools(ToolRegistration... toolRegistrations) { - for (ToolRegistration tool : toolRegistrations) { - this.tools.add(tool); - } - return this; - } - - /** - * Registers multiple resources with their handlers using a Map. This method is - * useful when resources are dynamically generated or loaded from a configuration - * source. - * @param resourceRegsitrations Map of resource name to registration. Must not be - * null. - * @return This builder instance for method chaining - * @throws IllegalArgumentException if resourceRegsitrations is null - * @see #resources(ResourceRegistration...) - */ - public Builder resources(Map resourceRegsitrations) { - Assert.notNull(resourceRegsitrations, "Resource handlers map must not be null"); - this.resources.putAll(resourceRegsitrations); - return this; - } - - /** - * Registers multiple resources with their handlers using a List. This method is - * useful when resources need to be added in bulk from a collection. - * @param resourceRegsitrations List of resource registrations. Must not be null. - * @return This builder instance for method chaining - * @throws IllegalArgumentException if resourceRegsitrations is null - * @see #resources(ResourceRegistration...) - */ - public Builder resources(List resourceRegsitrations) { - Assert.notNull(resourceRegsitrations, "Resource handlers list must not be null"); - for (ResourceRegistration resource : resourceRegsitrations) { - this.resources.put(resource.resource().uri(), resource); - } - return this; - } - - /** - * Registers multiple resources with their handlers using varargs. This method - * provides a convenient way to register multiple resources inline. - * - *

- * Example usage:

{@code
-		 * .resources(
-		 *     new ResourceRegistration(fileResource, fileHandler),
-		 *     new ResourceRegistration(dbResource, dbHandler),
-		 *     new ResourceRegistration(apiResource, apiHandler)
-		 * )
-		 * }
- * @param resourceRegistrations The resource registrations to add. Must not be - * null. - * @return This builder instance for method chaining - * @throws IllegalArgumentException if resourceRegistrations is null - */ - public Builder resources(ResourceRegistration... resourceRegistrations) { - Assert.notNull(resourceRegistrations, "Resource handlers list must not be null"); - for (ResourceRegistration resource : resourceRegistrations) { - this.resources.put(resource.resource().uri(), resource); - } - return this; - } - - /** - * Sets the resource templates that define patterns for dynamic resource access. - * Templates use URI patterns with placeholders that can be filled at runtime. - * - *

- * Example usage:

{@code
-		 * .resourceTemplates(
-		 *     new ResourceTemplate("file://{path}", "Access files by path"),
-		 *     new ResourceTemplate("db://{table}/{id}", "Access database records")
-		 * )
-		 * }
- * @param resourceTemplates List of resource templates. If null, clears existing - * templates. - * @return This builder instance for method chaining - * @see #resourceTemplates(ResourceTemplate...) - */ - public Builder resourceTemplates(List resourceTemplates) { - this.resourceTemplates = resourceTemplates; - return this; - } - - /** - * Sets the resource templates using varargs for convenience. This is an - * alternative to {@link #resourceTemplates(List)}. - * @param resourceTemplates The resource templates to set. - * @return This builder instance for method chaining - * @see #resourceTemplates(List) - */ - public Builder resourceTemplates(ResourceTemplate... resourceTemplates) { - for (ResourceTemplate resourceTemplate : resourceTemplates) { - this.resourceTemplates.add(resourceTemplate); - } - return this; - } - - /** - * Registers multiple prompts with their handlers using a Map. This method is - * useful when prompts are dynamically generated or loaded from a configuration - * source. - * - *

- * Example usage:

{@code
-		 * Map prompts = new HashMap<>();
-		 * prompts.put("analysis", new PromptRegistration(
-		 *     new Prompt("analysis", "Code analysis template"),
-		 *     request -> new GetPromptResult(generateAnalysisPrompt(request))
-		 * ));
-		 * .prompts(prompts)
-		 * }
- * @param prompts Map of prompt name to registration. Must not be null. - * @return This builder instance for method chaining - * @throws IllegalArgumentException if prompts is null - */ - public Builder prompts(Map prompts) { - this.prompts.putAll(prompts); - return this; - } - - /** - * Registers multiple prompts with their handlers using a List. This method is - * useful when prompts need to be added in bulk from a collection. - * @param prompts List of prompt registrations. Must not be null. - * @return This builder instance for method chaining - * @throws IllegalArgumentException if prompts is null - * @see #prompts(PromptRegistration...) - */ - public Builder prompts(List prompts) { - for (PromptRegistration prompt : prompts) { - this.prompts.put(prompt.prompt().name(), prompt); - } - return this; - } - - /** - * Registers multiple prompts with their handlers using varargs. This method - * provides a convenient way to register multiple prompts inline. - * - *

- * Example usage:

{@code
-		 * .prompts(
-		 *     new PromptRegistration(analysisPrompt, analysisHandler),
-		 *     new PromptRegistration(summaryPrompt, summaryHandler),
-		 *     new PromptRegistration(reviewPrompt, reviewHandler)
-		 * )
-		 * }
- * @param prompts The prompt registrations to add. Must not be null. - * @return This builder instance for method chaining - * @throws IllegalArgumentException if prompts is null - */ - public Builder prompts(PromptRegistration... prompts) { - for (PromptRegistration prompt : prompts) { - this.prompts.put(prompt.prompt().name(), prompt); - } - return this; - } - - /** - * Registers a consumer that will be notified when the list of roots changes. This - * is useful for updating resource availability dynamically, such as when new - * files are added or removed. - * @param consumer The consumer to register. Must not be null. - * @return This builder instance for method chaining - * @throws IllegalArgumentException if consumer is null - */ - public Builder rootsChangeConsumer(Consumer> consumer) { - Assert.notNull(consumer, "Consumer must not be null"); - this.rootsChangeConsumers.add(consumer); - return this; - } - - /** - * Registers multiple consumers that will be notified when the list of roots - * changes. This method is useful when multiple consumers need to be registered at - * once. - * @param consumers The list of consumers to register. Must not be null. - * @return This builder instance for method chaining - * @throws IllegalArgumentException if consumers is null - */ - public Builder rootsChangeConsumers(List>> consumers) { - Assert.notNull(consumers, "Consumers list must not be null"); - this.rootsChangeConsumers.addAll(consumers); - return this; - } - - /** - * Registers multiple consumers that will be notified when the list of roots - * changes using varargs. This method provides a convenient way to register - * multiple consumers inline. - * @param consumers The consumers to register. Must not be null. - * @return This builder instance for method chaining - * @throws IllegalArgumentException if consumers is null - */ - public Builder rootsChangeConsumers(Consumer>... consumers) { - for (Consumer> consumer : consumers) { - this.rootsChangeConsumers.add(consumer); - } - return this; - } - - /** - * Builds a synchronous MCP server that provides blocking operations. Synchronous - * servers process each request to completion before handling the next one, making - * them simpler to implement but potentially less performant for concurrent - * operations. - * @return A new instance of {@link McpSyncServer} configured with this builder's - * settings - * @deprecated Use {@link #sync(ServerMcpTransport)}. - */ - @Deprecated - public McpSyncServer sync() { - return new McpSyncServer(async()); - } - - /** - * Builds an asynchronous MCP server that provides non-blocking operations. - * Asynchronous servers can handle multiple requests concurrently using - * CompletableFuture, making them more efficient for high-concurrency scenarios - * but more complex to implement. - * @return A new instance of {@link McpAsyncServer} configured with this builder's - * settings - * @deprecated Use {@link #async(ServerMcpTransport)} - */ - @Deprecated - public McpAsyncServer async() { - return new McpAsyncServer(transport, serverInfo, serverCapabilities, tools, resources, resourceTemplates, - prompts, rootsChangeConsumers); - } - - } - - /** - * Registration of a tool with its handler function. Tools are the primary way for MCP - * servers to expose functionality to AI models. Each tool represents a specific - * capability, such as: - *
    - *
  • Performing calculations - *
  • Accessing external APIs - *
  • Querying databases - *
  • Manipulating files - *
  • Executing system commands - *
- * - *

- * Example tool registration:

{@code
-	 * new ToolRegistration(
-	 *     new Tool(
-	 *         "calculator",
-	 *         "Performs mathematical calculations",
-	 *         new JsonSchemaObject()
-	 *             .required("expression")
-	 *             .property("expression", JsonSchemaType.STRING)
-	 *     ),
-	 *     args -> {
-	 *         String expr = (String) args.get("expression");
-	 *         return new CallToolResult("Result: " + evaluate(expr));
-	 *     }
-	 * )
-	 * }
- * - * @param tool The tool definition including name, description, and parameter schema - * @param call The function that implements the tool's logic, receiving arguments and - * returning results - * @deprecated Use {@link McpServerFeatures.SyncToolRegistration} or - * {@link McpServerFeatures.AsyncToolRegistration}. - */ - @Deprecated - public static record ToolRegistration(McpSchema.Tool tool, - Function, McpSchema.CallToolResult> call) { - } - - /** - * Registration of a resource with its handler function. Resources provide context to - * AI models by exposing data such as: - *
    - *
  • File contents - *
  • Database records - *
  • API responses - *
  • System information - *
  • Application state - *
- * - *

- * Example resource registration:

{@code
-	 * new ResourceRegistration(
-	 *     new Resource("docs", "Documentation files", "text/markdown"),
-	 *     request -> {
-	 *         String content = readFile(request.getPath());
-	 *         return new ReadResourceResult(content);
-	 *     }
-	 * )
-	 * }
- * - * @param resource The resource definition including name, description, and MIME type - * @param readHandler The function that handles resource read requests - * @deprecated Use {@link McpServerFeatures.SyncResourceRegistration} or - * {@link McpServerFeatures.AsyncResourceRegistration}. - */ - @Deprecated - public static record ResourceRegistration(McpSchema.Resource resource, - Function readHandler) { - } - - /** - * Registration of a prompt template with its handler function. Prompts provide - * structured templates for AI model interactions, supporting: - *
    - *
  • Consistent message formatting - *
  • Parameter substitution - *
  • Context injection - *
  • Response formatting - *
  • Instruction templating - *
- * - *

- * Example prompt registration:

{@code
-	 * new PromptRegistration(
-	 *     new Prompt("analyze", "Code analysis template"),
-	 *     request -> {
-	 *         String code = request.getArguments().get("code");
-	 *         return new GetPromptResult(
-	 *             "Analyze this code:\n\n" + code + "\n\nProvide feedback on:"
-	 *         );
-	 *     }
-	 * )
-	 * }
- * - * @param prompt The prompt definition including name and description - * @param promptHandler The function that processes prompt requests and returns - * formatted templates - * @deprecated Use {@link McpServerFeatures.SyncPromptRegistration} or - * {@link McpServerFeatures.AsyncPromptRegistration}. - */ - @Deprecated - public static record PromptRegistration(McpSchema.Prompt prompt, - Function promptHandler) { - } - - static McpServerFeatures.AsyncToolRegistration mapDeprecatedTool(ToolRegistration oldTool) { - return new McpServerFeatures.AsyncToolRegistration(oldTool.tool(), - map -> Mono.fromCallable(() -> oldTool.call().apply(map)).subscribeOn(Schedulers.boundedElastic())); - } - - static McpServerFeatures.AsyncResourceRegistration mapDeprecatedResource(ResourceRegistration oldResource) { - return new McpServerFeatures.AsyncResourceRegistration(oldResource.resource(), - req -> Mono.fromCallable(() -> oldResource.readHandler().apply(req)) - .subscribeOn(Schedulers.boundedElastic())); - } - - static McpServerFeatures.AsyncPromptRegistration mapDeprecatedPrompt(PromptRegistration oldPrompt) { - return new McpServerFeatures.AsyncPromptRegistration(oldPrompt.prompt(), - req -> Mono.fromCallable(() -> oldPrompt.promptHandler().apply(req)) - .subscribeOn(Schedulers.boundedElastic())); - } - - static List mapDeprecatedTools(List oldTools) { - return oldTools.stream().map(McpServer::mapDeprecatedTool).toList(); - } - - static Map mapDeprecatedResources( - Map oldResources) { - return oldResources.entrySet() - .stream() - .collect(Collectors.toMap(Map.Entry::getKey, e -> mapDeprecatedResource(e.getValue()))); - } - - static Map mapDeprecatedPrompts( - Map oldPrompts) { - return oldPrompts.entrySet() - .stream() - .collect(Collectors.toMap(Map.Entry::getKey, e -> mapDeprecatedPrompt(e.getValue()))); - } - } diff --git a/mcp/src/main/java/org/modelcontextprotocol/server/McpSyncServer.java b/mcp/src/main/java/org/modelcontextprotocol/server/McpSyncServer.java index 898b1c690..aab1457c2 100644 --- a/mcp/src/main/java/org/modelcontextprotocol/server/McpSyncServer.java +++ b/mcp/src/main/java/org/modelcontextprotocol/server/McpSyncServer.java @@ -4,9 +4,6 @@ package org.modelcontextprotocol.server; -import org.modelcontextprotocol.server.McpServer.PromptRegistration; -import org.modelcontextprotocol.server.McpServer.ResourceRegistration; -import org.modelcontextprotocol.server.McpServer.ToolRegistration; import org.modelcontextprotocol.spec.McpError; import org.modelcontextprotocol.spec.McpSchema; import org.modelcontextprotocol.spec.McpSchema.ClientCapabilities; @@ -93,16 +90,6 @@ public void addTool(McpServerFeatures.SyncToolRegistration toolHandler) { this.asyncServer.addTool(McpServerFeatures.AsyncToolRegistration.fromSync(toolHandler)).block(); } - /** - * Add a new tool handler. - * @param toolHandler The tool handler to add - * @deprecated Use {@link #addTool(McpServerFeatures.SyncToolRegistration)}. - */ - @Deprecated - public void addTool(ToolRegistration toolHandler) { - this.asyncServer.addTool(toolHandler).block(); - } - /** * Remove a tool handler. * @param toolName The name of the tool handler to remove @@ -119,16 +106,6 @@ public void addResource(McpServerFeatures.SyncResourceRegistration resourceHandl this.asyncServer.addResource(McpServerFeatures.AsyncResourceRegistration.fromSync(resourceHandler)).block(); } - /** - * Add a new resource handler. - * @param resourceHandler The resource handler to add - * @deprecated Use {@link #addResource(McpServerFeatures.SyncResourceRegistration)}. - */ - @Deprecated - public void addResource(ResourceRegistration resourceHandler) { - this.asyncServer.addResource(resourceHandler).block(); - } - /** * Remove a resource handler. * @param resourceUri The URI of the resource handler to remove @@ -145,16 +122,6 @@ public void addPrompt(McpServerFeatures.SyncPromptRegistration promptRegistratio this.asyncServer.addPrompt(McpServerFeatures.AsyncPromptRegistration.fromSync(promptRegistration)).block(); } - /** - * Add a new prompt handler. - * @param promptRegistration The prompt registration to add - * @deprecated Use {@link #addPrompt(McpServerFeatures.SyncPromptRegistration)}. - */ - @Deprecated - public void addPrompt(PromptRegistration promptRegistration) { - this.asyncServer.addPrompt(promptRegistration).block(); - } - /** * Remove a prompt handler. * @param promptName The name of the prompt handler to remove