zwcad-mcp 0.2.1 → 0.2.2
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 +4 -1
- package/lib/proxy.js +135 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
中望CAD(ZWCAD)MCP 服务,让大模型通过 [MCP 协议](https://modelcontextprotocol.io)直接操控中望CAD 平台与中望机械CAD,完成绘图、标注、图层/块/样式管理,以及图框、标题栏、明细表(BOM)等机械操作。
|
|
4
4
|
|
|
5
|
-
**共计
|
|
5
|
+
**共计 41 个工具**:平台 26 个(不依赖机械环境)+ 机械 11 个(需中望机械CAD)+ 诊断 2 个 + 启动器侧 2 个。
|
|
6
|
+
|
|
7
|
+
> 启动器侧的两个工具(`zwcad_wait_ready`、`zwcad_launcher_diagnose`)在 Python 运行时尚未就绪时也可用,
|
|
8
|
+
> 用于等待初始化完成、以及在首次安装失败时自助排查。
|
|
6
9
|
|
|
7
10
|
## 为什么需要 Node 启动器
|
|
8
11
|
|
package/lib/proxy.js
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
const fs = require('fs');
|
|
31
31
|
const path = require('path');
|
|
32
32
|
const { spawn } = require('child_process');
|
|
33
|
-
const { logError } = require('./errorlog');
|
|
33
|
+
const { logError, logFilePath } = require('./errorlog');
|
|
34
34
|
|
|
35
35
|
const PROTOCOL_VERSION = '2024-11-05';
|
|
36
36
|
|
|
@@ -72,6 +72,26 @@ const PROXY_TOOLS = [
|
|
|
72
72
|
additionalProperties: false,
|
|
73
73
|
},
|
|
74
74
|
},
|
|
75
|
+
{
|
|
76
|
+
name: 'zwcad_launcher_diagnose',
|
|
77
|
+
description:
|
|
78
|
+
'诊断启动器与 Python 运行时的状态,并返回最近的错误记录。\n\n' +
|
|
79
|
+
'与 zwcad_diagnose 的区别:本工具由 JS 启动器实现,**在 Python 尚未就绪时也可用**,' +
|
|
80
|
+
'因此是排查「首次安装失败 / 一直停在初始化」的首选;' +
|
|
81
|
+
'zwcad_diagnose 依赖 Python 侧,只能在其就绪后调用。\n\n' +
|
|
82
|
+
'返回内容包括:运行时状态、Python 解释器路径与版本、虚拟环境状态、' +
|
|
83
|
+
'最近若干条错误日志(含事件名与诊断细节)。',
|
|
84
|
+
inputSchema: {
|
|
85
|
+
type: 'object',
|
|
86
|
+
properties: {
|
|
87
|
+
limit: {
|
|
88
|
+
type: 'number',
|
|
89
|
+
description: '返回最近多少条错误日志,默认 10,上限 50。',
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
additionalProperties: false,
|
|
93
|
+
},
|
|
94
|
+
},
|
|
75
95
|
];
|
|
76
96
|
|
|
77
97
|
/**
|
|
@@ -494,13 +514,14 @@ function createProxy(opts) {
|
|
|
494
514
|
} else if (r.state === 'failed') {
|
|
495
515
|
text =
|
|
496
516
|
`服务初始化失败:${r.error}\n\n` +
|
|
497
|
-
|
|
517
|
+
`下一步:调用 zwcad_launcher_diagnose 获取完整的错误记录与运行环境信息,` +
|
|
518
|
+
`据此判断原因并告知用户具体怎么修。\n\n` +
|
|
498
519
|
`常见原因:未安装 Python 3.10+、网络无法访问 PyPI、磁盘空间不足。`;
|
|
499
520
|
} else {
|
|
500
521
|
text =
|
|
501
522
|
`已等待 ${seconds} 秒,初始化仍未完成,仍在安装依赖。\n\n` +
|
|
502
|
-
|
|
503
|
-
|
|
523
|
+
`下一步:可再调用一次本工具继续等待;若累计超过 2 分钟仍无进展,` +
|
|
524
|
+
`调用 zwcad_launcher_diagnose 查看错误记录。`;
|
|
504
525
|
}
|
|
505
526
|
|
|
506
527
|
writeJson({
|
|
@@ -514,6 +535,19 @@ function createProxy(opts) {
|
|
|
514
535
|
return;
|
|
515
536
|
}
|
|
516
537
|
|
|
538
|
+
if (name === 'zwcad_launcher_diagnose') {
|
|
539
|
+
let limit = Number(args.limit);
|
|
540
|
+
if (!Number.isFinite(limit) || limit <= 0) limit = 10;
|
|
541
|
+
limit = Math.min(limit, 50);
|
|
542
|
+
|
|
543
|
+
writeJson({
|
|
544
|
+
jsonrpc: '2.0',
|
|
545
|
+
id: msg.id,
|
|
546
|
+
result: { content: [{ type: 'text', text: buildDiagnosisReport(limit) }] },
|
|
547
|
+
});
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
|
|
517
551
|
writeJson({
|
|
518
552
|
jsonrpc: '2.0',
|
|
519
553
|
id: msg.id,
|
|
@@ -524,6 +558,103 @@ function createProxy(opts) {
|
|
|
524
558
|
});
|
|
525
559
|
}
|
|
526
560
|
|
|
561
|
+
/**
|
|
562
|
+
* 生成诊断报告。
|
|
563
|
+
*
|
|
564
|
+
* 目标是让 AI 读完就能判断问题出在哪,并把结论用自然语言转述给用户,
|
|
565
|
+
* 而不是把一坨技术细节甩给用户让他自己去跑命令。
|
|
566
|
+
*/
|
|
567
|
+
function buildDiagnosisReport(limit) {
|
|
568
|
+
const lines = [];
|
|
569
|
+
const stateText =
|
|
570
|
+
runtimeState === 'ready' && childReady
|
|
571
|
+
? '已就绪'
|
|
572
|
+
: runtimeState === 'failed'
|
|
573
|
+
? '失败'
|
|
574
|
+
: '正在准备';
|
|
575
|
+
|
|
576
|
+
lines.push('## 运行时状态');
|
|
577
|
+
lines.push(`状态:${stateText}`);
|
|
578
|
+
if (runtimeState === 'failed' && runtimeError) {
|
|
579
|
+
lines.push(`错误:${runtimeError}`);
|
|
580
|
+
}
|
|
581
|
+
if (childReady) {
|
|
582
|
+
lines.push('Python 侧已完成握手,可以正常接受请求。');
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
lines.push('');
|
|
586
|
+
lines.push('## 运行环境');
|
|
587
|
+
lines.push(`Node:${process.version}(${process.platform}/${process.arch})`);
|
|
588
|
+
lines.push(`启动器版本:${opts.version || '未知'}`);
|
|
589
|
+
lines.push(`工作目录:${cwd}`);
|
|
590
|
+
lines.push(
|
|
591
|
+
`环境变量:ZWCAD_MCP_PYTHON=${process.env.ZWCAD_MCP_PYTHON || '(未设置)'},` +
|
|
592
|
+
`ZWCAD_MCP_PIP_INDEX=${process.env.ZWCAD_MCP_PIP_INDEX || '(未设置)'}`
|
|
593
|
+
);
|
|
594
|
+
|
|
595
|
+
lines.push('');
|
|
596
|
+
lines.push('## 错误记录');
|
|
597
|
+
const entries = readRecentErrors(limit);
|
|
598
|
+
if (entries.length === 0) {
|
|
599
|
+
lines.push('无错误记录。若问题仍存在,可能是尚在安装中,或故障未落到日志点。');
|
|
600
|
+
} else {
|
|
601
|
+
lines.push(`日志文件:${logFilePath()}`);
|
|
602
|
+
lines.push('');
|
|
603
|
+
for (const e of entries) {
|
|
604
|
+
lines.push(`- [${e.time}] **${e.event}**`);
|
|
605
|
+
if (e.detail) {
|
|
606
|
+
lines.push(' ' + e.detail.replace(/\n/g, '\n '));
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
lines.push('');
|
|
612
|
+
lines.push('## 如何判读');
|
|
613
|
+
lines.push('- `runtime.python.not_found`:本机没有 3.10+ 的 Python。');
|
|
614
|
+
lines.push(' 让用户装 Python(建议 3.13),或设 `ZWCAD_MCP_PYTHON` 指向已有解释器。');
|
|
615
|
+
lines.push('- `runtime.install.failed`:装依赖失败。看 detail 里的 `pipIndex` 与 `error`,');
|
|
616
|
+
lines.push(' 多为网络问题,让用户设 `ZWCAD_MCP_PIP_INDEX` 为国内镜像后重连。');
|
|
617
|
+
lines.push('- `python.spawn.failed` / `python.exited`:Python 进程起不来或崩溃,');
|
|
618
|
+
lines.push(' 看 detail 里的 `stderrTail`(Python 自身的异常堆栈)。');
|
|
619
|
+
lines.push('- 无任何错误记录但仍不可用:多半仍在安装,让其再等或重连。');
|
|
620
|
+
|
|
621
|
+
return lines.join('\n');
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* 读取最近 N 条错误日志,转成易读文本。
|
|
626
|
+
*
|
|
627
|
+
* 读取失败一律返回空数组——诊断工具自己不能因为读不到日志而报错。
|
|
628
|
+
*/
|
|
629
|
+
function readRecentErrors(limit) {
|
|
630
|
+
let raw;
|
|
631
|
+
try {
|
|
632
|
+
raw = fs.readFileSync(logFilePath(), 'utf8');
|
|
633
|
+
} catch {
|
|
634
|
+
return [];
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
const out = [];
|
|
638
|
+
const all = raw.trim().split('\n').slice(-limit);
|
|
639
|
+
for (const line of all) {
|
|
640
|
+
try {
|
|
641
|
+
const r = JSON.parse(line);
|
|
642
|
+
let detail = '';
|
|
643
|
+
if (r.detail && Object.keys(r.detail).length > 0) {
|
|
644
|
+
try {
|
|
645
|
+
detail = JSON.stringify(r.detail, null, 1);
|
|
646
|
+
} catch {
|
|
647
|
+
detail = '[无法序列化]';
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
out.push({ time: r.time, event: r.event, detail });
|
|
651
|
+
} catch {
|
|
652
|
+
/* 跳过损坏行 */
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
return out;
|
|
656
|
+
}
|
|
657
|
+
|
|
527
658
|
/**
|
|
528
659
|
* 未就绪时的工具调用:返回结构化错误,让 AI 知道该等还是该修环境。
|
|
529
660
|
*
|