diff --git a/ruoyi-modules-api/ruoyi-chat-api/pom.xml b/ruoyi-modules-api/ruoyi-chat-api/pom.xml
index 633977bc..72fd0d2a 100644
--- a/ruoyi-modules-api/ruoyi-chat-api/pom.xml
+++ b/ruoyi-modules-api/ruoyi-chat-api/pom.xml
@@ -18,12 +18,44 @@
UTF-8
+
+
+
+ org.springframework.ai
+ spring-ai-bom
+ 1.0.0-M6
+ pom
+ import
+
+
+
+
org.ruoyi
ruoyi-common-chat
+
+
+ org.springframework.ai
+ spring-ai-mcp-client-spring-boot-starter
+
+
+
+ org.springframework.ai
+ spring-ai-mcp
+
+
+
+ org.springframework.ai
+ spring-ai-openai-spring-boot-starter
+
+
+
+ org.springframework.ai
+ spring-ai-ollama-spring-boot-starter
+
diff --git a/ruoyi-modules/ruoyi-chat/pom.xml b/ruoyi-modules/ruoyi-chat/pom.xml
index 2a14fb14..5749f4e8 100644
--- a/ruoyi-modules/ruoyi-chat/pom.xml
+++ b/ruoyi-modules/ruoyi-chat/pom.xml
@@ -114,6 +114,20 @@
ruoyi-system-api
+
+ org.springframework.ai
+ spring-ai-core
+ 1.0.0-M6
+ compile
+
+
+
+ org.springframework.ai
+ spring-ai-ollama
+ 1.0.0-M6
+ compile
+
+
diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/IChatService.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/IChatService.java
index 3a6c0aac..1fbadd4b 100644
--- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/IChatService.java
+++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/IChatService.java
@@ -16,4 +16,11 @@ public interface IChatService {
* @param chatRequest 请求对象
*/
SseEmitter chat(ChatRequest chatRequest,SseEmitter emitter);
+
+
+ /**
+ * 客户端发送消息到服务端
+ * @param chatRequest 请求对象
+ */
+ SseEmitter mcpChat(ChatRequest chatRequest,SseEmitter emitter);
}
diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/OllamaServiceImpl.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/OllamaServiceImpl.java
index 7537e0b1..8b337b24 100644
--- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/OllamaServiceImpl.java
+++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/OllamaServiceImpl.java
@@ -6,8 +6,6 @@ import io.github.ollama4j.models.chat.OllamaChatMessageRole;
import io.github.ollama4j.models.chat.OllamaChatRequestBuilder;
import io.github.ollama4j.models.chat.OllamaChatRequestModel;
import io.github.ollama4j.models.generate.OllamaStreamHandler;
-import jakarta.servlet.http.HttpServletRequest;
-import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.ruoyi.chat.service.chat.IChatService;
import org.ruoyi.chat.util.SSEUtil;
@@ -15,7 +13,16 @@ import org.ruoyi.common.chat.entity.chat.Message;
import org.ruoyi.common.chat.request.ChatRequest;
import org.ruoyi.domain.vo.ChatModelVo;
import org.ruoyi.service.IChatModelService;
+import org.springframework.ai.chat.client.ChatClient;
+import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
+import org.springframework.ai.chat.memory.ChatMemory;
+import org.springframework.ai.chat.memory.InMemoryChatMemory;
+import org.springframework.ai.chat.messages.UserMessage;
+import org.springframework.ai.ollama.api.OllamaModel;
+import org.springframework.ai.ollama.api.OllamaOptions;
+import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
@@ -32,6 +39,13 @@ public class OllamaServiceImpl implements IChatService {
@Autowired
private IChatModelService chatModelService;
+ @Autowired
+ private ChatClient chatClient;
+ @Autowired
+ private ToolCallbackProvider tools;
+
+ private final ChatMemory chatMemory = new InMemoryChatMemory();
+
@Override
public SseEmitter chat(ChatRequest chatRequest,SseEmitter emitter) {
@@ -77,4 +91,50 @@ public class OllamaServiceImpl implements IChatService {
return emitter;
}
+
+ @Override
+ public SseEmitter mcpChat(ChatRequest chatRequest, SseEmitter emitter) {
+ List msgList = chatRequest.getMessages();
+ // 添加记忆
+ for (int i = 0; i < msgList.size(); i++) {
+ org.springframework.ai.chat.messages.Message springAiMessage = new UserMessage(msgList.get(i).getContent().toString());
+ chatMemory.add(String.valueOf(i),springAiMessage);
+ }
+ var messageChatMemoryAdvisor = new MessageChatMemoryAdvisor(chatMemory, chatRequest.getUserId(), 10);
+
+ this.chatClient.prompt(chatRequest.getPrompt())
+ .advisors(messageChatMemoryAdvisor)
+ .tools(tools)
+ .options(OllamaOptions.builder()
+ .model(OllamaModel.QWEN_2_5_7B)
+ .temperature(0.4)
+ .build())
+ .stream()
+ .chatResponse()
+ .subscribe(
+ chatResponse -> {
+ try {
+ emitter.send(chatResponse, MediaType.APPLICATION_JSON);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ },
+ error -> {
+ try {
+ emitter.completeWithError(error);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ },
+ () -> {
+ try {
+ emitter.complete();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ );
+
+ return emitter;
+ }
}
diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/OpenAIServiceImpl.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/OpenAIServiceImpl.java
index b9d2012d..ebf37e13 100644
--- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/OpenAIServiceImpl.java
+++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/OpenAIServiceImpl.java
@@ -5,14 +5,29 @@ import org.ruoyi.chat.config.ChatConfig;
import org.ruoyi.chat.listener.SSEEventSourceListener;
import org.ruoyi.chat.service.chat.IChatService;
import org.ruoyi.common.chat.entity.chat.ChatCompletion;
+import org.ruoyi.common.chat.entity.chat.Message;
import org.ruoyi.common.chat.openai.OpenAiStreamClient;
import org.ruoyi.common.chat.request.ChatRequest;
import org.ruoyi.domain.vo.ChatModelVo;
import org.ruoyi.service.IChatModelService;
+import org.springframework.ai.chat.client.ChatClient;
+import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
+import org.springframework.ai.chat.memory.ChatMemory;
+import org.springframework.ai.chat.memory.InMemoryChatMemory;
+import org.springframework.ai.chat.messages.UserMessage;
+import org.springframework.ai.openai.OpenAiChatOptions;
+import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * @author ageer
+ */
@Service
@Slf4j
public class OpenAIServiceImpl implements IChatService {
@@ -23,6 +38,13 @@ public class OpenAIServiceImpl implements IChatService {
private ChatConfig chatConfig;
@Autowired
private OpenAiStreamClient openAiStreamClient;
+ @Autowired
+ private ChatClient chatClient;
+
+ @Autowired
+ private ToolCallbackProvider tools;
+
+ private final ChatMemory chatMemory = new InMemoryChatMemory();
@Override
public SseEmitter chat(ChatRequest chatRequest,SseEmitter emitter) {
@@ -47,4 +69,46 @@ public class OpenAIServiceImpl implements IChatService {
return emitter;
}
+
+ @Override
+ public SseEmitter mcpChat(ChatRequest chatRequest, SseEmitter emitter) {
+ List msgList = chatRequest.getMessages();
+ // 添加记忆
+ for (int i = 0; i < msgList.size(); i++) {
+ org.springframework.ai.chat.messages.Message springAiMessage = new UserMessage(msgList.get(i).getContent().toString());
+ chatMemory.add(String.valueOf(i),springAiMessage);
+ }
+ var messageChatMemoryAdvisor = new MessageChatMemoryAdvisor(chatMemory, chatRequest.getUserId(), 10);
+ this.chatClient.prompt(chatRequest.getPrompt())
+ .advisors(messageChatMemoryAdvisor)
+ .tools(tools)
+ .options(OpenAiChatOptions.builder().model(chatRequest.getModel()).build())
+ .stream()
+ .chatResponse()
+ .subscribe(
+ chatResponse -> {
+ try {
+ emitter.send(chatResponse, MediaType.APPLICATION_JSON);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ },
+ error -> {
+ try {
+ emitter.completeWithError(error);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ },
+ () -> {
+ try {
+ emitter.complete();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ );
+
+ return emitter;
+ }
}
diff --git a/ruoyi-modules/ruoyi-mcp-server/pom.xml b/ruoyi-modules/ruoyi-mcp-server/pom.xml
new file mode 100644
index 00000000..113e41c1
--- /dev/null
+++ b/ruoyi-modules/ruoyi-mcp-server/pom.xml
@@ -0,0 +1,63 @@
+
+
+ 4.0.0
+
+ org.ruoyi
+ ruoyi-modules
+ ${revision}
+ ../pom.xml
+
+
+ ruoyi-mcp-server
+
+
+ 17
+ 17
+ UTF-8
+
+
+
+
+
+ org.springframework.ai
+ spring-ai-bom
+ 1.0.0-M6
+ pom
+ import
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.springframework.ai
+ spring-ai-mcp-server-webmvc-spring-boot-starter
+
+
+
+ org.springframework.ai
+ spring-ai-mcp
+
+
+
+
+
+
diff --git a/ruoyi-modules/ruoyi-mcp-server/src/main/java/org/ruoyi/mcp/McpServerApplication.java b/ruoyi-modules/ruoyi-mcp-server/src/main/java/org/ruoyi/mcp/McpServerApplication.java
new file mode 100644
index 00000000..bf6b0ff1
--- /dev/null
+++ b/ruoyi-modules/ruoyi-mcp-server/src/main/java/org/ruoyi/mcp/McpServerApplication.java
@@ -0,0 +1,16 @@
+package org.ruoyi.mcp;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * @author ageer
+ */
+@SpringBootApplication
+public class McpServerApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(McpServerApplication.class, args);
+ }
+
+}
diff --git a/ruoyi-modules/ruoyi-mcp-server/src/main/java/org/ruoyi/mcp/config/McpServerConfig.java b/ruoyi-modules/ruoyi-mcp-server/src/main/java/org/ruoyi/mcp/config/McpServerConfig.java
new file mode 100644
index 00000000..4ba45e10
--- /dev/null
+++ b/ruoyi-modules/ruoyi-mcp-server/src/main/java/org/ruoyi/mcp/config/McpServerConfig.java
@@ -0,0 +1,100 @@
+package org.ruoyi.mcp.config;
+
+import org.ruoyi.mcp.service.McpCustomService;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.modelcontextprotocol.server.McpServerFeatures;
+import io.modelcontextprotocol.spec.McpSchema;
+import org.springframework.ai.tool.ToolCallbackProvider;
+import org.springframework.ai.tool.method.MethodToolCallbackProvider;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+
+
+/**
+ * @author ageer
+ */
+@Configuration
+@EnableWebMvc
+public class McpServerConfig implements WebMvcConfigurer {
+
+ @Bean
+ public ToolCallbackProvider openLibraryTools(McpCustomService mcpService) {
+ return MethodToolCallbackProvider.builder().toolObjects(mcpService).build();
+ }
+
+ @Bean
+ public List resourceRegistrations() {
+
+ // Create a resource registration for system information
+ var systemInfoResource = new McpSchema.Resource(
+ "system://info",
+ "System Information",
+ "Provides basic system information including Java version, OS, etc.",
+ "application/json", null
+ );
+
+ var resourceRegistration = new McpServerFeatures.SyncResourceRegistration(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);
+ }
+ });
+
+ return List.of(resourceRegistration);
+ }
+
+
+
+ @Bean
+ public List promptRegistrations() {
+
+ var prompt = new McpSchema.Prompt("greeting", "A friendly greeting prompt",
+ List.of(new McpSchema.PromptArgument("name", "The name to greet", true)));
+
+ var promptRegistration = new McpServerFeatures.SyncPromptRegistration(prompt, getPromptRequest -> {
+
+ String nameArgument = (String) getPromptRequest.arguments().get("name");
+ if (nameArgument == null) {
+ nameArgument = "friend";
+ }
+
+ var userMessage = new McpSchema.PromptMessage(McpSchema.Role.USER,
+ new McpSchema.TextContent("Hello " + nameArgument + "! How can I assist you today?"));
+
+ return new McpSchema.GetPromptResult("A personalized greeting message", List.of(userMessage));
+ });
+
+ return List.of(promptRegistration);
+ }
+
+
+ @Bean
+ public Consumer> rootsChangeConsumer() {
+ return roots -> {
+ System.out.println("rootsChange");
+ };
+ }
+
+
+
+
+}
diff --git a/ruoyi-modules/ruoyi-mcp-server/src/main/java/org/ruoyi/mcp/service/McpCustomService.java b/ruoyi-modules/ruoyi-mcp-server/src/main/java/org/ruoyi/mcp/service/McpCustomService.java
new file mode 100644
index 00000000..f5f34210
--- /dev/null
+++ b/ruoyi-modules/ruoyi-mcp-server/src/main/java/org/ruoyi/mcp/service/McpCustomService.java
@@ -0,0 +1,24 @@
+package org.ruoyi.mcp.service;
+
+import org.springframework.ai.tool.annotation.Tool;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * @author ageer
+ */
+@Service
+public class McpCustomService {
+
+
+ public record Book(List isbn, String title, List authorName) {
+ }
+
+ @Tool(description = "Get list of Books by title")
+ public List getBooks(String title) {
+ // 这里模拟查询DB操作
+ return List.of(new Book(List.of("ISBN-888"), "SpringAI教程", List.of("熊猫助手写的书")));
+ }
+
+}
diff --git a/ruoyi-modules/ruoyi-mcp-server/src/main/resources/application.yaml b/ruoyi-modules/ruoyi-mcp-server/src/main/resources/application.yaml
new file mode 100644
index 00000000..57f9979d
--- /dev/null
+++ b/ruoyi-modules/ruoyi-mcp-server/src/main/resources/application.yaml
@@ -0,0 +1,10 @@
+spring:
+ application:
+ name: mcp-server
+ ai:
+ mcp:
+ server:
+ name: webmvc-mcp-server
+ version: 1.0.0
+ type: SYNC
+ sse-message-endpoint: /mcp/messages
diff --git a/ruoyi-modules/ruoyi-system/src/main/resources/application.yml b/ruoyi-modules/ruoyi-system/src/main/resources/application.yml
index 26173564..a414d371 100644
--- a/ruoyi-modules/ruoyi-system/src/main/resources/application.yml
+++ b/ruoyi-modules/ruoyi-system/src/main/resources/application.yml
@@ -318,5 +318,22 @@ wechat:
token: ''
aesKey: ''
-
-
+ # spring ai配置
+spring:
+ ai:
+ openai:
+ api-key: sk-xx
+ base-url: https://api.pandarobot.chat/
+ mcp:
+ client:
+ enabled: true
+ name: call-mcp-server
+ sse:
+ connections:
+ server1:
+ url: http://127.0.0.1:8080
+ ollama:
+ init:
+ pull-model-strategy: always
+ timeout: 60s
+ max-retries: 1