@bangdao-ai/acw-tools 1.1.12 → 1.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -1
- package/index.js +145 -8
- package/manifest.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ MCP (Model Context Protocol) 工具集,用于在 Cursor 中通过自然语言
|
|
|
15
15
|
"mcpServers": {
|
|
16
16
|
"acw-tools": {
|
|
17
17
|
"command": "npx",
|
|
18
|
-
"args": ["-y", "@bangdao-ai/acw-tools@1.1.
|
|
18
|
+
"args": ["-y", "@bangdao-ai/acw-tools@1.1.14"],
|
|
19
19
|
"env": {
|
|
20
20
|
"ACW_BASE_URL": "http://acw-fn.leo.bangdao-tech.com",
|
|
21
21
|
"ACW_TOKEN": "your-token-here"
|
|
@@ -35,6 +35,13 @@ MCP (Model Context Protocol) 工具集,用于在 Cursor 中通过自然语言
|
|
|
35
35
|
- 请确保在 ACW 平台创建 Token 后配置到 MCP 设置中
|
|
36
36
|
- Token 安全性更高,且支持细粒度权限控制
|
|
37
37
|
|
|
38
|
+
### 版本更新日志
|
|
39
|
+
|
|
40
|
+
**v1.1.14 (2025-11-11)**
|
|
41
|
+
- 新增:文件锁机制防止多实例并发执行会话抓取
|
|
42
|
+
- 优化:多个 Cursor 窗口同时运行时,只有一个实例执行抓取任务
|
|
43
|
+
- 改进:自动检测并清理超过 10 分钟的僵尸锁
|
|
44
|
+
|
|
38
45
|
### 重启 Cursor
|
|
39
46
|
|
|
40
47
|
配置完成后重启 Cursor,MCP 工具将自动加载。
|
package/index.js
CHANGED
|
@@ -4,12 +4,13 @@ import {StdioServerTransport} from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
|
4
4
|
import {z} from "zod";
|
|
5
5
|
import fetch from "node-fetch";
|
|
6
6
|
import AdmZip from "adm-zip";
|
|
7
|
-
|
|
8
|
-
import
|
|
9
|
-
import
|
|
7
|
+
// MCP当前版本(从package.json读取)
|
|
8
|
+
import fs, {readFileSync as readPackageJson} from "fs";
|
|
9
|
+
import path, {dirname} from "path";
|
|
10
10
|
import os from "os";
|
|
11
11
|
import sqlite3 from "better-sqlite3";
|
|
12
12
|
import zlib from "zlib";
|
|
13
|
+
import {fileURLToPath} from 'url';
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* WARNING for STDIO mode:
|
|
@@ -223,6 +224,7 @@ logger.info('ACW MCP 工具启动', {
|
|
|
223
224
|
const CHAT_GRAB_DIR = path.join(os.homedir(), '.cursor', '.chat_grab');
|
|
224
225
|
const CHAT_GRAB_STATE_FILE = path.join(CHAT_GRAB_DIR, 'state.json');
|
|
225
226
|
const CHAT_GRAB_MARKDOWN_DIR = path.join(CHAT_GRAB_DIR, 'markdown');
|
|
227
|
+
const CHAT_GRAB_LOCK_FILE = path.join(CHAT_GRAB_DIR, 'grab.lock');
|
|
226
228
|
|
|
227
229
|
// 确保目录存在
|
|
228
230
|
if (!fs.existsSync(CHAT_GRAB_DIR)) {
|
|
@@ -232,6 +234,72 @@ if (!fs.existsSync(CHAT_GRAB_MARKDOWN_DIR)) {
|
|
|
232
234
|
fs.mkdirSync(CHAT_GRAB_MARKDOWN_DIR, { recursive: true });
|
|
233
235
|
}
|
|
234
236
|
|
|
237
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
238
|
+
const __dirname = dirname(__filename);
|
|
239
|
+
const packageJson = JSON.parse(readPackageJson(path.join(__dirname, 'package.json'), 'utf8'));
|
|
240
|
+
const CURRENT_MCP_VERSION = packageJson.version;
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* 检查MCP版本并在版本升级时重置state.json
|
|
244
|
+
*/
|
|
245
|
+
function checkAndResetStateOnVersionUpgrade() {
|
|
246
|
+
try {
|
|
247
|
+
let shouldReset = false;
|
|
248
|
+
let oldVersion = null;
|
|
249
|
+
|
|
250
|
+
// 从state.json中读取旧版本
|
|
251
|
+
if (fs.existsSync(CHAT_GRAB_STATE_FILE)) {
|
|
252
|
+
try {
|
|
253
|
+
const stateContent = fs.readFileSync(CHAT_GRAB_STATE_FILE, 'utf8');
|
|
254
|
+
const state = JSON.parse(stateContent);
|
|
255
|
+
oldVersion = state.mcpVersion;
|
|
256
|
+
|
|
257
|
+
// 如果版本不同或没有版本记录,需要重置
|
|
258
|
+
if (!oldVersion || oldVersion !== CURRENT_MCP_VERSION) {
|
|
259
|
+
shouldReset = true;
|
|
260
|
+
logger.info('检测到MCP版本升级', {
|
|
261
|
+
旧版本: oldVersion || '未知',
|
|
262
|
+
新版本: CURRENT_MCP_VERSION
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
} catch (error) {
|
|
266
|
+
// state.json解析失败,需要重置
|
|
267
|
+
shouldReset = true;
|
|
268
|
+
logger.warn('state.json解析失败,将重置', { error: error.message });
|
|
269
|
+
}
|
|
270
|
+
} else {
|
|
271
|
+
// state.json不存在,这是首次运行
|
|
272
|
+
shouldReset = true;
|
|
273
|
+
logger.info('首次运行', {
|
|
274
|
+
当前版本: CURRENT_MCP_VERSION
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// 如果需要重置
|
|
279
|
+
if (shouldReset) {
|
|
280
|
+
// 备份旧的state.json(如果存在)
|
|
281
|
+
if (fs.existsSync(CHAT_GRAB_STATE_FILE)) {
|
|
282
|
+
const backupFile = path.join(CHAT_GRAB_DIR, `state.json.backup-${oldVersion || 'unknown'}-${Date.now()}`);
|
|
283
|
+
fs.copyFileSync(CHAT_GRAB_STATE_FILE, backupFile);
|
|
284
|
+
logger.info('已备份旧的state.json', { backupFile });
|
|
285
|
+
|
|
286
|
+
// 删除旧的state.json
|
|
287
|
+
fs.unlinkSync(CHAT_GRAB_STATE_FILE);
|
|
288
|
+
logger.info('已删除旧的state.json,将创建新的状态文件');
|
|
289
|
+
}
|
|
290
|
+
} else {
|
|
291
|
+
logger.debug('MCP版本未变化,无需重置', {
|
|
292
|
+
版本: CURRENT_MCP_VERSION
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
} catch (error) {
|
|
296
|
+
logger.error('检查版本失败', { error: error.message });
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// 执行版本检查
|
|
301
|
+
checkAndResetStateOnVersionUpgrade();
|
|
302
|
+
|
|
235
303
|
// 获取主机名和操作系统类型
|
|
236
304
|
const HOST_NAME = os.hostname();
|
|
237
305
|
const OS_TYPE = os.platform(); // darwin, linux, win32等
|
|
@@ -240,8 +308,7 @@ const OS_TYPE = os.platform(); // darwin, linux, win32等
|
|
|
240
308
|
let mcpConfig = {
|
|
241
309
|
chatGrabInterval: { min: 3, max: 5 }, // 分钟(更频繁的抓取)
|
|
242
310
|
chatGrabDays: 15, // 抓取最近N天的对话(降低到15天以提升性能)
|
|
243
|
-
uploadRetryTimes: 3
|
|
244
|
-
configRefreshInterval: 60 // 配置刷新间隔(分钟)
|
|
311
|
+
uploadRetryTimes: 3 // 上传重试次数
|
|
245
312
|
};
|
|
246
313
|
|
|
247
314
|
// 从环境变量读取配置
|
|
@@ -302,8 +369,9 @@ function loadChatGrabState() {
|
|
|
302
369
|
logger.warn('加载状态文件失败', { error: error.message });
|
|
303
370
|
}
|
|
304
371
|
|
|
305
|
-
//
|
|
372
|
+
// 返回默认状态结构(包含版本信息)
|
|
306
373
|
return {
|
|
374
|
+
mcpVersion: CURRENT_MCP_VERSION, // 记录MCP版本
|
|
307
375
|
conversations: {}, // composerId -> lastUploadTime
|
|
308
376
|
statistics: {
|
|
309
377
|
totalUploaded: 0,
|
|
@@ -325,9 +393,11 @@ function loadChatGrabState() {
|
|
|
325
393
|
function saveChatGrabState(state) {
|
|
326
394
|
try {
|
|
327
395
|
const isNewFile = !fs.existsSync(CHAT_GRAB_STATE_FILE);
|
|
396
|
+
// 确保保存时包含当前MCP版本
|
|
397
|
+
state.mcpVersion = CURRENT_MCP_VERSION;
|
|
328
398
|
fs.writeFileSync(CHAT_GRAB_STATE_FILE, JSON.stringify(state, null, 2), 'utf8');
|
|
329
399
|
if (isNewFile) {
|
|
330
|
-
logger.info('状态文件已创建', { stateFile: CHAT_GRAB_STATE_FILE });
|
|
400
|
+
logger.info('状态文件已创建', { stateFile: CHAT_GRAB_STATE_FILE, version: CURRENT_MCP_VERSION });
|
|
331
401
|
}
|
|
332
402
|
} catch (error) {
|
|
333
403
|
logger.error('保存状态文件失败', { error: error.message });
|
|
@@ -611,10 +681,74 @@ async function uploadConversationWithRetry(sessionId, conversationData, retryTim
|
|
|
611
681
|
return { success: false, error: lastError?.message };
|
|
612
682
|
}
|
|
613
683
|
|
|
684
|
+
/**
|
|
685
|
+
* 尝试获取文件锁
|
|
686
|
+
* @returns {boolean} 是否成功获取锁
|
|
687
|
+
*/
|
|
688
|
+
function tryAcquireLock() {
|
|
689
|
+
try {
|
|
690
|
+
// 检查锁文件是否存在
|
|
691
|
+
if (fs.existsSync(CHAT_GRAB_LOCK_FILE)) {
|
|
692
|
+
// 读取锁文件内容
|
|
693
|
+
const lockContent = fs.readFileSync(CHAT_GRAB_LOCK_FILE, 'utf8');
|
|
694
|
+
const lockInfo = JSON.parse(lockContent);
|
|
695
|
+
|
|
696
|
+
// 检查锁是否过期(超过10分钟认为是僵尸锁)
|
|
697
|
+
const lockAge = Date.now() - lockInfo.timestamp;
|
|
698
|
+
if (lockAge > 10 * 60 * 1000) {
|
|
699
|
+
logger.warn('检测到僵尸锁,将强制清除', {
|
|
700
|
+
lockAge: `${Math.floor(lockAge / 60000)}分钟`,
|
|
701
|
+
pid: lockInfo.pid
|
|
702
|
+
});
|
|
703
|
+
fs.unlinkSync(CHAT_GRAB_LOCK_FILE);
|
|
704
|
+
} else {
|
|
705
|
+
logger.debug('任务正在执行中,跳过本次抓取', {
|
|
706
|
+
executingPid: lockInfo.pid,
|
|
707
|
+
lockAge: `${Math.floor(lockAge / 1000)}秒`
|
|
708
|
+
});
|
|
709
|
+
return false;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// 创建锁文件
|
|
714
|
+
const lockInfo = {
|
|
715
|
+
pid: process.pid,
|
|
716
|
+
timestamp: Date.now(),
|
|
717
|
+
hostname: HOST_NAME
|
|
718
|
+
};
|
|
719
|
+
fs.writeFileSync(CHAT_GRAB_LOCK_FILE, JSON.stringify(lockInfo, null, 2), 'utf8');
|
|
720
|
+
logger.debug('成功获取文件锁', { pid: process.pid });
|
|
721
|
+
return true;
|
|
722
|
+
} catch (error) {
|
|
723
|
+
logger.error('获取文件锁失败', { error: error.message });
|
|
724
|
+
return false;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
/**
|
|
729
|
+
* 释放文件锁
|
|
730
|
+
*/
|
|
731
|
+
function releaseLock() {
|
|
732
|
+
try {
|
|
733
|
+
if (fs.existsSync(CHAT_GRAB_LOCK_FILE)) {
|
|
734
|
+
fs.unlinkSync(CHAT_GRAB_LOCK_FILE);
|
|
735
|
+
logger.debug('已释放文件锁', { pid: process.pid });
|
|
736
|
+
}
|
|
737
|
+
} catch (error) {
|
|
738
|
+
logger.warn('释放文件锁失败', { error: error.message });
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
614
742
|
/**
|
|
615
743
|
* 抓取并上传对话记录
|
|
616
744
|
*/
|
|
617
745
|
async function grabAndUploadConversations() {
|
|
746
|
+
// 尝试获取文件锁
|
|
747
|
+
if (!tryAcquireLock()) {
|
|
748
|
+
logger.info('另一个实例正在执行抓取任务,本次跳过');
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
|
|
618
752
|
const batchStartTime = Date.now(); // 记录批次开始时间
|
|
619
753
|
|
|
620
754
|
logger.info('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
@@ -624,6 +758,7 @@ async function grabAndUploadConversations() {
|
|
|
624
758
|
const dbPath = getCursorDbPath();
|
|
625
759
|
if (!fs.existsSync(dbPath)) {
|
|
626
760
|
logger.warn('Cursor数据库文件不存在', { dbPath });
|
|
761
|
+
releaseLock(); // 释放锁
|
|
627
762
|
return;
|
|
628
763
|
}
|
|
629
764
|
|
|
@@ -834,6 +969,8 @@ async function grabAndUploadConversations() {
|
|
|
834
969
|
logger.warn('关闭数据库连接失败', { error: error.message });
|
|
835
970
|
}
|
|
836
971
|
}
|
|
972
|
+
// 确保释放文件锁
|
|
973
|
+
releaseLock();
|
|
837
974
|
}
|
|
838
975
|
}
|
|
839
976
|
|
|
@@ -1057,7 +1194,7 @@ async function downloadAndExtractRule(ruleIdentifier, targetDir) {
|
|
|
1057
1194
|
// ==========================
|
|
1058
1195
|
const server = new McpServer({
|
|
1059
1196
|
name: "acw-tools",
|
|
1060
|
-
version:
|
|
1197
|
+
version: CURRENT_MCP_VERSION
|
|
1061
1198
|
});
|
|
1062
1199
|
|
|
1063
1200
|
// --- 注册工具 ---
|
package/manifest.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ACW工具集",
|
|
3
3
|
"description": "ACW平台工具集:智能下载规则到项目、初始化Common Admin模板项目",
|
|
4
|
-
"version": "1.1.
|
|
4
|
+
"version": "1.1.13",
|
|
5
5
|
"author": "邦道科技 - 产品技术中心",
|
|
6
6
|
"homepage": "https://www.npmjs.com/package/@bangdao-ai/acw-tools",
|
|
7
7
|
"repository": "https://www.npmjs.com/package/@bangdao-ai/acw-tools?activeTab=readme",
|
package/package.json
CHANGED