feat: MCP 端点鉴权改用后台 Token 管理,废弃 mcp_kv 配置
build-and-deploy / build-and-deploy (push) Successful in 42s

- tokenService 支持 mcpEnabled 标记,新增 getMcpTokenValues 与迁移逻辑
- /mcp 鉴权改为校验 Token 表中标记为 MCP 访问的生效令牌
- 启动时自动把旧 mcp_token 配置迁成一条 MCP Token,保证旧客户端不中断
- admin 页面 Token 表新增 MCP 访问列与生成/编辑开关
- 新增 tokenService 测试(58 项全部通过)
This commit is contained in:
jif2.zhang
2026-09-20 21:45:18 +08:00
parent 26957bcb49
commit be2e4d9cdf
5 changed files with 169 additions and 21 deletions
+16 -3
View File
@@ -117,7 +117,7 @@
<table class="data-table" id="token-table">
<thead>
<tr>
<th>名称</th><th>关联项目</th><th>权限</th><th>Token</th>
<th>名称</th><th>关联项目</th><th>权限</th><th>MCP 访问</th><th>Token</th>
<th>状态</th><th>过期时间</th><th>创建时间</th><th>操作</th>
</tr>
</thead>
@@ -229,6 +229,12 @@
</select>
</div>
</div>
<div class="form-group">
<label class="checkbox-label" style="display:flex;align-items:center;gap:8px;cursor:pointer">
<input type="checkbox" id="input-token-mcp" style="width:16px;height:16px">
<span>作为 MCP 访问令牌(用于 /mcp 端点鉴权)</span>
</label>
</div>
<p class="hint">生成后 Token 明文仅展示一次,请及时复制并妥善保管。</p>
</div>
<div class="modal-footer">
@@ -486,7 +492,7 @@
function renderTokens(tokens) {
var tbody = document.getElementById("token-tbody");
if (!tokens.length) {
tbody.innerHTML = '<tr><td colspan="8" style="text-align:center;color:#999;padding:30px">暂无 Token</td></tr>';
tbody.innerHTML = '<tr><td colspan="9" style="text-align:center;color:#999;padding:30px">暂无 Token</td></tr>';
return;
}
tbody.innerHTML = tokens.map(function (t) {
@@ -499,6 +505,9 @@
"<td>" + esc(t.name) + "</td>" +
"<td>" + esc(t.projectId === "all" ? "全部项目" : t.projectId) + "</td>" +
'<td><span class="dim-chip">' + (t.permission === "readwrite" ? "读写" : "只读") + "</span></td>" +
'<td>' + (t.mcpEnabled
? '<span class="dim-chip" style="color:#0a7a3c;border-color:#0a7a3c">MCP</span>'
: '<span style="color:#aaa">—</span>') + "</td>" +
'<td><div class="token-reveal">' +
'<span class="token-value token-mask" data-id="' + t.id + '">' + masked + "</span>" +
'<button class="token-btn" onclick="toggleTokenVis(this)">显示</button>' +
@@ -508,7 +517,7 @@
"<td>" + fmtDate(t.expiresAt) + "</td>" +
"<td>" + fmtDate(t.createdAt) + "</td>" +
'<td style="white-space:nowrap">' +
'<button class="row-btn" onclick=\'editToken(' + JSON.stringify({ id: t.id, name: t.name, projectId: t.projectId, permission: t.permission }).replace(/'/g, "&#39;") + ")'>编辑</button> " +
'<button class="row-btn" onclick=\'editToken(' + JSON.stringify({ id: t.id, name: t.name, projectId: t.projectId, permission: t.permission, mcpEnabled: !!t.mcpEnabled }).replace(/'/g, "&#39;") + ")'>编辑</button> " +
(t.enabled
? '<button class="row-btn danger" onclick="revokeToken(\'' + t.id + "')\">吊销</button>"
: "") +
@@ -559,6 +568,7 @@
document.getElementById("input-token-project").value = "all";
document.getElementById("input-token-permission").value = "readonly";
document.getElementById("input-token-expiry").value = "90";
document.getElementById("input-token-mcp").checked = false;
document.getElementById("btn-save-token").textContent = "生成";
loadProjectsForSelect();
openModal("modal-token");
@@ -570,6 +580,7 @@
document.getElementById("input-token-name").value = t.name;
document.getElementById("input-token-project").value = t.projectId;
document.getElementById("input-token-permission").value = t.permission;
document.getElementById("input-token-mcp").checked = !!(t.mcpEnabled);
document.getElementById("btn-save-token").textContent = "保存";
loadProjectsForSelect();
openModal("modal-token");
@@ -585,6 +596,7 @@
name: name,
projectId: document.getElementById("input-token-project").value,
permission: document.getElementById("input-token-permission").value,
mcpEnabled: document.getElementById("input-token-mcp").checked,
});
closeModal("modal-token");
toast("Token 已更新", "success");
@@ -594,6 +606,7 @@
name: name,
projectId: document.getElementById("input-token-project").value,
permission: document.getElementById("input-token-permission").value,
mcpEnabled: document.getElementById("input-token-mcp").checked,
expiresInDays: parseInt(document.getElementById("input-token-expiry").value) || 0,
});
closeModal("modal-token");