75 lines
2.6 KiB
Plaintext
75 lines
2.6 KiB
Plaintext
|
|
#!/usr/bin/env node
|
||
|
|
/**
|
||
|
|
* token-saver-mcp-tracker.js · TSE Layer 4 · 2026-04-27
|
||
|
|
* PostToolUse (mcp__) · MCP 工具使用率追踪
|
||
|
|
* 每 20 次 MCP 调用检查一次, 建议 /mcp-prune
|
||
|
|
* 状态: session-state/tse-mcp-usage.json (跨会话累积)
|
||
|
|
* 行为: fail-open
|
||
|
|
*/
|
||
|
|
'use strict';
|
||
|
|
|
||
|
|
const fs = require('fs');
|
||
|
|
const path = require('path');
|
||
|
|
const CLAUDE_ROOT = require('./lib/root.js');
|
||
|
|
const readStdin = require('./lib/read-stdin.js');
|
||
|
|
|
||
|
|
const STATE_DIR = path.join(CLAUDE_ROOT, 'session-state');
|
||
|
|
const STATE_PATH = path.join(STATE_DIR, 'tse-mcp-usage.json');
|
||
|
|
const CHECK_INTERVAL = 20;
|
||
|
|
|
||
|
|
function loadState() {
|
||
|
|
try {
|
||
|
|
if (fs.existsSync(STATE_PATH)) return JSON.parse(fs.readFileSync(STATE_PATH, 'utf8'));
|
||
|
|
} catch {}
|
||
|
|
return { version: 1, servers: {}, totalCalls: 0, trackingSince: new Date().toISOString() };
|
||
|
|
}
|
||
|
|
function saveState(s) {
|
||
|
|
try {
|
||
|
|
if (!fs.existsSync(STATE_DIR)) fs.mkdirSync(STATE_DIR, { recursive: true });
|
||
|
|
const tmp = STATE_PATH + '.tmp.' + process.pid;
|
||
|
|
fs.writeFileSync(tmp, JSON.stringify(s, null, 2), 'utf8');
|
||
|
|
fs.renameSync(tmp, STATE_PATH);
|
||
|
|
} catch {}
|
||
|
|
}
|
||
|
|
|
||
|
|
(async () => {
|
||
|
|
try {
|
||
|
|
const hd = await readStdin();
|
||
|
|
const tn = hd.tool_name || '';
|
||
|
|
if (!tn.startsWith('mcp__')) process.exit(0);
|
||
|
|
|
||
|
|
var parts = tn.split('__');
|
||
|
|
if (parts.length < 3) process.exit(0);
|
||
|
|
var server = parts[1];
|
||
|
|
var method = parts.slice(2).join('__');
|
||
|
|
|
||
|
|
var state = loadState();
|
||
|
|
state.totalCalls = (state.totalCalls || 0) + 1;
|
||
|
|
state.lastCall = new Date().toISOString();
|
||
|
|
|
||
|
|
if (!state.servers[server]) {
|
||
|
|
state.servers[server] = { count: 0, tools: {}, firstSeen: new Date().toISOString() };
|
||
|
|
}
|
||
|
|
state.servers[server].count++;
|
||
|
|
state.servers[server].lastUsed = new Date().toISOString();
|
||
|
|
state.servers[server].tools[method] = (state.servers[server].tools[method] || 0) + 1;
|
||
|
|
|
||
|
|
saveState(state);
|
||
|
|
|
||
|
|
if (state.totalCalls % CHECK_INTERVAL === 0) {
|
||
|
|
var active = Object.keys(state.servers);
|
||
|
|
var summary = active.map(function(k) { return k + '(' + state.servers[k].count + ')'; }).join(', ');
|
||
|
|
var msg = '[TSE\xb7MCP_TRACKER] MCP \u8c03\u7528\u7edf\u8ba1 (\u7d2f\u8ba1 ' + state.totalCalls + ' \u6b21, ' + active.length + ' \u4e2a\u6d3b\u8dc3\u670d\u52a1\u5668)' +
|
||
|
|
'\n\u6d3b\u8dc3: ' + summary +
|
||
|
|
'\n\u5efa\u8bae: \u8fd0\u884c /mcp-prune \u68c0\u67e5\u96f6\u8c03\u7528 MCP \u670d\u52a1\u5668\u5e76\u8003\u8651\u7981\u7528\u4ee5\u51cf\u5c11\u542f\u52a8\u5ef6\u8fdf\u3002';
|
||
|
|
|
||
|
|
process.stdout.write(JSON.stringify({
|
||
|
|
continue: true,
|
||
|
|
hookSpecificOutput: { hookEventName: 'PostToolUse', additionalContext: msg }
|
||
|
|
}));
|
||
|
|
}
|
||
|
|
|
||
|
|
process.exit(0);
|
||
|
|
} catch { process.exit(0); }
|
||
|
|
})();
|