bookworm-smart-assistant/hooks/session-start-mcp-probe.js.bak-p02.1777279394122

145 lines
4.3 KiB
Plaintext
Raw Permalink Normal View History

#!/usr/bin/env node
'use strict';
/**
* SessionStart MCP Probe (Phase 1 · T1.2)
* sentinel: PHASE1_T1_2_MCP_PROBE_HOOK_2026_04_24
*
* 事件: UserPromptSubmit (Bookworm 无 SessionStart 键,用 UserPromptSubmit + 日期守卫替代)
* 目的: 每日首次会话轻量探测 MCP 配置健康度,结果写 logs/mcp-health-<date>.json
*
* 预算:
* - 今日 snapshot 已存在: <5ms (fs.existsSync 快速返回)
* - 首次运行: <500ms (22 个 MCP 的命令存在性检查)
*
* 容错:
* - Feature flag 关闭: 立即 exit 0
* - 任何异常: exit 0 (fail-open永不阻断用户输入)
*
* 不做的事:
* - 不 spawn MCP 子进程 (太慢,交给 /mcp-probe 技能)
* - 不做实际 HTTP 请求
* - 不修改 .claude.json
*/
const fs = require('fs');
const path = require('path');
const os = require('os');
const HOME = process.env.USERPROFILE || process.env.HOME || os.homedir();
const CLAUDE_ROOT = process.env.CLAUDE_HOME ||
(fs.existsSync(path.join(HOME, '.claude')) ? path.join(HOME, '.claude') : HOME);
const LOGS_DIR = path.join(CLAUDE_ROOT, 'logs');
const FEATURE_FLAGS_FILE = path.join(CLAUDE_ROOT, '.bookworm-features.json');
const TODAY = new Date().toISOString().slice(0, 10);
const HEALTH_FILE = path.join(LOGS_DIR, 'mcp-health-' + TODAY + '.json');
const CONFIG_FILE = path.join(HOME, '.claude.json');
function safeExit() { process.exit(0); }
// 已知在 PATH 上的命令(避免 which 调用开销)
const WELL_KNOWN_CMDS = /^(npx|node|python|python3|bash|sh|pnpm|yarn|uvx|go|deno|bun)$/;
function commandPlausible(cmd) {
if (!cmd || typeof cmd !== 'string') return false;
if (path.isAbsolute(cmd)) return fs.existsSync(cmd);
// Windows 可能配置带 .cmd/.exe/.bat 后缀 (e.g. npx.cmd),剥离后比对已知命令
const stripped = cmd.replace(/\.(exe|cmd|bat)$/i, '');
if (WELL_KNOWN_CMDS.test(stripped)) return true;
// 回退: 按 PATH 扫描 (受限于 PATH 数量10-30 次 existsSync 可接受)
const PATH_DIRS = (process.env.PATH || process.env.Path || '').split(path.delimiter).filter(Boolean);
const candidates = [cmd, cmd + '.exe', cmd + '.cmd', cmd + '.bat'];
for (const dir of PATH_DIRS) {
for (const c of candidates) {
try { if (fs.existsSync(path.join(dir, c))) return true; } catch {}
}
}
return false;
}
function probeMcp(name, cfg) {
try {
if (cfg && (cfg.type === 'http' || cfg.type === 'sse')) {
const hasUrl = !!cfg.url && /^https?:\/\//.test(cfg.url);
return {
kind: cfg.type,
url: cfg.url || null,
urlValid: hasUrl,
reachable: hasUrl
};
}
// 默认 stdio
const command = (cfg && cfg.command) || '';
const exists = commandPlausible(command);
return {
kind: 'stdio',
command,
commandExists: exists,
reachable: exists
};
} catch (e) {
return { error: String(e.message || e), reachable: false };
}
}
function main() {
// Feature flag 检查
try {
if (fs.existsSync(FEATURE_FLAGS_FILE)) {
const flags = JSON.parse(fs.readFileSync(FEATURE_FLAGS_FILE, 'utf8'));
if (flags && flags.mcp_probe === false) return safeExit();
}
} catch {}
// 今日 snapshot 已存在 → 跳过 (日级守卫)
if (fs.existsSync(HEALTH_FILE)) return safeExit();
// 读取 MCP 配置
let mcpServers = {};
try {
const cfg = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
mcpServers = cfg.mcpServers || {};
} catch {
return safeExit();
}
const results = {};
let reachable = 0;
let unreachable = 0;
const unreachableList = [];
for (const name of Object.keys(mcpServers)) {
const r = probeMcp(name, mcpServers[name]);
results[name] = r;
if (r.reachable) reachable++;
else { unreachable++; unreachableList.push(name); }
}
const snapshot = {
schema_version: 1,
date: TODAY,
probedAt: new Date().toISOString(),
probeKind: 'lightweight-static',
totalMcps: Object.keys(mcpServers).length,
reachable,
unreachable,
unreachableList,
results
};
// 确保 logs 目录
try { fs.mkdirSync(LOGS_DIR, { recursive: true }); } catch {}
// 原子写
try {
const tmp = HEALTH_FILE + '.tmp';
fs.writeFileSync(tmp, JSON.stringify(snapshot, null, 2), 'utf8');
fs.renameSync(tmp, HEALTH_FILE);
} catch {}
safeExit();
}
if (require.main === module) main();
module.exports = { probeMcp, commandPlausible };