From d1006f50ad802db5857aed54ffcc36043a7eef0c Mon Sep 17 00:00:00 2001 From: ageerle <ageerle@163.com> Date: 星期三, 16 四月 2025 10:25:10 +0800 Subject: [PATCH] feat: mcp-1.0.0 --- ruoyi-extend/ruoyi-ai-mcp-webflux-server/src/main/java/com/ivy/mcp/sse/client/ClientWebflux.java | 34 +++++ ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/config/ChatConfig.java | 2 ruoyi-extend/ruoyi-ai-mcp-webflux-server/src/main/resources/application.properties | 7 + ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/request/ChatRequest.java | 5 ruoyi-extend/ruoyi-ai-mcp-webflux-server/src/main/java/com/ivy/mcp/sse/server/McpWebfluxServerApplication.java | 42 +++++++ ruoyi-admin/src/main/resources/application.yml | 15 -- /dev/null | 12 -- ruoyi-extend/pom.xml | 2 ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/ISseService.java | 7 - ruoyi-modules-api/ruoyi-chat-api/pom.xml | 14 +- ruoyi-extend/ruoyi-ai-mcp-webflux-server/pom.xml | 91 +++++++++++++++ ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/IChatService.java | 5 ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/OpenAIServiceImpl.java | 116 ++++++++++-------- 13 files changed, 250 insertions(+), 102 deletions(-) diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index d51ac50..bc65411 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -320,19 +320,4 @@ 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 789f3de..0172fa5 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 @@ -42,6 +42,11 @@ private Boolean search = Boolean.FALSE; /** + * 鏄惁寮�鍚痬cp + */ + private Boolean isMcp = Boolean.FALSE; + + /** * 鐭ヨ瘑搴搃d */ private String kid; diff --git a/ruoyi-extend/call-mcp-server/pom.xml b/ruoyi-extend/call-mcp-server/pom.xml deleted file mode 100644 index 3f06181..0000000 --- a/ruoyi-extend/call-mcp-server/pom.xml +++ /dev/null @@ -1,83 +0,0 @@ -<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/maven-v4_0_0.xsd"> - <modelVersion>4.0.0</modelVersion> - <parent> - <groupId>org.ruoyi</groupId> - <artifactId>ruoyi-ai</artifactId> - <version>1.0.0</version> - <relativePath>../../pom.xml</relativePath> - </parent> - <artifactId>call-mcp-server</artifactId> - <name>Archetype - call-mcp-server</name> - <url>http://maven.apache.org</url> - - <properties> - <java.version>17</java.version> - </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.boot</groupId> - <artifactId>spring-boot-starter-thymeleaf</artifactId> - </dependency> - - - <dependency> - <groupId>org.springframework.ai</groupId> - <artifactId>spring-ai-mcp-client-spring-boot-starter</artifactId> - <version>1.0.0-M6</version> - </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-mcp</artifactId> - </dependency> - - </dependencies> - - <build> - <plugins> - <plugin> - <groupId>org.springframework.boot</groupId> - <artifactId>spring-boot-maven-plugin</artifactId> - </plugin> - </plugins> - </build> - -</project> 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 213df75..0000000 --- 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 2693689..0000000 --- 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 27c837e..0000000 --- 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<ChatResponse> generateStream(HttpServletResponse response, @RequestParam("id") String id, @RequestParam("prompt") String prompt) { - response.setCharacterEncoding("UTF-8"); - var messageChatMemoryAdvisor = new MessageChatMemoryAdvisor(chatMemory, id, 10); - - - Flux<ChatResponse> chatResponseFlux = this.chatClient.prompt(prompt) - .advisors(messageChatMemoryAdvisor) - .stream() - .chatResponse(); - - Flux<String> 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<String> 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 ed03e33..0000000 --- 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 f4b4577..0000000 --- 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 842d695..0000000 --- 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 a639d15..0000000 --- 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 be98e85..0000000 --- a/ruoyi-extend/call-mcp-server/src/main/resources/templates/index.html +++ /dev/null @@ -1,148 +0,0 @@ -<!DOCTYPE html> -<html lang="zh-CN"> -<head> - <meta charset="UTF-8"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <title>AI 瀵硅瘽鍔╂墜</title> - <script src="https://cdn.tailwindcss.com"></script> -</head> -<body class="bg-gray-100 min-h-screen"> -<div class="container mx-auto p-4 max-w-3xl"> - <!-- 鏍囬 --> - <div class="text-center mb-8"> - <h1 class="text-3xl font-bold text-gray-800">AI 瀵硅瘽鍔╂墜</h1> - <p class="text-gray-600 mt-2">鍩轰簬 Spring AI 鐨勬祦寮忓璇濈郴缁� By AhuCodingBeast</p> - </div> - - <!-- 鑱婂ぉ瀹瑰櫒 --> - <div id="chat-container" class="bg-white rounded-xl shadow-lg p-4 mb-4 h-[500px] overflow-y-auto space-y-4"> - <!-- 鍒濆娆㈣繋娑堟伅 --> - <div class="ai-message flex items-start gap-3"> - <div class="bg-green-100 p-3 rounded-lg max-w-[85%]"> - <span class="text-gray-800">鎮ㄥソ锛佹垜鏄疉I鍔╂墜锛屾湁浠�涔堝彲浠ュ府鎮紵</span> - </div> - </div> - </div> - - <!-- 杈撳叆鍖哄煙 --> - <div class="flex gap-2"> - <input type="text" id="message-input" - class="flex-1 border border-gray-300 rounded-xl px-4 py-3 focus:outline-none focus:ring-2 focus:ring-blue-500" - placeholder="杈撳叆鎮ㄧ殑闂..."> - <button id="send-button" - class="bg-blue-500 text-white px-6 py-3 rounded-xl hover:bg-blue-600 transition-colors flex items-center"> - <span>鍙戦��</span> - <svg id="loading-spinner" class="hidden w-4 h-4 ml-2 animate-spin" fill="none" viewBox="0 0 24 24"> - <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle> - <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path> - </svg> - </button> - </div> -</div> - -<script> - const chatContainer = document.getElementById('chat-container'); - const messageInput = document.getElementById('message-input'); - const sendButton = document.getElementById('send-button'); - const loadingSpinner = document.getElementById('loading-spinner'); - - // 鍙戦�佹秷鎭鐞� - function handleSend() { - const message = messageInput.value.trim(); - if (!message) return; - - // 娣诲姞鐢ㄦ埛娑堟伅 - addMessage(message, 'user'); - messageInput.value = ''; - - // 鏋勫缓API URL - const apiUrl = new URL('http://localhost:9999/dashscope/chat-client/generate_stream'); - apiUrl.searchParams.append('id', '01'); - apiUrl.searchParams.append('prompt', message); - - // 鏄剧ず鍔犺浇鐘舵�� - sendButton.disabled = true; - loadingSpinner.classList.remove('hidden'); - - // 鍒涘缓EventSource杩炴帴 - const eventSource = new EventSource(apiUrl); - let aiMessageElement = null; - - eventSource.onmessage = (event) => { - try { - const data = JSON.parse(event.data); - console.log(data); - const content = data.result?.output?.text || ''; - const finishReason = data.result?.metadata?.finishReason; - - // 鍒涘缓娑堟伅瀹瑰櫒锛堝鏋滀笉瀛樺湪锛� - if (!aiMessageElement) { - aiMessageElement = addMessage('', 'ai'); - } - - // 杩藉姞鍐呭 - if (content) { - aiMessageElement.querySelector('.message-content').textContent += content; - autoScroll(); - } - - // 澶勭悊缁撴潫 - if (finishReason === 'STOP') { - eventSource.close(); - sendButton.disabled = false; - loadingSpinner.classList.add('hidden'); - } - } catch (error) { - console.error('瑙f瀽閿欒:', error); - } - }; - - eventSource.onerror = (error) => { - console.error('杩炴帴閿欒:', error); - eventSource.close(); - sendButton.disabled = false; - loadingSpinner.classList.add('hidden'); - addMessage('瀵硅瘽杩炴帴寮傚父锛岃閲嶈瘯', 'ai', true); - }; - } - - // 娣诲姞娑堟伅鍒板鍣� - function addMessage(content, type, isError = false) { - const messageDiv = document.createElement('div'); - messageDiv.className = `${type}-message flex items-start gap-3`; - - const bubble = document.createElement('div'); - bubble.className = `p-3 rounded-lg max-w-[85%] ${ - type === 'user' - ? 'bg-blue-500 text-white ml-auto' - : `bg-green-100 ${isError ? 'text-red-500' : 'text-gray-800'}` - }`; - - const contentSpan = document.createElement('span'); - contentSpan.className = 'message-content'; - contentSpan.textContent = content; - - bubble.appendChild(contentSpan); - messageDiv.appendChild(bubble); - chatContainer.appendChild(messageDiv); - - autoScroll(); - return bubble; - } - - // 鑷姩婊氬姩鍒板簳閮� - function autoScroll() { - chatContainer.scrollTop = chatContainer.scrollHeight; - } - - // 浜嬩欢鐩戝惉 - sendButton.addEventListener('click', handleSend); - messageInput.addEventListener('keypress', (e) => { - if (e.key === 'Enter' && !e.shiftKey) { - e.preventDefault(); - handleSend(); - } - }); -</script> -</body> -</html> \ No newline at end of file diff --git a/ruoyi-extend/pom.xml b/ruoyi-extend/pom.xml index 3fef2b7..0709465 100644 --- a/ruoyi-extend/pom.xml +++ b/ruoyi-extend/pom.xml @@ -14,7 +14,7 @@ <packaging>pom</packaging> <modules> - <module>ruoyi-mcp-server</module> + <module>ruoyi-ai-mcp-webflux-server</module> </modules> </project> 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 0000000..ef14457 --- /dev/null +++ b/ruoyi-extend/ruoyi-ai-mcp-webflux-server/pom.xml @@ -0,0 +1,91 @@ +<?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.springframework.boot</groupId> + <artifactId>spring-boot-starter-parent</artifactId> + <version>3.4.4</version> + <relativePath /> <!-- lookup parent from repository --> + </parent> + + <groupId>com.ivy.mcp</groupId> + <artifactId>ruoyi-ai-mcp-webflux-server</artifactId> + <version>1.0.0-M6</version> + + <name>spring-ai-mcp-webflux-server</name> + <description>Spring AI MCP Server example and invoke by stdio</description> + + <properties> + <java.version>17</java.version> + <spring-ai.version>1.0.0-M6</spring-ai.version> + </properties> + + <dependencyManagement> + <dependencies> + <dependency> + <groupId>org.springframework.ai</groupId> + <artifactId>spring-ai-bom</artifactId> + <version>${spring-ai.version}</version> + <type>pom</type> + <scope>import</scope> + </dependency> + </dependencies> + </dependencyManagement> + + <dependencies> + <dependency> + <groupId>org.springframework.ai</groupId> + <artifactId>spring-ai-mcp-server-webflux-spring-boot-starter</artifactId> + <version>${spring-ai.version}</version> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-maven-plugin</artifactId> + <executions> + <execution> + <goals> + <goal>repackage</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> + + <repositories> + <repository> + <name>Central Portal Snapshots</name> + <id>central-portal-snapshots</id> + <url>https://central.sonatype.com/repository/maven-snapshots/</url> + <releases> + <enabled>false</enabled> + </releases> + <snapshots> + <enabled>true</enabled> + </snapshots> + </repository> + <repository> + <id>spring-milestones</id> + <name>Spring Milestones</name> + <url>https://repo.spring.io/milestone</url> + <snapshots> + <enabled>false</enabled> + </snapshots> + </repository> + <repository> + <id>spring-snapshots</id> + <name>Spring Snapshots</name> + <url>https://repo.spring.io/snapshot</url> + <releases> + <enabled>false</enabled> + </releases> + </repository> + </repositories> + +</project> 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 0000000..a66f61b --- /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 0000000..c1d8e72 --- /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<ToolCallback> 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 0000000..86fb3e7 --- /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 d35402c..0000000 --- a/ruoyi-extend/ruoyi-mcp-server/pom.xml +++ /dev/null @@ -1,64 +0,0 @@ -<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/maven-v4_0_0.xsd"> - <modelVersion>4.0.0</modelVersion> - <parent> - <groupId>org.ruoyi</groupId> - <artifactId>ruoyi-ai</artifactId> - <version>${revision}</version> - <relativePath>../../pom.xml</relativePath> - </parent> - - <artifactId>ruoyi-mcp-server</artifactId> - - <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> - -<!-- <dependency>--> -<!-- <groupId>org.ruoyi</groupId>--> -<!-- <artifactId>ruoyi-system-api</artifactId>--> -<!-- <exclusions>--> -<!-- <exclusion>--> -<!-- <groupId>org.ruoyi</groupId>--> -<!-- <artifactId>ruoyi-common-translation</artifactId>--> -<!-- </exclusion>--> -<!-- </exclusions>--> -<!-- </dependency>--> - - </dependencies> -</project> 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 bf6b0ff..0000000 --- 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 4ba45e1..0000000 --- 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<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-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 1e69376..0000000 --- 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 82b2bdd..0000000 --- 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 e2ed34a..62578a0 100644 --- a/ruoyi-modules-api/ruoyi-chat-api/pom.xml +++ b/ruoyi-modules-api/ruoyi-chat-api/pom.xml @@ -55,19 +55,17 @@ <dependency> <groupId>org.springframework.ai</groupId> - <artifactId>spring-ai-mcp-client-spring-boot-starter</artifactId> + <artifactId>spring-ai-mcp-client-webflux-spring-boot-starter</artifactId> </dependency> - -<!-- <dependency>--> -<!-- <groupId>org.springframework.ai</groupId>--> -<!-- <artifactId>spring-ai-ollama-spring-boot-starter</artifactId>--> -<!-- </dependency>--> <dependency> - <groupId>org.springframework.ai</groupId> - <artifactId>spring-ai-openai-spring-boot-starter</artifactId> + <groupId>io.modelcontextprotocol.sdk</groupId> + <artifactId>mcp-spring-webflux</artifactId> + <version>0.8.0</version> + <scope>compile</scope> </dependency> + </dependencies> </project> 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 560f2d8..e49dce7 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 @@ 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 649fce1..fc07072 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 @@ 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 65adeed..c025642 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 @@ */ 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 f2d1211..34585e2 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> 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<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); + 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<String, Object> 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<String> 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(); } + } -- Gitblit v1.9.3