From e9d54ab9e50e6550f558b75e60fbc5908b5d0d4c Mon Sep 17 00:00:00 2001 From: parthiv39731 <70740707+parthiv39731@users.noreply.github.com> Date: Mon, 12 Jan 2026 18:44:52 +0530 Subject: [PATCH] commit mcp client and server (#37) --- .gitignore | 3 + springboot-modules/pom.xml | 7 +- .../spring-ai-mcp/mcp-client/pom.xml | 32 +++++++++ .../ks/mcp/McpSpringClientApplication.java | 13 ++++ .../ks/mcp/config/McpClientConfiguration.java | 72 +++++++++++++++++++ .../src/main/resources/application.yaml | 30 ++++++++ .../com/ks/mcp/McpToolsIntegrationTest.java | 57 +++++++++++++++ .../spring-ai-mcp/mcp-server/pom.xml | 31 ++++++++ .../ks/mcp/NodeManagementMcpServerApp.java | 13 ++++ .../ks/mcp/service/NodeManagementService.java | 64 +++++++++++++++++ .../ks/mcp/service/NodeManagementTools.java | 50 +++++++++++++ .../src/main/resources/application.yaml | 17 +++++ springboot-modules/spring-ai-mcp/pom.xml | 37 ++++++++++ 13 files changed, 423 insertions(+), 3 deletions(-) create mode 100644 .gitignore create mode 100644 springboot-modules/spring-ai-mcp/mcp-client/pom.xml create mode 100644 springboot-modules/spring-ai-mcp/mcp-client/src/main/java/com/ks/mcp/McpSpringClientApplication.java create mode 100644 springboot-modules/spring-ai-mcp/mcp-client/src/main/java/com/ks/mcp/config/McpClientConfiguration.java create mode 100644 springboot-modules/spring-ai-mcp/mcp-client/src/main/resources/application.yaml create mode 100644 springboot-modules/spring-ai-mcp/mcp-client/src/test/java/com/ks/mcp/McpToolsIntegrationTest.java create mode 100644 springboot-modules/spring-ai-mcp/mcp-server/pom.xml create mode 100644 springboot-modules/spring-ai-mcp/mcp-server/src/main/java/com/ks/mcp/NodeManagementMcpServerApp.java create mode 100644 springboot-modules/spring-ai-mcp/mcp-server/src/main/java/com/ks/mcp/service/NodeManagementService.java create mode 100644 springboot-modules/spring-ai-mcp/mcp-server/src/main/java/com/ks/mcp/service/NodeManagementTools.java create mode 100644 springboot-modules/spring-ai-mcp/mcp-server/src/main/resources/application.yaml create mode 100644 springboot-modules/spring-ai-mcp/pom.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..32ff954 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +# Maven build output +target/ +.vscode/ \ No newline at end of file diff --git a/springboot-modules/pom.xml b/springboot-modules/pom.xml index 479a4c8..a304363 100644 --- a/springboot-modules/pom.xml +++ b/springboot-modules/pom.xml @@ -1,7 +1,7 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 com.kodesastra @@ -34,6 +34,7 @@ spring-ai-qdrant spring-ai + spring-ai-mcp @@ -58,4 +59,4 @@ 1.0.0-M8 - + \ No newline at end of file diff --git a/springboot-modules/spring-ai-mcp/mcp-client/pom.xml b/springboot-modules/spring-ai-mcp/mcp-client/pom.xml new file mode 100644 index 0000000..f7d0899 --- /dev/null +++ b/springboot-modules/spring-ai-mcp/mcp-client/pom.xml @@ -0,0 +1,32 @@ + + + 4.0.0 + + com.kodesastra + spring-ai-mcp + 1.0-SNAPSHOT + + + com.kodesastra + mcp-client + 1.0-SNAPSHOT + + 21 + 1.1.2 + + + + + org.springframework.ai + spring-ai-starter-mcp-client + + + + org.springframework.ai + spring-ai-starter-model-azure-openai + + + + \ No newline at end of file diff --git a/springboot-modules/spring-ai-mcp/mcp-client/src/main/java/com/ks/mcp/McpSpringClientApplication.java b/springboot-modules/spring-ai-mcp/mcp-client/src/main/java/com/ks/mcp/McpSpringClientApplication.java new file mode 100644 index 0000000..b03adc0 --- /dev/null +++ b/springboot-modules/spring-ai-mcp/mcp-client/src/main/java/com/ks/mcp/McpSpringClientApplication.java @@ -0,0 +1,13 @@ +package com.ks.mcp; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class McpSpringClientApplication { + + public static void main(String[] args) { + SpringApplication.run(McpSpringClientApplication.class, args); + } + +} diff --git a/springboot-modules/spring-ai-mcp/mcp-client/src/main/java/com/ks/mcp/config/McpClientConfiguration.java b/springboot-modules/spring-ai-mcp/mcp-client/src/main/java/com/ks/mcp/config/McpClientConfiguration.java new file mode 100644 index 0000000..0fcc43b --- /dev/null +++ b/springboot-modules/spring-ai-mcp/mcp-client/src/main/java/com/ks/mcp/config/McpClientConfiguration.java @@ -0,0 +1,72 @@ +package com.ks.mcp.config; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ai.azure.openai.AzureOpenAiChatModel; +import org.springframework.ai.azure.openai.AzureOpenAiChatOptions; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.tool.ToolCallbackProvider; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.azure.ai.openai.OpenAIClientBuilder; +import com.azure.core.credential.AzureKeyCredential; + +import io.modelcontextprotocol.client.McpSyncClient; +import io.modelcontextprotocol.spec.McpSchema.Tool; + +@Configuration +public class McpClientConfiguration { + final Logger logger = LoggerFactory.getLogger(McpClientConfiguration.class); + + @Value("${spring.ai.azure.openai.api-key}") + private String apiKey; + @Value("${spring.ai.azure.openai.endpoint}") + private String endpoint; + + @Value("${spring.ai.azure.openai.chat.options.deployment-name}") + private String deploymentName; + @Value("${spring.ai.azure.openai.chat.options.temperature}") + private String temperature; + + @Autowired + private ToolCallbackProvider toolCallbackProvider; + + private Set getTools(List mcpSyncClients) { + final String MONITORING_TOOL_NAME = "event-resolver-client - event-resolver-mcp"; + + return mcpSyncClients.stream() + .filter(e -> e.getClientInfo().name().equalsIgnoreCase(MONITORING_TOOL_NAME)) + .map(e -> e.listTools().tools()) + .flatMap(List::stream) + .map(Tool::name) + .collect(Collectors.toSet()); + } + + @Bean + public ChatModel chatClient(@Autowired List mcpSyncClients) { + AzureOpenAiChatOptions azureOpenAiChatOptions = AzureOpenAiChatOptions.builder() + .deploymentName(deploymentName) + .toolNames(getTools(mcpSyncClients)) + .toolCallbacks(toolCallbackProvider.getToolCallbacks()) + .temperature(Double.parseDouble(temperature)) + .build(); + + OpenAIClientBuilder openAIClientBuilder = new OpenAIClientBuilder() + .credential(new AzureKeyCredential(apiKey)) + .endpoint(endpoint); + + AzureOpenAiChatModel chatModel = AzureOpenAiChatModel.builder() + .openAIClientBuilder(openAIClientBuilder) + .defaultOptions(azureOpenAiChatOptions) + .build(); + + return chatModel; + } +} diff --git a/springboot-modules/spring-ai-mcp/mcp-client/src/main/resources/application.yaml b/springboot-modules/spring-ai-mcp/mcp-client/src/main/resources/application.yaml new file mode 100644 index 0000000..9213d8d --- /dev/null +++ b/springboot-modules/spring-ai-mcp/mcp-client/src/main/resources/application.yaml @@ -0,0 +1,30 @@ +spring: + ai: + azure: + openai: + api-key: FSP-xxxx + endpoint: https://xxxxx-eastus2.cognitiveservices.azure.com/ + chat: + options: + deployment-name: gpt-4.1 + temperature: 1 + + mcp: + client: + name: event-resolver-client + version: 1.0.0 + enabled: true + type: SYNC + toolcallback: + enabled: true + sse: + connections: + event-resolver-mcp: + url: http://localhost:8080 + sse-endpoint: /mcp/sse + sse-message-endpoint: /mcp/messages + +logging: + level: + root: INFO + com.ks.mcp.service: DEBUG \ No newline at end of file diff --git a/springboot-modules/spring-ai-mcp/mcp-client/src/test/java/com/ks/mcp/McpToolsIntegrationTest.java b/springboot-modules/spring-ai-mcp/mcp-client/src/test/java/com/ks/mcp/McpToolsIntegrationTest.java new file mode 100644 index 0000000..0863dbe --- /dev/null +++ b/springboot-modules/spring-ai-mcp/mcp-client/src/test/java/com/ks/mcp/McpToolsIntegrationTest.java @@ -0,0 +1,57 @@ +package com.ks.mcp; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.messages.SystemMessage; +import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest + +class McpToolsIntegrationTest { + + final Logger logger = LoggerFactory.getLogger(McpToolsIntegrationTest.class); + + @Autowired + private ChatModel chatClient; + + @Test + void whenNginxServiceDownThenRestartService() { + String action = """ + Event: + Nginx service on node 'staging-api-01' is unresponsive. + + Action: + 1. Check if the node is reachable + 2. If the node is in Available state Restart the Nginx service on the node + 3. Send a notification with the final status of the node to node operators + + Constraint: + Restart the service not more than two times. + """; + String systemMessageText = """ + **MANDATORY PROTOCOL:** + 1. NO recommendations, analysis, or speculation + 2. Strictly use the tools available + 3. Respond with EXACT tool output + status summary + """; + + Message systemMessage = new SystemMessage(systemMessageText); + Message userMessage = new UserMessage(action); + + String response = chatClient.call(systemMessage, userMessage); + + logger.info("Restart Node request Response: {}", response); + + assertThat(response) + .contains("staging-api-01") + .contains("restart"); + } + +} diff --git a/springboot-modules/spring-ai-mcp/mcp-server/pom.xml b/springboot-modules/spring-ai-mcp/mcp-server/pom.xml new file mode 100644 index 0000000..cf615ad --- /dev/null +++ b/springboot-modules/spring-ai-mcp/mcp-server/pom.xml @@ -0,0 +1,31 @@ + + + 4.0.0 + + com.kodesastra + spring-ai-mcp + 1.0-SNAPSHOT + + + com.kodesastra + mcp-server + 1.0-SNAPSHOT + + + UTF-8 + 21 + ${java.version} + ${java.version} + 1.1.2 + + + + + org.springframework.ai + spring-ai-starter-mcp-server-webmvc + + + + \ No newline at end of file diff --git a/springboot-modules/spring-ai-mcp/mcp-server/src/main/java/com/ks/mcp/NodeManagementMcpServerApp.java b/springboot-modules/spring-ai-mcp/mcp-server/src/main/java/com/ks/mcp/NodeManagementMcpServerApp.java new file mode 100644 index 0000000..f4e294f --- /dev/null +++ b/springboot-modules/spring-ai-mcp/mcp-server/src/main/java/com/ks/mcp/NodeManagementMcpServerApp.java @@ -0,0 +1,13 @@ +package com.ks.mcp; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class NodeManagementMcpServerApp { + + public static void main(String[] args) { + SpringApplication.run(NodeManagementMcpServerApp.class, args); + } + +} diff --git a/springboot-modules/spring-ai-mcp/mcp-server/src/main/java/com/ks/mcp/service/NodeManagementService.java b/springboot-modules/spring-ai-mcp/mcp-server/src/main/java/com/ks/mcp/service/NodeManagementService.java new file mode 100644 index 0000000..cdb069d --- /dev/null +++ b/springboot-modules/spring-ai-mcp/mcp-server/src/main/java/com/ks/mcp/service/NodeManagementService.java @@ -0,0 +1,64 @@ +package com.ks.mcp.service; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +@Service +public class NodeManagementService { + + final Logger logger = LoggerFactory.getLogger(NodeManagementService.class); + + public String getNodeDetails(String nodeId) { + logger.info("getNodeDetails called for nodeId: {}", nodeId); + return String.format(""" + Node Details for %s: + - Status: Available + - CPU Usage: 45%% + - Memory: 2.3GB/8GB + - Uptime: 23h 45m + - Last Check: 2026-01-07T20:29:00Z + """, nodeId); + } + + public String performBasicCheck(String nodeId, String checkType) { + logger.info("performBasicCheck called for nodeId: {}", nodeId); + return String.format(""" + Basic Check Results for %s: + - Ping: SUCCESS (12ms) + - SSH Port 22: OPEN + - Disk Space: 78%% used + - Overall: HEALTHY + """, nodeId); + } + + public String fetchServiceStatus(String nodeId, String serviceName) { + logger.info("fetchServiceStatus called for nodeId: {} and serviceName: {}", nodeId, serviceName); + return String.format("Service %s status: STOPPED", serviceName); + } + + public String restartNodeService(String nodeId, String serviceName) { + logger.info("restartNodeService called for nodeId: {} and serviceName: {}", nodeId, serviceName); + + return String.format(""" + Service Restart Initiated: + - Node: %s + - Service: %s + - Action: RESTART + - Status: SUCCESSFULL + """, nodeId, serviceName); + } + + public String sendNotification(String nodeId, String message, String priority) { + logger.info("sendNotification called for nodeId: {} and message: {}", nodeId, message); + String level = priority != null ? priority : "MEDIUM"; + return String.format(""" + Notification Sent: + - Node: %s + - Priority: %s + - Message: %s + - Channels: Slack, Email, PagerDuty + - Status: DELIVERED + """, nodeId, level, message); + } +} diff --git a/springboot-modules/spring-ai-mcp/mcp-server/src/main/java/com/ks/mcp/service/NodeManagementTools.java b/springboot-modules/spring-ai-mcp/mcp-server/src/main/java/com/ks/mcp/service/NodeManagementTools.java new file mode 100644 index 0000000..0d050e4 --- /dev/null +++ b/springboot-modules/spring-ai-mcp/mcp-server/src/main/java/com/ks/mcp/service/NodeManagementTools.java @@ -0,0 +1,50 @@ +package com.ks.mcp.service; + +import org.springaicommunity.mcp.annotation.McpTool; +import org.springaicommunity.mcp.annotation.McpToolParam; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class NodeManagementTools { + @Autowired + private NodeManagementService nodeManagementService; + + @McpTool(name = "GetNodeDetails", description = """ + Retrieve detailed information about a specific node including status, + configuration, and metrics + """) + public String getNodeDetails( + @McpToolParam(description = "Node identifier (ID or hostname)", required = true) String nodeId) { + return nodeManagementService.getNodeDetails(nodeId); + } + + @McpTool(name = "performBasicCheck", description = "Perform health and connectivity checks on a node without making changes") + public String performBasicCheck( + @McpToolParam(description = "Node identifier", required = true) String nodeId, + @McpToolParam(description = "Optional specific check type (ping, port, service)") String checkType) { + return nodeManagementService.performBasicCheck(nodeId, checkType); + } + + @McpTool(name = "fetchServiceStatus", description = "Fetch the status of a specific service on the target node.") + public String fetchServiceStatus( + @McpToolParam(description = "Node identifier", required = true) String nodeId, + @McpToolParam(description = "Service name to fetch status for", required = true) String serviceName) { + return nodeManagementService.fetchServiceStatus(nodeId, serviceName); + } + + @McpTool(name = "restartNodeService", description = "Restart a specific service on the target node.") + public String restartNodeService( + @McpToolParam(description = "Node identifier", required = true) String nodeId, + @McpToolParam(description = "Service name to restart", required = true) String serviceName) { + return nodeManagementService.restartNodeService(nodeId, serviceName); + } + + @McpTool(name = "sendNotification", description = "Send notification to node operators or monitoring systems") + public String sendNotification( + @McpToolParam(description = "Node identifier", required = true) String nodeId, + @McpToolParam(description = "Notification message", required = true) String message, + @McpToolParam(description = "Priority level (LOW, MEDIUM, HIGH, CRITICAL)", required = false) String priority) { + return nodeManagementService.sendNotification(nodeId, message, priority); + } +} diff --git a/springboot-modules/spring-ai-mcp/mcp-server/src/main/resources/application.yaml b/springboot-modules/spring-ai-mcp/mcp-server/src/main/resources/application.yaml new file mode 100644 index 0000000..ff53a21 --- /dev/null +++ b/springboot-modules/spring-ai-mcp/mcp-server/src/main/resources/application.yaml @@ -0,0 +1,17 @@ +spring: + ai: + mcp: + server: + enabled: true + type: SYNC + stdio: false + sse-message-endpoint: /mcp/messages + sse-endpoint: /mcp/sse + version: 2024-11-05 + annotation-scanner: + enabled: true + +logging: + level: + io.modelcontextprotocol.server: DEBUG + org.springframework.ai.mcp: DEBUG diff --git a/springboot-modules/spring-ai-mcp/pom.xml b/springboot-modules/spring-ai-mcp/pom.xml new file mode 100644 index 0000000..0f9881f --- /dev/null +++ b/springboot-modules/spring-ai-mcp/pom.xml @@ -0,0 +1,37 @@ + + + 4.0.0 + + com.kodesastra + springboot-modules + 1.0-SNAPSHOT + + + com.kodesastra + spring-ai-mcp + 1.0-SNAPSHOT + pom + + mcp-client + mcp-server + + + + 1.1.2 + + + + + + org.springframework.ai + spring-ai-bom + ${spring-ai.version} + pom + import + + + + + \ No newline at end of file