mirror of
https://github.com/xszyou/Fay.git
synced 2026-08-30 17:33:39 +08:00
feat: MCP 服务器支持开机自启
- 新增 autostart 字段与后端增删改接口 - 启动时后台自动连接所有 autostart=true 的服务器 - 卡片列表展示「开机启动」徽标,添加/编辑弹窗支持勾选
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
"id": 1,
|
||||
"name": "tools",
|
||||
"ip": "",
|
||||
"connection_time": "2026-03-27 10:58:07",
|
||||
"connection_time": "2026-04-15 21:58:59",
|
||||
"key": "",
|
||||
"transport": "stdio",
|
||||
"command": "python",
|
||||
@@ -11,7 +11,8 @@
|
||||
"test/mcp_stdio_example.py"
|
||||
],
|
||||
"cwd": "",
|
||||
"env": {}
|
||||
"env": {},
|
||||
"autostart": false
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
@@ -25,7 +26,8 @@
|
||||
"server.py"
|
||||
],
|
||||
"cwd": "mcp_servers/schedule_manager",
|
||||
"env": {}
|
||||
"env": {},
|
||||
"autostart": false
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
@@ -41,7 +43,8 @@
|
||||
"cwd": "mcp_servers/logseq",
|
||||
"env": {
|
||||
"LOGSEQ_GRAPH_DIR": "D:/iCloudDrive/iCloud~com~logseq~logseq/第二大脑"
|
||||
}
|
||||
},
|
||||
"autostart": false
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
@@ -55,7 +58,8 @@
|
||||
"mcp_servers/yueshen_rag/server.py"
|
||||
],
|
||||
"cwd": "",
|
||||
"env": {}
|
||||
"env": {},
|
||||
"autostart": false
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
@@ -69,13 +73,14 @@
|
||||
"mcp_servers/window_capture/server.py"
|
||||
],
|
||||
"cwd": "",
|
||||
"env": {}
|
||||
"env": {},
|
||||
"autostart": false
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"name": "课程知识库",
|
||||
"ip": "",
|
||||
"connection_time": "2026-04-10 10:19:46",
|
||||
"connection_time": "2026-04-17 16:28:31",
|
||||
"key": "",
|
||||
"transport": "stdio",
|
||||
"command": "python",
|
||||
@@ -85,6 +90,7 @@
|
||||
"./fay_player_knowledge"
|
||||
],
|
||||
"cwd": "",
|
||||
"env": {}
|
||||
"env": {},
|
||||
"autostart": true
|
||||
}
|
||||
]
|
||||
+43
-4
@@ -60,6 +60,8 @@ def load_mcp_servers():
|
||||
for server in servers:
|
||||
server['status'] = 'offline'
|
||||
server['latency'] = '0ms'
|
||||
if 'autostart' not in server:
|
||||
server['autostart'] = False
|
||||
return servers
|
||||
else:
|
||||
# 如果文件不存在,使用默认数据并保存
|
||||
@@ -122,7 +124,8 @@ def save_mcp_servers(servers):
|
||||
"command": server.get('command', ''),
|
||||
"args": server.get('args', []),
|
||||
"cwd": server.get('cwd', ''),
|
||||
"env": server.get('env', {})
|
||||
"env": server.get('env', {}),
|
||||
"autostart": bool(server.get('autostart', False))
|
||||
}
|
||||
servers_to_save.append(server_copy)
|
||||
|
||||
@@ -449,7 +452,8 @@ def add_mcp_server():
|
||||
"command": data.get('command', ''),
|
||||
"args": data.get('args', []),
|
||||
"cwd": data.get('cwd', ''),
|
||||
"env": data.get('env', {})
|
||||
"env": data.get('env', {}),
|
||||
"autostart": bool(data.get('autostart', False))
|
||||
}
|
||||
|
||||
# 如果请求中包含 auto_connect 字段并且为 True,则尝试连接
|
||||
@@ -651,6 +655,8 @@ def update_mcp_server(server_id):
|
||||
server['name'] = data.get('name', server['name'])
|
||||
transport = data.get('transport', server.get('transport', 'sse'))
|
||||
server['transport'] = transport
|
||||
if 'autostart' in data:
|
||||
server['autostart'] = bool(data.get('autostart'))
|
||||
|
||||
# 根据传输类型更新配置
|
||||
if transport == 'stdio':
|
||||
@@ -1660,18 +1666,51 @@ def run():
|
||||
)
|
||||
server.serve_forever()
|
||||
|
||||
# 启动时自动连接标记为 autostart 的服务器
|
||||
def _autostart_connect_servers():
|
||||
"""在 Flask 启动后,后台连接所有配置了开机启动的服务器。"""
|
||||
global mcp_servers
|
||||
# 稍等片刻让 Flask 完成启动,避免与其它初始化抢占日志
|
||||
time.sleep(2)
|
||||
targets = [s for s in mcp_servers if s.get('autostart')]
|
||||
if not targets:
|
||||
return
|
||||
util.log(1, f"开机启动:准备连接 {len(targets)} 个MCP服务器")
|
||||
for server in targets:
|
||||
try:
|
||||
server_id = server['id']
|
||||
success, updated_server, _ = connect_to_real_mcp(server)
|
||||
for i, s in enumerate(mcp_servers):
|
||||
if s['id'] == server_id:
|
||||
mcp_servers[i] = updated_server
|
||||
break
|
||||
if success:
|
||||
util.log(1, f"开机启动:{updated_server.get('name')} 已连接")
|
||||
else:
|
||||
util.log(1, f"开机启动:{updated_server.get('name')} 连接失败")
|
||||
except Exception as e:
|
||||
util.log(1, f"开机启动连接异常: {e}")
|
||||
try:
|
||||
save_mcp_servers(mcp_servers)
|
||||
except Exception as e:
|
||||
util.log(1, f"开机启动后保存状态失败: {e}")
|
||||
|
||||
|
||||
# 启动MCP服务器
|
||||
def start():
|
||||
# 启动连接检查定时任务
|
||||
# start_connection_check() TODO 暂时取消定时检查任务
|
||||
|
||||
|
||||
# 输出启动信息
|
||||
util.log(1, "MCP服务已启动在端口5010")
|
||||
|
||||
|
||||
# 启动服务器
|
||||
from scheduler.thread_manager import MyThread
|
||||
MyThread(target=run).start()
|
||||
|
||||
# 后台触发 autostart 连接
|
||||
MyThread(target=_autostart_connect_servers).start()
|
||||
|
||||
if __name__ == '__main__':
|
||||
import logging
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
@@ -58,6 +58,20 @@
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
}
|
||||
.autostart-badge {
|
||||
margin-left: 8px;
|
||||
padding: 1px 8px;
|
||||
border-radius: 10px;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
background-color: #409eff;
|
||||
color: #fff;
|
||||
white-space: nowrap;
|
||||
display: inline-block;
|
||||
}
|
||||
.autostart-badge.hidden {
|
||||
display: none;
|
||||
}
|
||||
.status-online {
|
||||
color: #67c23a;
|
||||
display: flex;
|
||||
@@ -511,9 +525,12 @@
|
||||
<div class="mcp-container">
|
||||
<!-- 使用Jinja2模板循环渲染MCP服务器卡片 -->
|
||||
{% for server in mcp_servers %}
|
||||
<div class="mcp-card tooltip" data-server-id="{{ server.id }}" data-transport="{{ server.transport or 'sse' }}" data-command="{{ server.command or '' }}" data-args="{{ (server.args | join(' ')) if server.args else '' }}" data-cwd="{{ server.cwd or '' }}" data-key="{{ server.key or '' }}" data-env='{{ server.env | tojson }}'>
|
||||
<div class="mcp-card tooltip" data-server-id="{{ server.id }}" data-transport="{{ server.transport or 'sse' }}" data-command="{{ server.command or '' }}" data-args="{{ (server.args | join(' ')) if server.args else '' }}" data-cwd="{{ server.cwd or '' }}" data-key="{{ server.key or '' }}" data-env='{{ server.env | tojson }}' data-autostart="{{ '1' if server.autostart else '0' }}">
|
||||
<div class="mcp-header">
|
||||
<div class="mcp-title">{{ server.name }}</div>
|
||||
<div style="display:flex; align-items:center;">
|
||||
<div class="mcp-title">{{ server.name }}</div>
|
||||
<span class="autostart-badge{% if not server.autostart %} hidden{% endif %}">开机启动</span>
|
||||
</div>
|
||||
<div class="status-{{ server.status }}">
|
||||
<div class="status-dot {{ server.status }}-dot"></div>
|
||||
{{ '在线' if server.status == 'online' else '离线' }}
|
||||
@@ -600,6 +617,10 @@
|
||||
<input type="text" class="mcp-input" id="mcpEnv" placeholder='例如: {"MODE":"local"}'>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mcp-form-item">
|
||||
<label class="mcp-label">开机启动:</label>
|
||||
<input type="checkbox" id="mcpAutostart">
|
||||
</div>
|
||||
</div>
|
||||
<div class="mcp-dialog-footer">
|
||||
<button class="add-server-btn" id="addServerBtn">添加</button>
|
||||
@@ -656,6 +677,10 @@
|
||||
<input type="text" class="mcp-input" id="editMcpEnv" placeholder='例如: {"MODE":"local"}'>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mcp-form-item">
|
||||
<label class="mcp-label">开机启动:</label>
|
||||
<input type="checkbox" id="editAutostart">
|
||||
</div>
|
||||
<div class="mcp-form-item">
|
||||
<label class="mcp-label">保存后重连:</label>
|
||||
<input type="checkbox" id="editAutoReconnect" checked>
|
||||
@@ -773,6 +798,7 @@
|
||||
const args = card.getAttribute('data-args') || '';
|
||||
const cwd = card.getAttribute('data-cwd') || '';
|
||||
const env = card.getAttribute('data-env') || '';
|
||||
const autostart = card.getAttribute('data-autostart') === '1';
|
||||
|
||||
// 填充表单
|
||||
document.getElementById('editMcpName').value = name;
|
||||
@@ -783,6 +809,7 @@
|
||||
document.getElementById('editMcpArgs').value = args;
|
||||
document.getElementById('editMcpCwd').value = cwd;
|
||||
document.getElementById('editMcpEnv').value = env && env !== 'null' ? env : '';
|
||||
document.getElementById('editAutostart').checked = autostart;
|
||||
|
||||
// 切换显示
|
||||
const editTransportSel = document.getElementById('editMcpTransport');
|
||||
@@ -817,6 +844,7 @@
|
||||
const cwd = document.getElementById('editMcpCwd').value.trim();
|
||||
const envText = document.getElementById('editMcpEnv').value.trim();
|
||||
const autoReconnect = document.getElementById('editAutoReconnect').checked;
|
||||
const autostart = document.getElementById('editAutostart').checked;
|
||||
|
||||
// 获取保存按钮并设置加载状态
|
||||
const saveBtn = document.getElementById('editSaveBtn');
|
||||
@@ -824,7 +852,7 @@
|
||||
saveBtn.textContent = '保存中...';
|
||||
saveBtn.disabled = true;
|
||||
|
||||
let payload = { name, transport, auto_reconnect: autoReconnect };
|
||||
let payload = { name, transport, auto_reconnect: autoReconnect, autostart };
|
||||
if (transport === 'sse') {
|
||||
payload.ip = ip;
|
||||
payload.key = key;
|
||||
@@ -857,6 +885,12 @@
|
||||
|
||||
// 传输类型与属性
|
||||
card.setAttribute('data-transport', transport);
|
||||
card.setAttribute('data-autostart', autostart ? '1' : '0');
|
||||
const badgeEl = card.querySelector('.autostart-badge');
|
||||
if (badgeEl) {
|
||||
if (autostart) badgeEl.classList.remove('hidden');
|
||||
else badgeEl.classList.add('hidden');
|
||||
}
|
||||
if (transport === 'sse') {
|
||||
const ipEl = card.querySelector('.mcp-info .mcp-value');
|
||||
if (ipEl) {
|
||||
@@ -913,8 +947,9 @@
|
||||
// 获取输入值
|
||||
const name = document.getElementById('mcpName').value.trim();
|
||||
const transport = document.getElementById('mcpTransport').value;
|
||||
const autostart = document.getElementById('mcpAutostart').checked;
|
||||
|
||||
let data = { name, transport };
|
||||
let data = { name, transport, autostart };
|
||||
if (transport === 'stdio') {
|
||||
const command = document.getElementById('mcpCommand').value.trim();
|
||||
const argsStr = document.getElementById('mcpArgs').value.trim();
|
||||
|
||||
Reference in New Issue
Block a user