zwcad-mcp 0.1.0 → 0.2.0
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 +16 -4
- package/bridge.js +46 -72
- package/lib/proxy.js +564 -0
- package/package.json +3 -1
- package/tools.json +1323 -0
package/README.md
CHANGED
|
@@ -29,26 +29,38 @@ MCP 客户端 ──stdio──► bridge.js ──spawn──► python -m zwca
|
|
|
29
29
|
|
|
30
30
|
## 快速开始
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
无需安装,客户端会自动拉起。把下面整段复制进 MCP 客户端的配置文件即可。
|
|
33
|
+
|
|
34
|
+
### 方案一:国内镜像(推荐国内用户)
|
|
33
35
|
|
|
34
36
|
```json
|
|
35
37
|
{
|
|
36
38
|
"mcpServers": {
|
|
37
39
|
"zwcad": {
|
|
38
40
|
"command": "npx",
|
|
39
|
-
"args": ["-y", "zwcad-mcp"],
|
|
41
|
+
"args": ["--registry=https://registry.npmmirror.com", "-y", "zwcad-mcp"],
|
|
40
42
|
"env": { "PYTHONUTF8": "1" }
|
|
41
43
|
}
|
|
42
44
|
}
|
|
43
45
|
}
|
|
44
46
|
```
|
|
45
47
|
|
|
46
|
-
|
|
48
|
+
### 方案二:npm 官方源
|
|
47
49
|
|
|
48
50
|
```json
|
|
49
|
-
|
|
51
|
+
{
|
|
52
|
+
"mcpServers": {
|
|
53
|
+
"zwcad": {
|
|
54
|
+
"command": "npx",
|
|
55
|
+
"args": ["-y", "zwcad-mcp"],
|
|
56
|
+
"env": { "PYTHONUTF8": "1" }
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
50
60
|
```
|
|
51
61
|
|
|
62
|
+
两者只有 `args` 里的 `--registry` 参数不同,功能完全一致。国内网络用方案一可显著加快首次拉取。
|
|
63
|
+
|
|
52
64
|
各平台的配置文件位置见 [`connector/`](connector/) 目录。
|
|
53
65
|
|
|
54
66
|
## 验证接入
|
package/bridge.js
CHANGED
|
@@ -91,12 +91,16 @@ function fatal(err) {
|
|
|
91
91
|
|
|
92
92
|
/**
|
|
93
93
|
* 同步执行并捕获 stdout,失败返回 null(用于探测,不抛异常)。
|
|
94
|
+
*
|
|
95
|
+
* 超时给得比较宽松:Windows 上首次启动 python.exe 可能被杀毒软件扫描,
|
|
96
|
+
* 实测出现过同一路径偶发 20 秒超时被误判为「不可用」的情况。
|
|
97
|
+
* 探测失败会直接导致挑不到可用解释器,代价远大于多等几秒。
|
|
94
98
|
*/
|
|
95
99
|
function tryRun(exe, args, options) {
|
|
96
100
|
try {
|
|
97
101
|
const r = spawnSync(exe, args, {
|
|
98
102
|
encoding: 'utf8',
|
|
99
|
-
timeout:
|
|
103
|
+
timeout: 60000,
|
|
100
104
|
windowsHide: true,
|
|
101
105
|
...options,
|
|
102
106
|
});
|
|
@@ -136,12 +140,23 @@ function runStreaming(exe, args, label) {
|
|
|
136
140
|
|
|
137
141
|
/**
|
|
138
142
|
* 返回 Python 主次版本号,例如 [3, 13];不可用返回 null。
|
|
143
|
+
*
|
|
144
|
+
* 失败时重试一次:Windows 上 python.exe 首次启动偶尔会异常缓慢
|
|
145
|
+
* (杀毒软件扫描、磁盘繁忙),直接判死会让本可用的解释器被跳过。
|
|
139
146
|
*/
|
|
140
147
|
function pythonVersion(exe) {
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
148
|
+
const probe = () => {
|
|
149
|
+
const out = tryRun(exe, ['-c', 'import sys;print("%d.%d"%sys.version_info[:2])']);
|
|
150
|
+
if (!out) return null;
|
|
151
|
+
const m = /^(\d+)\.(\d+)$/.exec(out);
|
|
152
|
+
return m ? [Number(m[1]), Number(m[2])] : null;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const first = probe();
|
|
156
|
+
if (first) return first;
|
|
157
|
+
|
|
158
|
+
// 一次失败不足以断定不可用,再试一次
|
|
159
|
+
return probe();
|
|
145
160
|
}
|
|
146
161
|
|
|
147
162
|
function versionAtLeast(ver, min) {
|
|
@@ -398,78 +413,38 @@ async function prepareRuntime() {
|
|
|
398
413
|
}
|
|
399
414
|
|
|
400
415
|
/* ------------------------------------------------------------------ *
|
|
401
|
-
*
|
|
416
|
+
* 启动(代理模式)
|
|
402
417
|
* ------------------------------------------------------------------ */
|
|
403
418
|
|
|
404
419
|
/**
|
|
405
|
-
*
|
|
420
|
+
* 以代理模式启动:立即完成 MCP 握手,Python 运行时在后台准备。
|
|
406
421
|
*
|
|
407
|
-
*
|
|
408
|
-
*
|
|
409
|
-
* - hadOutput:退出前是否已在 stdout 上产生过 MCP 消息
|
|
422
|
+
* 这样首次使用(需安装约 90 个 Python 依赖,实测 40~70 秒)也不会
|
|
423
|
+
* 触发客户端的 initialize 超时(通常 30 秒)。
|
|
410
424
|
*
|
|
411
|
-
*
|
|
425
|
+
* 具体协议处理见 lib/proxy.js。
|
|
412
426
|
*/
|
|
413
|
-
function
|
|
414
|
-
const
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
log('启动 MCP Server…');
|
|
426
|
-
const startedAt = Date.now();
|
|
427
|
-
let hadOutput = false;
|
|
428
|
-
|
|
429
|
-
const child = spawn(venvPython, ['-m', ENTRY_MODULE], {
|
|
430
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
431
|
-
env,
|
|
432
|
-
windowsHide: true,
|
|
427
|
+
function startWithProxy() {
|
|
428
|
+
const { createProxy } = require('./lib/proxy');
|
|
429
|
+
createProxy({
|
|
430
|
+
cwd: CONNECTOR_DIR,
|
|
431
|
+
version: pkgVersion(),
|
|
432
|
+
childArgs: ['-m', ENTRY_MODULE],
|
|
433
|
+
log,
|
|
434
|
+
ensureRuntime: async () => {
|
|
435
|
+
// 每次都要重新探测:用户可能刚装了 Python 或修好了环境
|
|
436
|
+
const py = await prepareRuntime();
|
|
437
|
+
return py;
|
|
438
|
+
},
|
|
433
439
|
});
|
|
440
|
+
}
|
|
434
441
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
process.stdin.pipe(child.stdin);
|
|
443
|
-
|
|
444
|
-
// 客户端关闭 stdin 时,优雅关闭子进程
|
|
445
|
-
process.stdin.on('end', () => {
|
|
446
|
-
try {
|
|
447
|
-
child.stdin.end();
|
|
448
|
-
} catch {
|
|
449
|
-
/* 子进程可能已退出 */
|
|
450
|
-
}
|
|
451
|
-
});
|
|
452
|
-
|
|
453
|
-
// 管道错误不应导致启动器崩溃
|
|
454
|
-
child.stdin.on('error', () => {});
|
|
455
|
-
child.stdout.on('error', () => {});
|
|
456
|
-
process.stdout.on('error', () => {});
|
|
457
|
-
|
|
458
|
-
return new Promise((resolve) => {
|
|
459
|
-
child.on('error', (e) => {
|
|
460
|
-
log('无法启动 MCP Server:' + e.message);
|
|
461
|
-
resolve({ code: 1, fastExit: true, hadOutput: false });
|
|
462
|
-
});
|
|
463
|
-
|
|
464
|
-
child.on('close', (code, signal) => {
|
|
465
|
-
if (signal) log('MCP Server 被信号终止:' + signal);
|
|
466
|
-
resolve({
|
|
467
|
-
code: code == null ? 1 : code,
|
|
468
|
-
fastExit: Date.now() - startedAt < 15000,
|
|
469
|
-
hadOutput,
|
|
470
|
-
});
|
|
471
|
-
});
|
|
472
|
-
});
|
|
442
|
+
function pkgVersion() {
|
|
443
|
+
try {
|
|
444
|
+
return JSON.parse(fs.readFileSync(path.join(CONNECTOR_DIR, 'package.json'), 'utf8')).version;
|
|
445
|
+
} catch {
|
|
446
|
+
return '0.0.0';
|
|
447
|
+
}
|
|
473
448
|
}
|
|
474
449
|
|
|
475
450
|
/* ------------------------------------------------------------------ *
|
|
@@ -535,9 +510,8 @@ async function main() {
|
|
|
535
510
|
return;
|
|
536
511
|
}
|
|
537
512
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
process.exit(result.code);
|
|
513
|
+
// 正常启动:走代理模式,先握手再后台准备运行时
|
|
514
|
+
startWithProxy();
|
|
541
515
|
}
|
|
542
516
|
|
|
543
517
|
main().catch(fatal);
|