UNPKG

autosnippet

Version:

Extract code patterns into a knowledge base for AI coding assistants

141 lines (140 loc) 5.18 kB
/** * autoApproveInjector.js — Cursor MCP autoApprove 自动注入 * * "首次手动授权,后续自动" 的安全实现: * * 1. 首次 bootstrap 成功 → 写标记文件 `.autosnippet/.auto-approve-pending` * (不碰 mcp.json,避免 Cursor 检测配置变更重启 MCP Server 中断当前 session) * 2. 下次 MCP Server 启动 → 检查标记 → 注入 autoApprove → 删标记 * (写入发生在连接建立前,安全无副作用) * 3. `asd upgrade` → 直接注入(不在 MCP session 中执行,无中断风险) * * 为什么不在 bootstrap 期间直接写 mcp.json? * Cursor 监听 .cursor/mcp.json 变更,可能触发 MCP Server 重启, * 导致内存中的 BootstrapSession 丢失,后续 submit/complete 全部失败。 * * @module external/mcp/autoApproveInjector */ import fs from 'node:fs'; import path from 'node:path'; /** * 所有 agent 层工具(用户日常使用的 15 个) * admin 层工具(enrich_candidates, knowledge_lifecycle, validate_candidate, check_duplicate) * 不加入自动授权 — 保留对高级操作的手动确认。 */ const AUTO_APPROVE_TOOLS = [ 'autosnippet_health', 'autosnippet_capabilities', 'autosnippet_search', 'autosnippet_knowledge', 'autosnippet_structure', 'autosnippet_graph', 'autosnippet_guard', 'autosnippet_submit_knowledge', 'autosnippet_skill', 'autosnippet_task', 'autosnippet_bootstrap', 'autosnippet_dimension_complete', 'autosnippet_wiki_plan', 'autosnippet_wiki_finalize', ]; /** 标记文件路径 */ function _markerPath(projectRoot) { return path.join(projectRoot, '.autosnippet', '.auto-approve-pending'); } /** * 写入标记文件 — 标记首次 bootstrap 已完成,下次启动时注入 autoApprove * * 在 bootstrap handler 中调用。只写一个轻量标记文件到 .autosnippet/, * 不触碰 .cursor/mcp.json,避免 Cursor 检测配置变更重启 MCP Server。 * * @param projectRoot 项目根目录 */ export function markAutoApproveNeeded(projectRoot, logger) { const marker = _markerPath(projectRoot); try { const dir = path.dirname(marker); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } fs.writeFileSync(marker, `${new Date().toISOString()}\n`); logger?.info?.('[AutoApprove] Marked for injection on next MCP startup'); return true; } catch (e) { const msg = e instanceof Error ? e.message : String(e); logger?.warn?.(`[AutoApprove] Failed to write marker: ${msg}`); return false; } } /** * 向 .cursor/mcp.json 中 autosnippet 服务器注入 autoApprove 工具列表 * * @param projectRoot 项目根目录 * @param [logger] 日志实例(可选) * @returns 是否成功写入(false = 文件不存在或无 autosnippet 配置) */ export function injectAutoApprove(projectRoot, logger) { const configPath = path.join(projectRoot, '.cursor', 'mcp.json'); // 如果 .cursor/mcp.json 不存在,不做任何操作(不创建文件) if (!fs.existsSync(configPath)) { return false; } let config; try { config = JSON.parse(fs.readFileSync(configPath, 'utf8')); } catch { logger?.warn?.('[AutoApprove] Failed to parse .cursor/mcp.json, skipping'); return false; } const serverConfig = config?.mcpServers?.autosnippet; if (!serverConfig) { return false; } // 幂等检查:已有完整 autoApprove 则跳过 const existing = serverConfig.autoApprove; if (Array.isArray(existing)) { const existingSet = new Set(existing); const allPresent = AUTO_APPROVE_TOOLS.every((t) => existingSet.has(t)); if (allPresent) { return true; // 已完整,无需写入 } } // 合并(保留用户手动添加的其他工具) const merged = new Set([...(existing || []), ...AUTO_APPROVE_TOOLS]); serverConfig.autoApprove = [...merged].sort(); try { fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`); logger?.info?.(`[AutoApprove] Injected ${AUTO_APPROVE_TOOLS.length} tools into .cursor/mcp.json autoApprove`); return true; } catch (e) { const msg = e instanceof Error ? e.message : String(e); logger?.warn?.(`[AutoApprove] Failed to write .cursor/mcp.json: ${msg}`); return false; } } /** * MCP Server 启动时调用 — 检查标记文件,如有则注入 autoApprove 并清除标记 * * 注入发生在 MCP 连接建立之前,写入 mcp.json 不影响当前启动。 * Cursor 下次读取 mcp.json 时(重启或新窗口)即生效。 */ export function applyPendingAutoApprove(projectRoot, logger) { const marker = _markerPath(projectRoot); if (!fs.existsSync(marker)) { return; } const injected = injectAutoApprove(projectRoot, logger); if (injected) { // 清除标记 try { fs.unlinkSync(marker); } catch { /* ignore */ } } } export { AUTO_APPROVE_TOOLS };