feat: 设置页 MCP 管理卡片(方案B)— DB 真开关 + 即时生效 + env 仅首次种子

- app_settings 表(key/value):MCP 开关与令牌存 DB,不属于任何用户、不参与同步协议
- 配置读取/写入收敛到 core/mcp.js(readMcpConfig/saveMcpToken/setMcpEnabled)
- 状态语义:未配置→404(端点视为不存在);已配置关闭→503(可重新开启);令牌错→401
- env MCP_AUTH_TOKEN 仅首次种子(库里从没有过令牌时迁入 DB),不设也行;后台改动即时生效无需重启
- 管理 API(登录态):GET /api/mcp/status(掩码)、POST /api/mcp/token(生成/重置,明文仅一次)、GET /api/mcp/token(复制用)、POST /api/mcp/enabled
- 设置页新增「AI 接入 (MCP)」卡片:开关 toggle、掩码展示、一键生成并复制(writeClipboard 兜底)、重置令牌;未登录徽章「本设备请先登录」
- 入口接线:node-server.js 与 worker.js 均挂 /mcp(seedToken 透传);sw.js 放行裸 /mcp(v2→v3)
- 两个适配器补 getSetting/setSetting(node:sqlite 与 D1 同语义)
- 测试:server/test/mcp.test.js 39 项(未配置关闭/鉴权/JSON-RPC 全链路/开关热切换/令牌轮换);npm test 全绿(107+72+39)
- 线上 E2E:生产令牌 initialize/tools 7 个/2026-09 大盘(出勤11天/82h/挣得1599);公网 HTTPS 链路通;UI puppeteer 17/17(含关→503→开→200 热切换,全程无重启)
- 文档:README + server/README(环境变量表/接口表/数据模型/MCP 章节)
This commit is contained in:
OpenClaw Agent
2026-09-13 10:24:33 +08:00
parent 9702d1949a
commit 26306eee52
14 changed files with 1006 additions and 12 deletions
+3 -3
View File
@@ -8,14 +8,14 @@
* 离线时用预缓存的骨架(本地记账照常,数据在 localStorage,与 SW 无关)。
* 3. 静态资源 network-first:在线优先最新(no-cache 本来就要求「用前先问」),
* 网络失败才回缓存 —— SW 不改变现有缓存策略,只补「彻底断网」这一层。
* 4. /api/* 与 /mcp/sse 一律不碰:同步引擎自己有超时与 offline 处理,
* 4. /api/* 与 /mcp* 一律不碰:同步引擎自己有超时与 offline 处理,
* SW 绝不缓存 API 响应,避免「读到旧数据以为同步成功」的假象。
*
* 版本升级:静态资源 no-cache+ETag,导航每次都回源,新 index.html 引用的
* vendor ?v= 指纹变化会触发重下,PRECACHE 键值同步更新即可。旧缓存条目
* activate 时统一清理,不做遗留兜底。
*/
const VERSION = 'v2';
const VERSION = 'v3';
const PRECACHE = `wht-precache-${VERSION}`;
const RUNTIME = `wht-runtime-${VERSION}`;
@@ -84,7 +84,7 @@ self.addEventListener('fetch', (event) => {
// 只管同源 GET;跨域与 /api/*、/mcp/* 直接放行给网络
if (url.origin !== self.location.origin) return;
if (event.request.method !== 'GET') return;
if (url.pathname.startsWith('/api/') || url.pathname.startsWith('/mcp/')) return;
if (url.pathname.startsWith('/api/') || url.pathname === '/mcp' || url.pathname.startsWith('/mcp/')) return;
if (event.request.mode === 'navigate') {
event.respondWith(networkFirst(event.request, RUNTIME));