@atlisp/mcp 1.8.18 → 1.8.20
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/dist/atlisp-mcp.js +3969 -2535
- package/dist/cad-worker.js +53 -19
- package/dist/config.js +41 -2
- package/dist/handlers/batch-transaction-handlers.js +70 -0
- package/dist/handlers/cad-handlers.js +66 -91
- package/dist/handlers/draw-handlers.js +118 -165
- package/dist/handlers/entity-handlers.js +61 -111
- package/dist/handlers/layer-handlers.js +70 -98
- package/dist/handlers/lint-handlers.js +64 -0
- package/dist/handlers/resource-defs.js +2 -0
- package/dist/handlers/resource-handlers.js +1288 -26
- package/dist/lisp-security.js +0 -17
- package/dist/pipelines/analyze-and-report.json +16 -0
- package/dist/pipelines/batch-export-pdfs.json +11 -0
- package/dist/pipelines/batch-layer-move.json +19 -0
- package/dist/pipelines/export-current-drawing.json +18 -0
- package/dist/pipelines/purge-and-save.json +16 -0
- package/dist/prompts/definitions/analyze-drawing.json +6 -0
- package/dist/prompts/definitions/batch-draw-lines.json +14 -0
- package/dist/prompts/definitions/coding-conventions.json +11 -0
- package/dist/prompts/definitions/color-convert.json +30 -0
- package/dist/prompts/definitions/curve-analysis.json +26 -0
- package/dist/prompts/definitions/excel-report.json +9 -0
- package/dist/prompts/definitions/export-entities.json +17 -0
- package/dist/prompts/definitions/generate-dimension.json +8 -0
- package/dist/prompts/definitions/geometry-calc.json +9 -0
- package/dist/prompts/definitions/json-exchange.json +22 -0
- package/dist/prompts/definitions/manage-blocks.json +28 -0
- package/dist/prompts/definitions/manage-layers.json +35 -0
- package/dist/prompts/definitions/pickset-filter.json +31 -0
- package/dist/prompts/definitions/string-process.json +40 -0
- package/dist/prompts/definitions/test-autolisp.json +33 -0
- package/dist/prompts/definitions/text-process.json +26 -0
- package/dist/prompts/definitions/workflow-3d-model.json +24 -0
- package/dist/prompts/definitions/workflow-batch-operations.json +24 -0
- package/dist/prompts/definitions/workflow-block-manage.json +32 -0
- package/dist/prompts/definitions/workflow-drawing-export.json +23 -0
- package/dist/prompts/definitions/workflow-entity-analysis.json +23 -0
- package/dist/prompts/definitions/workflow-layer-management.json +38 -0
- package/dist/prompts/definitions/workflow-plot-publish.json +28 -0
- package/dist/prompts/definitions/workflow-sheetset.json +29 -0
- package/dist/prompts/definitions/workflow-text-process.json +29 -0
- package/dist/prompts/definitions/workflow-xref-manage.json +28 -0
- package/package.json +7 -3
package/dist/cad-worker.js
CHANGED
|
@@ -13,19 +13,30 @@ const BUSY_DELAY = parseInt(process.env.BUSY_DELAY || '500', 10);
|
|
|
13
13
|
const LISP_CACHE = new Map();
|
|
14
14
|
const LISP_CACHE_MAX = parseInt(process.env.LISP_CACHE_MAX || '500', 10);
|
|
15
15
|
|
|
16
|
+
const CACHE_TTL = parseInt(process.env.LISP_CACHE_TTL || '300000', 10);
|
|
17
|
+
|
|
16
18
|
function getCachedLisp(code) {
|
|
17
19
|
const hash = crypto.createHash('sha256').update(code).digest('hex');
|
|
18
|
-
|
|
19
|
-
return null;
|
|
20
|
+
const entry = LISP_CACHE.get(hash);
|
|
21
|
+
if (!entry) return null;
|
|
22
|
+
if (Date.now() - entry.ts > CACHE_TTL) {
|
|
23
|
+
LISP_CACHE.delete(hash);
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
entry.ts = Date.now();
|
|
27
|
+
return entry.compiled;
|
|
20
28
|
}
|
|
21
29
|
|
|
22
30
|
function setCachedLisp(code, compiled) {
|
|
23
31
|
const hash = crypto.createHash('sha256').update(code).digest('hex');
|
|
24
32
|
if (LISP_CACHE.size >= LISP_CACHE_MAX) {
|
|
25
|
-
|
|
26
|
-
LISP_CACHE
|
|
33
|
+
let oldest = null;
|
|
34
|
+
for (const [k, v] of LISP_CACHE) {
|
|
35
|
+
if (!oldest || v.ts < oldest.ts) oldest = { k, ts: v.ts };
|
|
36
|
+
}
|
|
37
|
+
if (oldest) LISP_CACHE.delete(oldest.k);
|
|
27
38
|
}
|
|
28
|
-
LISP_CACHE.set(hash, compiled);
|
|
39
|
+
LISP_CACHE.set(hash, { compiled, ts: Date.now() });
|
|
29
40
|
return compiled;
|
|
30
41
|
}
|
|
31
42
|
|
|
@@ -151,16 +162,34 @@ const cadSendWithResult = edge.func(function() {/*
|
|
|
151
162
|
string code = d.code.ToString();
|
|
152
163
|
string platform = d.platform.ToString();
|
|
153
164
|
string encoding = d.encoding != null ? d.encoding.ToString() : "";
|
|
165
|
+
string codeHash = d.codeHash != null ? d.codeHash.ToString() : "";
|
|
166
|
+
bool cacheEnabled = d.cacheEnabled != null && d.cacheEnabled.ToString() == "True";
|
|
167
|
+
bool hasCacheKey = cacheEnabled && !string.IsNullOrEmpty(codeHash);
|
|
154
168
|
string progId = platform == "BricsCAD" ? "BricscadApp.AcadApplication" : platform + ".Application";
|
|
155
169
|
string tempDir = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "@lisp", "tmp");
|
|
156
170
|
System.IO.Directory.CreateDirectory(tempDir);
|
|
157
171
|
|
|
172
|
+
// 结果文件始终使用 GUID 命名(每次调用唯一)
|
|
158
173
|
tempFile = System.IO.Path.Combine(tempDir, "atlisp_result_" + System.Guid.NewGuid().ToString("N") + ".txt");
|
|
159
|
-
lispFile = System.IO.Path.Combine(tempDir, "atlisp_code_" + System.Guid.NewGuid().ToString("N") + ".lsp");
|
|
160
|
-
userCodeFile = System.IO.Path.Combine(tempDir, "atlisp_user_" + System.Guid.NewGuid().ToString("N") + ".lsp");
|
|
161
174
|
|
|
162
|
-
//
|
|
163
|
-
|
|
175
|
+
// 代码文件:启用缓存时使用 hash 命名,否则使用 GUID 命名
|
|
176
|
+
string fileKey = hasCacheKey ? codeHash : System.Guid.NewGuid().ToString("N");
|
|
177
|
+
userCodeFile = System.IO.Path.Combine(tempDir, "atlisp_user_" + fileKey + ".lsp");
|
|
178
|
+
lispFile = System.IO.Path.Combine(tempDir, "atlisp_code_" + fileKey + ".lsp");
|
|
179
|
+
|
|
180
|
+
bool useCachedUserCode = false;
|
|
181
|
+
if (hasCacheKey) {
|
|
182
|
+
if (System.IO.File.Exists(userCodeFile)) {
|
|
183
|
+
var userFileInfo = new System.IO.FileInfo(userCodeFile);
|
|
184
|
+
if ((DateTime.Now - userFileInfo.LastWriteTime).TotalHours < 1) {
|
|
185
|
+
useCachedUserCode = true;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (!useCachedUserCode) {
|
|
191
|
+
System.IO.File.WriteAllText(userCodeFile, code, System.Text.Encoding.GetEncoding(System.Globalization.CultureInfo.CurrentCulture.TextInfo.ANSICodePage));
|
|
192
|
+
}
|
|
164
193
|
|
|
165
194
|
string lispCode = "(progn"
|
|
166
195
|
+ "(setq f(open \"" + tempFile.Replace("\\", "\\\\") + "\" \"w\"))"
|
|
@@ -171,7 +200,6 @@ string encoding = d.encoding != null ? d.encoding.ToString() : "";
|
|
|
171
200
|
+ "(close f)"
|
|
172
201
|
+ "(princ))";
|
|
173
202
|
|
|
174
|
-
// 使用系统 ANSI 编码以兼容 GstarCAD / ZWCAD
|
|
175
203
|
System.IO.File.WriteAllText(lispFile, lispCode, System.Text.Encoding.GetEncoding(System.Globalization.CultureInfo.CurrentCulture.TextInfo.ANSICodePage));
|
|
176
204
|
|
|
177
205
|
dynamic cad = System.Runtime.InteropServices.Marshal.GetActiveObject(progId);
|
|
@@ -193,11 +221,9 @@ string encoding = d.encoding != null ? d.encoding.ToString() : "";
|
|
|
193
221
|
byte[] bytes = System.IO.File.ReadAllBytes(tempFile);
|
|
194
222
|
System.Text.Encoding enc;
|
|
195
223
|
|
|
196
|
-
// 优先使用指定的编码
|
|
197
224
|
if (!string.IsNullOrEmpty(encoding)) {
|
|
198
225
|
enc = System.Text.Encoding.GetEncoding(encoding);
|
|
199
226
|
} else {
|
|
200
|
-
// 自动检测编码:先尝试 UTF-8,如果失败则使用 GBK
|
|
201
227
|
try {
|
|
202
228
|
string utf8Result = System.Text.Encoding.UTF8.GetString(bytes);
|
|
203
229
|
if (!utf8Result.Contains("\ufffd")) {
|
|
@@ -216,14 +242,13 @@ string encoding = d.encoding != null ? d.encoding.ToString() : "";
|
|
|
216
242
|
|
|
217
243
|
result = enc.GetString(bytes).Trim();
|
|
218
244
|
|
|
219
|
-
// 检查是否有错误前缀
|
|
220
245
|
if (result.StartsWith("ERROR:")) {
|
|
221
246
|
return new { success = false, error = result.Substring(6).Trim() };
|
|
222
247
|
}
|
|
223
248
|
|
|
224
249
|
try { System.IO.File.Delete(tempFile); } catch {}
|
|
225
250
|
try { System.IO.File.Delete(lispFile); } catch {}
|
|
226
|
-
try { System.IO.File.Delete(userCodeFile); } catch {}
|
|
251
|
+
if (!hasCacheKey) { try { if (System.IO.File.Exists(userCodeFile)) System.IO.File.Delete(userCodeFile); } catch {} }
|
|
227
252
|
break;
|
|
228
253
|
} catch (System.IO.IOException) {
|
|
229
254
|
System.Threading.Thread.Sleep(200);
|
|
@@ -471,8 +496,6 @@ async function waitForIdle(platform) {
|
|
|
471
496
|
return false;
|
|
472
497
|
}
|
|
473
498
|
|
|
474
|
-
export { checkParens } from './lisp-security.js';
|
|
475
|
-
|
|
476
499
|
async function connectToPlatform(targetPlatform) {
|
|
477
500
|
return new Promise((resolve, reject) => {
|
|
478
501
|
cadConnectByName(targetPlatform, (e, r) => {
|
|
@@ -482,6 +505,13 @@ async function connectToPlatform(targetPlatform) {
|
|
|
482
505
|
});
|
|
483
506
|
}
|
|
484
507
|
|
|
508
|
+
function computeCodeHash(code, cacheEnabled) {
|
|
509
|
+
if (cacheEnabled) {
|
|
510
|
+
return crypto.createHash('sha256').update(code).digest('hex');
|
|
511
|
+
}
|
|
512
|
+
return '';
|
|
513
|
+
}
|
|
514
|
+
|
|
485
515
|
async function handleMessage(msg) {
|
|
486
516
|
if (msg.type === 'ping') {
|
|
487
517
|
return { pong: true };
|
|
@@ -554,9 +584,11 @@ async function handleMessage(msg) {
|
|
|
554
584
|
return { success: false, error: `安全限制: ${injectionCheck.error}` };
|
|
555
585
|
}
|
|
556
586
|
if (msg.code.length > 255) {
|
|
557
|
-
|
|
587
|
+
const cacheEnabled = process.env.LONG_CODE_CACHE === '1';
|
|
588
|
+
const codeHash = computeCodeHash(msg.code, cacheEnabled);
|
|
589
|
+
log(`send (long code ${msg.code.length} chars, fallback to sendResult, cache=${cacheEnabled}): ${msg.code.substring(0, 80)}...`);
|
|
558
590
|
const r = await new Promise((resolve, reject) => {
|
|
559
|
-
cadSendWithResult({ code: msg.code, platform: msg.platform || 'AutoCAD', encoding: '' }, (e, r) => {
|
|
591
|
+
cadSendWithResult({ code: msg.code, platform: msg.platform || 'AutoCAD', encoding: '', codeHash, cacheEnabled }, (e, r) => {
|
|
560
592
|
if (e) reject(e);
|
|
561
593
|
else resolve(r);
|
|
562
594
|
});
|
|
@@ -606,6 +638,8 @@ async function handleMessage(msg) {
|
|
|
606
638
|
if (!injectionCheck.valid) {
|
|
607
639
|
return { success: false, error: `安全限制: ${injectionCheck.error}` };
|
|
608
640
|
}
|
|
641
|
+
const cacheEnabled = process.env.LONG_CODE_CACHE === '1';
|
|
642
|
+
const codeHash = computeCodeHash(msg.code, cacheEnabled);
|
|
609
643
|
const maxRetriesResult = 3;
|
|
610
644
|
let lastErrorResult = null;
|
|
611
645
|
for (let i = 0; i < maxRetriesResult; i++) {
|
|
@@ -614,7 +648,7 @@ async function handleMessage(msg) {
|
|
|
614
648
|
log(`sendResult: ${msg.code}`);
|
|
615
649
|
try {
|
|
616
650
|
const r = await new Promise((resolve, reject) => {
|
|
617
|
-
cadSendWithResult({ code: msg.code, platform: msg.platform, encoding: msg.encoding || '' }, (e, r) => {
|
|
651
|
+
cadSendWithResult({ code: msg.code, platform: msg.platform, encoding: msg.encoding || '', codeHash, cacheEnabled }, (e, r) => {
|
|
618
652
|
if (e) reject(e);
|
|
619
653
|
else resolve(r);
|
|
620
654
|
});
|
package/dist/config.js
CHANGED
|
@@ -33,6 +33,11 @@ const envSchema = z.object({
|
|
|
33
33
|
LLM_MODEL: z.string().optional().default('gpt-4o-mini'),
|
|
34
34
|
LLM_MAX_TOKENS: z.string().optional().default('4096'),
|
|
35
35
|
LLM_TEMPERATURE: z.string().optional().default('0.7'),
|
|
36
|
+
WORKER_POOL_SIZE: z.string().optional().default('2'),
|
|
37
|
+
LINT_PRESET: z.string().optional().default(''),
|
|
38
|
+
LINT_CONFIG_PATH: z.string().optional().default(''),
|
|
39
|
+
LINT_LEVEL: z.string().optional().default(''),
|
|
40
|
+
NO_LINT: z.string().optional().default(''),
|
|
36
41
|
});
|
|
37
42
|
|
|
38
43
|
const configSchema = z.object({
|
|
@@ -71,6 +76,11 @@ const configSchema = z.object({
|
|
|
71
76
|
llmMaxTokens: z.number().optional(),
|
|
72
77
|
llmTemperature: z.number().optional(),
|
|
73
78
|
maxPoolSize: z.number().optional(),
|
|
79
|
+
workerPoolSize: z.number().optional(),
|
|
80
|
+
lintPreset: z.string().optional(),
|
|
81
|
+
lintConfigPath: z.string().optional(),
|
|
82
|
+
lintLevel: z.string().optional(),
|
|
83
|
+
noLint: z.boolean().optional(),
|
|
74
84
|
});
|
|
75
85
|
|
|
76
86
|
function parseArgs() {
|
|
@@ -101,6 +111,17 @@ function parseArgs() {
|
|
|
101
111
|
i++;
|
|
102
112
|
} else if (args[i] === '--debug') {
|
|
103
113
|
result.debug = true;
|
|
114
|
+
} else if (args[i] === '--lint-preset' && args[i + 1]) {
|
|
115
|
+
result.lintPreset = args[i + 1];
|
|
116
|
+
i++;
|
|
117
|
+
} else if (args[i] === '--lint-config' && args[i + 1]) {
|
|
118
|
+
result.lintConfigPath = args[i + 1];
|
|
119
|
+
i++;
|
|
120
|
+
} else if (args[i] === '--lint-level' && args[i + 1]) {
|
|
121
|
+
result.lintLevel = args[i + 1];
|
|
122
|
+
i++;
|
|
123
|
+
} else if (args[i] === '--no-lint' || args[i] === '--lint-off') {
|
|
124
|
+
result.noLint = true;
|
|
104
125
|
}
|
|
105
126
|
}
|
|
106
127
|
return result;
|
|
@@ -171,8 +192,18 @@ const config = {
|
|
|
171
192
|
llmMaxTokens: fileConfig?.llmMaxTokens || parseInt(env.LLM_MAX_TOKENS, 10),
|
|
172
193
|
llmTemperature: fileConfig?.llmTemperature || parseFloat(env.LLM_TEMPERATURE),
|
|
173
194
|
maxPoolSize: fileConfig?.maxPoolSize || 3,
|
|
195
|
+
workerPoolSize: fileConfig?.workerPoolSize || parseInt(env.WORKER_POOL_SIZE, 10),
|
|
196
|
+
lintPreset: fileConfig?.lintPreset || cliArgs.lintPreset || env.LINT_PRESET || undefined,
|
|
197
|
+
lintConfigPath: fileConfig?.lintConfigPath || cliArgs.lintConfigPath || env.LINT_CONFIG_PATH || undefined,
|
|
198
|
+
lintLevel: fileConfig?.lintLevel || cliArgs.lintLevel || env.LINT_LEVEL || undefined,
|
|
199
|
+
noLint: fileConfig?.noLint ?? cliArgs.noLint ?? (env.NO_LINT === 'true'),
|
|
174
200
|
};
|
|
175
201
|
|
|
202
|
+
if (config.lintConfigPath) process.env.LINT_CONFIG_PATH = config.lintConfigPath;
|
|
203
|
+
if (config.lintPreset) process.env.LINT_PRESET = config.lintPreset;
|
|
204
|
+
if (config.lintLevel) process.env.LINT_LEVEL = config.lintLevel;
|
|
205
|
+
if (config.noLint) process.env.NO_LINT = 'true';
|
|
206
|
+
|
|
176
207
|
export function validateConfig() {
|
|
177
208
|
const errors = [];
|
|
178
209
|
if (config.transport === 'http' || config.transport === 'ws') {
|
|
@@ -201,16 +232,20 @@ export function validateConfig() {
|
|
|
201
232
|
}
|
|
202
233
|
|
|
203
234
|
let configWatchers = [];
|
|
235
|
+
let configReloadLock = false;
|
|
204
236
|
|
|
205
237
|
if (configFilePath && fs.existsSync(configFilePath)) {
|
|
206
238
|
try {
|
|
207
239
|
fs.watch(configFilePath, (eventType) => {
|
|
208
240
|
if (eventType === 'change') {
|
|
241
|
+
if (configReloadLock) return;
|
|
242
|
+
configReloadLock = true;
|
|
209
243
|
const newFileConfig = loadConfigFile(configFilePath);
|
|
210
244
|
if (newFileConfig) {
|
|
211
245
|
Object.assign(config, newFileConfig);
|
|
212
|
-
configWatchers.forEach(fn => fn(config));
|
|
246
|
+
configWatchers.forEach(fn => { try { fn(config); } catch {} });
|
|
213
247
|
}
|
|
248
|
+
configReloadLock = false;
|
|
214
249
|
}
|
|
215
250
|
});
|
|
216
251
|
} catch (e) { console.error(`Config file watch error: ${e.message}`); }
|
|
@@ -225,12 +260,16 @@ export function getAllConfig() {
|
|
|
225
260
|
}
|
|
226
261
|
|
|
227
262
|
export function reloadConfig() {
|
|
263
|
+
if (configReloadLock) return false;
|
|
264
|
+
configReloadLock = true;
|
|
228
265
|
const newFileConfig = loadConfigFile(config.configFile);
|
|
229
266
|
if (newFileConfig) {
|
|
230
267
|
Object.assign(config, newFileConfig);
|
|
231
|
-
configWatchers.forEach(fn => fn(config));
|
|
268
|
+
configWatchers.forEach(fn => { try { fn(config); } catch {} });
|
|
269
|
+
configReloadLock = false;
|
|
232
270
|
return true;
|
|
233
271
|
}
|
|
272
|
+
configReloadLock = false;
|
|
234
273
|
return false;
|
|
235
274
|
}
|
|
236
275
|
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { cad } from '../cad.js';
|
|
2
|
+
import { log, error as logError } from '../logger.js';
|
|
3
|
+
import { mcpSuccess, mcpError, ensureCadConnected } from '../handler-utils.js';
|
|
4
|
+
import { handleToolCall } from '../mcp-tools.js';
|
|
5
|
+
|
|
6
|
+
export async function batchTransaction({ operations }) {
|
|
7
|
+
try {
|
|
8
|
+
await ensureCadConnected();
|
|
9
|
+
|
|
10
|
+
if (!operations || !Array.isArray(operations) || operations.length === 0) {
|
|
11
|
+
return mcpError('operations 必须是非空数组');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
log(`batch_transaction: starting ${operations.length} operations`);
|
|
15
|
+
|
|
16
|
+
await cad.sendCommand('(command "_.UNDO" "_BEGIN")');
|
|
17
|
+
|
|
18
|
+
const results = [];
|
|
19
|
+
let hasError = false;
|
|
20
|
+
|
|
21
|
+
for (let i = 0; i < operations.length; i++) {
|
|
22
|
+
const op = operations[i];
|
|
23
|
+
log(`batch_transaction: step ${i + 1}/${operations.length} -> ${op.tool}`);
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const result = await handleToolCall(op.tool, op.args || {});
|
|
27
|
+
results.push({
|
|
28
|
+
step: i,
|
|
29
|
+
tool: op.tool,
|
|
30
|
+
success: !result.isError,
|
|
31
|
+
result,
|
|
32
|
+
});
|
|
33
|
+
if (result.isError) {
|
|
34
|
+
hasError = true;
|
|
35
|
+
}
|
|
36
|
+
} catch (e) {
|
|
37
|
+
logError(`batch_transaction: step ${i} (${op.tool}) threw: ${e.message}`);
|
|
38
|
+
results.push({
|
|
39
|
+
step: i,
|
|
40
|
+
tool: op.tool,
|
|
41
|
+
success: false,
|
|
42
|
+
error: e.message,
|
|
43
|
+
});
|
|
44
|
+
hasError = true;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
await cad.sendCommand('(command "_.UNDO" "_END")');
|
|
50
|
+
} catch (e) {
|
|
51
|
+
logError(`batch_transaction: UNDO _END failed: ${e.message}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (hasError) {
|
|
55
|
+
try {
|
|
56
|
+
await cad.sendCommand('(command "_.UNDO" "1")');
|
|
57
|
+
log('batch_transaction: rolled back due to errors');
|
|
58
|
+
} catch (e) {
|
|
59
|
+
logError(`batch_transaction: UNDO rollback failed: ${e.message}`);
|
|
60
|
+
}
|
|
61
|
+
return mcpError(`批量事务部分操作失败,已回滚: ${JSON.stringify(results)}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
log(`batch_transaction: all ${operations.length} operations succeeded`);
|
|
65
|
+
return mcpSuccess(JSON.stringify(results));
|
|
66
|
+
} catch (e) {
|
|
67
|
+
logError(`batch_transaction: outer error: ${e.message}`);
|
|
68
|
+
return mcpError(`批量事务错误: ${e.message}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { cad } from '../cad.js';
|
|
2
2
|
import { log } from '../logger.js';
|
|
3
|
-
import { escapeLispString } from '../handler-utils.js';
|
|
3
|
+
import { withCadConnection, mcpSuccess, mcpError, escapeLispString } from '../handler-utils.js';
|
|
4
|
+
import { analyzeLispCode } from '../lint-analyzer.js';
|
|
4
5
|
|
|
5
6
|
export async function connectCad(platform = null) {
|
|
6
7
|
log(`connect_cad called${platform ? ` (target: ${platform})` : ''}, cad.connected before: ${cad.connected}`);
|
|
@@ -11,92 +12,82 @@ export async function connectCad(platform = null) {
|
|
|
11
12
|
if (connected) {
|
|
12
13
|
const messages = [`已连接到 ${cad.getPlatform()} ${cad.getVersion()}`];
|
|
13
14
|
initAtlisp().catch(e => log(`connect_cad: initAtlisp failed: ${e.message}`));
|
|
14
|
-
return
|
|
15
|
+
return mcpSuccess(messages.join(';'));
|
|
15
16
|
}
|
|
16
|
-
return
|
|
17
|
+
return mcpError('未能连接或启动 CAD,请确保 CAD 已安装');
|
|
17
18
|
} catch (e) {
|
|
18
19
|
log('connect_cad error: ' + e.message);
|
|
19
|
-
return
|
|
20
|
+
return mcpError(`连接 CAD 失败: ${e.message}`);
|
|
20
21
|
}
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
export
|
|
24
|
-
if (!cad.connected) { await cad.connect(); }
|
|
25
|
-
if (!cad.connected) {
|
|
26
|
-
return { content: [{ type: 'text', text: '未连接 CAD' }], isError: true };
|
|
27
|
-
}
|
|
24
|
+
export const evalLisp = withCadConnection(async (code, withResult = false, encoding = null) => {
|
|
28
25
|
const trimmed = (code || '').trim();
|
|
29
|
-
if (!trimmed) return
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
26
|
+
if (!trimmed) return mcpSuccess('命令为空,未发送');
|
|
27
|
+
|
|
28
|
+
const analysis = analyzeLispCode(trimmed);
|
|
29
|
+
if (!analysis.valid) {
|
|
30
|
+
const detail = analysis.errors.map(e => ` L${e.line}: [${e.rule}] ${e.message}`).join('\n');
|
|
31
|
+
return mcpError(`代码存在安全问题:\n${detail}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (withResult) {
|
|
35
|
+
const result = await cad.sendCommandWithResult(trimmed, encoding);
|
|
36
|
+
if (result !== null) {
|
|
37
|
+
return mcpSuccess(result);
|
|
40
38
|
}
|
|
41
|
-
|
|
42
|
-
return { content: [{ type: 'text', text: `错误: ${e.message}` }], isError: true };
|
|
39
|
+
return mcpError('执行失败或无返回值');
|
|
43
40
|
}
|
|
44
|
-
|
|
41
|
+
await cad.sendCommand(trimmed + '\n');
|
|
42
|
+
return mcpSuccess('已发送命令');
|
|
43
|
+
});
|
|
45
44
|
|
|
46
|
-
export
|
|
47
|
-
if (!cad.connected) { await cad.connect(); }
|
|
48
|
-
if (!cad.connected) return { content: [{ type: 'text', text: 'CAD 未连接' }] };
|
|
45
|
+
export const getCadInfo = withCadConnection(async () => {
|
|
49
46
|
const info = await cad.getInfo();
|
|
50
|
-
return
|
|
51
|
-
}
|
|
47
|
+
return mcpSuccess(info);
|
|
48
|
+
});
|
|
52
49
|
|
|
53
|
-
export
|
|
54
|
-
if (!cad.connected) return { content: [{ type: 'text', text: '请先连接 CAD' }], isError: true };
|
|
50
|
+
export const atCommand = withCadConnection(async (command) => {
|
|
55
51
|
const trimmed = (command || '').trim();
|
|
56
|
-
if (!trimmed) return
|
|
52
|
+
if (!trimmed) return mcpSuccess('命令为空,未发送');
|
|
53
|
+
|
|
54
|
+
const analysis = analyzeLispCode(trimmed);
|
|
55
|
+
if (!analysis.valid) {
|
|
56
|
+
const detail = analysis.errors.map(e => ` L${e.line}: [${e.rule}] ${e.message}`).join('\n');
|
|
57
|
+
return mcpError(`代码存在安全问题:\n${detail}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
57
60
|
await cad.sendCommand(trimmed + '\n');
|
|
58
|
-
return
|
|
59
|
-
}
|
|
61
|
+
return mcpSuccess('已发送命令');
|
|
62
|
+
});
|
|
60
63
|
|
|
61
|
-
export
|
|
62
|
-
if (!cad.connected) { await cad.connect(); }
|
|
63
|
-
if (!cad.connected) return { content: [{ type: 'text', text: '未连接 CAD' }], isError: true };
|
|
64
|
+
export const newDocument = withCadConnection(async () => {
|
|
64
65
|
const result = await cad.newDoc();
|
|
65
66
|
if (result) {
|
|
66
|
-
return
|
|
67
|
+
return mcpSuccess('已在 CAD 中新建空白文档');
|
|
67
68
|
}
|
|
68
|
-
return
|
|
69
|
-
}
|
|
69
|
+
return mcpError('新建文档失败');
|
|
70
|
+
});
|
|
70
71
|
|
|
71
|
-
export
|
|
72
|
-
if (!cad.connected) { await cad.connect(); }
|
|
73
|
-
if (!cad.connected) return { content: [{ type: 'text', text: '未连接 CAD' }], isError: true };
|
|
72
|
+
export const bringToFront = withCadConnection(async () => {
|
|
74
73
|
const result = await cad.bringToFront();
|
|
75
74
|
if (result) {
|
|
76
|
-
return
|
|
75
|
+
return mcpSuccess('已将 CAD 窗口切换到前台');
|
|
77
76
|
}
|
|
78
|
-
return
|
|
79
|
-
}
|
|
77
|
+
return mcpError('切换前台失败');
|
|
78
|
+
});
|
|
80
79
|
|
|
81
|
-
export
|
|
82
|
-
if (!cad.connected) { await cad.connect(); }
|
|
83
|
-
if (!cad.connected) return { content: [{ type: 'text', text: '未连接 CAD' }], isError: true };
|
|
80
|
+
export const installAtlisp = withCadConnection(async () => {
|
|
84
81
|
const installCode = '(progn(vl-load-com)(setq s strcat h "http" o(vlax-create-object (s"win"h".win"h"request.5.1"))v vlax-invoke e eval r read)(v o\'open "get" (s h"://atlisp.""cn/@"):vlax-true)(v o\'send)(v o\'WaitforResponse 1000)(e(r(vlax-get-property o\'ResponseText))))';
|
|
85
82
|
await cad.sendCommand(installCode + '\n');
|
|
86
|
-
return
|
|
87
|
-
}
|
|
83
|
+
return mcpSuccess('已发送 @lisp 安装代码到 CAD');
|
|
84
|
+
});
|
|
88
85
|
|
|
89
|
-
export
|
|
90
|
-
|
|
91
|
-
if (
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if (result) return { content: [{ type: 'text', text: result }] };
|
|
95
|
-
return { content: [{ type: 'text', text: 'CAD 函数列表为空' }] };
|
|
96
|
-
} catch (e) {
|
|
97
|
-
return { content: [{ type: 'text', text: `错误: ${e.message}` }], isError: true };
|
|
98
|
-
}
|
|
99
|
-
}
|
|
86
|
+
export const listFunctionsInCad = withCadConnection(async () => {
|
|
87
|
+
const result = await cad.sendCommandWithResult('(atoms-family 0)', null);
|
|
88
|
+
if (result) return mcpSuccess(result);
|
|
89
|
+
return mcpError('CAD 函数列表为空');
|
|
90
|
+
});
|
|
100
91
|
|
|
101
92
|
export async function getSystemStatus() {
|
|
102
93
|
const status = {
|
|
@@ -140,9 +131,7 @@ export async function getSystemStatus() {
|
|
|
140
131
|
return { content: [{ type: 'text', text: JSON.stringify(status, null, 2) }] };
|
|
141
132
|
}
|
|
142
133
|
|
|
143
|
-
export
|
|
144
|
-
if (!cad.connected) { await cad.connect(); }
|
|
145
|
-
if (!cad.connected) return { content: [{ type: 'text', text: '未连接 CAD' }], isError: true };
|
|
134
|
+
export const initAtlisp = withCadConnection(async () => {
|
|
146
135
|
const code = `(if (null @::load-module)
|
|
147
136
|
(progn
|
|
148
137
|
(vl-load-com)
|
|
@@ -151,30 +140,16 @@ export async function initAtlisp() {
|
|
|
151
140
|
(v o'send)
|
|
152
141
|
(v o'WaitforResponse 1000)
|
|
153
142
|
(e(r(vlax-get o'ResponseText)))))`;
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
} catch (e) {
|
|
168
|
-
return { content: [{ type: 'text', text: `错误: ${e.message}` }], isError: true };
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
export async function setSystemVariable(name, value) {
|
|
172
|
-
if (!cad.connected) { await cad.connect(); }
|
|
173
|
-
if (!cad.connected) return { content: [{ type: 'text', text: '未连接 CAD' }], isError: true };
|
|
174
|
-
try {
|
|
175
|
-
await cad.sendCommand(`(setvar "${escapeLispString(name)}" ${value})\n`);
|
|
176
|
-
return { content: [{ type: 'text', text: `已设置 ${name} = ${value}` }] };
|
|
177
|
-
} catch (e) {
|
|
178
|
-
return { content: [{ type: 'text', text: `错误: ${e.message}` }], isError: true };
|
|
179
|
-
}
|
|
180
|
-
}
|
|
143
|
+
await cad.sendCommand(code + '\n');
|
|
144
|
+
return mcpSuccess('已发送 @lisp 函数库加载代码到 CAD');
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
export const getSystemVariable = withCadConnection(async (name) => {
|
|
148
|
+
const result = await cad.sendCommandWithResult(`(vl-princ-to-string (getvar "${escapeLispString(name)}"))`);
|
|
149
|
+
return mcpSuccess(result || 'nil');
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
export const setSystemVariable = withCadConnection(async (name, value) => {
|
|
153
|
+
await cad.sendCommand(`(setvar "${escapeLispString(name)}" ${value})\n`);
|
|
154
|
+
return mcpSuccess(`已设置 ${name} = ${value}`);
|
|
155
|
+
});
|