From d1006f50ad802db5857aed54ffcc36043a7eef0c Mon Sep 17 00:00:00 2001 From: ageerle Date: Wed, 16 Apr 2025 10:25:10 +0800 Subject: [PATCH] feat: mcp-1.0.0 --- .../src/main/resources/application.yml | 15 -- .../common/chat/request/ChatRequest.java | 5 + ruoyi-extend/call-mcp-server/pom.xml | 83 ---------- .../CallMcpServerApplication.java | 13 -- .../callmcpserver/cofing/McpClientCfg.java | 22 --- .../callmcpserver/view/ChatController.java | 74 --------- .../callmcpserver/view/IndexController.java | 21 --- .../src/main/resources/application.yaml | 16 -- .../src/main/resources/mcp-server-bak.json | 40 ----- .../src/main/resources/mcp-server.json | 20 --- .../src/main/resources/templates/index.html | 148 ------------------ ruoyi-extend/pom.xml | 2 +- .../ruoyi-ai-mcp-webflux-server/pom.xml | 91 +++++++++++ .../com/ivy/mcp/sse/client/ClientWebflux.java | 34 ++++ .../server/McpWebfluxServerApplication.java | 42 +++++ .../src/main/resources/application.properties | 7 + ruoyi-extend/ruoyi-mcp-server/pom.xml | 64 -------- .../org/ruoyi/mcp/McpServerApplication.java | 16 -- .../org/ruoyi/mcp/config/McpServerConfig.java | 100 ------------ .../ruoyi/mcp/service/McpCustomService.java | 20 --- .../src/main/resources/application.yml | 12 -- ruoyi-modules-api/ruoyi-chat-api/pom.xml | 14 +- .../org/ruoyi/chat/config/ChatConfig.java | 2 +- .../ruoyi/chat/service/chat/IChatService.java | 5 - .../ruoyi/chat/service/chat/ISseService.java | 7 - .../service/chat/impl/OpenAIServiceImpl.java | 116 +++++++------- 26 files changed, 250 insertions(+), 739 deletions(-) delete mode 100644 ruoyi-extend/call-mcp-server/pom.xml delete mode 100644 ruoyi-extend/call-mcp-server/src/main/java/org/ruoyi/rocket/callmcpserver/CallMcpServerApplication.java delete mode 100644 ruoyi-extend/call-mcp-server/src/main/java/org/ruoyi/rocket/callmcpserver/cofing/McpClientCfg.java delete mode 100644 ruoyi-extend/call-mcp-server/src/main/java/org/ruoyi/rocket/callmcpserver/view/ChatController.java delete mode 100644 ruoyi-extend/call-mcp-server/src/main/java/org/ruoyi/rocket/callmcpserver/view/IndexController.java delete mode 100644 ruoyi-extend/call-mcp-server/src/main/resources/application.yaml delete mode 100644 ruoyi-extend/call-mcp-server/src/main/resources/mcp-server-bak.json delete mode 100644 ruoyi-extend/call-mcp-server/src/main/resources/mcp-server.json delete mode 100644 ruoyi-extend/call-mcp-server/src/main/resources/templates/index.html create mode 100644 ruoyi-extend/ruoyi-ai-mcp-webflux-server/pom.xml create mode 100644 ruoyi-extend/ruoyi-ai-mcp-webflux-server/src/main/java/com/ivy/mcp/sse/client/ClientWebflux.java create mode 100644 ruoyi-extend/ruoyi-ai-mcp-webflux-server/src/main/java/com/ivy/mcp/sse/server/McpWebfluxServerApplication.java create mode 100644 ruoyi-extend/ruoyi-ai-mcp-webflux-server/src/main/resources/application.properties delete mode 100644 ruoyi-extend/ruoyi-mcp-server/pom.xml delete mode 100644 ruoyi-extend/ruoyi-mcp-server/src/main/java/org/ruoyi/mcp/McpServerApplication.java delete mode 100644 ruoyi-extend/ruoyi-mcp-server/src/main/java/org/ruoyi/mcp/config/McpServerConfig.java delete mode 100644 ruoyi-extend/ruoyi-mcp-server/src/main/java/org/ruoyi/mcp/service/McpCustomService.java delete mode 100644 ruoyi-extend/ruoyi-mcp-server/src/main/resources/application.yml diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index d51ac509..bc654111 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -320,19 +320,4 @@ wechat: token: '' aesKey: '' -spring: - ai: - openai: - api-key: sk-xX - base-url: https://api.pandarobot.chat/ - ollama: - base-url: http://localhost:11434 - mcp: - client: - enabled: true - name: call-mcp-server - sse: - connections: - server1: - url: http://127.0.0.1:6040 diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/request/ChatRequest.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/request/ChatRequest.java index 789f3def..0172fa53 100644 --- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/request/ChatRequest.java +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/request/ChatRequest.java @@ -41,6 +41,11 @@ public class ChatRequest { */ private Boolean search = Boolean.FALSE; + /** + * 是否开启mcp + */ + private Boolean isMcp = Boolean.FALSE; + /** * 知识库id */ diff --git a/ruoyi-extend/call-mcp-server/pom.xml b/ruoyi-extend/call-mcp-server/pom.xml deleted file mode 100644 index 3f061817..00000000 --- a/ruoyi-extend/call-mcp-server/pom.xml +++ /dev/null @@ -1,83 +0,0 @@ - - 4.0.0 - - org.ruoyi - ruoyi-ai - 1.0.0 - ../../pom.xml - - call-mcp-server - Archetype - call-mcp-server - http://maven.apache.org - - - 17 - - - - - - - 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.boot - spring-boot-starter-thymeleaf - - - - - org.springframework.ai - spring-ai-mcp-client-spring-boot-starter - 1.0.0-M6 - - - - org.springframework.ai - spring-ai-openai-spring-boot-starter - - - - - org.springframework.ai - spring-ai-mcp - - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/ruoyi-extend/call-mcp-server/src/main/java/org/ruoyi/rocket/callmcpserver/CallMcpServerApplication.java b/ruoyi-extend/call-mcp-server/src/main/java/org/ruoyi/rocket/callmcpserver/CallMcpServerApplication.java deleted file mode 100644 index 213df755..00000000 --- a/ruoyi-extend/call-mcp-server/src/main/java/org/ruoyi/rocket/callmcpserver/CallMcpServerApplication.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.ruoyi.rocket.callmcpserver; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class CallMcpServerApplication { - - public static void main(String[] args) { - SpringApplication.run(CallMcpServerApplication.class, args); - } - -} diff --git a/ruoyi-extend/call-mcp-server/src/main/java/org/ruoyi/rocket/callmcpserver/cofing/McpClientCfg.java b/ruoyi-extend/call-mcp-server/src/main/java/org/ruoyi/rocket/callmcpserver/cofing/McpClientCfg.java deleted file mode 100644 index 26936890..00000000 --- a/ruoyi-extend/call-mcp-server/src/main/java/org/ruoyi/rocket/callmcpserver/cofing/McpClientCfg.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.ruoyi.rocket.callmcpserver.cofing; - -import io.modelcontextprotocol.client.McpClient; -import org.springframework.ai.mcp.customizer.McpSyncClientCustomizer; -import org.springframework.context.annotation.Configuration; - -import java.time.Duration; - - -/** - * @author ageer - */ -@Configuration -public class McpClientCfg implements McpSyncClientCustomizer { - - - @Override - public void customize(String name, McpClient.SyncSpec spec) { - // do nothing - spec.requestTimeout(Duration.ofSeconds(30)); - } -} diff --git a/ruoyi-extend/call-mcp-server/src/main/java/org/ruoyi/rocket/callmcpserver/view/ChatController.java b/ruoyi-extend/call-mcp-server/src/main/java/org/ruoyi/rocket/callmcpserver/view/ChatController.java deleted file mode 100644 index 27c837e7..00000000 --- a/ruoyi-extend/call-mcp-server/src/main/java/org/ruoyi/rocket/callmcpserver/view/ChatController.java +++ /dev/null @@ -1,74 +0,0 @@ -package org.ruoyi.rocket.callmcpserver.view; - -import jakarta.servlet.http.HttpServletResponse; -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.model.ChatResponse; -import org.springframework.ai.openai.OpenAiChatOptions; -import org.springframework.ai.tool.ToolCallbackProvider; -import org.springframework.web.bind.annotation.*; -import reactor.core.publisher.Flux; - - -/** - * @author jianzhang - * 2025/03/18/下午8:00 - */ -@RestController -@RequestMapping("/dashscope/chat-client") -public class ChatController { - - private final ChatClient chatClient; - - private final ChatMemory chatMemory = new InMemoryChatMemory(); - - public ChatController(ChatClient.Builder chatClientBuilder,ToolCallbackProvider tools) { - this.chatClient = chatClientBuilder - .defaultTools(tools) - .defaultOptions( - OpenAiChatOptions.builder().model("gpt-4o-mini").build()) - .build(); - } - - @RequestMapping(value = "/generate_stream", method = RequestMethod.GET) - public Flux generateStream(HttpServletResponse response, @RequestParam("id") String id, @RequestParam("prompt") String prompt) { - response.setCharacterEncoding("UTF-8"); - var messageChatMemoryAdvisor = new MessageChatMemoryAdvisor(chatMemory, id, 10); - - - Flux chatResponseFlux = this.chatClient.prompt(prompt) - .advisors(messageChatMemoryAdvisor) - .stream() - .chatResponse(); - - Flux content = this.chatClient.prompt(prompt) - .advisors(messageChatMemoryAdvisor) - .stream() - .content(); - - content.subscribe( - content1 -> System.out.println("chatResponse"+content1) - ); - return chatResponseFlux; - - - } - - - @GetMapping("/advisor/chat/{id}/{prompt}") - public Flux advisorChat( - HttpServletResponse response, - @PathVariable String id, - @PathVariable String prompt) { - - response.setCharacterEncoding("UTF-8"); - var messageChatMemoryAdvisor = new MessageChatMemoryAdvisor(chatMemory, id, 10); - return this.chatClient.prompt(prompt) - .advisors(messageChatMemoryAdvisor).stream().content(); - } - - - -} diff --git a/ruoyi-extend/call-mcp-server/src/main/java/org/ruoyi/rocket/callmcpserver/view/IndexController.java b/ruoyi-extend/call-mcp-server/src/main/java/org/ruoyi/rocket/callmcpserver/view/IndexController.java deleted file mode 100644 index ed03e33a..00000000 --- a/ruoyi-extend/call-mcp-server/src/main/java/org/ruoyi/rocket/callmcpserver/view/IndexController.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.ruoyi.rocket.callmcpserver.view; - -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.GetMapping; - -/** - * @author jianzhang - * 2025/03/18/下午8:00 - */ -@Controller -public class IndexController { - - @GetMapping("/") - public String chat(Model model) { - //model.addAttribute("name", "User"); - // 返回视图名称,对应 templates/index.html - return "index"; - } - -} diff --git a/ruoyi-extend/call-mcp-server/src/main/resources/application.yaml b/ruoyi-extend/call-mcp-server/src/main/resources/application.yaml deleted file mode 100644 index f4b4577f..00000000 --- a/ruoyi-extend/call-mcp-server/src/main/resources/application.yaml +++ /dev/null @@ -1,16 +0,0 @@ -server: - port: 9999 -spring: - ai: - openai: - api-key: sk-xXe1WMPjhlVb1aiI1b4c6c8934D8463f9e4b67Ed8718B772 - base-url: https://api.pandarobot.chat/ - mcp: - client: - enabled: true - name: call-mcp-server - sse: - connections: - server1: - url: http://127.0.0.1:6040 - diff --git a/ruoyi-extend/call-mcp-server/src/main/resources/mcp-server-bak.json b/ruoyi-extend/call-mcp-server/src/main/resources/mcp-server-bak.json deleted file mode 100644 index 842d6955..00000000 --- a/ruoyi-extend/call-mcp-server/src/main/resources/mcp-server-bak.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "mcpServers": { - "fileSystem": { - "command": "D:\\software\\nodeJs\\npx.cmd", - "args": [ - "-y", - "@modelcontextprotocol/server-filesystem", - "D:\\software\\sqlite" - ] - }, - "sqlLite": { - "command": "D:\\Program Files\\python3.12.3\\Scripts\\uvx.exe", - "args": [ - "mcp-server-sqlite", - "--db-path", - "D:\\work-space-study\\spring-ai-mcp-demo\\mcp-client\\src\\main\\resources\\test.db" - ] - }, - "fetch": { - "command": "D:\\Program Files\\python3.12.3\\Scripts\\uvx.exe", - "args": [ - "mcp-server-fetch" - ] - }, - "baidu-map": { - "command": "D:\\Program Files\\python3.12.3\\Scripts\\uvx.exe", - "args": [ - "run", - "--with", - "mcp[cli]", - "mcp", - "run", - "D:\\work-space-python\\python-baidu-map\\baidu_map_mcp_server\\map.py" - ], - "env": { - "BAIDU_MAPS_API_KEY": "{百度地图API-KEY}" - } - } - } -} \ No newline at end of file diff --git a/ruoyi-extend/call-mcp-server/src/main/resources/mcp-server.json b/ruoyi-extend/call-mcp-server/src/main/resources/mcp-server.json deleted file mode 100644 index a639d152..00000000 --- a/ruoyi-extend/call-mcp-server/src/main/resources/mcp-server.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "mcpServers": { - "fileSystem": { - "command": "D:\\software\\nodeJs\\npx.cmd", - "args": [ - "-y", - "@modelcontextprotocol/server-filesystem", - "D:\\software\\sqlite" - ] - }, - "sqlLite": { - "command": "D:\\Program Files\\python3.12.3\\Scripts\\uvx.exe", - "args": [ - "mcp-server-sqlite", - "--db-path", - "D:\\work-space-study\\spring-ai-mcp-demo\\mcp-client\\src\\main\\resources\\test.db" - ] - } - } -} diff --git a/ruoyi-extend/call-mcp-server/src/main/resources/templates/index.html b/ruoyi-extend/call-mcp-server/src/main/resources/templates/index.html deleted file mode 100644 index be98e857..00000000 --- a/ruoyi-extend/call-mcp-server/src/main/resources/templates/index.html +++ /dev/null @@ -1,148 +0,0 @@ - - - - - - AI 对话助手 - - - -
- -
-

AI 对话助手

-

基于 Spring AI 的流式对话系统 By AhuCodingBeast

-
- - -
- -
-
- 您好!我是AI助手,有什么可以帮您? -
-
-
- - -
- - -
-
- - - - \ No newline at end of file diff --git a/ruoyi-extend/pom.xml b/ruoyi-extend/pom.xml index 3fef2b70..07094656 100644 --- a/ruoyi-extend/pom.xml +++ b/ruoyi-extend/pom.xml @@ -14,7 +14,7 @@ pom - ruoyi-mcp-server + ruoyi-ai-mcp-webflux-server diff --git a/ruoyi-extend/ruoyi-ai-mcp-webflux-server/pom.xml b/ruoyi-extend/ruoyi-ai-mcp-webflux-server/pom.xml new file mode 100644 index 00000000..ef14457b --- /dev/null +++ b/ruoyi-extend/ruoyi-ai-mcp-webflux-server/pom.xml @@ -0,0 +1,91 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.4.4 + + + + com.ivy.mcp + ruoyi-ai-mcp-webflux-server + 1.0.0-M6 + + spring-ai-mcp-webflux-server + Spring AI MCP Server example and invoke by stdio + + + 17 + 1.0.0-M6 + + + + + + org.springframework.ai + spring-ai-bom + ${spring-ai.version} + pom + import + + + + + + + org.springframework.ai + spring-ai-mcp-server-webflux-spring-boot-starter + ${spring-ai.version} + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + + + + + Central Portal Snapshots + central-portal-snapshots + https://central.sonatype.com/repository/maven-snapshots/ + + false + + + true + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + false + + + + + diff --git a/ruoyi-extend/ruoyi-ai-mcp-webflux-server/src/main/java/com/ivy/mcp/sse/client/ClientWebflux.java b/ruoyi-extend/ruoyi-ai-mcp-webflux-server/src/main/java/com/ivy/mcp/sse/client/ClientWebflux.java new file mode 100644 index 00000000..a66f61b9 --- /dev/null +++ b/ruoyi-extend/ruoyi-ai-mcp-webflux-server/src/main/java/com/ivy/mcp/sse/client/ClientWebflux.java @@ -0,0 +1,34 @@ +package com.ivy.mcp.sse.client; + + +import io.modelcontextprotocol.client.McpClient; +import io.modelcontextprotocol.client.transport.WebFluxSseClientTransport; +import io.modelcontextprotocol.spec.McpSchema; +import org.springframework.web.reactive.function.client.WebClient; + +import java.util.Map; + +public class ClientWebflux { + + public static void main(String[] args) { + + var transport = new WebFluxSseClientTransport(WebClient.builder().baseUrl("http://localhost:8080")); + try (var client = McpClient.sync(transport).build()) { + + client.initialize(); +// client.ping(); + + McpSchema.ListToolsResult toolsList = client.listTools(); + System.out.println("Available Tools = " + toolsList); + + McpSchema.CallToolResult sumResult = client.callTool(new McpSchema.CallToolRequest("add", + Map.of("a", 1, "b", 2))); + System.out.println("add a+ b = " + sumResult.content().get(0)); + + + McpSchema.CallToolResult currentTimResult = client.callTool(new McpSchema.CallToolRequest("getCurrentTime", Map.of())); + System.out.println("current time Response = " + currentTimResult); + } + } + +} diff --git a/ruoyi-extend/ruoyi-ai-mcp-webflux-server/src/main/java/com/ivy/mcp/sse/server/McpWebfluxServerApplication.java b/ruoyi-extend/ruoyi-ai-mcp-webflux-server/src/main/java/com/ivy/mcp/sse/server/McpWebfluxServerApplication.java new file mode 100644 index 00000000..c1d8e72c --- /dev/null +++ b/ruoyi-extend/ruoyi-ai-mcp-webflux-server/src/main/java/com/ivy/mcp/sse/server/McpWebfluxServerApplication.java @@ -0,0 +1,42 @@ +package com.ivy.mcp.sse.server; + +import org.springframework.ai.tool.ToolCallback; +import org.springframework.ai.tool.ToolCallbacks; +import org.springframework.ai.tool.annotation.Tool; +import org.springframework.ai.tool.annotation.ToolParam; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.List; + +@SpringBootApplication +public class McpWebfluxServerApplication { + public static void main(String[] args) { + SpringApplication.run(McpWebfluxServerApplication.class, args); + } + + + @Bean + public List tools(MyTools myTools) { + return List.of(ToolCallbacks.from(myTools)); + } + + @Service + public static class MyTools { + + @Tool(description = "add two numbers") + public Integer add(@ToolParam(description = "first number") int a, + @ToolParam(description = "second number") int b) { + + return a + b; + } + + @Tool(description = "get current time") + public LocalDateTime getCurrentTime() { + return LocalDateTime.now(); + } + } +} diff --git a/ruoyi-extend/ruoyi-ai-mcp-webflux-server/src/main/resources/application.properties b/ruoyi-extend/ruoyi-ai-mcp-webflux-server/src/main/resources/application.properties new file mode 100644 index 00000000..86fb3e7e --- /dev/null +++ b/ruoyi-extend/ruoyi-ai-mcp-webflux-server/src/main/resources/application.properties @@ -0,0 +1,7 @@ +spring.main.banner-mode=off +logging.pattern.console= +logging.file.name=mcp-server/spring-ai-mcp-webflux-server/target/target/mcp.mytools.log + +spring.ai.mcp.server.enabled=true +spring.ai.mcp.server.name=webflux-server +spring.ai.mcp.server.version=1.0.0 diff --git a/ruoyi-extend/ruoyi-mcp-server/pom.xml b/ruoyi-extend/ruoyi-mcp-server/pom.xml deleted file mode 100644 index d35402c7..00000000 --- a/ruoyi-extend/ruoyi-mcp-server/pom.xml +++ /dev/null @@ -1,64 +0,0 @@ - - 4.0.0 - - org.ruoyi - ruoyi-ai - ${revision} - ../../pom.xml - - - ruoyi-mcp-server - - - - - 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-extend/ruoyi-mcp-server/src/main/java/org/ruoyi/mcp/McpServerApplication.java b/ruoyi-extend/ruoyi-mcp-server/src/main/java/org/ruoyi/mcp/McpServerApplication.java deleted file mode 100644 index bf6b0ff1..00000000 --- a/ruoyi-extend/ruoyi-mcp-server/src/main/java/org/ruoyi/mcp/McpServerApplication.java +++ /dev/null @@ -1,16 +0,0 @@ -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-extend/ruoyi-mcp-server/src/main/java/org/ruoyi/mcp/config/McpServerConfig.java b/ruoyi-extend/ruoyi-mcp-server/src/main/java/org/ruoyi/mcp/config/McpServerConfig.java deleted file mode 100644 index 4ba45e10..00000000 --- a/ruoyi-extend/ruoyi-mcp-server/src/main/java/org/ruoyi/mcp/config/McpServerConfig.java +++ /dev/null @@ -1,100 +0,0 @@ -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-extend/ruoyi-mcp-server/src/main/java/org/ruoyi/mcp/service/McpCustomService.java b/ruoyi-extend/ruoyi-mcp-server/src/main/java/org/ruoyi/mcp/service/McpCustomService.java deleted file mode 100644 index 1e693762..00000000 --- a/ruoyi-extend/ruoyi-mcp-server/src/main/java/org/ruoyi/mcp/service/McpCustomService.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.ruoyi.mcp.service; - -import org.springframework.ai.tool.annotation.Tool; -import org.springframework.stereotype.Service; - -/** - * @author ageer - */ -@Service -public class McpCustomService { - - public record User(String userName, String userBalance) { - } - - @Tool(description = "根据用户名称查询用户信息") - public User getUserBalance(String username) { - return new User("admin","99.99"); - } - -} diff --git a/ruoyi-extend/ruoyi-mcp-server/src/main/resources/application.yml b/ruoyi-extend/ruoyi-mcp-server/src/main/resources/application.yml deleted file mode 100644 index 82b2bdd9..00000000 --- a/ruoyi-extend/ruoyi-mcp-server/src/main/resources/application.yml +++ /dev/null @@ -1,12 +0,0 @@ -server: - port: 6040 -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-api/ruoyi-chat-api/pom.xml b/ruoyi-modules-api/ruoyi-chat-api/pom.xml index e2ed34a7..62578a0c 100644 --- a/ruoyi-modules-api/ruoyi-chat-api/pom.xml +++ b/ruoyi-modules-api/ruoyi-chat-api/pom.xml @@ -55,19 +55,17 @@ org.springframework.ai - spring-ai-mcp-client-spring-boot-starter + spring-ai-mcp-client-webflux-spring-boot-starter - - - - - - org.springframework.ai - spring-ai-openai-spring-boot-starter + io.modelcontextprotocol.sdk + mcp-spring-webflux + 0.8.0 + compile + diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/config/ChatConfig.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/config/ChatConfig.java index 560f2d86..e49dce7d 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/config/ChatConfig.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/config/ChatConfig.java @@ -36,7 +36,7 @@ public class ChatConfig { return openAiStreamClient; } - public OpenAiStreamClient createOpenAiStreamClient(String apiHost, String apiKey) { + public static OpenAiStreamClient createOpenAiStreamClient(String apiHost, String apiKey) { HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(new OpenAILogger()); httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS); OkHttpClient okHttpClient = new OkHttpClient.Builder() 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 649fce17..fc070721 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 @@ -18,9 +18,4 @@ public interface IChatService { SseEmitter chat(ChatRequest chatRequest,SseEmitter emitter); - /** - * 客户端发送消息到服务端 - * @param chatRequest 请求对象 - */ - void mcpChat(ChatRequest chatRequest,SseEmitter emitter); } diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/ISseService.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/ISseService.java index 65adeed0..c0256427 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/ISseService.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/ISseService.java @@ -55,11 +55,4 @@ public interface ISseService { */ String wxCpChat(String prompt); - /** - * 联网查询 - * - * @param prompt 提示词 - * @return 查询内容 - */ - String webSearch (String prompt); } 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 f2d12112..34585e22 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 @@ -1,78 +1,88 @@ package org.ruoyi.chat.service.chat.impl; +import cn.hutool.json.JSONUtil; +import io.modelcontextprotocol.client.McpClient; +import io.modelcontextprotocol.client.transport.WebFluxSseClientTransport; +import io.modelcontextprotocol.spec.McpSchema; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.ruoyi.chat.service.chat.IChatService; -import org.ruoyi.common.chat.entity.chat.Message; +import org.ruoyi.common.chat.entity.chat.ChatChoice; +import org.ruoyi.common.chat.entity.chat.ChatCompletion; +import org.ruoyi.common.chat.entity.chat.ChatCompletionResponse; +import org.ruoyi.common.chat.entity.chat.Parameters; +import org.ruoyi.common.chat.entity.chat.tool.ToolCallFunction; +import org.ruoyi.common.chat.entity.chat.tool.Tools; +import org.ruoyi.common.chat.entity.chat.tool.ToolsFunction; +import org.ruoyi.common.chat.openai.OpenAiStreamClient; import org.ruoyi.common.chat.request.ChatRequest; -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.stereotype.Service; +import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; -import reactor.core.publisher.Flux; -import reactor.core.scheduler.Schedulers; import java.io.IOException; +import java.util.ArrayList; import java.util.List; +import java.util.Map; @Service @Slf4j +@RequiredArgsConstructor public class OpenAIServiceImpl implements IChatService { - private final ChatClient chatClient; + private final OpenAiStreamClient openAiStreamClient; - private final ChatMemory chatMemory = new InMemoryChatMemory(); - - - public OpenAIServiceImpl(ChatClient.Builder chatClientBuilder, ToolCallbackProvider tools) { - this.chatClient = chatClientBuilder - .defaultTools(tools) - .defaultOptions( - OpenAiChatOptions.builder() - .model("gpt-4o-mini") - .temperature(0.4) - .build()) - .build(); - } @Override public SseEmitter chat(ChatRequest chatRequest,SseEmitter emitter) { - return emitter; - } + WebFluxSseClientTransport webFluxSseClientTransport = new WebFluxSseClientTransport(WebClient.builder().baseUrl("http://localhost:8080")); + ChatCompletion completion = ChatCompletion + .builder() + .messages(chatRequest.getMessages()) + .model(chatRequest.getModel()) + .stream(false) + .build(); + List tools = new ArrayList<>(); + try (var client = McpClient.sync(webFluxSseClientTransport).build()) { + client.initialize(); + McpSchema.ListToolsResult toolsList = client.listTools(); - @Override - public void 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); + for (McpSchema.Tool mcpTool : toolsList.tools()) { + + McpSchema.JsonSchema jsonSchema = mcpTool.inputSchema(); + + Parameters parameters = Parameters.builder() + .type(jsonSchema.type()) + .properties(jsonSchema.properties()) + .required(jsonSchema.required()).build(); + + Tools tool = Tools.builder() + .type(Tools.Type.FUNCTION.getName()) + .function(ToolsFunction.builder().name(mcpTool.name()).description(mcpTool.description()).parameters(parameters).build()) + .build(); + tools.add(tool); + } + + completion.setTools(tools); + ChatCompletionResponse chatCompletionResponse = openAiStreamClient.chatCompletion(completion); + String arguments = chatCompletionResponse.getChoices().get(0).getMessage().getToolCalls().get(0).getFunction().getArguments(); + String name = chatCompletionResponse.getChoices().get(0).getMessage().getToolCalls().get(0).getFunction().getName(); + Map map = JSONUtil.toBean(arguments, Map.class); + McpSchema.CallToolResult sumResult = client.callTool(new McpSchema.CallToolRequest(name, map)); + System.out.println("add a+ b = " + sumResult.content().get(0)); + + + McpSchema.Content content = sumResult.content().get(0); + + emitter.send(sumResult.content().get(0)); + + } catch (IOException e) { + emitter.completeWithError(e); } - var messageChatMemoryAdvisor = new MessageChatMemoryAdvisor(chatMemory, chatRequest.getUserId().toString(), 10); + emitter.complete(); + return emitter; - Flux content = chatClient - .prompt(chatRequest.getPrompt()) - .advisors(messageChatMemoryAdvisor) - .stream().content(); - - content.publishOn(Schedulers.boundedElastic()) - .doOnNext(text -> { - try { - emitter.send(text); - } catch (IOException e) { - emitter.completeWithError(e); - } - }) - .doOnError(error -> { - log.error("Error in SSE stream: ", error); - emitter.completeWithError(error); - }) - .doOnComplete(emitter::complete) - .subscribe(); } + }