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