feat: MCP 管理进后台 UI (v2.1) — 开关/Token 即时生效无需重启

- settings 表新增 mcp_enabled / mcp_token,鉴权中间件动态读库,后台改动即时生效(实测验证:关->503、开->200、换 token 旧401新200,均不重启)
- 首次启动自动把 TONAV_MCP_TOKEN 环境变量迁移进数据库(兼容旧部署,systemd 现有 token 无感切换)
- 后台新增 API:GET /admin/api/mcp/status(token 掩码)、POST /admin/api/mcp/token(重新生成+自动启用,仅一次明文展示)、POST /admin/api/mcp/enabled
- 后台设置弹窗新增 MCP Server 区块:开关 checkbox、token 掩码展示、一键重新生成
- README 同步
This commit is contained in:
2026-09-11 14:46:47 +08:00
parent 9d7282074f
commit 909387ef52
4 changed files with 184 additions and 40 deletions
+61
View File
@@ -129,6 +129,24 @@
</div>
</div>
<div style="margin-bottom:20px; padding:15px; background:#f9f9f9; border-radius:10px">
<h3 style="font-size:14px; margin-bottom:10px">🤖 MCP Server</h3>
<div style="display:flex; align-items:center; gap:8px; margin-bottom:10px">
<input type="checkbox" id="cfg_mcp_enabled" style="width:16px; height:16px" onchange="toggleMCP()">
<label for="cfg_mcp_enabled" style="margin:0; font-size:13px">启用 MCP 接口(AI 可通过它管理导航页)</label>
</div>
<div id="mcpTokenRow" style="display:none">
<label style="font-size:12px; color:#666">Token</label>
<div style="display:flex; gap:8px; align-items:center">
<input type="text" id="cfg_mcp_token" class="input" readonly style="flex:1; background:#fff; color:#555">
<button class="btn btn-primary" onclick="regenMCPToken()" style="padding:8px 14px; white-space:nowrap">🔄 重新生成</button>
</div>
<div style="font-size:11px; color:#999; margin-top:6px">
端点: <code>POST/GET /mcp</code>(Streamable HTTP + SSE)· 改动即时生效,无需重启
</div>
</div>
</div>
<button class="btn btn-primary" onclick="saveSettings()" style="width:100%; margin-bottom:15px">保存设置</button>
<div style="padding:15px; border:1px solid #eee; border-radius:10px">
@@ -150,9 +168,52 @@
document.getElementById('cfg_pass').value = data.webdav_password || '';
document.getElementById('cfg_auto_backup').checked = data.auto_backup === 'true';
document.getElementById('settingsModal').style.display = 'flex';
loadMCPStatus();
loadCloudBackups();
}
async function loadMCPStatus() {
try {
const resp = await fetch('/admin/api/mcp/status');
const d = await resp.json();
const cb = document.getElementById('cfg_mcp_enabled');
const row = document.getElementById('mcpTokenRow');
cb.checked = d.enabled;
row.style.display = d.enabled ? 'block' : 'none';
document.getElementById('cfg_mcp_token').value = d.token_masked || '(未生成,点重新生成创建)';
} catch(e) {}
}
function toggleMCP() {
const enabled = document.getElementById('cfg_mcp_enabled').checked;
document.getElementById('mcpTokenRow').style.display = enabled ? 'block' : 'none';
fetch('/admin/api/mcp/enabled', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({enabled: enabled})
}).then(r => r.json()).then(res => {
if (!res.success) {
alert(res.message);
document.getElementById('cfg_mcp_enabled').checked = !enabled;
document.getElementById('mcpTokenRow').style.display = enabled ? 'none' : 'block';
}
});
}
async function regenMCPToken() {
if (!confirm('确定重新生成 Token?\n\n旧 Token 将立即失效,已接入的 AI 客户端需要更新为新 Token。')) return;
try {
const resp = await fetch('/admin/api/mcp/token', {method: 'POST'});
const res = await resp.json();
if (res.success) {
alert('✅ 新 Token 已生成(仅此一次明文展示,请立即复制):\n\n' + res.token);
loadMCPStatus();
} else {
alert('❌ ' + res.message);
}
} catch(e) { alert('生成失败'); }
}
async function loadCloudBackups() {
const listDiv = document.getElementById('backupList');
try {