+if ($env:MVNW_REPOURL) {
+ $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" }
+ $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')"
+}
+$distributionUrlName = $distributionUrl -replace '^.*/',''
+$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
+$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain"
+if ($env:MAVEN_USER_HOME) {
+ $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain"
+}
+$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
+$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
+
+if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
+ Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+ Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
+ exit $?
+}
+
+if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
+ Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
+}
+
+# prepare tmp dir
+$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
+$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
+$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
+trap {
+ if ($TMP_DOWNLOAD_DIR.Exists) {
+ try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+ catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+ }
+}
+
+New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
+
+# Download and Install Apache Maven
+Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+Write-Verbose "Downloading from: $distributionUrl"
+Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
+
+$webclient = New-Object System.Net.WebClient
+if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
+ $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
+}
+[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
+
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
+if ($distributionSha256Sum) {
+ if ($USE_MVND) {
+ Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
+ }
+ Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
+ if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
+ Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
+ }
+}
+
+# unzip and move
+Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
+Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null
+try {
+ Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
+} catch {
+ if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
+ Write-Error "fail to move MAVEN_HOME"
+ }
+} finally {
+ try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+ catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+}
+
+Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
diff --git a/model-context-protocol/mcp-webflux-server/pom.xml b/model-context-protocol/mcp-webflux-server/pom.xml
new file mode 100644
index 0000000..9ed6c28
--- /dev/null
+++ b/model-context-protocol/mcp-webflux-server/pom.xml
@@ -0,0 +1,79 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.3.6
+
+
+
+ com.example
+
+ mcp-webflux-server
+ 0.0.1-SNAPSHOT
+
+ Spring AI MCP StdIO and WebFluxSSE Sample
+ Sample Spring Boot application demonstrating MCP server usage
+
+
+ 0.5.0-SNAPSHOT
+
+
+
+
+ org.springframework.experimental
+ mcp-webflux-sse-transport
+ ${spring-ai-mcp.version}
+
+
+
+ org.springframework.experimental
+ spring-ai-mcp
+ ${spring-ai-mcp.version}
+
+
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ repackage
+
+
+
+
+
+ io.spring.javaformat
+ spring-javaformat-maven-plugin
+ 0.0.43
+
+
+ validate
+ true
+
+ validate
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/model-context-protocol/mcp-webflux-server/src/main/java/org/springframework/ai/mcp/sample/client/ClientStdio.java b/model-context-protocol/mcp-webflux-server/src/main/java/org/springframework/ai/mcp/sample/client/ClientStdio.java
new file mode 100644
index 0000000..5760160
--- /dev/null
+++ b/model-context-protocol/mcp-webflux-server/src/main/java/org/springframework/ai/mcp/sample/client/ClientStdio.java
@@ -0,0 +1,43 @@
+/*
+* Copyright 2024 - 2024 the original author or authors.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* https://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package org.springframework.ai.mcp.sample.client;
+
+import java.io.File;
+
+import org.springframework.ai.mcp.client.transport.ServerParameters;
+import org.springframework.ai.mcp.client.transport.StdioClientTransport;
+
+/**
+ * With stdio transport, the MCP server is automatically started by the client. But you
+ * have to build the server jar first: ./mvnw clean install -DskipTests
+ */
+public class ClientStdio {
+
+ public static void main(String[] args) {
+
+ System.out.println(new File(".").getAbsolutePath());
+
+ var stdioParams = ServerParameters.builder("java")
+ .args("-Dtransport.mode=stdio", "-jar",
+ "model-context-protocol/mcp-webflux-server/target/mcp-webflux-server-0.0.1-SNAPSHOT.jar")
+ .build();
+
+ var transport = new StdioClientTransport(stdioParams);
+
+ new SampleClient(transport).run();
+ }
+
+}
diff --git a/model-context-protocol/mcp-webflux-server/src/main/java/org/springframework/ai/mcp/sample/client/ClientWebFluxSse.java b/model-context-protocol/mcp-webflux-server/src/main/java/org/springframework/ai/mcp/sample/client/ClientWebFluxSse.java
new file mode 100644
index 0000000..de0b211
--- /dev/null
+++ b/model-context-protocol/mcp-webflux-server/src/main/java/org/springframework/ai/mcp/sample/client/ClientWebFluxSse.java
@@ -0,0 +1,32 @@
+/*
+* Copyright 2024 - 2024 the original author or authors.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* https://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package org.springframework.ai.mcp.sample.client;
+
+import org.springframework.ai.mcp.client.transport.WebFluxSseClientTransport;
+import org.springframework.web.reactive.function.client.WebClient;
+
+/**
+ * @author Christian Tzolov
+ */
+public class ClientWebFluxSse {
+
+ public static void main(String[] args) {
+ var transport = new WebFluxSseClientTransport(WebClient.builder().baseUrl("http://localhost:8080"));
+
+ new SampleClient(transport).run();
+ }
+
+}
diff --git a/model-context-protocol/mcp-webflux-server/src/main/java/org/springframework/ai/mcp/sample/client/SampleClient.java b/model-context-protocol/mcp-webflux-server/src/main/java/org/springframework/ai/mcp/sample/client/SampleClient.java
new file mode 100644
index 0000000..61e855a
--- /dev/null
+++ b/model-context-protocol/mcp-webflux-server/src/main/java/org/springframework/ai/mcp/sample/client/SampleClient.java
@@ -0,0 +1,87 @@
+/*
+* Copyright 2024 - 2024 the original author or authors.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* https://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package org.springframework.ai.mcp.sample.client;
+
+import java.util.Map;
+
+import org.springframework.ai.mcp.client.McpClient;
+import org.springframework.ai.mcp.spec.ClientMcpTransport;
+import org.springframework.ai.mcp.spec.McpSchema.CallToolRequest;
+import org.springframework.ai.mcp.spec.McpSchema.CallToolResult;
+import org.springframework.ai.mcp.spec.McpSchema.GetPromptRequest;
+import org.springframework.ai.mcp.spec.McpSchema.ListPromptsResult;
+import org.springframework.ai.mcp.spec.McpSchema.ListToolsResult;
+import org.springframework.ai.mcp.spec.McpSchema.ReadResourceRequest;
+
+/**
+ * @author Christian Tzolov
+ */
+
+public class SampleClient {
+
+ private final ClientMcpTransport transport;
+
+ public SampleClient(ClientMcpTransport transport) {
+ this.transport = transport;
+ }
+
+ public void run() {
+
+ var client = McpClient.using(this.transport).sync();
+
+ client.initialize();
+
+ client.ping();
+
+ // List and demonstrate tools
+ ListToolsResult toolsList = client.listTools();
+ System.out.println("Available Tools = " + toolsList);
+
+ CallToolResult weatherResponse = client.callTool(new CallToolRequest("weather", Map.of("city", "Sofia")));
+ System.out.println("Weather Response = " + weatherResponse);
+
+ CallToolResult calcResponse = client
+ .callTool(new CallToolRequest("calculator", Map.of("operation", "multiply", "a", 2.0, "b", 3.0)));
+ System.out.println("Calculator Response = " + calcResponse);
+
+ CallToolResult paymentStatus = client.callTool(
+ new CallToolRequest("paymentTransactionStatus", Map.of("transactionId", "006", "accountName", "John")));
+ System.out.println("Payment Status Response = " + paymentStatus);
+
+ CallToolResult parks = client.callTool(new CallToolRequest("getBooks", Map.of("title", "Spring Framework")));
+ System.out.println("Books Response = " + parks);
+
+ // List and demonstrate resources
+ var resourcesList = client.listResources();
+ System.out.println("\nAvailable Resources = " + resourcesList);
+
+ // Read the system info resource
+ var systemInfo = client.readResource(new ReadResourceRequest("system://info"));
+ System.out.println("System Info = " + systemInfo);
+
+ // List and demonstrate prompts
+ ListPromptsResult promptsList = client.listPrompts();
+ System.out.println("\nAvailable Prompts = " + promptsList);
+
+ // Try the greeting prompt
+ var greetingResponse = client.getPrompt(new GetPromptRequest("greeting", Map.of("name", "Spring")));
+ System.out.println("Greeting Response = " + greetingResponse);
+
+ client.closeGracefully();
+
+ }
+
+}
diff --git a/model-context-protocol/mcp-webflux-server/src/main/java/org/springframework/ai/mcp/sample/server/McpServerApplication.java b/model-context-protocol/mcp-webflux-server/src/main/java/org/springframework/ai/mcp/sample/server/McpServerApplication.java
new file mode 100644
index 0000000..586f8d8
--- /dev/null
+++ b/model-context-protocol/mcp-webflux-server/src/main/java/org/springframework/ai/mcp/sample/server/McpServerApplication.java
@@ -0,0 +1,13 @@
+package org.springframework.ai.mcp.sample.server;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class McpServerApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(McpServerApplication.class, args);
+ }
+
+}
diff --git a/model-context-protocol/mcp-webflux-server/src/main/java/org/springframework/ai/mcp/sample/server/McpServerConfig.java b/model-context-protocol/mcp-webflux-server/src/main/java/org/springframework/ai/mcp/sample/server/McpServerConfig.java
new file mode 100644
index 0000000..de7a95c
--- /dev/null
+++ b/model-context-protocol/mcp-webflux-server/src/main/java/org/springframework/ai/mcp/sample/server/McpServerConfig.java
@@ -0,0 +1,308 @@
+package org.springframework.ai.mcp.sample.server;
+
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.springframework.ai.mcp.server.McpAsyncServer;
+import org.springframework.ai.mcp.server.McpServer;
+import org.springframework.ai.mcp.server.McpServer.PromptRegistration;
+import org.springframework.ai.mcp.server.McpServer.ResourceRegistration;
+import org.springframework.ai.mcp.server.McpServer.ToolRegistration;
+import org.springframework.ai.mcp.server.transport.StdioServerTransport;
+import org.springframework.ai.mcp.server.transport.WebFluxSseServerTransport;
+import org.springframework.ai.mcp.spec.McpSchema;
+import org.springframework.ai.mcp.spec.McpSchema.CallToolResult;
+import org.springframework.ai.mcp.spec.McpSchema.GetPromptResult;
+import org.springframework.ai.mcp.spec.McpSchema.LoggingMessageNotification;
+import org.springframework.ai.mcp.spec.McpSchema.PromptMessage;
+import org.springframework.ai.mcp.spec.McpSchema.Role;
+import org.springframework.ai.mcp.spec.McpSchema.TextContent;
+import org.springframework.ai.mcp.spec.ServerMcpTransport;
+import org.springframework.ai.mcp.spring.ToolHelper;
+import org.springframework.ai.model.function.FunctionCallback;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.client.RestClient;
+import org.springframework.web.reactive.function.server.RouterFunction;
+
+@Configuration
+public class McpServerConfig {
+
+ private static final Logger logger = LoggerFactory.getLogger(McpServerConfig.class);
+
+ // STDIO transport
+ @Bean
+ @ConditionalOnProperty(prefix = "transport", name = "mode", havingValue = "stdio")
+ public StdioServerTransport stdioServerTransport() {
+ return new StdioServerTransport();
+ }
+
+ // SSE transport
+ @Bean
+ @ConditionalOnProperty(prefix = "transport", name = "mode", havingValue = "sse")
+ public WebFluxSseServerTransport sseServerTransport() {
+ return new WebFluxSseServerTransport(new ObjectMapper(), "/mcp/message");
+ }
+
+ // Router function for SSE transport used by Spring WebFlux to start an HTTP
+ // server.
+ @Bean
+ @ConditionalOnProperty(prefix = "transport", name = "mode", havingValue = "sse")
+ public RouterFunction> mcpRouterFunction(WebFluxSseServerTransport transport) {
+ return transport.getRouterFunction();
+ }
+
+ // SSE transport
+ // @Bean
+ // @ConditionalOnProperty(prefix = "transport", name = "mode", havingValue = "sse2")
+ // public HttpServletSseServerTransport sseServerTransport2() {
+ // var httpTransport = new HttpServletSseServerTransport(new ObjectMapper(),
+ // "/mcp/message");
+
+ // // Create and configure Jetty server
+ // Server server = new Server(8080);
+
+ // ServletContextHandler context = new
+ // ServletContextHandler(ServletContextHandler.SESSIONS);
+ // context.setContextPath("/");
+ // server.setHandler(context);
+
+ // // Add our SSE servlet
+ // context.addServlet(new ServletHolder(httpTransport), "/sse");
+
+ // // Start the server
+ // try {
+ // server.start();
+ // System.out.println("Server started on http://localhost:8080/sse");
+ // server.join();
+ // }
+ // catch (Exception e) {
+ // e.printStackTrace();
+ // try {
+ // server.stop();
+ // }
+ // catch (Exception e1) {
+ // e1.printStackTrace();
+ // }
+ // }
+
+ // return httpTransport;
+ // }
+
+ @Bean
+ public McpAsyncServer mcpServer(ServerMcpTransport transport, OpenLibrary openLibrary) { // @formatter:off
+
+ // Configure server capabilities with resource support
+ var capabilities = McpSchema.ServerCapabilities.builder()
+ .resources(false, true) // No subscribe support, but list changes notifications
+ .tools(true) // Tool support with list changes notifications
+ .prompts(true) // Prompt support with list changes notifications
+ .logging() // Logging support
+ .build();
+
+ // Create the server with both tool and resource capabilities
+ var server = McpServer.using(transport)
+ .serverInfo("MCP Demo Server", "1.0.0")
+ .capabilities(capabilities)
+ .resources(systemInfoResourceRegistration())
+ .prompts(greetingPromptRegistration())
+ .tools(calculatorToolRegistration(),
+ ToolHelper.toToolRegistration(
+ FunctionCallback.builder()
+ .method("paymentTransactionStatus",String.class, String.class)
+ .description("Get transaction payment status")
+ .targetClass(McpServerConfig.class)
+ .build()),
+ ToolHelper.toToolRegistration(
+ FunctionCallback.builder()
+ .function("toUpperCase", new Function() {
+ @Override
+ public String apply(String s) {
+ return s.toUpperCase();
+ }
+ })
+ .description("To upper case")
+ .inputType(String.class)
+ .build()))
+ .tools(openLibraryToolRegistrations(openLibrary))
+ .async();
+
+ server.addTool(weatherToolRegistration(server));
+ return server; // @formatter:on
+ } // @formatter:on
+
+ public static List openLibraryToolRegistrations(OpenLibrary openLibrary) {
+
+ var books = FunctionCallback.builder()
+ .method("getBooks", String.class)
+ .description("Get list of Books by title")
+ .targetObject(openLibrary)
+ .build();
+
+ var bookTitlesByAuthor = FunctionCallback.builder()
+ .method("getBookTitlesByAuthor", String.class)
+ .description("Get book titles by author")
+ .targetObject(openLibrary)
+ .build();
+
+ return ToolHelper.toToolRegistration(books, bookTitlesByAuthor);
+ }
+
+ private static ResourceRegistration systemInfoResourceRegistration() {
+
+ // Create a resource registration for system information
+ var systemInfoResource = new McpSchema.Resource( // @formatter:off
+ "system://info",
+ "System Information",
+ "Provides basic system information including Java version, OS, etc.",
+ "application/json", null
+ );
+
+ var resourceRegistration = new ResourceRegistration(systemInfoResource, (request) -> {
+ try {
+ var systemInfo = Map.of(
+ "javaVersion", System.getProperty("java.version"),
+ "osName", System.getProperty("os.name"),
+ "osVersion", System.getProperty("os.version"),
+ "osArch", System.getProperty("os.arch"),
+ "processors", Runtime.getRuntime().availableProcessors(),
+ "timestamp", System.currentTimeMillis());
+
+ String jsonContent = new ObjectMapper().writeValueAsString(systemInfo);
+
+ return new McpSchema.ReadResourceResult(
+ List.of(new McpSchema.TextResourceContents(request.uri(), "application/json", jsonContent)));
+ }
+ catch (Exception e) {
+ throw new RuntimeException("Failed to generate system info", e);
+ }
+ }); // @formatter:on
+
+ return resourceRegistration;
+ }
+
+ private static PromptRegistration greetingPromptRegistration() {
+
+ var prompt = new McpSchema.Prompt("greeting", "A friendly greeting prompt",
+ List.of(new McpSchema.PromptArgument("name", "The name to greet", true)));
+
+ return new PromptRegistration(prompt, getPromptRequest -> {
+
+ String nameArgument = (String) getPromptRequest.arguments().get("name");
+ if (nameArgument == null) {
+ nameArgument = "friend";
+ }
+
+ var userMessage = new PromptMessage(Role.USER,
+ new TextContent("Hello " + nameArgument + "! How can I assist you today?"));
+
+ return new GetPromptResult("A personalized greeting message", List.of(userMessage));
+ });
+ }
+
+ private static ToolRegistration weatherToolRegistration(McpAsyncServer server) {
+ String emptyJsonSchema = """
+ {
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "type": "object",
+ "properties": {"city" : "string"}
+ }
+ """;
+ return new ToolRegistration(new McpSchema.Tool("weather", "Weather forecast tool by location", emptyJsonSchema),
+ (arguments) -> {
+ String city = (String) arguments.get("city");
+
+ // Create the result
+ var result = new CallToolResult(
+ List.of(new TextContent("Weather forecast for " + city + " is sunny")), false);
+
+ // Send the logging notification and ignore its completion
+ server
+ .loggingNotification(LoggingMessageNotification.builder()
+ .data("This is a log message from the weather tool")
+ .build())
+ .subscribe(null, error -> {
+ // Log any errors but don't fail the operation
+ logger.error("Failed to send logging notification", error);
+ });
+
+ return result;
+ });
+ }
+
+ private static ToolRegistration calculatorToolRegistration() {
+ return new ToolRegistration(new McpSchema.Tool("calculator",
+ "Performs basic arithmetic operations (add, subtract, multiply, divide)", """
+ {
+ "type": "object",
+ "properties": {
+ "operation": {
+ "type": "string",
+ "enum": ["add", "subtract", "multiply", "divide"],
+ "description": "The arithmetic operation to perform"
+ },
+ "a": {
+ "type": "number",
+ "description": "First operand"
+ },
+ "b": {
+ "type": "number",
+ "description": "Second operand"
+ }
+ },
+ "required": ["operation", "a", "b"]
+ }
+ """), arguments -> {
+ String operation = (String) arguments.get("operation");
+ double a = (Double) arguments.get("a");
+ double b = (Double) arguments.get("b");
+
+ double result;
+ switch (operation) {
+ case "add":
+ result = a + b;
+ break;
+ case "subtract":
+ result = a - b;
+ break;
+ case "multiply":
+ result = a * b;
+ break;
+ case "divide":
+ if (b == 0) {
+ return new McpSchema.CallToolResult(
+ java.util.List.of(new McpSchema.TextContent("Division by zero")), true);
+ }
+ result = a / b;
+ break;
+ default:
+ return new McpSchema.CallToolResult(
+ java.util.List.of(new McpSchema.TextContent("Unknown operation: " + operation)),
+ true);
+ }
+
+ return new McpSchema.CallToolResult(
+ java.util.List.of(new McpSchema.TextContent(String.valueOf(result))), false);
+ });
+ }
+
+ public static String paymentTransactionStatus(String transactionId, String accountName) {
+ return "The status for " + transactionId + ", by " + accountName + " is PENDING";
+ }
+
+ public Function toUpperCase() {
+ return String::toUpperCase;
+ }
+
+ @Bean
+ public OpenLibrary openLibrary() {
+ return new OpenLibrary(RestClient.builder());
+ }
+
+}
diff --git a/model-context-protocol/mcp-webflux-server/src/main/java/org/springframework/ai/mcp/sample/server/OpenLibrary.java b/model-context-protocol/mcp-webflux-server/src/main/java/org/springframework/ai/mcp/sample/server/OpenLibrary.java
new file mode 100644
index 0000000..7f96573
--- /dev/null
+++ b/model-context-protocol/mcp-webflux-server/src/main/java/org/springframework/ai/mcp/sample/server/OpenLibrary.java
@@ -0,0 +1,74 @@
+/*
+* Copyright 2024 - 2024 the original author or authors.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* https://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package org.springframework.ai.mcp.sample.server;
+
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.web.client.RestClient;
+
+/**
+ * @author Christian Tzolov
+ */
+
+public class OpenLibrary {
+
+ private RestClient restClient;
+
+ public OpenLibrary(RestClient.Builder restClientBuilder) {
+ this.restClient = restClientBuilder.baseUrl("https://openlibrary.org").build();
+ }
+
+ public record Books(Integer numFound, Integer start, Boolean numFoundExact, List