diff --git a/.gitignore b/.gitignore index 03567532..d9cc6d06 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,4 @@ nbdist/ !*/build/*.xml .flattened-pom.xml +/.claude/settings.local.json diff --git a/pom.xml b/pom.xml index dbd40c94..36bf6d39 100644 --- a/pom.xml +++ b/pom.xml @@ -397,13 +397,6 @@ ${revision} - - - org.ruoyi - ruoyi-mcp - ${revision} - - com.github.binarywang diff --git a/ruoyi-admin/pom.xml b/ruoyi-admin/pom.xml index 153e82da..49750f04 100644 --- a/ruoyi-admin/pom.xml +++ b/ruoyi-admin/pom.xml @@ -110,11 +110,6 @@ ruoyi-aiflow - - - org.ruoyi - ruoyi-mcp - de.codecentric diff --git a/ruoyi-admin/src/main/java/org/ruoyi/config/MapperConflictResolver.java b/ruoyi-admin/src/main/java/org/ruoyi/config/MapperConflictResolver.java new file mode 100644 index 00000000..467c96db --- /dev/null +++ b/ruoyi-admin/src/main/java/org/ruoyi/config/MapperConflictResolver.java @@ -0,0 +1,48 @@ +package org.ruoyi.config; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; +import org.springframework.context.annotation.Configuration; + +/** + * BeanDefinitionRegistry后置处理器 + * 解决 MapStruct Plus 生成的 mapper 冲突问题 + * + * @author ruoyi team + */ +@Configuration +public class MapperConflictResolver implements BeanDefinitionRegistryPostProcessor { + + private static final Logger log = LoggerFactory.getLogger(MapperConflictResolver.class); + + @Override + public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { + String[] beanNames = registry.getBeanDefinitionNames(); + + // 查找冲突的 mapper bean + for (String beanName : beanNames) { + if (beanName.equals("chatMessageBoToChatMessageMapperImpl")) { + BeanDefinition beanDefinition = registry.getBeanDefinition(beanName); + String beanClassName = beanDefinition.getBeanClassName(); + + log.info("Found mapper bean: {} -> {}", beanName, beanClassName); + + // 如果是 org.ruoyi.domain.bo.chat 包下的(冲突的),移除它 + if (beanClassName != null && beanClassName.startsWith("org.ruoyi.domain.bo.chat")) { + log.warn("Removing conflicting bean definition: {} ({})", beanName, beanClassName); + registry.removeBeanDefinition(beanName); + } + } + } + } + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + // 不需要实现 + } +} diff --git a/ruoyi-modules/pom.xml b/ruoyi-modules/pom.xml index 73fe7257..5db99df1 100644 --- a/ruoyi-modules/pom.xml +++ b/ruoyi-modules/pom.xml @@ -15,7 +15,6 @@ ruoyi-demo ruoyi-generator ruoyi-job - ruoyi-mcp ruoyi-system ruoyi-wechat ruoyi-workflow diff --git a/ruoyi-modules/ruoyi-chat/docs/frontend-guide.md b/ruoyi-modules/ruoyi-chat/docs/frontend-guide.md new file mode 100644 index 00000000..05cb5b9f --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/docs/frontend-guide.md @@ -0,0 +1,244 @@ +# MCP工具管理前端开发指南 + +## 前置条件 + +- Node.js >= 16.0 +- Vue 3 +- Element Plus +- ECharts (用于图表展示) +- Axios (用于HTTP请求) + +## 安装依赖 + +```bash +npm install element-plus echarts axios +``` + +## 项目结构 + +``` +ruoyi-ui/ +├── src/ +│ ├── api/ +│ │ └── mcp/ +│ │ └── tool.js # API接口 +│ ├── views/ +│ │ └── mcp/ +│ │ ├── tool/ +│ │ │ └── index.vue # 工具管理页面 +│ │ ├── market/ +│ │ │ └── index.vue # 市场管理页面 +│ │ └── log/ +│ │ └── index.vue # 调用日志页面 +│ └── utils/ +│ └── request.js # Axios封装 +``` + +## 菜单配置 + +在系统菜单管理中添加以下菜单: + +### 1. MCP工具管理 + +| 字段 | 值 | +|------|-----| +| 菜单名称 | MCP工具管理 | +| 菜单类型 | 目录 | +| 显示顺序 | 1 | +| 路由地址 | mcp | +| 组件路径 | | + +#### 子菜单:工具列表 + +| 字段 | 值 | +|------|-----| +| 菜单名称 | 工具列表 | +| 菜单类型 | 菜单 | +| 显示顺序 | 1 | +| 路由地址 | tool | +| 组件路径 | mcp/tool/index | +| 权限标识 | mcp:tool:list | + +#### 子菜单:市场管理 + +| 字段 | 值 | +|------|-----| +| 菜单名称 | 市场管理 | +| 菜单类型 | 菜单 | +| 显示顺序 | 2 | +| 路由地址 | market | +| 组件路径 | mcp/market/index | +| 权限标识 | mcp:market:list | + +#### 子菜单:调用日志 + +| 字段 | 值 | +|------|-----| +| 菜单名称 | 调用日志 | +| 菜单类型 | 菜单 | +| 显示顺序 | 3 | +| 路由地址 | log | +| 组件路径 | mcp/log/index | +| 权限标识 | mcp:tool:query | + +## 权限配置 + +| 权限标识 | 权限名称 | 说明 | +|----------|----------|------| +| mcp:tool:list | 工具列表 | 查看工具列表 | +| mcp:tool:query | 工具查询 | 查看工具详情 | +| mcp:tool:add | 工具新增 | 新增工具 | +| mcp:tool:edit | 工具修改 | 修改工具 | +| mcp:tool:remove | 工具删除 | 删除工具 | +| mcp:tool:export | 工具导出 | 导出工具数据 | +| mcp:market:list | 市场列表 | 查看市场列表 | +| mcp:market:query | 市场查询 | 查看市场详情 | +| mcp:market:add | 市场新增 | 新增市场 | +| mcp:market:edit | 市场修改 | 修改市场 | +| mcp:market:remove | 市场删除 | 删除市场 | + +## 路由配置 + +在路由配置文件中添加: + +```javascript +{ + path: '/mcp', + component: Layout, + redirect: '/mcp/tool', + name: 'Mcp', + meta: { title: 'MCP工具管理', icon: 'tools' }, + children: [ + { + path: 'tool', + name: 'McpTool', + component: () => import('@/views/mcp/tool/index'), + meta: { title: '工具列表', icon: 'tool' } + }, + { + path: 'market', + name: 'McpMarket', + component: () => import('@/views/mcp/market/index'), + meta: { title: '市场管理', icon: 'shop' } + }, + { + path: 'log', + name: 'McpLog', + component: () => import('@/views/mcp/log/index'), + meta: { title: '调用日志', icon: 'document' } + } + ] +} +``` + +## API请求配置 + +确保Axios请求拦截器正确配置: + +```javascript +// src/utils/request.js +import axios from 'axios' +import { ElMessage } from 'element-plus' + +const service = axios.create({ + baseURL: process.env.VUE_APP_BASE_API, + timeout: 30000 +}) + +// 请求拦截器 +service.interceptors.request.use( + config => { + // 添加token + const token = localStorage.getItem('Admin-Token') + if (token) { + config.headers['Authorization'] = 'Bearer ' + token + } + return config + }, + error => { + return Promise.reject(error) + } +) + +// 响应拦截器 +service.interceptors.response.use( + response => { + const res = response.data + if (res.code !== 200) { + ElMessage.error(res.msg || '请求失败') + return Promise.reject(new Error(res.msg || '请求失败')) + } + return res + }, + error => { + ElMessage.error(error.message) + return Promise.reject(error) + } +) + +export default service +``` + +## 开发步骤 + +1. **复制代码文件** + - 将 `tool.js` 复制到 `src/api/mcp/` 目录 + - 将 `*.vue` 文件复制到对应的视图目录 + +2. **安装依赖** + ```bash + npm install element-plus echarts + ``` + +3. **配置路由** + - 在路由配置中添加MCP相关路由 + +4. **配置菜单** + - 在系统管理中添加菜单 + +5. **配置权限** + - 在系统管理中添加权限标识 + +6. **测试功能** + - 启动开发服务器 + - 测试各项功能 + +## 注意事项 + +1. **工具类型说明** + - BUILTIN: 内置工具(系统自带,不可编辑) + - LOCAL: 本地STDIO工具(通过命令行启动) + - REMOTE: 远程HTTP工具(通过网络连接) + +2. **配置JSON格式** + - LOCAL类型: `{"command": "npx", "args": ["-y", "@example/tool"], "env": {}}` + - REMOTE类型: `{"baseUrl": "http://localhost:8080/mcp"}` + +3. **错误处理** + - 工具连接测试可能超时,请合理设置超时时间 + - 删除工具前请确认没有正在运行的Agent使用该工具 + +4. **性能优化** + - 调用日志数据量大时,建议使用分页加载 + - 图表数据建议缓存处理,避免频繁请求 + +## 常见问题 + +### 1. 跨域问题 +在 `vue.config.js` 中配置代理: +```javascript +devServer: { + proxy: { + '/api': { + target: 'http://localhost:8080', + changeOrigin: true + } + } +} +``` + +### 2. 图表不显示 +确保ECharts容器有固定高度,并在数据加载后初始化图表。 + +### 3. 权限不生效 +检查菜单权限配置和后端接口权限注解是否一致。 diff --git a/ruoyi-modules/ruoyi-chat/docs/mcp-api-spec.md b/ruoyi-modules/ruoyi-chat/docs/mcp-api-spec.md new file mode 100644 index 00000000..9d8460b7 --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/docs/mcp-api-spec.md @@ -0,0 +1,336 @@ +# MCP工具管理模块 - API接口文档 + +## 概述 + +本文档描述了MCP工具管理模块的REST API接口,供前端开发人员参考。 + +## 基础信息 + +- **Base URL**: `/api/mcp` +- **认证方式**: Bearer Token (SaToken) +- **响应格式**: JSON + +--- + +## 1. MCP工具管理 + +### 1.1 查询工具列表(分页) + +**接口**: `GET /tool/list` + +**权限**: `mcp:tool:list` + +**请求参数**: +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| name | String | 否 | 工具名称(模糊查询) | +| description | String | 否 | 工具描述(模糊查询) | +| type | String | 否 | 工具类型:LOCAL/REMOTE/BUILTIN | +| status | String | 否 | 状态:0-启用, 1-禁用 | +| pageNum | Integer | 是 | 页码,默认1 | +| pageSize | Integer | 是 | 每页数量,默认10 | + +**响应示例**: +```json +{ + "rows": [ + { + "id": 1, + "name": "ReadFileTool", + "description": "读取文件内容工具", + "type": "BUILTIN", + "status": "0", + "configJson": null, + "createTime": "2026-03-08 10:00:00", + "updateTime": "2026-03-08 10:00:00" + } + ], + "total": 1 +} +``` + +### 1.2 查询工具列表(不分页) + +**接口**: `GET /tool/all` + +**权限**: `mcp:tool:list` + +**请求参数**: +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| keyword | String | 否 | 关键词 | +| type | String | 否 | 工具类型 | +| status | String | 否 | 状态 | + +**响应示例**: +```json +{ + "tools": [ + { + "id": 1, + "name": "ReadFileTool", + "description": "读取文件内容工具", + "type": "BUILTIN", + "status": "0" + } + ], + "total": 1 +} +``` + +### 1.3 获取工具详情 + +**接口**: `GET /tool/{id}` + +**权限**: `mcp:tool:query` + +**路径参数**: +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| id | Long | 是 | 工具ID | + +### 1.4 新增工具 + +**接口**: `POST /tool` + +**权限**: `mcp:tool:add` + +**请求体**: +```json +{ + "name": "MyMcpTool", + "description": "我的MCP工具", + "type": "REMOTE", + "status": "0", + "configJson": "{\"baseUrl\": \"http://localhost:8080/mcp\"}" +} +``` + +### 1.5 修改工具 + +**接口**: `PUT /tool` + +**权限**: `mcp:tool:edit` + +**请求体**: 同新增工具 + +### 1.6 删除工具 + +**接口**: `DELETE /tool/{ids}` + +**权限**: `mcp:tool:remove` + +**路径参数**: +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| ids | String | 是 | 工具ID,多个用逗号分隔 | + +### 1.7 更新工具状态 + +**接口**: `PUT /tool/{id}/status` + +**权限**: `mcp:tool:edit` + +**路径参数**: +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| id | Long | 是 | 工具ID | + +**请求参数**: +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| status | String | 是 | 状态:0-启用, 1-禁用 | + +### 1.8 测试工具连接 + +**接口**: `POST /tool/{id}/test` + +**权限**: `mcp:tool:query` + +**响应示例**: +```json +{ + "success": true, + "message": "连接测试成功", + "toolCount": 5, + "tools": ["tool1", "tool2", "tool3", "tool4", "tool5"] +} +``` + +--- + +## 2. MCP市场管理 + +### 2.1 查询市场列表 + +**接口**: `GET /market/list` + +**权限**: `mcp:market:list` + +**请求参数**: +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| name | String | 否 | 市场名称 | +| description | String | 否 | 市场描述 | +| status | String | 否 | 状态 | +| pageNum | Integer | 是 | 页码 | +| pageSize | Integer | 是 | 每页数量 | + +### 2.2 获取市场工具列表 + +**接口**: `GET /market/{marketId}/tools` + +**权限**: `mcp:market:query` + +**路径参数**: +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| marketId | Long | 是 | 市场ID | + +**请求参数**: +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| page | Integer | 否 | 页码,默认1 | +| size | Integer | 否 | 每页数量,默认10 | + +### 2.3 刷新市场工具 + +**接口**: `POST /market/{marketId}/refresh` + +**权限**: `mcp:market:edit` + +**响应示例**: +```json +{ + "success": true, + "message": "刷新成功", + "addedCount": 3, + "updatedCount": 5 +} +``` + +### 2.4 加载工具到本地 + +**接口**: `POST /market/tool/{toolId}/load` + +**权限**: `mcp:market:edit` + +**路径参数**: +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| toolId | Long | 是 | 市场工具ID | + +### 2.5 批量加载工具 + +**接口**: `POST /market/tools/batchLoad` + +**权限**: `mcp:market:edit` + +**请求体**: +```json +{ + "toolIds": [1, 2, 3] +} +``` + +--- + +## 3. 工具调用日志 + +### 3.1 查询调用日志 + +**接口**: `GET /tool/callLog` + +**权限**: `mcp:tool:query` + +**请求参数**: +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| toolId | Long | 否 | 工具ID | +| sessionId | Long | 否 | 会话ID | +| startDate | Date | 否 | 开始日期 | +| endDate | Date | 否 | 结束日期 | +| pageNum | Integer | 是 | 页码 | +| pageSize | Integer | 是 | 每页数量 | + +### 3.2 获取工具统计 + +**接口**: `GET /tool/{toolId}/metrics` + +**权限**: `mcp:tool:query` + +**响应示例**: +```json +{ + "toolId": 1, + "toolName": "ReadFileTool", + "today": { + "callCount": 100, + "successCount": 95, + "failureCount": 5, + "avgDurationMs": 150, + "successRate": 95.0 + }, + "week": { + "callCount": 500, + "successCount": 475, + "failureCount": 25, + "avgDurationMs": 160, + "successRate": 95.0 + } +} +``` + +--- + +## 4. 状态码说明 + +| 状态码 | 说明 | +|--------|------| +| 200 | 请求成功 | +| 401 | 未认证 | +| 403 | 无权限 | +| 404 | 资源不存在 | +| 500 | 服务器错误 | + +--- + +## 5. 前端页面需求 + +### 5.1 MCP工具管理页面 (`/mcp/tool`) + +**功能**: +- 工具列表展示(分页) +- 工具搜索和筛选 +- 新增/编辑/删除工具 +- 工具状态切换 +- 工具连接测试 + +**表格列**: +- 工具名称 +- 工具描述 +- 工具类型(标签显示) +- 状态(开关) +- 创建时间 +- 操作(编辑、删除、测试) + +### 5.2 MCP市场管理页面 (`/mcp/market`) + +**功能**: +- 市场列表展示 +- 市场工具浏览 +- 刷新市场工具 +- 加载工具到本地 + +### 5.3 工具调用日志页面 (`/mcp/log`) + +**功能**: +- 调用日志列表 +- 按工具/日期筛选 +- 成功率统计 +- 响应时间统计 + +**图表**: +- 每日调用次数趋势图 +- 工具调用成功率饼图 +- 平均响应时间柱状图 diff --git a/ruoyi-modules/ruoyi-chat/pom.xml b/ruoyi-modules/ruoyi-chat/pom.xml index e88a1d87..f0a95fe5 100644 --- a/ruoyi-modules/ruoyi-chat/pom.xml +++ b/ruoyi-modules/ruoyi-chat/pom.xml @@ -19,11 +19,6 @@ ruoyi-common-chat - - org.ruoyi - ruoyi-mcp - - org.ruoyi ruoyi-common-sensitive diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/ChartGenerationAgent.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/ChartGenerationAgent.java index 2de1fcea..ab61ee62 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/ChartGenerationAgent.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/ChartGenerationAgent.java @@ -6,7 +6,7 @@ import dev.langchain4j.service.UserMessage; import dev.langchain4j.service.V; -public interface ChartGenerationAgent { +public interface ChartGenerationAgent extends Agent { @SystemMessage(""" You are a chart generation specialist. Your only task is to generate Apache ECharts diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/McpAgent.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/McpAgent.java index fda33dce..7bef6b4a 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/McpAgent.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/McpAgent.java @@ -5,7 +5,7 @@ import dev.langchain4j.service.SystemMessage; import dev.langchain4j.service.UserMessage; import dev.langchain4j.service.V; -public interface McpAgent { +public interface McpAgent extends Agent { /** * 系统提示词:通用工具调用智能体 diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/SqlAgent.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/SqlAgent.java index 99e1ff05..77b8e1ab 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/SqlAgent.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/SqlAgent.java @@ -2,7 +2,6 @@ package org.ruoyi.agent; import dev.langchain4j.agentic.Agent; import dev.langchain4j.service.SystemMessage; -import dev.langchain4j.service.TokenStream; import dev.langchain4j.service.UserMessage; import dev.langchain4j.service.V; @@ -12,7 +11,7 @@ import dev.langchain4j.service.V; * and returning relevant data and analysis results. * */ -public interface SqlAgent { +public interface SqlAgent extends Agent { @SystemMessage(""" This agent is designed for MySQL 5.7 diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/StreamingCreativeWriter.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/StreamingCreativeWriter.java deleted file mode 100644 index 9486b853..00000000 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/StreamingCreativeWriter.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.ruoyi.agent; - -import dev.langchain4j.agentic.Agent; -import dev.langchain4j.service.TokenStream; -import dev.langchain4j.service.UserMessage; -import dev.langchain4j.service.V; - -public interface StreamingCreativeWriter { - @UserMessage(""" - You are a creative writer. - Generate a draft of a story no more than - 3 sentences long around the given topic. - Return only the story and nothing else. - The topic is {{topic}}. - """) - @Agent("Generates a story based on the given topic") - TokenStream generateStory(@V("topic") String topic); -} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/WebSearchAgent.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/WebSearchAgent.java index 33f8737e..970e3a2b 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/WebSearchAgent.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/WebSearchAgent.java @@ -10,7 +10,7 @@ import dev.langchain4j.service.V; * A web search assistant that answers natural language questions by searching the internet * and returning relevant information from web pages. */ -public interface WebSearchAgent { +public interface WebSearchAgent extends Agent { @SystemMessage(""" You are a web search assistant. Answer questions by searching and retrieving web content. diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/manager/TableSchemaManager.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/manager/TableSchemaManager.java index a02eb78d..7366acc8 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/manager/TableSchemaManager.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/manager/TableSchemaManager.java @@ -40,7 +40,7 @@ import lombok.extern.slf4j.Slf4j; @Component @DS("agent") public class TableSchemaManager { - + @Autowired(required = false) private DataSource agentDataSource; @@ -77,7 +77,7 @@ public class TableSchemaManager { loadAllowedTableSchemas(); initialized = true; log.info("Schema cache initialized with {} tables", schemaCache.size()); - + } catch (Exception e) { log.error("Failed to initialize schema cache", e); } @@ -87,7 +87,7 @@ public class TableSchemaManager { /** * 加载所有允许的表的结构信息 */ - private void loadAllowedTableSchemas() throws SQLException { + private void loadAllowedTableSchemas() { List allowedTables = getAllowedTableNames(); for (String tableName : allowedTables) { try { @@ -191,10 +191,7 @@ public class TableSchemaManager { } List allowedTables = getAllowedTableNames(); - return allowedTables.stream() - .map(schemaCache::get) - .filter(Objects::nonNull) - .collect(Collectors.toList()); + return allowedTables.stream().map(schemaCache::get).filter(Objects::nonNull).collect(Collectors.toList()); } /** diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/tool/ExecuteSqlQueryTool.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/tool/ExecuteSqlQueryTool.java index 2cc33a67..7fb56ad6 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/tool/ExecuteSqlQueryTool.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/tool/ExecuteSqlQueryTool.java @@ -11,13 +11,14 @@ import java.util.Map; import javax.sql.DataSource; -import org.springframework.beans.factory.annotation.Autowired; +import org.ruoyi.common.core.utils.SpringUtils; import org.springframework.stereotype.Component; import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder; import dev.langchain4j.agent.tool.Tool; import lombok.extern.slf4j.Slf4j; +import org.ruoyi.mcp.service.core.BuiltinToolProvider; /** * 执行 SQL 查询的 Tool @@ -25,10 +26,12 @@ import lombok.extern.slf4j.Slf4j; */ @Slf4j @Component -public class ExecuteSqlQueryTool { +public class ExecuteSqlQueryTool implements BuiltinToolProvider { - @Autowired(required = false) - private DataSource dataSource; + // 使用延迟初始化,避免在构造函数中调用 SpringUtils.getBean() + private DataSource getDataSource() { + return SpringUtils.getBean(DataSource.class); + } /** * 执行 SELECT SQL 查询 @@ -52,6 +55,7 @@ public class ExecuteSqlQueryTool { } try { + DataSource dataSource = getDataSource(); if (dataSource == null) { return "Error: Database datasource not configured"; } @@ -177,4 +181,19 @@ public class ExecuteSqlQueryTool { } return str; } + + @Override + public String getToolName() { + return "execute_sql_query"; + } + + @Override + public String getDisplayName() { + return "执行SQL查询"; + } + + @Override + public String getDescription() { + return "Execute a SELECT SQL query and return the results. Example: SELECT * FROM sys_user"; + } } diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/tool/QueryAllTablesTool.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/tool/QueryAllTablesTool.java index d9ed0666..9fd6637c 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/tool/QueryAllTablesTool.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/tool/QueryAllTablesTool.java @@ -4,11 +4,12 @@ import java.util.List; import org.ruoyi.agent.domain.TableStructure; import org.ruoyi.agent.manager.TableSchemaManager; -import org.springframework.beans.factory.annotation.Autowired; +import org.ruoyi.common.core.utils.SpringUtils; import org.springframework.stereotype.Component; import dev.langchain4j.agent.tool.Tool; import lombok.extern.slf4j.Slf4j; +import org.ruoyi.mcp.service.core.BuiltinToolProvider; /** * 查询数据库所有表的 Tool @@ -16,11 +17,13 @@ import lombok.extern.slf4j.Slf4j; */ @Slf4j @Component -public class QueryAllTablesTool { +public class QueryAllTablesTool implements BuiltinToolProvider { + + // 使用延迟初始化,避免在构造函数中调用 SpringUtils.getBean() + private TableSchemaManager getTableSchemaManager() { + return SpringUtils.getBean(TableSchemaManager.class); + } - - @Autowired - private TableSchemaManager tableSchemaManager; // 注入管理器 /** * 查询数据库中所有表 * 返回数据库中存在的所有表的列表 @@ -30,36 +33,49 @@ public class QueryAllTablesTool { @Tool("Query all tables in the database and return table names and basic information") public String queryAllTables() { try { - // 1. 从管理器获取所有允许的表结构信息(内部已包含初始化/缓存逻辑) - List tableSchemas = tableSchemaManager.getAllowedTableSchemas(); - - if (tableSchemas == null || tableSchemas.isEmpty()) { - return "No tables found in database or cache is empty."; - } - - // 2. 格式化结果 - StringBuilder result = new StringBuilder(); - result.append("Found ").append(tableSchemas.size()).append(" tables in cache:\n"); - - for (TableStructure schema : tableSchemas) { - String tableName = schema.getTableName(); - String tableType = schema.getTableType() != null ? schema.getTableType() : "TABLE"; - String tableComment = schema.getTableComment(); - - result.append(String.format("- %s (%s) - %s\n", - tableName, - tableType, - tableComment != null ? tableComment : "No comment")); - } - - log.info("Successfully retrieved {} tables from schema cache", tableSchemas.size()); - return result.toString(); - - } catch (Exception e) { - log.error("Error retrieving tables from cache", e); - return "Error: " + e.getMessage(); + // 1. 从管理器获取所有允许的表结构信息(内部已包含初始化/缓存逻辑) + List tableSchemas = getTableSchemaManager().getAllowedTableSchemas(); + + if (tableSchemas == null || tableSchemas.isEmpty()) { + return "No tables found in database or cache is empty."; } - - + + // 2. 格式化结果 + StringBuilder result = new StringBuilder(); + result.append("Found ").append(tableSchemas.size()).append(" tables in cache:\n"); + + for (TableStructure schema : tableSchemas) { + String tableName = schema.getTableName(); + String tableType = schema.getTableType() != null ? schema.getTableType() : "TABLE"; + String tableComment = schema.getTableComment(); + + result.append(String.format("- %s (%s) - %s\n", + tableName, + tableType, + tableComment != null ? tableComment : "No comment")); + } + + log.info("Successfully retrieved {} tables from schema cache", tableSchemas.size()); + return result.toString(); + + } catch (Exception e) { + log.error("Error retrieving tables from cache", e); + return "Error: " + e.getMessage(); + } + } + + @Override + public String getToolName() { + return "query_all_tables"; + } + + @Override + public String getDisplayName() { + return "查询所有表"; + } + + @Override + public String getDescription() { + return "Query all tables in the database and return table names and basic information"; } } diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/tool/QueryTableSchemaTool.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/tool/QueryTableSchemaTool.java index 33d06b27..73a62670 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/tool/QueryTableSchemaTool.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/tool/QueryTableSchemaTool.java @@ -6,20 +6,23 @@ import java.sql.ResultSet; import javax.sql.DataSource; -import org.springframework.beans.factory.annotation.Autowired; +import org.ruoyi.common.core.utils.SpringUtils; import org.springframework.stereotype.Component; import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder; import dev.langchain4j.agent.tool.Tool; import lombok.extern.slf4j.Slf4j; +import org.ruoyi.mcp.service.core.BuiltinToolProvider; @Component @Slf4j -public class QueryTableSchemaTool { +public class QueryTableSchemaTool implements BuiltinToolProvider { - @Autowired(required = false) - private DataSource dataSource; + // 使用延迟初始化,避免在构造函数中调用 SpringUtils.getBean() + private DataSource getDataSource() { + return SpringUtils.getBean(DataSource.class); + } @Tool("Query the CREATE TABLE statement (DDL) for a specific table by table name") public String queryTableSchema(String tableName) { @@ -35,7 +38,7 @@ public class QueryTableSchemaTool { String sql = "SHOW CREATE TABLE `" + tableName + "`"; - try (Connection connection = dataSource.getConnection(); + try (Connection connection = getDataSource().getConnection(); PreparedStatement ps = connection.prepareStatement(sql); ResultSet rs = ps.executeQuery()) { @@ -54,4 +57,19 @@ public class QueryTableSchemaTool { DynamicDataSourceContextHolder.clear(); } } + + @Override + public String getToolName() { + return "query_table_schema"; + } + + @Override + public String getDisplayName() { + return "查询表结构"; + } + + @Override + public String getDescription() { + return "Query the CREATE TABLE statement (DDL) for a specific table by table name"; + } } diff --git a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/config/SystemToolInitializer.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/config/mcp/SystemToolInitializer.java similarity index 95% rename from ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/config/SystemToolInitializer.java rename to ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/config/mcp/SystemToolInitializer.java index 30647c65..8426db52 100644 --- a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/config/SystemToolInitializer.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/config/mcp/SystemToolInitializer.java @@ -1,11 +1,11 @@ -package org.ruoyi.mcp.config; +package org.ruoyi.config.mcp; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.ruoyi.mcp.domain.entity.McpTool; -import org.ruoyi.mcp.enums.McpToolStatus; -import org.ruoyi.mcp.mapper.McpToolMapper; +import org.ruoyi.domain.entity.mcp.McpTool; +import org.ruoyi.enums.McpToolStatus; +import org.ruoyi.mapper.mcp.McpToolMapper; import org.ruoyi.mcp.service.core.BuiltinToolDefinition; import org.ruoyi.mcp.service.core.BuiltinToolRegistry; import org.springframework.boot.ApplicationArguments; diff --git a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/controller/McpMarketController.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/controller/mcp/McpMarketController.java similarity index 92% rename from ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/controller/McpMarketController.java rename to ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/controller/mcp/McpMarketController.java index 52a0eacc..3f840425 100644 --- a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/controller/McpMarketController.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/controller/mcp/McpMarketController.java @@ -1,4 +1,4 @@ -package org.ruoyi.mcp.controller; +package org.ruoyi.controller.mcp; import cn.dev33.satoken.annotation.SaCheckPermission; import jakarta.servlet.http.HttpServletResponse; @@ -11,11 +11,12 @@ import org.ruoyi.common.log.enums.BusinessType; import org.ruoyi.common.mybatis.core.page.PageQuery; import org.ruoyi.common.mybatis.core.page.TableDataInfo; import org.ruoyi.common.web.core.BaseController; -import org.ruoyi.mcp.domain.bo.McpMarketBo; -import org.ruoyi.mcp.domain.dto.McpMarketListResult; -import org.ruoyi.mcp.domain.dto.McpMarketToolListResult; -import org.ruoyi.mcp.domain.vo.McpMarketVo; -import org.ruoyi.mcp.service.IMcpMarketService; +import org.ruoyi.domain.bo.mcp.McpMarketBo; +import org.ruoyi.domain.dto.mcp.McpMarketListResult; +import org.ruoyi.domain.dto.mcp.McpMarketRefreshResult; +import org.ruoyi.domain.dto.mcp.McpMarketToolListResult; +import org.ruoyi.domain.vo.mcp.McpMarketVo; +import org.ruoyi.service.mcp.IMcpMarketService; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -143,7 +144,7 @@ public class McpMarketController extends BaseController { @SaCheckPermission("mcp:market:edit") @Log(title = "MCP市场管理", businessType = BusinessType.UPDATE) @PostMapping("/{marketId}/refresh") - public R refreshMarketTools(@PathVariable Long marketId) { + public R refreshMarketTools(@PathVariable Long marketId) { return R.ok(mcpMarketService.refreshMarketTools(marketId)); } diff --git a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/controller/McpToolController.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/controller/mcp/McpToolController.java similarity index 93% rename from ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/controller/McpToolController.java rename to ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/controller/mcp/McpToolController.java index eae82f72..96d51298 100644 --- a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/controller/McpToolController.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/controller/mcp/McpToolController.java @@ -1,4 +1,4 @@ -package org.ruoyi.mcp.controller; +package org.ruoyi.controller.mcp; import cn.dev33.satoken.annotation.SaCheckPermission; import jakarta.servlet.http.HttpServletResponse; @@ -11,11 +11,11 @@ import org.ruoyi.common.log.enums.BusinessType; import org.ruoyi.common.mybatis.core.page.PageQuery; import org.ruoyi.common.mybatis.core.page.TableDataInfo; import org.ruoyi.common.web.core.BaseController; -import org.ruoyi.mcp.domain.bo.McpToolBo; -import org.ruoyi.mcp.domain.dto.McpToolListResult; -import org.ruoyi.mcp.domain.dto.McpToolTestResult; -import org.ruoyi.mcp.domain.vo.McpToolVo; -import org.ruoyi.mcp.service.IMcpToolService; +import org.ruoyi.domain.bo.mcp.McpToolBo; +import org.ruoyi.domain.dto.mcp.McpToolListResult; +import org.ruoyi.domain.dto.mcp.McpToolTestResult; +import org.ruoyi.domain.vo.mcp.McpToolVo; +import org.ruoyi.service.mcp.IMcpToolService; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; diff --git a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/domain/bo/McpMarketBo.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/mcp/McpMarketBo.java similarity index 93% rename from ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/domain/bo/McpMarketBo.java rename to ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/mcp/McpMarketBo.java index 00493b8d..e0306c0d 100644 --- a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/domain/bo/McpMarketBo.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/mcp/McpMarketBo.java @@ -1,4 +1,4 @@ -package org.ruoyi.mcp.domain.bo; +package org.ruoyi.domain.bo.mcp; import io.github.linpeilie.annotations.AutoMapper; import jakarta.validation.constraints.NotBlank; @@ -6,7 +6,7 @@ import jakarta.validation.constraints.Size; import lombok.Data; import lombok.EqualsAndHashCode; import org.ruoyi.common.mybatis.core.domain.BaseEntity; -import org.ruoyi.mcp.domain.entity.McpMarket; +import org.ruoyi.domain.entity.mcp.McpMarket; /** * MCP 市场业务对象 diff --git a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/domain/bo/McpToolBo.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/mcp/McpToolBo.java similarity index 94% rename from ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/domain/bo/McpToolBo.java rename to ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/mcp/McpToolBo.java index 7bb2005a..74d699e6 100644 --- a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/domain/bo/McpToolBo.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/mcp/McpToolBo.java @@ -1,4 +1,4 @@ -package org.ruoyi.mcp.domain.bo; +package org.ruoyi.domain.bo.mcp; import io.github.linpeilie.annotations.AutoMapper; import jakarta.validation.constraints.NotBlank; @@ -6,7 +6,7 @@ import jakarta.validation.constraints.Size; import lombok.Data; import lombok.EqualsAndHashCode; import org.ruoyi.common.mybatis.core.domain.BaseEntity; -import org.ruoyi.mcp.domain.entity.McpTool; +import org.ruoyi.domain.entity.mcp.McpTool; import java.io.Serial; diff --git a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/domain/dto/McpMarketListResult.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/dto/mcp/McpMarketListResult.java similarity index 90% rename from ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/domain/dto/McpMarketListResult.java rename to ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/dto/mcp/McpMarketListResult.java index 69f0a5d9..eba96d0a 100644 --- a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/domain/dto/McpMarketListResult.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/dto/mcp/McpMarketListResult.java @@ -1,10 +1,10 @@ -package org.ruoyi.mcp.domain.dto; +package org.ruoyi.domain.dto.mcp; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import org.ruoyi.mcp.domain.entity.McpMarket; +import org.ruoyi.domain.entity.mcp.McpMarket; import java.util.List; diff --git a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/domain/dto/McpMarketRefreshResult.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/dto/mcp/McpMarketRefreshResult.java similarity index 94% rename from ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/domain/dto/McpMarketRefreshResult.java rename to ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/dto/mcp/McpMarketRefreshResult.java index d52e91c7..e9c1002d 100644 --- a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/domain/dto/McpMarketRefreshResult.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/dto/mcp/McpMarketRefreshResult.java @@ -1,4 +1,4 @@ -package org.ruoyi.mcp.domain.dto; +package org.ruoyi.domain.dto.mcp; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/domain/dto/McpMarketToolListResult.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/dto/mcp/McpMarketToolListResult.java similarity index 92% rename from ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/domain/dto/McpMarketToolListResult.java rename to ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/dto/mcp/McpMarketToolListResult.java index 8f2e6a81..a66cfd9a 100644 --- a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/domain/dto/McpMarketToolListResult.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/dto/mcp/McpMarketToolListResult.java @@ -1,10 +1,10 @@ -package org.ruoyi.mcp.domain.dto; +package org.ruoyi.domain.dto.mcp; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import org.ruoyi.mcp.domain.entity.McpMarketTool; +import org.ruoyi.domain.entity.mcp.McpMarketTool; import java.util.List; diff --git a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/domain/dto/McpToolListResult.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/dto/mcp/McpToolListResult.java similarity index 90% rename from ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/domain/dto/McpToolListResult.java rename to ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/dto/mcp/McpToolListResult.java index e330abc2..385edcae 100644 --- a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/domain/dto/McpToolListResult.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/dto/mcp/McpToolListResult.java @@ -1,10 +1,10 @@ -package org.ruoyi.mcp.domain.dto; +package org.ruoyi.domain.dto.mcp; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import org.ruoyi.mcp.domain.entity.McpTool; +import org.ruoyi.domain.entity.mcp.McpTool; import java.util.List; diff --git a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/domain/dto/McpToolTestResult.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/dto/mcp/McpToolTestResult.java similarity index 96% rename from ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/domain/dto/McpToolTestResult.java rename to ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/dto/mcp/McpToolTestResult.java index eeb6f3dd..71b873da 100644 --- a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/domain/dto/McpToolTestResult.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/dto/mcp/McpToolTestResult.java @@ -1,4 +1,4 @@ -package org.ruoyi.mcp.domain.dto; +package org.ruoyi.domain.dto.mcp; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/domain/entity/McpMarket.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/entity/mcp/McpMarket.java similarity index 96% rename from ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/domain/entity/McpMarket.java rename to ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/entity/mcp/McpMarket.java index 8d2cf21e..1f805fe2 100644 --- a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/domain/entity/McpMarket.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/entity/mcp/McpMarket.java @@ -1,4 +1,4 @@ -package org.ruoyi.mcp.domain.entity; +package org.ruoyi.domain.entity.mcp; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; diff --git a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/domain/entity/McpMarketTool.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/entity/mcp/McpMarketTool.java similarity index 96% rename from ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/domain/entity/McpMarketTool.java rename to ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/entity/mcp/McpMarketTool.java index 69b942ab..ac7e9c02 100644 --- a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/domain/entity/McpMarketTool.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/entity/mcp/McpMarketTool.java @@ -1,4 +1,4 @@ -package org.ruoyi.mcp.domain.entity; +package org.ruoyi.domain.entity.mcp; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; diff --git a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/domain/entity/McpTool.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/entity/mcp/McpTool.java similarity index 96% rename from ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/domain/entity/McpTool.java rename to ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/entity/mcp/McpTool.java index 0a5b3088..8ce47880 100644 --- a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/domain/entity/McpTool.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/entity/mcp/McpTool.java @@ -1,4 +1,4 @@ -package org.ruoyi.mcp.domain.entity; +package org.ruoyi.domain.entity.mcp; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; diff --git a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/domain/vo/McpMarketVo.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/vo/mcp/McpMarketVo.java similarity index 94% rename from ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/domain/vo/McpMarketVo.java rename to ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/vo/mcp/McpMarketVo.java index 243604cf..0abbea71 100644 --- a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/domain/vo/McpMarketVo.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/vo/mcp/McpMarketVo.java @@ -1,10 +1,10 @@ -package org.ruoyi.mcp.domain.vo; +package org.ruoyi.domain.vo.mcp; import cn.idev.excel.annotation.ExcelIgnoreUnannotated; import cn.idev.excel.annotation.ExcelProperty; import io.github.linpeilie.annotations.AutoMapper; import lombok.Data; -import org.ruoyi.mcp.domain.entity.McpMarket; +import org.ruoyi.domain.entity.mcp.McpMarket; import java.io.Serial; import java.io.Serializable; diff --git a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/domain/vo/McpToolVo.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/vo/mcp/McpToolVo.java similarity index 88% rename from ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/domain/vo/McpToolVo.java rename to ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/vo/mcp/McpToolVo.java index 88cd2494..a5fbd748 100644 --- a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/domain/vo/McpToolVo.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/vo/mcp/McpToolVo.java @@ -1,12 +1,11 @@ -package org.ruoyi.mcp.domain.vo; +package org.ruoyi.domain.vo.mcp; import cn.idev.excel.annotation.ExcelIgnoreUnannotated; import cn.idev.excel.annotation.ExcelProperty; import io.github.linpeilie.annotations.AutoMapper; import lombok.Data; -import org.ruoyi.mcp.domain.entity.McpTool; +import org.ruoyi.domain.entity.mcp.McpTool; -import java.io.Serial; import java.io.Serializable; import java.util.Date; @@ -20,9 +19,6 @@ import java.util.Date; @AutoMapper(target = McpTool.class) public class McpToolVo implements Serializable { - @Serial - private static final long serialVersionUID = 1L; - /** * 工具ID */ diff --git a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/enums/McpToolStatus.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/enums/McpToolStatus.java similarity index 96% rename from ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/enums/McpToolStatus.java rename to ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/enums/McpToolStatus.java index caf8c49b..7facd02a 100644 --- a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/enums/McpToolStatus.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/enums/McpToolStatus.java @@ -1,4 +1,4 @@ -package org.ruoyi.mcp.enums; +package org.ruoyi.enums; import lombok.Getter; diff --git a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/mapper/McpMarketMapper.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mapper/mcp/McpMarketMapper.java similarity index 68% rename from ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/mapper/McpMarketMapper.java rename to ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mapper/mcp/McpMarketMapper.java index 7d9ed31d..6864f2c4 100644 --- a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/mapper/McpMarketMapper.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mapper/mcp/McpMarketMapper.java @@ -1,9 +1,9 @@ -package org.ruoyi.mcp.mapper; +package org.ruoyi.mapper.mcp; import org.apache.ibatis.annotations.Mapper; import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; -import org.ruoyi.mcp.domain.entity.McpMarket; -import org.ruoyi.mcp.domain.vo.McpMarketVo; +import org.ruoyi.domain.entity.mcp.McpMarket; +import org.ruoyi.domain.vo.mcp.McpMarketVo; /** * MCP 市场信息 Mapper diff --git a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/mapper/McpMarketToolMapper.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mapper/mcp/McpMarketToolMapper.java similarity index 76% rename from ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/mapper/McpMarketToolMapper.java rename to ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mapper/mcp/McpMarketToolMapper.java index 2211906d..4580f33c 100644 --- a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/mapper/McpMarketToolMapper.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mapper/mcp/McpMarketToolMapper.java @@ -1,8 +1,8 @@ -package org.ruoyi.mcp.mapper; +package org.ruoyi.mapper.mcp; import org.apache.ibatis.annotations.Mapper; import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; -import org.ruoyi.mcp.domain.entity.McpMarketTool; +import org.ruoyi.domain.entity.mcp.McpMarketTool; /** * MCP 市场工具关联 Mapper diff --git a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/mapper/McpToolMapper.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mapper/mcp/McpToolMapper.java similarity index 68% rename from ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/mapper/McpToolMapper.java rename to ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mapper/mcp/McpToolMapper.java index 5e46f399..a3ce4d33 100644 --- a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/mapper/McpToolMapper.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mapper/mcp/McpToolMapper.java @@ -1,9 +1,9 @@ -package org.ruoyi.mcp.mapper; +package org.ruoyi.mapper.mcp; import org.apache.ibatis.annotations.Mapper; import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; -import org.ruoyi.mcp.domain.entity.McpTool; -import org.ruoyi.mcp.domain.vo.McpToolVo; +import org.ruoyi.domain.entity.mcp.McpTool; +import org.ruoyi.domain.vo.mcp.McpToolVo; /** * MCP 工具信息 Mapper diff --git a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/service/core/BuiltinToolDefinition.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mcp/service/core/BuiltinToolDefinition.java similarity index 100% rename from ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/service/core/BuiltinToolDefinition.java rename to ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mcp/service/core/BuiltinToolDefinition.java diff --git a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/service/core/BuiltinToolProvider.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mcp/service/core/BuiltinToolProvider.java similarity index 100% rename from ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/service/core/BuiltinToolProvider.java rename to ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mcp/service/core/BuiltinToolProvider.java diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mcp/service/core/BuiltinToolRegistry.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mcp/service/core/BuiltinToolRegistry.java new file mode 100644 index 00000000..48e1034f --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mcp/service/core/BuiltinToolRegistry.java @@ -0,0 +1,174 @@ +package org.ruoyi.mcp.service.core; + +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.util.ClassUtils; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 内置工具注册表 + * 自动发现并注册所有实现 {@link BuiltinToolProvider} 接口的工具 + * + *

工具注册流程: + *

    + *
  1. Spring 自动注入所有 {@link BuiltinToolProvider} 实现
  2. + *
  3. {@link #init()} 方法在 Bean 初始化后自动调用
  4. + *
  5. 将所有工具注册到内部 Map
  6. + *
+ * + *

添加新工具只需: + *

    + *
  1. 创建一个类实现 {@link BuiltinToolProvider} 接口
  2. + *
  3. 添加 {@code @Component} 注解
  4. + *
  5. 工具会自动被发现和注册
  6. + *
+ * + * @author ruoyi team + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class BuiltinToolRegistry { + + /** + * 工具类型常量 + */ + public static final String TYPE_BUILTIN = "BUILTIN"; + + /** + * Spring 自动注入所有实现 BuiltinToolProvider 接口的 Bean + * 注意:这些是 Spring 代理,不能直接用于 LangChain4j + * 我们需要提取 Class 信息以便创建新实例 + */ + private final List toolProviders; + + /** + * 内置工具类映射表 (工具名称 -> 工具类) + * 存储 Class 对象而不是实例,以便创建不带 Spring 代理的新实例 + */ + private final Map> registeredToolClasses = new ConcurrentHashMap<>(); + + /** + * 内置工具显示名称映射表 (工具名称 -> 显示名称) + */ + private final Map displayNames = new ConcurrentHashMap<>(); + + /** + * 初始化方法,在 Bean 创建后自动调用 + * 提取工具类信息而不是存储 Spring 代理实例 + */ + @PostConstruct + public void init() { + log.info("开始注册内置工具,发现 {} 个工具提供者", toolProviders.size()); + + // 1. 注册通过 Spring 自动发现工具 + for (BuiltinToolProvider provider : toolProviders) { + String toolName = provider.getToolName(); + + if (registeredToolClasses.containsKey(toolName)) { + log.warn("工具名称重复: {},将覆盖原有注册", toolName); + } + + // 使用 ClassUtils.getUserClass 获取原始类,避免 Spring CGLIB 代理类 + Class targetClass = ClassUtils.getUserClass(provider); + registeredToolClasses.put(toolName, targetClass); + displayNames.put(toolName, provider.getDisplayName()); + log.info("注册内置工具: {} ({}) - 原始类: {}", toolName, provider.getDisplayName(), targetClass.getName()); + } + + log.info("内置工具注册完成,共 {} 个工具", registeredToolClasses.size()); + } + + /** + * 获取工具提供者(返回 Spring 代理,仅用于元数据查询) + * + * @param toolName 工具名称 + * @return 工具提供者,如果不存在则返回 null + */ + public BuiltinToolProvider getToolProvider(String toolName) { + // 这个方法返回 Spring 代理,仅用于获取元数据 + for (BuiltinToolProvider provider : toolProviders) { + if (provider.getToolName().equals(toolName)) { + return provider; + } + } + return null; + } + + /** + * 检查工具是否已注册 + * + * @param toolName 工具名称 + * @return 是否已注册 + */ + public boolean hasTool(String toolName) { + return registeredToolClasses.containsKey(toolName); + } + + /** + * 获取所有内置工具定义 + * + * @return 内置工具定义集合 + */ + public Collection getAllBuiltinTools() { + return displayNames.entrySet().stream() + .map(entry -> new BuiltinToolDefinition( + entry.getKey(), + entry.getValue(), + "" // Description can be added later if needed + )) + .toList(); + } + + /** + * 获取所有内置工具对象 + * 这些对象包含 @Tool 注解的方法,可直接用于 AgenticServices + * 注意:每次调用都创建新实例,以避免 Spring CGLIB 代理问题 + * + * @return 内置工具对象列表 + */ + public List getAllBuiltinToolObjects() { + List toolInstances = new java.util.ArrayList<>(); + + for (java.util.Map.Entry> entry : registeredToolClasses.entrySet()) { + try { + // 使用无参构造函数创建新实例,保留 @Tool 注解 + Object instance = entry.getValue().getDeclaredConstructor().newInstance(); + toolInstances.add(instance); + log.debug("创建工具实例: {}", entry.getKey()); + } catch (Exception e) { + log.error("创建工具实例失败: {} - {}", entry.getKey(), e.getMessage()); + } + } + + return toolInstances; + } + + /** + * 根据工具名称获取工具对象 + * 注意:每次调用都创建新实例,以避免 Spring CGLIB 代理问题 + * + * @param toolName 工具名称 + * @return 工具对象,如果不存在则返回 null + */ + public Object getBuiltinToolObject(String toolName) { + Class toolClass = registeredToolClasses.get(toolName); + if (toolClass == null) { + return null; + } + + try { + // 使用无参构造函数创建新实例,保留 @Tool 注解 + return toolClass.getDeclaredConstructor().newInstance(); + } catch (Exception e) { + log.error("创建工具实例失败: {} - {}", toolName, e.getMessage()); + return null; + } + } +} diff --git a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/service/core/LangChain4jMcpToolProviderService.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mcp/service/core/LangChain4jMcpToolProviderService.java similarity index 90% rename from ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/service/core/LangChain4jMcpToolProviderService.java rename to ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mcp/service/core/LangChain4jMcpToolProviderService.java index 7abc9fd9..1c09793e 100644 --- a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/service/core/LangChain4jMcpToolProviderService.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mcp/service/core/LangChain4jMcpToolProviderService.java @@ -1,5 +1,6 @@ package org.ruoyi.mcp.service.core; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import dev.langchain4j.mcp.McpToolProvider; @@ -12,9 +13,9 @@ import dev.langchain4j.service.tool.ToolProvider; import jakarta.annotation.PreDestroy; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.ruoyi.mcp.domain.entity.McpTool; -import org.ruoyi.mcp.enums.McpToolStatus; -import org.ruoyi.mcp.mapper.McpToolMapper; +import org.ruoyi.domain.entity.mcp.McpTool; +import org.ruoyi.enums.McpToolStatus; +import org.ruoyi.mapper.mcp.McpToolMapper; import org.springframework.stereotype.Service; import java.util.ArrayList; @@ -99,7 +100,7 @@ public class LangChain4jMcpToolProviderService { */ public ToolProvider getAllEnabledToolsProvider() { List enabledTools = mcpToolMapper.selectList( - new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() + new LambdaQueryWrapper() .eq(McpTool::getStatus, McpToolStatus.ENABLED.getValue()) ); @@ -330,6 +331,11 @@ public class LangChain4jMcpToolProviderService { // 处理 Windows 系统的命令 command = resolveCommand(command); + // 检查命令是否可用 + if (!isCommandAvailable(command)) { + throw new IllegalArgumentException("Command '" + command + "' is not available on this system. Please install the required package or use a different tool."); + } + // 构建完整命令列表 List fullCommand = new ArrayList<>(); fullCommand.add(command); @@ -349,6 +355,29 @@ public class LangChain4jMcpToolProviderService { .build(); } + /** + * 检查命令是否在系统上可用 + */ + private boolean isCommandAvailable(String command) { + try { + ProcessBuilder pb = new ProcessBuilder(command, "--version"); + pb.redirectErrorStream(true); + Process process = pb.start(); + boolean finished = process.waitFor(5, java.util.concurrent.TimeUnit.SECONDS); + if (!finished) { + process.destroyForcibly(); + return false; + } + int exitCode = process.exitValue(); + // 对于某些命令,--version 可能返回非零退出码,所以我们只检查进程是否能启动 + // 如果进程能启动并退出(无论退出码是什么),我们认为命令可用 + return true; + } catch (Exception e) { + log.debug("Command '{}' is not available: {}", command, e.getMessage()); + return false; + } + } + /** * 创建远程 HTTP/SSE Client */ diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mcp/service/core/ToolProviderFactory.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mcp/service/core/ToolProviderFactory.java new file mode 100644 index 00000000..189593e0 --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mcp/service/core/ToolProviderFactory.java @@ -0,0 +1,49 @@ +package org.ruoyi.mcp.service.core; + +import dev.langchain4j.service.tool.ToolProvider; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 统一工具提供工厂 + * 整合所有类型的MCP工具提供者,为Agent和Chat服务提供统一的工具获取入口 + * + *

支持的工具类型: + *

    + *
  • BUILTIN - 内置工具(如文件操作工具)
  • + *
  • LOCAL - 本地STDIO工具(通过命令行启动的MCP服务器)
  • + *
  • REMOTE - 远程HTTP/SSE工具(通过网络连接的MCP服务器)
  • + *
+ * + * @author ruoyi team + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class ToolProviderFactory { + + private final BuiltinToolRegistry builtinToolRegistry; + private final LangChain4jMcpToolProviderService langChain4jMcpToolProviderService; + + /** + * 获取所有已启用的MCP工具的ToolProvider + * + * @return ToolProvider实例 + */ + public ToolProvider getAllEnabledMcpToolsProvider() { + return langChain4jMcpToolProviderService.getAllEnabledToolsProvider(); + } + + /** + * 获取所有 BUILTIN 工具对象 + * 这些对象包含 @Tool 注解的方法,可直接用于 AgenticServices + * + * @return BUILTIN 工具对象列表 + */ + public List getAllBuiltinToolObjects() { + return builtinToolRegistry.getAllBuiltinToolObjects(); + } +} diff --git a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/tools/EditFileTool.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mcp/tools/EditFileTool.java similarity index 100% rename from ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/tools/EditFileTool.java rename to ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mcp/tools/EditFileTool.java diff --git a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/tools/ListDirectoryTool.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mcp/tools/ListDirectoryTool.java similarity index 100% rename from ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/tools/ListDirectoryTool.java rename to ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mcp/tools/ListDirectoryTool.java diff --git a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/tools/ReadFileTool.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mcp/tools/ReadFileTool.java similarity index 100% rename from ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/tools/ReadFileTool.java rename to ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mcp/tools/ReadFileTool.java diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/AbstractStreamingChatService.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/AbstractStreamingChatService.java index 2e81a9a1..958aacfc 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/AbstractStreamingChatService.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/AbstractStreamingChatService.java @@ -1,50 +1,41 @@ package org.ruoyi.service.chat.impl; import dev.langchain4j.agentic.AgenticServices; -import dev.langchain4j.agentic.supervisor.SupervisorAgent; -import dev.langchain4j.agentic.supervisor.SupervisorResponseStrategy; import dev.langchain4j.community.model.dashscope.QwenChatModel; import dev.langchain4j.data.message.ChatMessage; import dev.langchain4j.data.message.UserMessage; -import dev.langchain4j.mcp.McpToolProvider; -import dev.langchain4j.mcp.client.DefaultMcpClient; -import dev.langchain4j.mcp.client.McpClient; -import dev.langchain4j.mcp.client.transport.McpTransport; -import dev.langchain4j.mcp.client.transport.stdio.StdioMcpTransport; import dev.langchain4j.memory.chat.MessageWindowChatMemory; import dev.langchain4j.model.chat.StreamingChatModel; import dev.langchain4j.model.chat.response.ChatResponse; import dev.langchain4j.model.chat.response.StreamingChatResponseHandler; -import dev.langchain4j.model.openai.OpenAiChatModel; import dev.langchain4j.service.tool.ToolProvider; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; -import org.ruoyi.agent.ChartGenerationAgent; -import org.ruoyi.agent.SqlAgent; -import org.ruoyi.agent.WebSearchAgent; -import org.ruoyi.agent.tool.ExecuteSqlQueryTool; -import org.ruoyi.agent.tool.QueryAllTablesTool; -import org.ruoyi.agent.tool.QueryTableSchemaTool; +import org.ruoyi.agent.McpAgent; import org.ruoyi.common.chat.base.ThreadContext; +import org.ruoyi.common.chat.domain.dto.request.ChatRequest; import org.ruoyi.common.chat.domain.dto.request.ReSumeRunner; import org.ruoyi.common.chat.domain.dto.request.WorkFlowRunner; +import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; +import org.ruoyi.common.chat.entity.chat.ChatContext; import org.ruoyi.common.chat.enums.RoleType; import org.ruoyi.common.chat.service.chat.IChatService; -import org.ruoyi.common.chat.domain.dto.request.ChatRequest; -import org.ruoyi.common.chat.entity.chat.ChatContext; -import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; import org.ruoyi.common.chat.service.chatMessage.AbstractChatMessageService; import org.ruoyi.common.chat.service.workFlow.IWorkFlowStarterService; import org.ruoyi.common.core.utils.ObjectUtils; import org.ruoyi.common.core.utils.SpringUtils; import org.ruoyi.common.core.utils.StringUtils; import org.ruoyi.common.sse.utils.SseMessageUtils; +import org.ruoyi.mcp.service.core.ToolProviderFactory; import org.ruoyi.service.chat.impl.memory.PersistentChatMemoryStore; import org.springframework.util.CollectionUtils; import org.springframework.validation.annotation.Validated; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; /** @@ -213,17 +204,6 @@ public abstract class AbstractStreamingChatService extends AbstractChatMessageSe }); } - /** - * 清理指定会话的内存缓存(可选) - * 在会话结束时调用,释放内存资源 - * - * @param sessionId 会话ID - */ - public static void clearChatMemory(Object sessionId) { - memoryCache.remove(sessionId); - log.debug("已清理会话 {} 的内存缓存", sessionId); - } - /** * 执行聊天(钩子方法 - 子类必须实现) * 注意:messages 已包含完整的历史上下文和当前消息 @@ -232,7 +212,8 @@ public abstract class AbstractStreamingChatService extends AbstractChatMessageSe * @param chatRequest 聊天请求 * @param handler 响应处理器 */ - protected abstract void doChat(ChatModelVo chatModelVo, ChatRequest chatRequest, List messagesWithMemory, StreamingChatResponseHandler handler); + protected abstract void doChat(ChatModelVo chatModelVo, ChatRequest chatRequest, + List messagesWithMemory, StreamingChatResponseHandler handler); /** * 创建标准的响应处理器 @@ -302,103 +283,80 @@ public abstract class AbstractStreamingChatService extends AbstractChatMessageSe }; } - /** - * 构建具体厂商的 StreamingChatModel - * 子类必须实现此方法,返回对应厂商的模型实例 - */ - protected abstract StreamingChatModel buildStreamingChatModel(ChatModelVo chatModelVo, ChatRequest chatRequest); - - /** * 获取提供者名称(子类必须实现) */ public abstract String getProviderName(); protected String doAgent(String userMessage, ChatModelVo chatModelVo) { - // 步骤1: 配置MCP传输层 - 连接到bing-cn-mcp服务器 - // 该服务提供两个工具: bing_search (必应搜索) 和 crawl_webpage (网页抓取) - // McpTransport transport = new StdioMcpTransport.Builder() - // .command(List.of("C:\\Program Files\\nodejs\\npx.cmd", "-y", - // "bing-cn-mcp" - // )) - // .logEvents(true) - // .build(); - - // // 步骤2: 创建MCP客户端 - // McpClient mcpClient = new DefaultMcpClient.Builder() - // .transport(transport) - // .build(); - - // // 步骤3: 配置工具提供者 - // ToolProvider toolProvider = McpToolProvider.builder() - // .mcpClients(List.of(mcpClient)) - // .build(); - - - McpTransport transport1 = new StdioMcpTransport.Builder() - .command(List.of("npx", "-y", - "mcp-echarts" - )) - .logEvents(true) - .build(); - - // 步骤2: 创建MCP客户端 - McpClient mcpClient1 = new DefaultMcpClient.Builder() - .transport(transport1) - .build(); - - // 步骤3: 配置工具提供者 - ToolProvider toolProvider1 = McpToolProvider.builder() - .mcpClients(List.of(mcpClient1)) - .build(); - - // 步骤4: 配置OpenAI模型 - // OpenAiChatModel PLANNER_MODEL = OpenAiChatModel.builder() - // .baseUrl(chatModelVo.getApiHost()) - // .apiKey(chatModelVo.getApiKey()) - // .modelName(chatModelVo.getModelName()) - // .build(); - - - QwenChatModel qwenChatModel = QwenChatModel.builder() - // .baseUrl(chatModelVo.getApiHost()) - .apiKey(chatModelVo.getApiKey()) - .modelName(chatModelVo.getModelName()) - .build(); - - SqlAgent sqlAgent = AgenticServices.agentBuilder(SqlAgent.class) - .chatModel( - qwenChatModel) - .tools( - SpringUtils.getBean(QueryAllTablesTool.class), // 必须通过 getBean 获取 - SpringUtils.getBean(QueryTableSchemaTool.class), - SpringUtils.getBean(ExecuteSqlQueryTool.class) - ) - .build(); - - // WebSearchAgent searchAgent = AgenticServices.agentBuilder(WebSearchAgent.class) - // .chatModel(PLANNER_MODEL) - // .toolProvider(toolProvider) - // .build(); - - ChartGenerationAgent chartGenerationAgent = AgenticServices.agentBuilder(ChartGenerationAgent.class) - .chatModel( - qwenChatModel) - .toolProvider(toolProvider1) - .build(); - String res = sqlAgent.getData(userMessage); - String res1 = chartGenerationAgent.generateChart(res); - System.out.println(res1); - System.out.println(res); - SupervisorAgent supervisor = AgenticServices - .supervisorBuilder() - .chatModel(qwenChatModel) - .subAgents(sqlAgent, chartGenerationAgent) - .responseStrategy(SupervisorResponseStrategy.LAST) - .build(); - - String invoke = supervisor.invoke(userMessage); - System.out.println(invoke); - return res1; + log.info("执行Agent任务,消息: {}", userMessage); + // 加载所有可用的 Agent,让 Supervisor 根据任务类型自动选择 + return doAgentWithAllAgents(userMessage, chatModelVo); } + + /** + * 使用单一 Agent 处理所有任务 + * 不使用 Supervisor 模式,而是使用 MCP Agent 来处理所有任务 + * + * @param userMessage 用户消息 + * @param chatModelVo 聊天模型配置 + * @return Agent 响应结果 + */ + protected String doAgentWithAllAgents(String userMessage, ChatModelVo chatModelVo) { + + try { + // 1. 加载 LLM 模型 + QwenChatModel qwenChatModel = QwenChatModel.builder() + .apiKey(chatModelVo.getApiKey()) + .modelName(chatModelVo.getModelName()) + .build(); + + // 2. 获取统一工具提供工厂 + ToolProviderFactory toolProviderFactory = SpringUtils.getBean(ToolProviderFactory.class); + + // 3. 获取所有可用的工具 + + // 3.1 添加 BUILTIN 工具对象(包括 SQL 工具) + List builtinTools = toolProviderFactory.getAllBuiltinToolObjects(); + + List allTools = new ArrayList<>(builtinTools); + + log.debug("Loaded {} builtin tools (including SQL tools)", builtinTools.size()); + + log.debug("Total tools: {}", allTools.size()); + + // 4. 获取 MCP 工具提供者 + ToolProvider mcpToolProvider = toolProviderFactory.getAllEnabledMcpToolsProvider(); + + // 5. 创建 MCP Agent(包含所有工具) + var agentBuilder = AgenticServices.agentBuilder(McpAgent.class).chatModel(qwenChatModel); + + // 添加所有工具 + if (!allTools.isEmpty()) { + agentBuilder.tools(allTools.toArray(new Object[0])); + } + + // 添加 MCP 工具 + if (mcpToolProvider != null) { + agentBuilder.toolProvider(mcpToolProvider); + } + + McpAgent mcpAgent = agentBuilder.build(); + + // 6. 调用大模型LLM + String result = mcpAgent.callMcpTool(userMessage); + log.info("Agent 执行完成,结果长度: {}", result.length()); + return result; + + } catch (Exception e) { + log.error("Agent 模式执行失败: {}", e.getMessage(), e); + } + return null; + } + + /** + * 创建流式聊天模型 + * 子类必须实现此方法,返回对应厂商的模型实例 + */ + protected abstract StreamingChatModel buildStreamingChatModel(ChatModelVo chatModelVo, ChatRequest chatRequest); } diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/provider/QianWenChatServiceImpl.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/provider/QianWenChatServiceImpl.java index 0b65a1f9..22a9ae0e 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/provider/QianWenChatServiceImpl.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/provider/QianWenChatServiceImpl.java @@ -1,30 +1,21 @@ package org.ruoyi.service.chat.impl.provider; -import dev.langchain4j.agentic.AgenticServices; -import dev.langchain4j.agentic.supervisor.SupervisorAgent; -import dev.langchain4j.agentic.supervisor.SupervisorResponseStrategy; -import dev.langchain4j.community.model.dashscope.QwenChatModel; import dev.langchain4j.community.model.dashscope.QwenStreamingChatModel; import dev.langchain4j.data.message.ChatMessage; import dev.langchain4j.data.message.SystemMessage; import dev.langchain4j.data.message.UserMessage; import dev.langchain4j.model.chat.StreamingChatModel; import dev.langchain4j.model.chat.response.StreamingChatResponseHandler; -import dev.langchain4j.service.tool.ToolProvider; import lombok.extern.slf4j.Slf4j; -import org.ruoyi.agent.McpAgent; import org.ruoyi.common.chat.domain.dto.request.ChatRequest; import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; -import org.ruoyi.common.core.utils.SpringUtils; import org.ruoyi.enums.ChatModeType; -import org.ruoyi.mcp.service.core.ToolProviderFactory; import org.ruoyi.service.chat.impl.AbstractStreamingChatService; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.locks.ReentrantLock; /** * qianWenAI服务调用 @@ -39,9 +30,6 @@ public class QianWenChatServiceImpl extends AbstractStreamingChatService { // 添加文档解析的前缀字段 private static final String UPLOAD_FILE_API_PREFIX = "fileid"; - // 用于线程安全的锁 - private final ReentrantLock cacheLock = new ReentrantLock(); - @Override protected StreamingChatModel buildStreamingChatModel(ChatModelVo chatModelVo,ChatRequest chatRequest) { return QwenStreamingChatModel.builder() @@ -91,69 +79,6 @@ public class QianWenChatServiceImpl extends AbstractStreamingChatService { }).orElse(messagesWithMemory); } - /** - * 调用MCP服务(智能体) - * 使用统一的ToolProviderFactory获取所有已配置的工具(BUILTIN + MCP) - * - * @param userMessage 用户信息 - * @param chatModelVo 模型信息 - * @return 返回LLM信息 - */ - protected String doAgent(String userMessage, ChatModelVo chatModelVo) { - try { - // 步骤1: 获取统一工具提供工厂 - ToolProviderFactory toolProviderFactory = SpringUtils.getBean(ToolProviderFactory.class); - - // 步骤2: 获取 BUILTIN 工具对象 - List builtinTools = toolProviderFactory.getAllBuiltinToolObjects(); - - // 步骤3: 获取 MCP 工具提供者 - ToolProvider mcpToolProvider = toolProviderFactory.getAllEnabledMcpToolsProvider(); - - log.info("doAgent: BUILTIN tools count = {}, MCP tools enabled = {}", - builtinTools.size(), mcpToolProvider != null); - - // 步骤4: 加载LLM模型 - QwenChatModel qwenChatModel = QwenChatModel.builder() - .apiKey(chatModelVo.getApiKey()) - .modelName(chatModelVo.getModelName()) - .build(); - - // 步骤5: 创建MCP Agent,使用所有已配置的工具 - // 使用 .tools() 传入 BUILTIN 工具对象(Java 对象,带 @Tool 注解的方法) - // 使用 .toolProvider() 传入 MCP 工具提供者(MCP 协议工具) - var agentBuilder = AgenticServices.agentBuilder(McpAgent.class) - .chatModel(qwenChatModel); - - // 添加 BUILTIN 工具(如果有) - if (!builtinTools.isEmpty()) { - agentBuilder.tools(builtinTools.toArray(new Object[0])); - log.debug("Added {} BUILTIN tools to agent", builtinTools.size()); - } - - // 添加 MCP 工具(如果有) - if (mcpToolProvider != null) { - agentBuilder.toolProvider(mcpToolProvider); - log.debug("Added MCP tool provider to agent"); - } - - McpAgent mcpAgent = agentBuilder.build(); - - // 步骤6: 创建超级智能体协调MCP Agent - SupervisorAgent supervisor = AgenticServices - .supervisorBuilder() - .chatModel(qwenChatModel) - .subAgents(mcpAgent) - .responseStrategy(SupervisorResponseStrategy.LAST) - .build(); - - // 步骤7: 调用大模型LLM - return supervisor.invoke(userMessage); - } finally { - cacheLock.unlock(); - } - } - @Override public String getProviderName() { return ChatModeType.QIAN_WEN.getCode(); diff --git a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/service/IMcpMarketService.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/mcp/IMcpMarketService.java similarity index 89% rename from ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/service/IMcpMarketService.java rename to ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/mcp/IMcpMarketService.java index b7a68b1f..e359100b 100644 --- a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/service/IMcpMarketService.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/mcp/IMcpMarketService.java @@ -1,12 +1,12 @@ -package org.ruoyi.mcp.service; +package org.ruoyi.service.mcp; import org.ruoyi.common.mybatis.core.page.PageQuery; import org.ruoyi.common.mybatis.core.page.TableDataInfo; -import org.ruoyi.mcp.domain.bo.McpMarketBo; -import org.ruoyi.mcp.domain.dto.McpMarketListResult; -import org.ruoyi.mcp.domain.dto.McpMarketRefreshResult; -import org.ruoyi.mcp.domain.dto.McpMarketToolListResult; -import org.ruoyi.mcp.domain.vo.McpMarketVo; +import org.ruoyi.domain.bo.mcp.McpMarketBo; +import org.ruoyi.domain.dto.mcp.McpMarketListResult; +import org.ruoyi.domain.dto.mcp.McpMarketRefreshResult; +import org.ruoyi.domain.dto.mcp.McpMarketToolListResult; +import org.ruoyi.domain.vo.mcp.McpMarketVo; import java.util.List; diff --git a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/service/IMcpToolService.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/mcp/IMcpToolService.java similarity index 88% rename from ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/service/IMcpToolService.java rename to ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/mcp/IMcpToolService.java index d7cba323..d61ef823 100644 --- a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/service/IMcpToolService.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/mcp/IMcpToolService.java @@ -1,11 +1,11 @@ -package org.ruoyi.mcp.service; +package org.ruoyi.service.mcp; import org.ruoyi.common.mybatis.core.page.PageQuery; import org.ruoyi.common.mybatis.core.page.TableDataInfo; -import org.ruoyi.mcp.domain.bo.McpToolBo; -import org.ruoyi.mcp.domain.dto.McpToolListResult; -import org.ruoyi.mcp.domain.dto.McpToolTestResult; -import org.ruoyi.mcp.domain.vo.McpToolVo; +import org.ruoyi.domain.bo.mcp.McpToolBo; +import org.ruoyi.domain.dto.mcp.McpToolListResult; +import org.ruoyi.domain.dto.mcp.McpToolTestResult; +import org.ruoyi.domain.vo.mcp.McpToolVo; import java.util.List; diff --git a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/service/impl/McpMarketServiceImpl.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/mcp/impl/McpMarketServiceImpl.java similarity index 94% rename from ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/service/impl/McpMarketServiceImpl.java rename to ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/mcp/impl/McpMarketServiceImpl.java index b3bed3ab..46eaad7b 100644 --- a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/service/impl/McpMarketServiceImpl.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/mcp/impl/McpMarketServiceImpl.java @@ -1,4 +1,4 @@ -package org.ruoyi.mcp.service.impl; +package org.ruoyi.service.mcp.impl; import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpResponse; @@ -13,19 +13,19 @@ import org.ruoyi.common.core.exception.ServiceException; import org.ruoyi.common.core.utils.MapstructUtils; import org.ruoyi.common.mybatis.core.page.PageQuery; import org.ruoyi.common.mybatis.core.page.TableDataInfo; -import org.ruoyi.mcp.domain.bo.McpMarketBo; -import org.ruoyi.mcp.domain.dto.McpMarketListResult; -import org.ruoyi.mcp.domain.dto.McpMarketRefreshResult; -import org.ruoyi.mcp.domain.dto.McpMarketToolListResult; -import org.ruoyi.mcp.domain.entity.McpMarket; -import org.ruoyi.mcp.domain.entity.McpMarketTool; -import org.ruoyi.mcp.domain.entity.McpTool; -import org.ruoyi.mcp.domain.vo.McpMarketVo; -import org.ruoyi.mcp.enums.McpToolStatus; -import org.ruoyi.mcp.mapper.McpMarketMapper; -import org.ruoyi.mcp.mapper.McpMarketToolMapper; -import org.ruoyi.mcp.mapper.McpToolMapper; -import org.ruoyi.mcp.service.IMcpMarketService; +import org.ruoyi.domain.bo.mcp.McpMarketBo; +import org.ruoyi.domain.dto.mcp.McpMarketListResult; +import org.ruoyi.domain.dto.mcp.McpMarketRefreshResult; +import org.ruoyi.domain.dto.mcp.McpMarketToolListResult; +import org.ruoyi.domain.entity.mcp.McpMarket; +import org.ruoyi.domain.entity.mcp.McpMarketTool; +import org.ruoyi.domain.entity.mcp.McpTool; +import org.ruoyi.domain.vo.mcp.McpMarketVo; +import org.ruoyi.enums.McpToolStatus; +import org.ruoyi.mapper.mcp.McpMarketMapper; +import org.ruoyi.mapper.mcp.McpMarketToolMapper; +import org.ruoyi.mapper.mcp.McpToolMapper; +import org.ruoyi.service.mcp.IMcpMarketService; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; diff --git a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/service/impl/McpToolServiceImpl.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/mcp/impl/McpToolServiceImpl.java similarity index 95% rename from ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/service/impl/McpToolServiceImpl.java rename to ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/mcp/impl/McpToolServiceImpl.java index 26c2d247..ce7c684d 100644 --- a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/service/impl/McpToolServiceImpl.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/mcp/impl/McpToolServiceImpl.java @@ -1,4 +1,4 @@ -package org.ruoyi.mcp.service.impl; +package org.ruoyi.service.mcp.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; @@ -9,14 +9,14 @@ import org.ruoyi.common.core.exception.ServiceException; import org.ruoyi.common.core.utils.MapstructUtils; import org.ruoyi.common.mybatis.core.page.PageQuery; import org.ruoyi.common.mybatis.core.page.TableDataInfo; -import org.ruoyi.mcp.domain.bo.McpToolBo; -import org.ruoyi.mcp.domain.dto.McpToolListResult; -import org.ruoyi.mcp.domain.dto.McpToolTestResult; -import org.ruoyi.mcp.domain.entity.McpTool; -import org.ruoyi.mcp.domain.vo.McpToolVo; -import org.ruoyi.mcp.enums.McpToolStatus; -import org.ruoyi.mcp.mapper.McpToolMapper; -import org.ruoyi.mcp.service.IMcpToolService; +import org.ruoyi.domain.bo.mcp.McpToolBo; +import org.ruoyi.domain.dto.mcp.McpToolListResult; +import org.ruoyi.domain.dto.mcp.McpToolTestResult; +import org.ruoyi.domain.entity.mcp.McpTool; +import org.ruoyi.domain.vo.mcp.McpToolVo; +import org.ruoyi.enums.McpToolStatus; +import org.ruoyi.mapper.mcp.McpToolMapper; +import org.ruoyi.service.mcp.IMcpToolService; import org.ruoyi.mcp.service.core.BuiltinToolRegistry; import org.ruoyi.mcp.service.core.LangChain4jMcpToolProviderService; import org.springframework.stereotype.Service; diff --git a/ruoyi-modules/ruoyi-mcp/pom.xml b/ruoyi-modules/ruoyi-mcp/pom.xml deleted file mode 100644 index e5dfa27a..00000000 --- a/ruoyi-modules/ruoyi-mcp/pom.xml +++ /dev/null @@ -1,77 +0,0 @@ - - - 4.0.0 - - - org.ruoyi - ruoyi-modules - ${revision} - - - ruoyi-mcp - - - MCP模块 - 管理MCP工具连接、市场集成和内置工具 - - - - - - org.ruoyi - ruoyi-common-core - - - - org.ruoyi - ruoyi-common-web - - - - org.ruoyi - ruoyi-common-mybatis - - - - org.ruoyi - ruoyi-common-log - - - - org.ruoyi - ruoyi-common-tenant - - - - org.ruoyi - ruoyi-common-security - - - - org.ruoyi - ruoyi-common-excel - - - - org.ruoyi - ruoyi-common-idempotent - - - - - dev.langchain4j - langchain4j-mcp - ${langchain4j.community.version} - - - - - com.fasterxml.jackson.core - jackson-databind - - - - - diff --git a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/config/McpProperties.java b/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/config/McpProperties.java deleted file mode 100644 index e686c9a2..00000000 --- a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/config/McpProperties.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.ruoyi.mcp.config; - -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.stereotype.Component; - -/** - * MCP 配置属性 - * - * @author ruoyi team - */ -@Data -@Component -@ConfigurationProperties(prefix = "app.mcp") -public class McpProperties { - - /** - * 客户端配置 - */ - private ClientConfig client = new ClientConfig(); - - - @Data - public static class ClientConfig { - /** - * 请求超时时间(秒) - */ - private int requestTimeout = 30; - - /** - * 连接超时时间(秒) - */ - private int connectionTimeout = 10; - - /** - * 最大重试次数 - */ - private int maxRetries = 3; - } -} diff --git a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/service/core/BuiltinToolRegistry.java b/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/service/core/BuiltinToolRegistry.java deleted file mode 100644 index b3953b7f..00000000 --- a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/service/core/BuiltinToolRegistry.java +++ /dev/null @@ -1,129 +0,0 @@ -package org.ruoyi.mcp.service.core; - -import jakarta.annotation.PostConstruct; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; - -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -/** - * 内置工具注册表 - * 自动发现并注册所有实现 {@link BuiltinToolProvider} 接口的工具 - * - *

工具注册流程: - *

    - *
  1. Spring 自动注入所有 {@link BuiltinToolProvider} 实现
  2. - *
  3. {@link #init()} 方法在 Bean 初始化后自动调用
  4. - *
  5. 将所有工具注册到内部 Map
  6. - *
- * - *

添加新工具只需: - *

    - *
  1. 创建一个类实现 {@link BuiltinToolProvider} 接口
  2. - *
  3. 添加 {@code @Component} 注解
  4. - *
  5. 工具会自动被发现和注册
  6. - *
- * - * @author ruoyi team - */ -@Slf4j -@Component -@RequiredArgsConstructor -public class BuiltinToolRegistry { - - /** - * 工具类型常量 - */ - public static final String TYPE_BUILTIN = "BUILTIN"; - - /** - * Spring 自动注入所有实现 BuiltinToolProvider 接口的 Bean - */ - private final List toolProviders; - - /** - * 内置工具定义映射表 (工具名称 -> 工具提供者) - */ - private final Map registeredTools = new ConcurrentHashMap<>(); - - /** - * 初始化方法,在 Bean 创建后自动调用 - * 将所有 BuiltinToolProvider 注册到内部 Map - */ - @PostConstruct - public void init() { - log.info("开始注册内置工具,发现 {} 个工具提供者", toolProviders.size()); - - for (BuiltinToolProvider provider : toolProviders) { - String toolName = provider.getToolName(); - - if (registeredTools.containsKey(toolName)) { - log.warn("工具名称重复: {},将覆盖原有注册", toolName); - } - - registeredTools.put(toolName, provider); - log.info("注册内置工具: {} ({})", toolName, provider.getDisplayName()); - } - - log.info("内置工具注册完成,共 {} 个工具", registeredTools.size()); - } - - /** - * 获取工具提供者 - * - * @param toolName 工具名称 - * @return 工具提供者,如果不存在则返回 null - */ - public BuiltinToolProvider getToolProvider(String toolName) { - return registeredTools.get(toolName); - } - - /** - * 检查工具是否已注册 - * - * @param toolName 工具名称 - * @return 是否已注册 - */ - public boolean hasTool(String toolName) { - return registeredTools.containsKey(toolName); - } - - /** - * 获取所有内置工具定义 - * - * @return 内置工具定义集合 - */ - public Collection getAllBuiltinTools() { - return registeredTools.values().stream() - .map(provider -> new BuiltinToolDefinition( - provider.getToolName(), - provider.getDisplayName(), - provider.getDescription() - )) - .toList(); - } - - /** - * 获取所有内置工具对象 - * 这些对象包含 @Tool 注解的方法,可直接用于 AgenticServices - * - * @return 内置工具对象列表 - */ - public List getAllBuiltinToolObjects() { - return List.copyOf(registeredTools.values()); - } - - /** - * 根据工具名称获取工具对象 - * - * @param toolName 工具名称 - * @return 工具对象,如果不存在则返回 null - */ - public Object getBuiltinToolObject(String toolName) { - return registeredTools.get(toolName); - } -} diff --git a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/service/core/ToolProviderFactory.java b/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/service/core/ToolProviderFactory.java deleted file mode 100644 index edf21bb3..00000000 --- a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/service/core/ToolProviderFactory.java +++ /dev/null @@ -1,171 +0,0 @@ -package org.ruoyi.mcp.service.core; - -import dev.langchain4j.mcp.McpToolProvider; -import dev.langchain4j.service.tool.ToolProvider; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.ruoyi.mcp.domain.entity.McpTool; -import org.ruoyi.mcp.enums.McpToolStatus; -import org.ruoyi.mcp.mapper.McpToolMapper; -import org.springframework.stereotype.Service; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -/** - * 统一工具提供工厂 - * 整合所有类型的MCP工具提供者,为Agent和Chat服务提供统一的工具获取入口 - * - *

支持的工具类型: - *

    - *
  • BUILTIN - 内置工具(如文件操作工具)
  • - *
  • LOCAL - 本地STDIO工具(通过命令行启动的MCP服务器)
  • - *
  • REMOTE - 远程HTTP/SSE工具(通过网络连接的MCP服务器)
  • - *
- * - * @author ruoyi team - */ -@Slf4j -@Service -@RequiredArgsConstructor -public class ToolProviderFactory { - - /** - * 工具类型常量 - */ - public static final String TYPE_BUILTIN = "BUILTIN"; - public static final String TYPE_LOCAL = "LOCAL"; - public static final String TYPE_REMOTE = "REMOTE"; - private final BuiltinToolRegistry builtinToolRegistry; - private final LangChain4jMcpToolProviderService langChain4jMcpToolProviderService; - private final McpToolMapper mcpToolMapper; - - /** - * 根据工具ID列表获取LangChain4j的ToolProvider - * 用于LangChain4j Agent框架使用工具 - * - * @param toolIds 工具ID列表 - * @return ToolProvider实例 - */ - public ToolProvider getToolProvider(List toolIds) { - if (toolIds == null || toolIds.isEmpty()) { - return McpToolProvider.builder().build(); - } - - // 只获取非内置工具(LangChain4j的MCP工具) - List mcpToolIds = new ArrayList<>(); - - for (Long toolId : toolIds) { - McpTool tool = mcpToolMapper.selectById(toolId); - if (tool != null && McpToolStatus.isEnabled(tool.getStatus())) { - if (!TYPE_BUILTIN.equals(tool.getType())) { - mcpToolIds.add(toolId); - } - } - } - - // 使用LangChain4j服务获取MCP工具的ToolProvider - return langChain4jMcpToolProviderService.getToolProvider(mcpToolIds); - } - - /** - * 根据工具名称列表获取LangChain4j的ToolProvider - * - * @param toolNames 工具名称列表 - * @return ToolProvider实例 - */ - public ToolProvider getToolProviderByNames(List toolNames) { - if (toolNames == null || toolNames.isEmpty()) { - return McpToolProvider.builder().build(); - } - - // 直接使用LangChain4j服务,它已经实现了按名称查询 - return langChain4jMcpToolProviderService.getToolProviderByNames(toolNames); - } - - /** - * 获取所有已启用的MCP工具的ToolProvider - * - * @return ToolProvider实例 - */ - public ToolProvider getAllEnabledMcpToolsProvider() { - return langChain4jMcpToolProviderService.getAllEnabledToolsProvider(); - } - - /** - * 检查工具是否为内置工具 - * - * @param toolName 工具名称 - * @return 是否为内置工具 - */ - public boolean isBuiltinTool(String toolName) { - return builtinToolRegistry.hasTool(toolName); - } - - /** - * 根据工具名称获取工具ID - * - * @param toolName 工具名称 - * @return 工具ID,未找到返回null - */ - public Long getToolIdByName(String toolName) { - McpTool tool = mcpToolMapper.selectOne( - new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() - .eq(McpTool::getName, toolName) - .last("LIMIT 1") - ); - return tool != null ? tool.getId() : null; - } - - /** - * 根据工具名称列表获取工具ID列表 - * - * @param toolNames 工具名称列表 - * @return 工具ID列表 - */ - public List getToolIdsByNames(List toolNames) { - if (toolNames == null || toolNames.isEmpty()) { - return List.of(); - } - - List tools = mcpToolMapper.selectList( - new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() - .in(McpTool::getName, toolNames) - .eq(McpTool::getStatus, McpToolStatus.ENABLED.getValue()) - ); - - return tools.stream() - .map(McpTool::getId) - .toList(); - } - - /** - * 刷新工具连接 - * - * @param toolId 工具ID - */ - public void refreshTool(Long toolId) { - langChain4jMcpToolProviderService.refreshClient(toolId); - log.info("已刷新工具连接: toolId={}", toolId); - } - - /** - * 获取工具健康状态 - * - * @return 工具ID -> 健康状态的映射 - */ - public Map getToolsHealthStatus() { - return langChain4jMcpToolProviderService.getAllToolsHealthStatus(); - } - - /** - * 获取所有 BUILTIN 工具对象 - * 这些对象包含 @Tool 注解的方法,可直接用于 AgenticServices - * - * @return BUILTIN 工具对象列表 - */ - public List getAllBuiltinToolObjects() { - return builtinToolRegistry.getAllBuiltinToolObjects(); - } -}