From 9b32e3b3b226723cd43bfa37026e1e4487f6ef69 Mon Sep 17 00:00:00 2001 From: ageer <ageerle@163.com> Date: 星期六, 12 四月 2025 10:08:51 +0800 Subject: [PATCH] feat: mcp支持 --- ruoyi-modules/ruoyi-mcp-server/pom.xml | 63 +++++++++ ruoyi-modules/ruoyi-mcp-server/src/main/java/org/ruoyi/mcp/McpServerApplication.java | 16 ++ ruoyi-modules/ruoyi-chat/pom.xml | 14 ++ ruoyi-modules-api/ruoyi-chat-api/pom.xml | 32 ++++ ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/OllamaServiceImpl.java | 64 ++++++++ ruoyi-modules/ruoyi-mcp-server/src/main/resources/application.yaml | 10 + ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/IChatService.java | 7 + ruoyi-modules/ruoyi-mcp-server/src/main/java/org/ruoyi/mcp/config/McpServerConfig.java | 100 ++++++++++++++ ruoyi-modules/ruoyi-mcp-server/src/main/java/org/ruoyi/mcp/service/McpCustomService.java | 24 +++ ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/OpenAIServiceImpl.java | 64 +++++++++ ruoyi-modules/ruoyi-system/src/main/resources/application.yml | 21 ++ 11 files changed, 411 insertions(+), 4 deletions(-) diff --git a/ruoyi-modules-api/ruoyi-chat-api/pom.xml b/ruoyi-modules-api/ruoyi-chat-api/pom.xml index 633977b..72fd0d2 100644 --- a/ruoyi-modules-api/ruoyi-chat-api/pom.xml +++ b/ruoyi-modules-api/ruoyi-chat-api/pom.xml @@ -18,12 +18,44 @@ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> + <dependencyManagement> + <dependencies> + <dependency> + <groupId>org.springframework.ai</groupId> + <artifactId>spring-ai-bom</artifactId> + <version>1.0.0-M6</version> + <type>pom</type> + <scope>import</scope> + </dependency> + </dependencies> + </dependencyManagement> + <!-- 瀵硅瘽鍩虹妯″潡 --> <dependencies> <dependency> <groupId>org.ruoyi</groupId> <artifactId>ruoyi-common-chat</artifactId> </dependency> + + <dependency> + <groupId>org.springframework.ai</groupId> + <artifactId>spring-ai-mcp-client-spring-boot-starter</artifactId> + </dependency> + + <dependency> + <groupId>org.springframework.ai</groupId> + <artifactId>spring-ai-mcp</artifactId> + </dependency> + + <dependency> + <groupId>org.springframework.ai</groupId> + <artifactId>spring-ai-openai-spring-boot-starter</artifactId> + </dependency> + + <dependency> + <groupId>org.springframework.ai</groupId> + <artifactId>spring-ai-ollama-spring-boot-starter</artifactId> + </dependency> </dependencies> </project> diff --git a/ruoyi-modules/ruoyi-chat/pom.xml b/ruoyi-modules/ruoyi-chat/pom.xml index 2a14fb1..5749f4e 100644 --- a/ruoyi-modules/ruoyi-chat/pom.xml +++ b/ruoyi-modules/ruoyi-chat/pom.xml @@ -114,6 +114,20 @@ <artifactId>ruoyi-system-api</artifactId> </dependency> + <dependency> + <groupId>org.springframework.ai</groupId> + <artifactId>spring-ai-core</artifactId> + <version>1.0.0-M6</version> + <scope>compile</scope> + </dependency> + + <dependency> + <groupId>org.springframework.ai</groupId> + <artifactId>spring-ai-ollama</artifactId> + <version>1.0.0-M6</version> + <scope>compile</scope> + </dependency> + </dependencies> </project> 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 3a6c0aa..1fbadd4 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 @@ * @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 7537e0b..8b337b2 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.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.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 @@ @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 @@ return emitter; } + + @Override + public SseEmitter mcpChat(ChatRequest chatRequest, SseEmitter emitter) { + List<Message> 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 b9d2012..ebf37e1 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.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 @@ 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 @@ return emitter; } + + @Override + public SseEmitter mcpChat(ChatRequest chatRequest, SseEmitter emitter) { + List<Message> 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 0000000..113e41c --- /dev/null +++ b/ruoyi-modules/ruoyi-mcp-server/pom.xml @@ -0,0 +1,63 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + 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"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.ruoyi</groupId> + <artifactId>ruoyi-modules</artifactId> + <version>${revision}</version> + <relativePath>../pom.xml</relativePath> + </parent> + + <artifactId>ruoyi-mcp-server</artifactId> + + <properties> + <maven.compiler.source>17</maven.compiler.source> + <maven.compiler.target>17</maven.compiler.target> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + </properties> + + <dependencyManagement> + <dependencies> + <dependency> + <groupId>org.springframework.ai</groupId> + <artifactId>spring-ai-bom</artifactId> + <version>1.0.0-M6</version> + <type>pom</type> + <scope>import</scope> + </dependency> + </dependencies> + </dependencyManagement> + + <dependencies> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter</artifactId> + </dependency> + + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-test</artifactId> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-web</artifactId> + </dependency> + + <dependency> + <groupId>org.springframework.ai</groupId> + <artifactId>spring-ai-mcp-server-webmvc-spring-boot-starter</artifactId> + </dependency> + + <dependency> + <groupId>org.springframework.ai</groupId> + <artifactId>spring-ai-mcp</artifactId> + </dependency> + + </dependencies> + + +</project> 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 0000000..bf6b0ff --- /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 0000000..4ba45e1 --- /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<McpServerFeatures.SyncResourceRegistration> 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<McpServerFeatures.SyncPromptRegistration> 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<List<McpSchema.Root>> 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 0000000..f5f3421 --- /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<String> isbn, String title, List<String> authorName) { + } + + @Tool(description = "Get list of Books by title") + public List<Book> 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 0000000..57f9979 --- /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 2617356..a414d37 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 @@ 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 -- Gitblit v1.9.3