@atlisp/mcp 1.8.30 → 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.
Binary file
@@ -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
- const cacheEnabled = process.env.LONG_CODE_CACHE === '1';
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
- log(`send: ${msg.code}`);
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
+ }
@@ -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();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlisp/mcp",
3
- "version": "1.8.30",
3
+ "version": "1.8.32",
4
4
  "description": "MCP Server for @lisp on CAD,support AutoCAD/GstarCAD/ZWCAD/BricsCAD or CAD platform compatible with AutoLISP",
5
5
  "type": "module",
6
6
  "bin": {