From 77aeabb4be3193d8671e17a3c09e4a1665e315d4 Mon Sep 17 00:00:00 2001
From: OpenX123 <2113239898goole@gmail.com>
Date: Thu, 11 Dec 2025 20:41:04 +0800
Subject: [PATCH] =?UTF-8?q?=E4=B8=BAMCP=E6=9C=8D=E5=8A=A1=E5=99=A8?=
=?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=96=87=E4=BB=B6=E6=93=8D=E4=BD=9C=E3=80=81?=
=?UTF-8?q?=E5=9B=BE=E7=89=87=E6=90=9C=E7=B4=A2=E3=80=81PlantUML=E7=94=9F?=
=?UTF-8?q?=E6=88=90=E3=80=81=E7=BD=91=E9=A1=B5=E6=90=9C=E7=B4=A2=E3=80=81?=
=?UTF-8?q?=E7=BB=88=E7=AB=AF=E5=91=BD=E4=BB=A4=E3=80=81=E6=96=87=E6=A1=A3?=
=?UTF-8?q?=E8=A7=A3=E6=9E=90=E7=AD=89=E5=AE=9E=E7=94=A8=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../src/main/resources/application-dev.yml | 4 +-
ruoyi-extend/ruoyi-mcp-server/pom.xml | 33 ++
.../mcpserve/config/ToolsProperties.java | 65 ++++
.../ruoyi/mcpserve/service/ToolService.java | 306 +++++++++++++++++-
.../src/main/resources/application.yml | 11 +
5 files changed, 414 insertions(+), 5 deletions(-)
create mode 100644 ruoyi-extend/ruoyi-mcp-server/src/main/java/org/ruoyi/mcpserve/config/ToolsProperties.java
diff --git a/ruoyi-admin/src/main/resources/application-dev.yml b/ruoyi-admin/src/main/resources/application-dev.yml
index e952157f..da406558 100644
--- a/ruoyi-admin/src/main/resources/application-dev.yml
+++ b/ruoyi-admin/src/main/resources/application-dev.yml
@@ -15,9 +15,9 @@ spring:
master:
type: ${spring.datasource.type}
driverClassName: com.mysql.cj.jdbc.Driver
- url: jdbc:mysql://127.0.0.1:3306/ruoyi-ai?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
+ url: jdbc:mysql://127.0.0.1:3306/ruoyi_ai?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
username: root
- password: root
+ password: 1234
hikari:
# 最大连接池数量
diff --git a/ruoyi-extend/ruoyi-mcp-server/pom.xml b/ruoyi-extend/ruoyi-mcp-server/pom.xml
index a35f0140..6dfb5d17 100644
--- a/ruoyi-extend/ruoyi-mcp-server/pom.xml
+++ b/ruoyi-extend/ruoyi-mcp-server/pom.xml
@@ -47,6 +47,39 @@
test
+
+
+ cn.hutool
+ hutool-all
+ 5.8.25
+
+
+
+
+ com.squareup.okhttp3
+ okhttp
+ 4.12.0
+
+
+
+
+ net.sourceforge.plantuml
+ plantuml
+ 1.2024.3
+
+
+
+
+ org.springframework.ai
+ spring-ai-tika-document-reader
+
+
+
+
+ org.projectlombok
+ lombok
+ true
+
diff --git a/ruoyi-extend/ruoyi-mcp-server/src/main/java/org/ruoyi/mcpserve/config/ToolsProperties.java b/ruoyi-extend/ruoyi-mcp-server/src/main/java/org/ruoyi/mcpserve/config/ToolsProperties.java
new file mode 100644
index 00000000..18cf16ff
--- /dev/null
+++ b/ruoyi-extend/ruoyi-mcp-server/src/main/java/org/ruoyi/mcpserve/config/ToolsProperties.java
@@ -0,0 +1,65 @@
+package org.ruoyi.mcpserve.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * 工具配置属性类
+ *
+ * @author OpenX
+ */
+@Data
+@Component
+@ConfigurationProperties(prefix = "tools")
+public class ToolsProperties {
+
+ /**
+ * Pexels图片搜索配置
+ */
+ private Pexels pexels = new Pexels();
+
+ /**
+ * Tavily搜索配置
+ */
+ private Tavily tavily = new Tavily();
+
+ /**
+ * 文件操作配置
+ */
+ private FileConfig file = new FileConfig();
+
+ @Data
+ public static class Pexels {
+ /**
+ * Pexels API密钥
+ */
+ private String apiKey;
+
+ /**
+ * API地址
+ */
+ private String apiUrl = "https://api.pexels.com/v1/search";
+ }
+
+ @Data
+ public static class Tavily {
+ /**
+ * Tavily API密钥
+ */
+ private String apiKey;
+
+ /**
+ * API地址
+ */
+ private String baseUrl = "https://api.tavily.com/search";
+ }
+
+ @Data
+ public static class FileConfig {
+ /**
+ * 文件保存目录
+ */
+ private String saveDir = "./tmp";
+ }
+}
diff --git a/ruoyi-extend/ruoyi-mcp-server/src/main/java/org/ruoyi/mcpserve/service/ToolService.java b/ruoyi-extend/ruoyi-mcp-server/src/main/java/org/ruoyi/mcpserve/service/ToolService.java
index a12b1d20..3bf07a65 100644
--- a/ruoyi-extend/ruoyi-mcp-server/src/main/java/org/ruoyi/mcpserve/service/ToolService.java
+++ b/ruoyi-extend/ruoyi-mcp-server/src/main/java/org/ruoyi/mcpserve/service/ToolService.java
@@ -1,21 +1,57 @@
package org.ruoyi.mcpserve.service;
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.http.HttpUtil;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import net.sourceforge.plantuml.FileFormat;
+import net.sourceforge.plantuml.FileFormatOption;
+import net.sourceforge.plantuml.SourceStringReader;
+import okhttp3.*;
+import org.ruoyi.mcpserve.config.ToolsProperties;
+import org.springframework.ai.document.Document;
+import org.springframework.ai.reader.tika.TikaDocumentReader;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
+import org.springframework.core.io.UrlResource;
import org.springframework.stereotype.Service;
+import java.io.*;
+import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
-import java.util.UUID;
-
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
/**
- * @author ageer
+ * MCP工具服务类
+ * 整合了文件操作、图片搜索、PlantUML生成、网页搜索、终端命令、文档解析等功能
+ *
+ * @author ageer,OpenX
*/
@Service
public class ToolService {
+ private final ToolsProperties toolsProperties;
+ private final OkHttpClient httpClient;
+ private final ObjectMapper objectMapper;
+
+ public ToolService(ToolsProperties toolsProperties) {
+ this.toolsProperties = toolsProperties;
+ this.httpClient = new OkHttpClient.Builder()
+ .connectTimeout(30, TimeUnit.SECONDS)
+ .readTimeout(30, TimeUnit.SECONDS)
+ .build();
+ this.objectMapper = new ObjectMapper();
+ }
+
+ // ==================== 基础工具 ====================
+
@Tool(description = "获取一个指定前缀的随机数")
public String add(@ToolParam(description = "字符前缀") String prefix) {
// 定义日期格式
@@ -31,4 +67,268 @@ public class ToolService {
public LocalDateTime getCurrentTime() {
return LocalDateTime.now();
}
+
+ // ==================== 文件操作工具 ====================
+
+ @Tool(description = "Read content from a file")
+ public String readFile(@ToolParam(description = "Name of the file to read") String fileName) {
+ String fileDir = toolsProperties.getFile().getSaveDir() + "/file";
+ String filePath = fileDir + "/" + fileName;
+ try {
+ return FileUtil.readUtf8String(filePath);
+ } catch (Exception e) {
+ return "Error reading file: " + e.getMessage();
+ }
+ }
+
+ @Tool(description = "Write content to a file")
+ public String writeFile(
+ @ToolParam(description = "Name of the file to write") String fileName,
+ @ToolParam(description = "Content to write to the file") String content) {
+ String fileDir = toolsProperties.getFile().getSaveDir() + "/file";
+ String filePath = fileDir + "/" + fileName;
+ try {
+ FileUtil.mkdir(fileDir);
+ FileUtil.writeUtf8String(content, filePath);
+ return "File written successfully to: " + filePath;
+ } catch (Exception e) {
+ return "Error writing to file: " + e.getMessage();
+ }
+ }
+
+ // ==================== 图片搜索工具 ====================
+
+ @Tool(description = "Search for images from Pexels")
+ public String searchImage(@ToolParam(description = "Image search keywords") String query) {
+ try {
+ String apiKey = toolsProperties.getPexels().getApiKey();
+ String apiUrl = toolsProperties.getPexels().getApiUrl();
+
+ Map headers = new HashMap<>();
+ headers.put("Authorization", apiKey);
+
+ Map params = new HashMap<>();
+ params.put("query", query);
+
+ String response = HttpUtil.createGet(apiUrl)
+ .addHeaders(headers)
+ .form(params)
+ .execute()
+ .body();
+
+ List images = JSONUtil.parseObj(response)
+ .getJSONArray("photos")
+ .stream()
+ .map(photoObj -> (JSONObject) photoObj)
+ .map(photoObj -> photoObj.getJSONObject("src"))
+ .map(photo -> photo.getStr("medium"))
+ .filter(StrUtil::isNotBlank)
+ .collect(Collectors.toList());
+
+ return String.join(",", images);
+ } catch (Exception e) {
+ return "Error search image: " + e.getMessage();
+ }
+ }
+
+ // ==================== PlantUML工具 ====================
+
+ @Tool(description = "Generate a PlantUML diagram and return SVG code")
+ public String generatePlantUmlSvg(
+ @ToolParam(description = "UML diagram source code") String umlCode) {
+ try {
+ if (umlCode == null || umlCode.trim().isEmpty()) {
+ return "Error: UML代码不能为空";
+ }
+
+ System.setProperty("PLANTUML_LIMIT_SIZE", "32768");
+ System.setProperty("java.awt.headless", "true");
+
+ String normalizedUmlCode = normalizeUmlCode(umlCode);
+
+ SourceStringReader reader = new SourceStringReader(normalizedUmlCode);
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ reader.generateImage(outputStream, new FileFormatOption(FileFormat.SVG));
+
+ byte[] svgBytes = outputStream.toByteArray();
+ if (svgBytes.length == 0) {
+ return "Error: 生成的SVG内容为空,请检查UML语法是否正确";
+ }
+
+ return new String(svgBytes, StandardCharsets.UTF_8);
+ } catch (Exception e) {
+ return "Error generating PlantUML: " + e.getMessage();
+ }
+ }
+
+ private String normalizeUmlCode(String umlCode) {
+ umlCode = umlCode.trim();
+ if (umlCode.contains("@startuml")) {
+ int startIndex = umlCode.indexOf("@startuml");
+ int endIndex = umlCode.lastIndexOf("@enduml");
+ if (endIndex > startIndex) {
+ String startPart = umlCode.substring(startIndex);
+ int firstNewLine = startPart.indexOf('\n');
+ String content = firstNewLine > 0 ? startPart.substring(firstNewLine + 1) : "";
+ if (content.contains("@enduml")) {
+ content = content.substring(0, content.lastIndexOf("@enduml")).trim();
+ }
+ umlCode = content;
+ }
+ }
+
+ StringBuilder normalizedCode = new StringBuilder();
+ normalizedCode.append("@startuml\n");
+ normalizedCode.append("!pragma layout smetana\n");
+ normalizedCode.append("skinparam charset UTF-8\n");
+ normalizedCode.append("skinparam defaultFontName SimHei\n");
+ normalizedCode.append("skinparam defaultFontSize 12\n");
+ normalizedCode.append("skinparam dpi 150\n");
+ normalizedCode.append("\n");
+ normalizedCode.append(umlCode);
+ if (!umlCode.endsWith("\n")) {
+ normalizedCode.append("\n");
+ }
+ normalizedCode.append("@enduml");
+ return normalizedCode.toString();
+ }
+
+ // ==================== 网页搜索工具 ====================
+
+ @Tool(description = "Search for information from web search engines")
+ public String webSearch(
+ @ToolParam(description = "Search query text") String query,
+ @ToolParam(description = "Max results count") int maxResults) {
+ List