diff --git a/backend/components.d.ts b/backend/components.d.ts index 6fa544f..e281758 100644 --- a/backend/components.d.ts +++ b/backend/components.d.ts @@ -25,6 +25,8 @@ declare module 'vue' { ElCheckbox: typeof import('element-plus/es')['ElCheckbox'] ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup'] ElCol: typeof import('element-plus/es')['ElCol'] + ElCollapse: typeof import('element-plus/es')['ElCollapse'] + ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem'] ElCollapseTransition: typeof import('element-plus/es')['ElCollapseTransition'] ElColorPicker: typeof import('element-plus/es')['ElColorPicker'] ElContainer: typeof import('element-plus/es')['ElContainer'] diff --git a/backend/src/api/ai.js b/backend/src/api/ai.js index 9b5078c..a4c8341 100644 --- a/backend/src/api/ai.js +++ b/backend/src/api/ai.js @@ -140,3 +140,112 @@ export function smartGenerateCompany(data) { data }) } + +// MCP工具管理 +export function getMcpToolList() { + return request({ + url: '/backend/mcp/tool/list', + method: 'get' + }) +} + +export function createMcpTool(data) { + return request({ + url: '/backend/mcp/tool', + method: 'post', + data + }) +} + +export function updateMcpTool(id, data) { + return request({ + url: `/backend/mcp/tool/${id}`, + method: 'put', + data + }) +} + +export function deleteMcpTool(id) { + return request({ + url: `/backend/mcp/tool/${id}`, + method: 'delete' + }) +} + +export function testMcpTool(data) { + return request({ + url: '/backend/mcp/tool/test', + method: 'post', + data + }) +} + +// MCP服务器配置管理 +export function getMcpServerList() { + return request({ + url: '/backend/mcp/server/list', + method: 'get' + }) +} + +export function createMcpServer(data) { + return request({ + url: '/backend/mcp/server', + method: 'post', + data + }) +} + +export function updateMcpServer(id, data) { + return request({ + url: `/backend/mcp/server/${id}`, + method: 'put', + data + }) +} + +export function deleteMcpServer(id) { + return request({ + url: `/backend/mcp/server/${id}`, + method: 'delete' + }) +} + +export function toggleMcpServer(id, enabled) { + return request({ + url: `/backend/mcp/server/${id}/toggle`, + method: 'put', + data: { enabled } + }) +} + +export function testMcpServer(data) { + return request({ + url: '/backend/mcp/server/test', + method: 'post', + data + }) +} + +export function getMcpServerTools(id) { + return request({ + url: `/backend/mcp/server/${id}/tools`, + method: 'post' + }) +} + +// MCP市场 +export function getMcpMarket() { + return request({ + url: '/backend/mcp/server/market', + method: 'get' + }) +} + +export function addMcpFromMarket(data) { + return request({ + url: '/backend/mcp/server/from-market', + method: 'post', + data + }) +} diff --git a/backend/src/views/apps/ai/README.md b/backend/src/views/apps/ai/README.md new file mode 100644 index 0000000..996476a --- /dev/null +++ b/backend/src/views/apps/ai/README.md @@ -0,0 +1,98 @@ +# MCP 工具中心(AI 模块) + +本模块为「AI 智能体」接入 **MCP(Model Context Protocol)** 工具调用能力: +- **MCP 市场**:内置一批可一键添加的服务,以及项目自带的「内置演示 MCP」(零配置,直接可用)。 +- **选择即用**:在「我的服务」里通过「启用」开关控制哪些 MCP 在会话中被调用;**只有启用状态的服务** 才会注入到 AI 会话。 +- **会话自动调用**:聊天时,AI 会自动决定是否调用已启用的 MCP 工具,前端实时展示工具调用卡片(参数 / 结果 / 状态)。 + +--- + +## 一、如何使用 + +1. 打开「AI 广场」→ 左侧边栏点击 **CPU(MCP 工具中心)** 按钮。 +2. 「MCP 市场」页 → 点击「内置演示 MCP」的 **添加**(或添加天眼查/企查查等,需自行申请对应的 URL/Key)。 +3. 切到「我的服务」页 → 把要用的服务 **启用** 开关打开;可点击「测试」验证连接并预览工具。 +4. 回到聊天窗 → 提问。例如: + - `现在几点钟了?`(触发 `get_time`) + - `帮我计算 (3+5)*2`(触发 `calculate`) + - `连云港今天天气怎么样?`(触发 `weather_query`) +5. 回答过程中,AI 气泡内会出现工具调用卡片:展开可看参数与返回结果。 + +> 头部「MCP」绿色指示器表示当前会话已接入 N 个 MCP 工具。 + +## 二、手动新增 MCP 服务 + +「我的服务」→「新增配置」,支持三种传输: + +| 传输 | 说明 | 必填字段 | +|---|---|---| +| **stdio** | 本地进程(MCP Server 以子进程方式启动) | 启动命令 command;可选参数 args(JSON数组)、环境变量 env(JSON对象) | +| **http** | Streamable HTTP 传输 | 服务地址 url;可选请求头 headers(JSON对象) | +| **sse** | 传统 SSE 传输 | 服务地址 url;可选请求头 headers(JSON对象) | + +示例(stdio + npx 远程包,Windows 会自动用 `cmd /c` 包装): +``` +命令: npx -y @modelcontextprotocol/server-everything +``` +示例(http): +``` +地址: https://mcp.tianyancha.com/mcp +请求头: {"Authorization":"Bearer 你的Key"} +``` + +## 三、内置演示 MCP(cmd/demo-mcp) + +项目自带一个 Go 编写的 stdio MCP Server,提供 5 个工具,用于零配置验证全链路: +`get_time`(当前时间)、`calculate`(表达式计算)、`weather_query`(模拟天气)、`echo`、`get_user_profile`。 + +重新编译(如需): +```bash +cd go +go build -o bin/demo-mcp.exe ./cmd/demo-mcp +``` + +## 四、实现说明(文件清单) + +### 后端(Go / Beego,`go/`) +| 文件 | 作用 | +|---|---| +| `models/backend_mcp_server.go` | MCP 服务器配置模型(传输、命令、URL、启用、状态、工具数) | +| `controllers/backend_mcp_server.go` | MCP 服务器接口:市场 / 增删改查 / 测试 / 工具发现 / 启停 | +| `services/mcp_client.go` | MCP 客户端管理器:stdio/http/sse 连接、工具列表、工具调用、连接缓存 | +| `services/mcp_market.go` | MCP 市场目录(内置演示 + 天眼查/企查查/前瞻/IT桔子/BOSS直聘) | +| `controllers/backend_ai_chat.go` | 聊天工具调用循环:注入已启用 MCP 工具 → LLM 返回 tool_calls → 执行 → 回填 → 流式输出 | +| `cmd/demo-mcp/main.go` | 内置演示 MCP Server(stdio) | +| `sql/create_backend_mcp_server.sql` | 建表 SQL(`yz_backend_mcp_server`) | + +**SSE 事件扩展**(`POST /backend/ai/chat/send-stream`): +`session` / `tool_list`(本次启用的工具)/ `tool_start`(开始调用某工具)/ `tool_result`(工具返回)/ `content`(文本增量)/ `done` / `error` + +### 前端(Vue3 + Element Plus,`backend/src/`) +| 文件 | 作用 | +|---|---| +| `views/apps/ai/components/mcpConfig.vue` | MCP 工具中心(市场 + 我的服务 + 新增/编辑 + 工具预览) | +| `views/apps/ai/components/chatWindow.vue` | 聊天窗:SSE 工具事件解析 + 工具调用卡片渲染 + MCP 状态指示 | +| `views/apps/ai/components/chatSidebar.vue` | CPU 按钮入口(标题更新为「MCP 工具中心」) | +| `api/ai.js` | MCP 服务器 / 市场 API 封装 | + +### 模型要求 +- 需支持 **Function Calling / Tool Calling**(OpenAI 兼容与 Anthropic 均已适配)。 +- 已内置两种接入:OpenAI 兼容(含豆包/DeepSeek/OpenRouter 等)与 Anthropic。 + +## 五、依赖说明 + +- 新增 Go 依赖:`github.com/mark3labs/mcp-go v0.33.0`(MCP 官方协议的 Go 客户端/服务端库)。 +- `go.mod` 的 `go` 指令由 `1.17` 调整为 `1.23`(mcp-go v0.33 的最低要求;本机 Go 1.25 完全兼容)。 +- 数据库:需先执行 `sql/create_backend_mcp_server.sql`(本环境已执行建表)。 + +## 六、构建与运行 + +```bash +# 后端(go/ 目录) +go build -o main.exe . +./main.exe # 监听 9000 + +# 前端(backend/ 目录,Vue SPA) +npm run dev # 开发,vite 端口 4001,/api 代理到 9000 +npm run build # 生产构建 +``` diff --git a/backend/src/views/apps/ai/aiplayground/index.vue b/backend/src/views/apps/ai/aiplayground/index.vue index e69de29..ae35ce0 100644 --- a/backend/src/views/apps/ai/aiplayground/index.vue +++ b/backend/src/views/apps/ai/aiplayground/index.vue @@ -0,0 +1,18 @@ + + + + + diff --git a/backend/src/views/apps/ai/components/chatSidebar.vue b/backend/src/views/apps/ai/components/chatSidebar.vue index 503ab98..74c6a3a 100644 --- a/backend/src/views/apps/ai/components/chatSidebar.vue +++ b/backend/src/views/apps/ai/components/chatSidebar.vue @@ -4,6 +4,7 @@ 新建对话 + @@ -52,15 +53,17 @@ + + + diff --git a/backend/src/views/apps/ai/components/providerDialog.vue b/backend/src/views/apps/ai/components/providerDialog.vue index c8cb690..6556afd 100644 --- a/backend/src/views/apps/ai/components/providerDialog.vue +++ b/backend/src/views/apps/ai/components/providerDialog.vue @@ -126,9 +126,23 @@ - + + + + {{ model }} + 首个 + + +
选择后,智能添加供应商/客户等企业级功能将调用此模型
+
+ -
设为默认后,智能添加供应商/客户等企业级功能将调用此接入
+
设为默认后,此接入将作为租户的默认AI接入
@@ -176,6 +190,7 @@ const defaultForm = () => ({ api_base: '', api_key: '', models: [''], + default_model: '', is_default: 0, status: 1, remark: '' @@ -286,6 +301,7 @@ function openEdit(row) { api_base: row.api_base, api_key: row.api_key, models: row.models && row.models.length > 0 ? [...row.models] : [''], + default_model: row.default_model || '', is_default: row.is_default || 0, status: row.status, remark: row.remark @@ -311,7 +327,11 @@ async function handleSave() { submitting.value = true try { - const payload = { ...form, models: validModels } + const payload = { + ...form, + models: validModels, + default_model: form.default_model + } if (editData.value) { await updateAiProvider(editData.value.id, payload) ElMessage.success('更新成功') @@ -349,23 +369,12 @@ async function handleTestProvider(row) { return } - // 需要先编辑才能看到真实的api_key进行测试 - // 或者直接从当前编辑的form中获取(如果正在编辑这个provider) - let apiKey = row.api_key - - // 如果是脱敏的key,需要打开编辑对话框 - if (apiKey && apiKey.includes('****')) { - ElMessage.warning('需要打开编辑表单才能看到完整API Key进行测试') - openEdit(row) - return - } - testingProviderId.value = row.id try { const res = await testAiProvider({ provider_type: row.provider_type, api_base: row.api_base, - api_key: apiKey, + api_key: row.api_key, models: row.models }) diff --git a/backend/src/views/apps/ai/settings/index.vue b/backend/src/views/apps/ai/settings/index.vue index e69de29..cf8efbd 100644 --- a/backend/src/views/apps/ai/settings/index.vue +++ b/backend/src/views/apps/ai/settings/index.vue @@ -0,0 +1,18 @@ + + + + + diff --git a/backend/src/views/apps/erp/components/smartAddCompany.vue b/backend/src/views/apps/erp/components/smartAddCompany.vue index bbd214c..bee7573 100644 --- a/backend/src/views/apps/erp/components/smartAddCompany.vue +++ b/backend/src/views/apps/erp/components/smartAddCompany.vue @@ -50,6 +50,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -57,7 +98,16 @@ - + + + + + + + + + + @@ -65,17 +115,32 @@ + + +
+ + + + + + + + - + + + - + + + @@ -157,12 +222,21 @@ + + + AI响应数据 + +
+
{{ formattedAiData }}
+
+
此数据为AI返回的原始响应,仅作查看参考,已自动填充可编辑字段到上方表单
+