@atlisp/mcp 1.8.31 → 1.8.32
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 +5 -1
- package/dist/atlisp-mcp.js +1705 -1344
- package/dist/cad-rothelper.dll +0 -0
- package/dist/cad-worker.js +33 -25
- package/dist/config.js +18 -0
- package/dist/handlers/dim-hatch-handlers.js +0 -24
- package/dist/handlers/file-handlers.js +1 -11
- package/dist/handlers/pipeline-handlers.js +135 -0
- package/dist/lisp-security.js +1 -3
- package/package.json +1 -1
package/dist/cad-rothelper.dll
CHANGED
|
Binary file
|
package/dist/cad-worker.js
CHANGED
|
@@ -616,6 +616,36 @@ async function executeWithRetry(fn, label) {
|
|
|
616
616
|
return { success: false, error: lastError?.message || '发送失败' };
|
|
617
617
|
}
|
|
618
618
|
|
|
619
|
+
async function executeLongCode(msg) {
|
|
620
|
+
const cacheEnabled = process.env.LONG_CODE_CACHE === '1';
|
|
621
|
+
const codeHash = computeCodeHash(msg.code, cacheEnabled);
|
|
622
|
+
log(`sendResult (${msg.code.length} chars, cache=${cacheEnabled}): ${msg.code.substring(0, 80)}...`);
|
|
623
|
+
return await executeWithRetry(async () => {
|
|
624
|
+
const r = await new Promise((resolve, reject) => {
|
|
625
|
+
cadSendWithResult({ code: msg.code, platform: msg.platform, encoding: msg.encoding || '', codeHash, cacheEnabled }, (e, r) => {
|
|
626
|
+
if (e) reject(e);
|
|
627
|
+
else resolve(r);
|
|
628
|
+
});
|
|
629
|
+
});
|
|
630
|
+
log(`sendResult result: ${JSON.stringify(r)}`);
|
|
631
|
+
return r;
|
|
632
|
+
}, 'sendResult');
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
async function executeShortCode(msg) {
|
|
636
|
+
log(`send: ${msg.code}`);
|
|
637
|
+
return await executeWithRetry(async () => {
|
|
638
|
+
const r = await new Promise((resolve, reject) => {
|
|
639
|
+
cadSend({ code: msg.code, platform: msg.platform }, (e, r) => {
|
|
640
|
+
if (e) reject(e);
|
|
641
|
+
else resolve(r);
|
|
642
|
+
});
|
|
643
|
+
});
|
|
644
|
+
log(`send result: ${JSON.stringify(r)}`);
|
|
645
|
+
return r;
|
|
646
|
+
}, 'send');
|
|
647
|
+
}
|
|
648
|
+
|
|
619
649
|
async function executeLisp(msg, withResult) {
|
|
620
650
|
if (!msg.code.endsWith('\n')) {
|
|
621
651
|
msg.code += '\n';
|
|
@@ -636,32 +666,10 @@ async function executeLisp(msg, withResult) {
|
|
|
636
666
|
}
|
|
637
667
|
|
|
638
668
|
if (withResult || msg.code.length > 255) {
|
|
639
|
-
|
|
640
|
-
const codeHash = computeCodeHash(msg.code, cacheEnabled);
|
|
641
|
-
log(`sendResult (${msg.code.length} chars, cache=${cacheEnabled}): ${msg.code.substring(0, 80)}...`);
|
|
642
|
-
return await executeWithRetry(async () => {
|
|
643
|
-
const r = await new Promise((resolve, reject) => {
|
|
644
|
-
cadSendWithResult({ code: msg.code, platform: msg.platform, encoding: msg.encoding || '', codeHash, cacheEnabled }, (e, r) => {
|
|
645
|
-
if (e) reject(e);
|
|
646
|
-
else resolve(r);
|
|
647
|
-
});
|
|
648
|
-
});
|
|
649
|
-
log(`sendResult result: ${JSON.stringify(r)}`);
|
|
650
|
-
return r;
|
|
651
|
-
}, 'sendResult');
|
|
669
|
+
return await executeLongCode(msg);
|
|
652
670
|
}
|
|
653
671
|
|
|
654
|
-
|
|
655
|
-
return await executeWithRetry(async () => {
|
|
656
|
-
const r = await new Promise((resolve, reject) => {
|
|
657
|
-
cadSend({ code: msg.code, platform: msg.platform }, (e, r) => {
|
|
658
|
-
if (e) reject(e);
|
|
659
|
-
else resolve(r);
|
|
660
|
-
});
|
|
661
|
-
});
|
|
662
|
-
log(`send result: ${JSON.stringify(r)}`);
|
|
663
|
-
return r;
|
|
664
|
-
}, 'send');
|
|
672
|
+
return await executeShortCode(msg);
|
|
665
673
|
}
|
|
666
674
|
|
|
667
675
|
const MESSAGE_HANDLERS = {
|
|
@@ -756,4 +764,4 @@ export async function handleMessage(msg) {
|
|
|
756
764
|
}
|
|
757
765
|
|
|
758
766
|
// Exported for testing
|
|
759
|
-
export { validateCode, computeCodeHash, getCachedLisp, setCachedLisp, MESSAGE_HANDLERS, executeLisp };
|
|
767
|
+
export { validateCode, computeCodeHash, getCachedLisp, setCachedLisp, MESSAGE_HANDLERS, executeLisp, executeLongCode, executeShortCode };
|
package/dist/config.js
CHANGED
|
@@ -40,6 +40,12 @@ const envSchema = z.object({
|
|
|
40
40
|
NO_LINT: z.coerce.boolean().optional().default(false),
|
|
41
41
|
SECURITY_LEVEL: z.string().optional().default('strict'),
|
|
42
42
|
CLIENT_ID: z.string().optional().default(''),
|
|
43
|
+
MAX_ENTITY_LIMIT: z.coerce.number().optional().default(20000),
|
|
44
|
+
READ_BATCH_WINDOW: z.coerce.number().optional().default(50),
|
|
45
|
+
READ_BATCH_SIZE: z.coerce.number().optional().default(10),
|
|
46
|
+
AUTO_CONNECT: z.coerce.boolean().optional().default(false),
|
|
47
|
+
AUTO_LAUNCH: z.coerce.boolean().optional().default(true),
|
|
48
|
+
LAUNCH_TIMEOUT: z.coerce.number().optional().default(30000),
|
|
43
49
|
});
|
|
44
50
|
|
|
45
51
|
const configSchema = z.object({
|
|
@@ -85,6 +91,12 @@ const configSchema = z.object({
|
|
|
85
91
|
noLint: z.boolean().optional(),
|
|
86
92
|
securityLevel: z.enum(['strict', 'standard', 'relaxed']).optional(),
|
|
87
93
|
clientId: z.string().optional(),
|
|
94
|
+
maxEntityLimit: z.number().optional(),
|
|
95
|
+
readBatchWindow: z.number().optional(),
|
|
96
|
+
readBatchSize: z.number().optional(),
|
|
97
|
+
autoConnect: z.boolean().optional(),
|
|
98
|
+
autoLaunch: z.boolean().optional(),
|
|
99
|
+
launchTimeout: z.number().optional(),
|
|
88
100
|
});
|
|
89
101
|
|
|
90
102
|
function parseArgs() {
|
|
@@ -206,6 +218,12 @@ const config = {
|
|
|
206
218
|
noLint: fileConfig?.noLint ?? cliArgs.noLint ?? env.NO_LINT,
|
|
207
219
|
securityLevel: fileConfig?.securityLevel ?? cliArgs.securityLevel ?? (env.SECURITY_LEVEL || 'strict'),
|
|
208
220
|
clientId: fileConfig?.clientId ?? (env.CLIENT_ID || ''),
|
|
221
|
+
maxEntityLimit: fileConfig?.maxEntityLimit ?? env.MAX_ENTITY_LIMIT,
|
|
222
|
+
readBatchWindow: fileConfig?.readBatchWindow ?? env.READ_BATCH_WINDOW,
|
|
223
|
+
readBatchSize: fileConfig?.readBatchSize ?? env.READ_BATCH_SIZE,
|
|
224
|
+
autoConnect: fileConfig?.autoConnect ?? env.AUTO_CONNECT,
|
|
225
|
+
autoLaunch: fileConfig?.autoLaunch ?? env.AUTO_LAUNCH,
|
|
226
|
+
launchTimeout: fileConfig?.launchTimeout ?? env.LAUNCH_TIMEOUT,
|
|
209
227
|
};
|
|
210
228
|
|
|
211
229
|
if (config.lintConfigPath) process.env.LINT_CONFIG_PATH = config.lintConfigPath;
|
|
@@ -314,27 +314,3 @@ export async function exportDwf(outputPath) {
|
|
|
314
314
|
return { content: [{ type: 'text', text: `错误: ${e.message}` }], isError: true };
|
|
315
315
|
}
|
|
316
316
|
}
|
|
317
|
-
|
|
318
|
-
export async function batchSetLayer(handles, layerName) {
|
|
319
|
-
if (!cad.connected) { await cad.connect(); }
|
|
320
|
-
if (!cad.connected) return { content: [{ type: 'text', text: '未连接 CAD' }], isError: true };
|
|
321
|
-
|
|
322
|
-
if (!Array.isArray(handles) || handles.length === 0) {
|
|
323
|
-
return { content: [{ type: 'text', text: '句柄列表为空' }], isError: true };
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
const handlesStr = handles.map(h => `"${escapeLispString(h)}"`).join(' ');
|
|
327
|
-
const code = `(progn
|
|
328
|
-
(vl-load-com)
|
|
329
|
-
(setq ss (ssadd))
|
|
330
|
-
${handlesStr.split('\n').map(h => `(ssadd (handent ${h}) ss)`).join('\n')}
|
|
331
|
-
(command "._CHPROP" ss "" "LA" "${escapeLispString(layerName)}" "")
|
|
332
|
-
(strcat "Updated " (itoa ${handles.length}) " entities")
|
|
333
|
-
)`;
|
|
334
|
-
try {
|
|
335
|
-
const result = await cad.sendCommandWithResult(code);
|
|
336
|
-
return { content: [{ type: 'text', text: result || `已批量设置 ${handles.length} 个实体图层为 ${layerName}` }] };
|
|
337
|
-
} catch (e) {
|
|
338
|
-
return { content: [{ type: 'text', text: `错误: ${e.message}` }], isError: true };
|
|
339
|
-
}
|
|
340
|
-
}
|
|
@@ -1,15 +1,5 @@
|
|
|
1
1
|
import { cad } from '../cad.js';
|
|
2
|
-
import { escapeLispString } from '../handler-utils.js';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
|
|
5
|
-
function validateFilePath(filePath) {
|
|
6
|
-
const normalized = path.normalize(filePath);
|
|
7
|
-
if (normalized.includes('..')) {
|
|
8
|
-
throw new Error(`路径不允许包含 "..": ${filePath}`);
|
|
9
|
-
}
|
|
10
|
-
const resolved = path.resolve(normalized);
|
|
11
|
-
return resolved;
|
|
12
|
-
}
|
|
2
|
+
import { escapeLispString, validateFilePath } from '../handler-utils.js';
|
|
13
3
|
|
|
14
4
|
export async function openDwg(filePath) {
|
|
15
5
|
if (!cad.connected) { await cad.connect(); }
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import { handleToolCall } from '../mcp-tools.js';
|
|
5
|
+
import { mcpSuccess, mcpError, ensureCadConnected } from '../handler-utils.js';
|
|
6
|
+
import { log, error as logError } from '../logger.js';
|
|
7
|
+
|
|
8
|
+
const PIPELINES_DIR = path.join(os.homedir(), '.atlisp', 'pipelines');
|
|
9
|
+
|
|
10
|
+
function ensureDir() {
|
|
11
|
+
if (!fs.existsSync(PIPELINES_DIR)) {
|
|
12
|
+
fs.mkdirSync(PIPELINES_DIR, { recursive: true });
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function pipelinePath(name) {
|
|
17
|
+
return path.join(PIPELINES_DIR, `${name}.json`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function pipelineExecute({ steps }) {
|
|
21
|
+
try {
|
|
22
|
+
if (!steps || !Array.isArray(steps) || steps.length === 0) {
|
|
23
|
+
return mcpError('steps 必须是非空数组');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
log(`pipeline_execute: starting ${steps.length} steps`);
|
|
27
|
+
const results = [];
|
|
28
|
+
|
|
29
|
+
for (let i = 0; i < steps.length; i++) {
|
|
30
|
+
const step = steps[i];
|
|
31
|
+
log(`pipeline_execute: step ${i + 1}/${steps.length} -> ${step.tool}`);
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const result = await handleToolCall(step.tool, step.args || {});
|
|
35
|
+
results.push({ step: i, tool: step.tool, success: !result.isError, result });
|
|
36
|
+
} catch (e) {
|
|
37
|
+
logError(`pipeline_execute: step ${i} threw: ${e.message}`);
|
|
38
|
+
results.push({ step: i, tool: step.tool, success: false, error: e.message });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return mcpSuccess(JSON.stringify(results));
|
|
43
|
+
} catch (e) {
|
|
44
|
+
logError(`pipeline_execute: outer error: ${e.message}`);
|
|
45
|
+
return mcpError(`管道执行错误: ${e.message}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function pipelineSave({ name, steps }) {
|
|
50
|
+
if (!name || typeof name !== 'string' || !name.trim()) {
|
|
51
|
+
return mcpError('管道名称不能为空');
|
|
52
|
+
}
|
|
53
|
+
if (!steps || !Array.isArray(steps) || steps.length === 0) {
|
|
54
|
+
return mcpError('管道步骤不能为空');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
ensureDir();
|
|
59
|
+
const safeName = name.trim().replace(/[<>:"/\\|?*]/g, '_');
|
|
60
|
+
const data = { name: safeName, steps, createdAt: new Date().toISOString() };
|
|
61
|
+
fs.writeFileSync(pipelinePath(safeName), JSON.stringify(data, null, 2), 'utf-8');
|
|
62
|
+
log(`pipeline_save: saved "${safeName}" (${steps.length} steps)`);
|
|
63
|
+
return mcpSuccess(`管道 "${safeName}" 已保存 (${steps.length} 步)`);
|
|
64
|
+
} catch (e) {
|
|
65
|
+
logError(`pipeline_save error: ${e.message}`);
|
|
66
|
+
return mcpError(`保存管道失败: ${e.message}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function pipelineList() {
|
|
71
|
+
try {
|
|
72
|
+
ensureDir();
|
|
73
|
+
const files = fs.readdirSync(PIPELINES_DIR).filter(f => f.endsWith('.json'));
|
|
74
|
+
const pipelines = files.map(f => {
|
|
75
|
+
try {
|
|
76
|
+
const data = JSON.parse(fs.readFileSync(path.join(PIPELINES_DIR, f), 'utf-8'));
|
|
77
|
+
return { name: data.name, steps: data.steps?.length || 0, createdAt: data.createdAt };
|
|
78
|
+
} catch {
|
|
79
|
+
return { name: f.replace(/\.json$/, ''), steps: 0 };
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
if (pipelines.length === 0) {
|
|
83
|
+
return mcpSuccess('暂无已保存的管道。使用 pipeline_save 保存管道定义。');
|
|
84
|
+
}
|
|
85
|
+
const text = pipelines.map(p => ` ${p.name} (${p.steps} 步)`).join('\n');
|
|
86
|
+
return mcpSuccess(`已保存的管道 (${pipelines.length}):\n${text}`);
|
|
87
|
+
} catch (e) {
|
|
88
|
+
logError(`pipeline_list error: ${e.message}`);
|
|
89
|
+
return mcpError(`列出管道失败: ${e.message}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export async function pipelineRun({ name }) {
|
|
94
|
+
if (!name || typeof name !== 'string' || !name.trim()) {
|
|
95
|
+
return mcpError('管道名称不能为空');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
ensureDir();
|
|
100
|
+
const safeName = name.trim().replace(/[<>:"/\\|?*]/g, '_');
|
|
101
|
+
const filePath = pipelinePath(safeName);
|
|
102
|
+
if (!fs.existsSync(filePath)) {
|
|
103
|
+
return mcpError(`管道 "${safeName}" 不存在。使用 pipeline_list 查看可用管道。`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const data = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
107
|
+
log(`pipeline_run: executing "${safeName}" (${data.steps.length} steps)`);
|
|
108
|
+
|
|
109
|
+
return await pipelineExecute({ steps: data.steps });
|
|
110
|
+
} catch (e) {
|
|
111
|
+
logError(`pipeline_run error: ${e.message}`);
|
|
112
|
+
return mcpError(`运行管道失败: ${e.message}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export async function pipelineDelete({ name }) {
|
|
117
|
+
if (!name || typeof name !== 'string' || !name.trim()) {
|
|
118
|
+
return mcpError('管道名称不能为空');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
ensureDir();
|
|
123
|
+
const safeName = name.trim().replace(/[<>:"/\\|?*]/g, '_');
|
|
124
|
+
const filePath = pipelinePath(safeName);
|
|
125
|
+
if (!fs.existsSync(filePath)) {
|
|
126
|
+
return mcpError(`管道 "${safeName}" 不存在。使用 pipeline_list 查看可用管道。`);
|
|
127
|
+
}
|
|
128
|
+
fs.unlinkSync(filePath);
|
|
129
|
+
log(`pipeline_delete: deleted "${safeName}"`);
|
|
130
|
+
return mcpSuccess(`管道 "${safeName}" 已删除`);
|
|
131
|
+
} catch (e) {
|
|
132
|
+
logError(`pipeline_delete error: ${e.message}`);
|
|
133
|
+
return mcpError(`删除管道失败: ${e.message}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
package/dist/lisp-security.js
CHANGED
|
@@ -50,7 +50,7 @@ export function refreshConfig() {
|
|
|
50
50
|
|
|
51
51
|
try {
|
|
52
52
|
onConfigChange(() => { _cachedLevel = null; });
|
|
53
|
-
} catch (
|
|
53
|
+
} catch (e) { logError('[lisp-security]', e?.message || e); }
|
|
54
54
|
|
|
55
55
|
function resolveSeverity(rule) {
|
|
56
56
|
if (_cachedLevel) return _cachedLevel;
|
|
@@ -117,8 +117,6 @@ export function checkParens(code) {
|
|
|
117
117
|
return { valid: true };
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
// === Session Sandbox (merged from lisp-sandbox.js) ===
|
|
121
|
-
|
|
122
120
|
export const RULES = SECURITY_RULES;
|
|
123
121
|
|
|
124
122
|
const sessionAllowlist = new Map();
|