mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-22 07:03:53 +08:00
update:页面显示接入点功能
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
# MCP 接入点部署使用指南
|
||||
|
||||
本教程包含2个部分
|
||||
- 1、如何开启mcp接入点
|
||||
- 2、如何为智能体接入一个简单的mcp功能,如计算器功能
|
||||
|
||||
部署的前提条件:
|
||||
- 1、你已经部署了全模块,因为mcp接入点需要全模块中的智控台功能
|
||||
- 2、你想在不修改xiaozhi-server项目的前提下,扩展小智的功能
|
||||
|
||||
# 如何开启mcp接入点
|
||||
|
||||
## 第一步,下载mcp接入点项目源码
|
||||
|
||||
浏览器打开[mcp接入点项目地址](https://github.com/xinnan-tech/mcp-endpoint-server)
|
||||
|
||||
打开完,找到页面中一个绿色的按钮,写着`Code`的按钮,点开它,然后你就看到`Download ZIP`的按钮。
|
||||
|
||||
点击它,下载本项目源码压缩包。下载到你电脑后,解压它,此时它的名字可能叫`mcp-endpoint-server-main`
|
||||
你需要把它重命名成`mcp-endpoint-server`。
|
||||
|
||||
## 第二步,启动程序
|
||||
这个项目是一个很简单的项目,建议使用docker运行。不过如果你不想使用docker运行,你可以参考[这个页面](https://github.com/xinnan-tech/mcp-endpoint-server/blob/main/README_dev.md)使用源码运行。以下是docker运行的方法
|
||||
|
||||
```
|
||||
# 进入本项目源码根目录
|
||||
cd mcp-endpoint-server
|
||||
|
||||
# 清除缓存
|
||||
docker compose -f docker-compose.yml down
|
||||
docker stop mcp-endpoint-server
|
||||
docker rm mcp-endpoint-server
|
||||
docker rmi ghcr.nju.edu.cn/xinnan-tech/mcp-endpoint-server:latest
|
||||
|
||||
# 启动docker容器
|
||||
docker compose -f docker-compose.yml up -d
|
||||
# 查看日志
|
||||
docker logs -f mcp-endpoint-server
|
||||
```
|
||||
|
||||
此时,日志里会输出类似以下的日志
|
||||
```
|
||||
======================================================
|
||||
接口地址: http://172.1.1.1:8004/mcp_endpoint/health?key=xxxx
|
||||
=======上面的地址是MCP接入点地址,请勿泄露给任何人============
|
||||
```
|
||||
|
||||
请你把接口地址复制出来:
|
||||
|
||||
由于你是docker部署,切不可直接使用上面的地址!
|
||||
|
||||
由于你是docker部署,切不可直接使用上面的地址!
|
||||
|
||||
由于你是docker部署,切不可直接使用上面的地址!
|
||||
|
||||
你先把地址复制出来,放在一个草稿里,你要知道你的电脑的局域网ip是什么,例如我的电脑局域网ip是`192.168.1.25`,那么
|
||||
原来我的接口地址
|
||||
```
|
||||
http://172.1.1.1:8004/mcp_endpoint/health?key=xxxx
|
||||
```
|
||||
就要改成
|
||||
```
|
||||
http://192.168.1.25:8004/mcp_endpoint/health?key=xxxx
|
||||
```
|
||||
|
||||
改好后,请使用浏览器直接访问这个接口。当浏览器出现类似这样的代码,说明是成功了。
|
||||
```
|
||||
{"result":{"status":"success","connections":{"tool_connections":0,"robot_connections":0,"total_connections":0}},"error":null,"id":null,"jsonrpc":"2.0"}
|
||||
```
|
||||
|
||||
请你保留好这个`接口地址`,下一步要用到。
|
||||
|
||||
## 第三步,配置智控台
|
||||
|
||||
使用管理员账号,登录智控台,点击顶部`参数字典`,选择`参数管理`功能。
|
||||
|
||||
然后搜索参数`server.mcp_endpoint`,此时,它的值应该是`null`值。
|
||||
点击修改按钮,把上一步得来的`接口地址`粘贴到`参数值`里。然后保存。
|
||||
|
||||
如果能保存成功,说明一切顺利,你可以去智能体查看效果了。如果不成功,说明智控台无法访问mcp接入点,很大概率是网络防火墙,或者没有填写正确的局域网ip。
|
||||
|
||||
# 如何为智能体接入一个简单的mcp功能,如计算器功能
|
||||
|
||||
如果以上步骤顺利,你可以进入智能体管理,点击`配置角色`,在`意图识别`的右边,有一个`编辑功能`的按钮。
|
||||
|
||||
点击这个按钮。在弹出的页面里,位于底部,会有`MCP接入点`,正常来说,会显示这个智能体的`MCP接入点地址`,接下来,我们来给这个智能体扩展一个基于MCP技术的计算器的功能。
|
||||
|
||||
这个`MCP接入点地址`很重要,你等一下会用到。
|
||||
|
||||
## 第一步 下载虾哥MCP计算器项目代码
|
||||
|
||||
浏览器打开虾哥写的[计算器项目](https://github.com/78/mcp-calculator),
|
||||
|
||||
打开完,找到页面中一个绿色的按钮,写着`Code`的按钮,点开它,然后你就看到`Download ZIP`的按钮。
|
||||
|
||||
点击它,下载本项目源码压缩包。下载到你电脑后,解压它,此时它的名字可能叫`mcp-calculatorr-main`
|
||||
你需要把它重命名成`mcp-calculator`。接下来,我们用命令行进入项目目录即安装依赖
|
||||
|
||||
|
||||
```bash
|
||||
# 进入项目目录
|
||||
cd mcp-calculator
|
||||
|
||||
conda remove -n mcp-calculator --all -y
|
||||
conda create -n mcp-calculator python=3.10 -y
|
||||
conda activate mcp-calculator
|
||||
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
## 第二步 启动
|
||||
|
||||
启动前,先从你的智控台的智能体里,复制到了MCP接入点的地址。
|
||||
|
||||
例如我的智能体的mcp地址是
|
||||
```
|
||||
ws://192.168.4.7:8004/mcp_endpoint/mcp/?token=abc
|
||||
```
|
||||
|
||||
开始输入命令
|
||||
|
||||
```bash
|
||||
export MCP_ENDPOINT=ws://192.168.4.7:8004/mcp_endpoint/mcp/?token=abc
|
||||
```
|
||||
|
||||
输入完后,启动程序
|
||||
|
||||
```bash
|
||||
python mcp_pipe.py calculator.py
|
||||
```
|
||||
|
||||
|
||||
启动完后,你再进入智控台,点击刷新MCP的接入状态,就会看到你扩展的功能列表了。
|
||||
|
||||
+19
-1
@@ -11,8 +11,11 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import xiaozhi.common.user.UserDetail;
|
||||
import xiaozhi.common.utils.Result;
|
||||
import xiaozhi.modules.agent.service.AgentMcpAccessPointService;
|
||||
import xiaozhi.modules.agent.service.AgentService;
|
||||
import xiaozhi.modules.security.user.SecurityUser;
|
||||
|
||||
@Tag(name = "智能体Mcp接入点管理")
|
||||
@RequiredArgsConstructor
|
||||
@@ -20,6 +23,7 @@ import xiaozhi.modules.agent.service.AgentMcpAccessPointService;
|
||||
@RequestMapping("/agent/mcp")
|
||||
public class AgentMcpAccessPointController {
|
||||
private final AgentMcpAccessPointService agentMcpAccessPointService;
|
||||
private final AgentService agentService;
|
||||
|
||||
/**
|
||||
* 获取智能体的Mcp接入点地址
|
||||
@@ -31,9 +35,16 @@ public class AgentMcpAccessPointController {
|
||||
@GetMapping("/address/{agentId}")
|
||||
@RequiresPermissions("sys:role:normal")
|
||||
public Result<String> getAgentMcpAccessAddress(@PathVariable("agentId") String agentId) {
|
||||
// 获取当前用户
|
||||
UserDetail user = SecurityUser.getUser();
|
||||
|
||||
// 检查权限
|
||||
if (!agentService.checkAgentPermission(agentId, user.getId())) {
|
||||
return new Result<String>().error("没有权限查看该智能体的MCP接入点地址");
|
||||
}
|
||||
String agentMcpAccessAddress = agentMcpAccessPointService.getAgentMcpAccessAddress(agentId);
|
||||
if (agentMcpAccessAddress == null) {
|
||||
return new Result<String>().ok("请进入参数管理配置mcp接入点地址");
|
||||
return new Result<String>().ok("请联系管理员进入参数管理配置mcp接入点地址");
|
||||
}
|
||||
return new Result<String>().ok(agentMcpAccessAddress);
|
||||
}
|
||||
@@ -42,6 +53,13 @@ public class AgentMcpAccessPointController {
|
||||
@GetMapping("/tools/{agentId}")
|
||||
@RequiresPermissions("sys:role:normal")
|
||||
public Result<List<String>> getAgentMcpToolsList(@PathVariable("agentId") String agentId) {
|
||||
// 获取当前用户
|
||||
UserDetail user = SecurityUser.getUser();
|
||||
|
||||
// 检查权限
|
||||
if (!agentService.checkAgentPermission(agentId, user.getId())) {
|
||||
return new Result<List<String>>().error("没有权限查看该智能体的MCP工具列表");
|
||||
}
|
||||
List<String> agentMcpToolsList = agentMcpAccessPointService.getAgentMcpToolsList(agentId);
|
||||
return new Result<List<String>>().ok(agentMcpToolsList);
|
||||
}
|
||||
|
||||
@@ -143,4 +143,34 @@ export default {
|
||||
});
|
||||
}).send();
|
||||
},
|
||||
// 获取智能体的MCP接入点地址
|
||||
getAgentMcpAccessAddress(agentId, callback) {
|
||||
RequestService.sendRequest()
|
||||
.url(`${getServiceUrl()}/agent/mcp/address/${agentId}`)
|
||||
.method('GET')
|
||||
.success((res) => {
|
||||
RequestService.clearRequestTime();
|
||||
callback(res);
|
||||
})
|
||||
.networkFail(() => {
|
||||
RequestService.reAjaxFun(() => {
|
||||
this.getAgentMcpAccessAddress(agentId, callback);
|
||||
});
|
||||
}).send();
|
||||
},
|
||||
// 获取智能体的MCP工具列表
|
||||
getAgentMcpToolsList(agentId, callback) {
|
||||
RequestService.sendRequest()
|
||||
.url(`${getServiceUrl()}/agent/mcp/tools/${agentId}`)
|
||||
.method('GET')
|
||||
.success((res) => {
|
||||
RequestService.clearRequestTime();
|
||||
callback(res);
|
||||
})
|
||||
.networkFail(() => {
|
||||
RequestService.reAjaxFun(() => {
|
||||
this.getAgentMcpToolsList(agentId, callback);
|
||||
});
|
||||
}).send();
|
||||
},
|
||||
}
|
||||
|
||||
@@ -101,39 +101,54 @@
|
||||
|
||||
<!-- MCP区域 -->
|
||||
<div class="mcp-access-point">
|
||||
<div class="mcp-header">
|
||||
<h3 class="bold-title">MCP接入点</h3>
|
||||
</div>
|
||||
<div class="mcp-container">
|
||||
<!-- 左侧区域 -->
|
||||
<div class="mcp-left">
|
||||
<div class="url-header">
|
||||
<h4>接入点地址</h4>
|
||||
<div class="address-desc">
|
||||
<span>以下是智能体的MCP接入点地址。</span>
|
||||
<a href="#" class="doc-link">查看接入点使用文档</a>
|
||||
<div class="mcp-left">
|
||||
<div class="mcp-header">
|
||||
<h3 class="bold-title">MCP接入点</h3>
|
||||
</div>
|
||||
<div class="url-header">
|
||||
<div class="address-desc">
|
||||
<span>以下是智能体的MCP接入点地址。</span>
|
||||
<a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/blob/main/docs/mcp-endpoint-integration.md"
|
||||
class="doc-link">查看接入点使用文档</a>
|
||||
</div>
|
||||
</div>
|
||||
<el-input v-model="mcpUrl" readonly class="url-input">
|
||||
<template #suffix>
|
||||
<el-button @click="copyUrl" class="inner-copy-btn" icon="el-icon-document-copy">
|
||||
复制
|
||||
</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
<el-input v-model="mcpUrl" readonly class="url-input">
|
||||
<template #suffix>
|
||||
<el-button @click="copyUrl" class="inner-copy-btn" icon="el-icon-document-copy">
|
||||
复制
|
||||
</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
|
||||
<!-- 右侧区域 -->
|
||||
<div class="mcp-right">
|
||||
<h4>接入点状态</h4>
|
||||
<div class="mcp-header">
|
||||
<h3 class="bold-title">接入点状态</h3>
|
||||
</div>
|
||||
<div class="status-container">
|
||||
<span class="status-indicator" :class="mcpStatus"></span>
|
||||
<span class="status-text">{{ mcpStatus === 'connected' ? '已连接' : '未连接' }}</span>
|
||||
<span class="status-text">{{
|
||||
mcpStatus === 'connected' ? '已连接' :
|
||||
mcpStatus === 'loading' ? '加载中...' : '未连接'
|
||||
}}</span>
|
||||
<button class="refresh-btn" @click="refreshStatus">
|
||||
<span class="refresh-icon">↻</span>
|
||||
<span>刷新</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="mcp-tools-list">
|
||||
<div v-if="mcpTools.length > 0" class="tools-grid">
|
||||
<el-button v-for="tool in mcpTools" :key="tool" size="small" class="tool-btn" plain>
|
||||
{{ tool }}
|
||||
</el-button>
|
||||
</div>
|
||||
<div v-else class="no-tools">
|
||||
<span>暂无可用工具</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -146,6 +161,8 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Api from '@/apis/api';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
value: Boolean,
|
||||
@@ -156,6 +173,10 @@ export default {
|
||||
allFunctions: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
agentId: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -173,9 +194,10 @@ export default {
|
||||
// 添加一个标志位来跟踪是否已经保存
|
||||
hasSaved: false,
|
||||
loading: false,
|
||||
|
||||
mcpUrl: "https://api.example.com/mcp/v1/endpoint",
|
||||
|
||||
mcpUrl: "",
|
||||
mcpStatus: "disconnected",
|
||||
mcpTools: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -219,6 +241,10 @@ export default {
|
||||
});
|
||||
// 右侧默认指向第一个
|
||||
this.currentFunction = this.selectedList[0] || null;
|
||||
|
||||
// 加载MCP数据
|
||||
this.loadMcpAddress();
|
||||
this.loadMcpTools();
|
||||
}
|
||||
},
|
||||
dialogVisible(newVal) {
|
||||
@@ -249,12 +275,35 @@ export default {
|
||||
},
|
||||
|
||||
refreshStatus() {
|
||||
// 模拟状态刷新
|
||||
this.mcpStatus = "loading";
|
||||
setTimeout(() => {
|
||||
// 随机切换状态用于演示
|
||||
this.mcpStatus = Math.random() > 0.5 ? "connected" : "disconnected";
|
||||
}, 800);
|
||||
this.loadMcpTools();
|
||||
},
|
||||
|
||||
// 加载MCP接入点地址
|
||||
loadMcpAddress() {
|
||||
Api.agent.getAgentMcpAccessAddress(this.agentId, (res) => {
|
||||
if (res.data.code === 0) {
|
||||
this.mcpUrl = res.data.data || "";
|
||||
} else {
|
||||
this.mcpUrl = "";
|
||||
console.error('获取MCP地址失败:', res.data.msg);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 加载MCP工具列表
|
||||
loadMcpTools() {
|
||||
Api.agent.getAgentMcpToolsList(this.agentId, (res) => {
|
||||
if (res.data.code === 0) {
|
||||
this.mcpTools = res.data.data || [];
|
||||
// 根据工具列表更新状态
|
||||
this.mcpStatus = this.mcpTools.length > 0 ? "connected" : "disconnected";
|
||||
} else {
|
||||
this.mcpTools = [];
|
||||
this.mcpStatus = "disconnected";
|
||||
console.error('获取MCP工具列表失败:', res.data.msg);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
flushArray(key) {
|
||||
@@ -608,13 +657,15 @@ export default {
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
.mcp-left, .mcp-right {
|
||||
.mcp-left,
|
||||
.mcp-right {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.url-header {
|
||||
margin-bottom: 8px;
|
||||
color: black;
|
||||
|
||||
h4 {
|
||||
margin: 0 0 15px 0;
|
||||
font-size: 16px;
|
||||
@@ -647,8 +698,9 @@ export default {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
::v-deep .el-input__inner{
|
||||
::v-deep .el-input__inner {
|
||||
background-color: #f5f5f5;
|
||||
padding-right: 80px;
|
||||
}
|
||||
|
||||
.url-input {
|
||||
@@ -657,20 +709,16 @@ export default {
|
||||
right: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-right: 15px;
|
||||
padding-right: 10px;
|
||||
|
||||
.inner-copy-btn {
|
||||
pointer-events: auto;
|
||||
border: none;
|
||||
background: white;
|
||||
color: black;
|
||||
background: #1677ff;
|
||||
color: white;
|
||||
padding: 6px;
|
||||
margin-top: 4px;
|
||||
margin-left: 4px;
|
||||
|
||||
&:hover {
|
||||
background: #1677ff;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -696,15 +744,18 @@ export default {
|
||||
margin-right: 8px;
|
||||
|
||||
&.disconnected {
|
||||
background-color: #909399; /* 灰色 - 未连接 */
|
||||
background-color: #909399;
|
||||
/* 灰色 - 未连接 */
|
||||
}
|
||||
|
||||
&.connected {
|
||||
background-color: #67C23A; /* 绿色 - 已连接 */
|
||||
background-color: #67C23A;
|
||||
/* 绿色 - 已连接 */
|
||||
}
|
||||
|
||||
&.loading {
|
||||
background-color: #E6A23C; /* 橙色 - 加载中 */
|
||||
background-color: #E6A23C;
|
||||
/* 橙色 - 加载中 */
|
||||
animation: pulse 1.5s infinite;
|
||||
}
|
||||
}
|
||||
@@ -740,8 +791,47 @@ export default {
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% { opacity: 1; }
|
||||
50% { opacity: 0.4; }
|
||||
100% { opacity: 1; }
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.mcp-tools-list {
|
||||
margin-top: 10px;
|
||||
|
||||
.tools-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.tool-btn {
|
||||
padding: 6px 12px;
|
||||
border-color: #1677ff;
|
||||
color: #1677ff;
|
||||
background-color: white;
|
||||
font-size: 12px;
|
||||
|
||||
&:hover {
|
||||
background-color: #1677ff;
|
||||
color: white;
|
||||
border-color: #1677ff;
|
||||
}
|
||||
}
|
||||
|
||||
.no-tools {
|
||||
text-align: center;
|
||||
color: #909399;
|
||||
font-size: 14px;
|
||||
padding: 10px 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -132,7 +132,7 @@
|
||||
</div>
|
||||
|
||||
<function-dialog v-model="showFunctionDialog" :functions="currentFunctions" :all-functions="allFunctions"
|
||||
@update-functions="handleUpdateFunctions" @dialog-closed="handleDialogClosed" />
|
||||
:agent-id="$route.query.agentId" @update-functions="handleUpdateFunctions" @dialog-closed="handleDialogClosed" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user