@atlisp/mcp 1.8.20 → 1.8.23

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.
@@ -579,9 +579,12 @@ async function handleMessage(msg) {
579
579
  if (!parenCheck.valid) {
580
580
  return { success: false, error: `括号错误: ${parenCheck.error} (位置 ${parenCheck.pos})` };
581
581
  }
582
- const injectionCheck = validateLispCodeWorker(msg.code);
583
- if (!injectionCheck.valid) {
584
- return { success: false, error: `安全限制: ${injectionCheck.error}` };
582
+ const securityLevel = process.env.SECURITY_LEVEL || 'strict';
583
+ if (securityLevel !== 'relaxed') {
584
+ const injectionCheck = validateLispCodeWorker(msg.code);
585
+ if (!injectionCheck.valid) {
586
+ return { success: false, error: `安全限制: ${injectionCheck.error}` };
587
+ }
585
588
  }
586
589
  if (msg.code.length > 255) {
587
590
  const cacheEnabled = process.env.LONG_CODE_CACHE === '1';
package/dist/config.js CHANGED
@@ -38,6 +38,7 @@ const envSchema = z.object({
38
38
  LINT_CONFIG_PATH: z.string().optional().default(''),
39
39
  LINT_LEVEL: z.string().optional().default(''),
40
40
  NO_LINT: z.string().optional().default(''),
41
+ SECURITY_LEVEL: z.string().optional().default('strict'),
41
42
  });
42
43
 
43
44
  const configSchema = z.object({
@@ -81,6 +82,7 @@ const configSchema = z.object({
81
82
  lintConfigPath: z.string().optional(),
82
83
  lintLevel: z.string().optional(),
83
84
  noLint: z.boolean().optional(),
85
+ securityLevel: z.enum(['strict', 'standard', 'relaxed']).optional(),
84
86
  });
85
87
 
86
88
  function parseArgs() {
@@ -122,6 +124,9 @@ function parseArgs() {
122
124
  i++;
123
125
  } else if (args[i] === '--no-lint' || args[i] === '--lint-off') {
124
126
  result.noLint = true;
127
+ } else if (args[i] === '--security-level' && args[i + 1]) {
128
+ result.securityLevel = args[i + 1];
129
+ i++;
125
130
  }
126
131
  }
127
132
  return result;
@@ -197,6 +202,7 @@ const config = {
197
202
  lintConfigPath: fileConfig?.lintConfigPath || cliArgs.lintConfigPath || env.LINT_CONFIG_PATH || undefined,
198
203
  lintLevel: fileConfig?.lintLevel || cliArgs.lintLevel || env.LINT_LEVEL || undefined,
199
204
  noLint: fileConfig?.noLint ?? cliArgs.noLint ?? (env.NO_LINT === 'true'),
205
+ securityLevel: fileConfig?.securityLevel ?? cliArgs.securityLevel ?? (env.SECURITY_LEVEL || 'strict'),
200
206
  };
201
207
 
202
208
  if (config.lintConfigPath) process.env.LINT_CONFIG_PATH = config.lintConfigPath;
@@ -0,0 +1,126 @@
1
+ import { withCadConnection, mcpSuccess, mcpError } from '../handler-utils.js';
2
+ import {
3
+ createSession, getSession, closeSession, listSessions,
4
+ loadHelpers, fastForward, executeStep, continueExecution, evaluateExpression,
5
+ } from '../debug-engine.js';
6
+
7
+ export const debugStart = withCadConnection(async (args) => {
8
+ try {
9
+ const { code, chunks, encoding } = args || {};
10
+ if (!code) return mcpError('Missing required argument: code');
11
+ if (!chunks || !Array.isArray(chunks)) return mcpError('Missing required argument: chunks (must be an array)');
12
+
13
+ const sessionId = await createSession(code, chunks, encoding);
14
+ const session = getSession(sessionId);
15
+ if (!session) return mcpError('Failed to create debug session');
16
+
17
+ await loadHelpers(session);
18
+
19
+ const ffResult = await fastForward(session);
20
+
21
+ if (session.state === 'error') {
22
+ await closeSession(sessionId);
23
+ return mcpError(session.error);
24
+ }
25
+
26
+ return mcpSuccess(JSON.stringify({
27
+ sessionId,
28
+ totalChunks: session.totalChunks,
29
+ ...ffResult,
30
+ }));
31
+ } catch (e) {
32
+ return mcpError(e.message);
33
+ }
34
+ });
35
+
36
+ export const debugStep = withCadConnection(async (args) => {
37
+ try {
38
+ const { sessionId, action } = args || {};
39
+ if (!sessionId) return mcpError('Missing required argument: sessionId');
40
+ if (!action) return mcpError('Missing required argument: action');
41
+
42
+ const session = getSession(sessionId);
43
+ if (!session) return mcpError(`Debug session not found: ${sessionId}`);
44
+
45
+ if (session.state !== 'paused' && session.state !== 'ready') {
46
+ return mcpError(`Invalid state: ${session.state} (expected paused or ready)`);
47
+ }
48
+
49
+ let result;
50
+ if (action === 'step') {
51
+ result = await executeStep(session);
52
+ } else if (action === 'continue') {
53
+ result = await continueExecution(session);
54
+ } else {
55
+ return mcpError(`Invalid action: ${action} (expected step or continue)`);
56
+ }
57
+
58
+ if (session.state === 'error') {
59
+ return mcpError(session.error);
60
+ }
61
+
62
+ return mcpSuccess(JSON.stringify(result));
63
+ } catch (e) {
64
+ return mcpError(e.message);
65
+ }
66
+ });
67
+
68
+ export const debugEvaluate = withCadConnection(async (args) => {
69
+ try {
70
+ const { sessionId, expression } = args || {};
71
+ if (!sessionId) return mcpError('Missing required argument: sessionId');
72
+ if (!expression) return mcpError('Missing required argument: expression');
73
+
74
+ const session = getSession(sessionId);
75
+ if (!session) return mcpError(`Debug session not found: ${sessionId}`);
76
+
77
+ if (session.state !== 'paused') {
78
+ return mcpError(`Invalid state: ${session.state} (expected paused)`);
79
+ }
80
+
81
+ const result = await evaluateExpression(session, expression);
82
+ if (result.error) return mcpError(result.error);
83
+
84
+ return mcpSuccess(JSON.stringify({ value: result.value }));
85
+ } catch (e) {
86
+ return mcpError(e.message);
87
+ }
88
+ });
89
+
90
+ export async function debugGetState(args) {
91
+ try {
92
+ const { sessionId } = args || {};
93
+ if (!sessionId) return mcpError('Missing required argument: sessionId');
94
+
95
+ const session = getSession(sessionId);
96
+ if (!session) return mcpError(`Debug session not found: ${sessionId}`);
97
+
98
+ const checkpoint = session.lastCheckpoint;
99
+ return mcpSuccess(JSON.stringify({
100
+ state: session.state,
101
+ currentIndex: session.currentIndex,
102
+ totalChunks: session.totalChunks,
103
+ currentCheckpoint: checkpoint || undefined,
104
+ error: session.error,
105
+ }));
106
+ } catch (e) {
107
+ return mcpError(e.message);
108
+ }
109
+ }
110
+
111
+ export async function debugStop(args) {
112
+ try {
113
+ const { sessionId } = args || {};
114
+ if (!sessionId) return mcpError('Missing required argument: sessionId');
115
+
116
+ const session = getSession(sessionId);
117
+ if (!session) {
118
+ return mcpError(`Debug session not found: ${sessionId}`);
119
+ }
120
+
121
+ const ok = await closeSession(sessionId);
122
+ return mcpSuccess(JSON.stringify({ success: ok }));
123
+ } catch (e) {
124
+ return mcpError(e.message);
125
+ }
126
+ }
@@ -1,36 +1,90 @@
1
1
  import { log, error as logError } from './logger.js';
2
+ import config, { onConfigChange } from './config.js';
2
3
 
3
4
  export const THREAT_BLOCK = 'block';
4
5
  export const THREAT_WARN = 'warn';
5
6
  export const THREAT_ALLOW = 'allow';
6
7
 
8
+ export const SECURITY_LEVELS = ['strict', 'standard', 'relaxed'];
9
+
10
+ const CVT = {
11
+ strict: THREAT_BLOCK,
12
+ standard: THREAT_WARN,
13
+ relaxed: THREAT_ALLOW,
14
+ };
15
+
16
+ function getLevel() {
17
+ const level = config?.securityLevel || 'strict';
18
+ return CVT[level] || THREAT_BLOCK;
19
+ }
20
+
7
21
  export const SECURITY_RULES = [
8
- { re: /\bstartapp\s*[(\s]/i, severity: THREAT_BLOCK, msg: '禁止使用 startapp 函数' },
9
- { re: /\bvl-bt\b/i, severity: THREAT_BLOCK, msg: '禁止使用 vl-bt 函数' },
10
- { re: /\bvlax-import-type-library\b/i, severity: THREAT_BLOCK, msg: '禁止导入类型库' },
11
- { re: /\bdos_command_string\s*[(\s]/i, severity: THREAT_BLOCK, msg: '禁止使用 dos_command_string' },
22
+ { re: /\bstartapp\s*[(\s]/i, baseSeverity: THREAT_BLOCK, msg: '禁止使用 startapp 函数' },
23
+ { re: /\bvl-bt\b/i, baseSeverity: THREAT_BLOCK, msg: '禁止使用 vl-bt 函数' },
24
+ { re: /\bvlax-import-type-library\b/i, baseSeverity: THREAT_BLOCK, msg: '禁止导入类型库' },
25
+ { re: /\bdos_command_string\s*[(\s]/i, baseSeverity: THREAT_BLOCK, msg: '禁止使用 dos_command_string' },
26
+ { re: /\beval\s*[(\s]/i, baseSeverity: THREAT_BLOCK, msg: '禁止使用 eval 函数' },
27
+ { re: /\bcommand\s*["\s]*[_(]?\s*["']?\.?\s*_?(?:shell|quit|exit|close|open|new|recover|audit|purge|wblock|export)/i, baseSeverity: THREAT_BLOCK, msg: '禁止使用危险 CAD 命令' },
28
+ { re: /\bload\s*[(\s"]/i, baseSeverity: THREAT_BLOCK, msg: 'load 函数可能加载外部代码' },
29
+ { re: /\b(?:read-line|read-char|write-line|write-char)\s*[(\s]/i, baseSeverity: THREAT_BLOCK, msg: '文件 I/O 操作可能影响系统' },
30
+ { re: /\bvl-file-copy\s*[(\s]/i, baseSeverity: THREAT_BLOCK, msg: '禁止文件复制' },
31
+ { re: /\bvl-file-delete\s*[(\s]/i, baseSeverity: THREAT_BLOCK, msg: '禁止文件删除' },
32
+ { re: /\bvl-file-rename\s*[(\s]/i, baseSeverity: THREAT_BLOCK, msg: '禁止文件重命名' },
33
+ { re: /\bvl-registry-write[\s\)]/i, baseSeverity: THREAT_BLOCK, msg: 'vl-registry-write 会修改注册表' },
34
+ { re: /\bvl-exit-with-value\b/i, baseSeverity: THREAT_BLOCK, msg: '禁止使用 vl-exit-with-value' },
35
+ { re: /\bvl-exit-with-error\b/i, baseSeverity: THREAT_BLOCK, msg: '禁止使用 vl-exit-with-error' },
36
+ { re: /\bvlax-add-cmd\b/i, baseSeverity: THREAT_BLOCK, msg: '禁止注册新命令' },
37
+ { re: /\bvla-Delete\b/i, baseSeverity: THREAT_BLOCK, msg: '禁止直接删除 vla 对象' },
38
+ { re: /\bvla-Save\s*[(\s]/i, baseSeverity: THREAT_BLOCK, msg: '禁止直接保存文档' },
39
+ { re: /\bdirectory\s*[(\s]/i, baseSeverity: THREAT_BLOCK, msg: '禁止使用 directory 函数' },
40
+ { re: /\bvla-deletefile\b/i, baseSeverity: THREAT_BLOCK, msg: '禁止删除文件' },
41
+ { re: /\bvla-copyfile\b/i, baseSeverity: THREAT_BLOCK, msg: '禁止复制文件' },
42
+ { re: /\bvla-movefile\b/i, baseSeverity: THREAT_BLOCK, msg: '禁止移动文件' },
12
43
  ];
13
44
 
14
- export function stripStringsAndComments(code) {
15
- const withoutStrings = code.replace(/"([^"\\]*(?:\\.[^"\\]*)*)"/g, '');
16
- return withoutStrings.replace(/;.*$/gm, '');
45
+ let _cachedLevel = null;
46
+
47
+ export function refreshConfig() {
48
+ _cachedLevel = null;
49
+ }
50
+
51
+ try {
52
+ onConfigChange(() => { _cachedLevel = null; });
53
+ } catch (_) {}
54
+
55
+ function resolveSeverity(rule) {
56
+ if (_cachedLevel) return _cachedLevel;
57
+ _cachedLevel = getLevel();
58
+ return _cachedLevel;
17
59
  }
18
60
 
19
61
  export function validateLispCodeWorker(code) {
20
62
  const clean = stripStringsAndComments(code);
21
- for (const { re, severity, msg } of SECURITY_RULES) {
63
+ const effectiveSeverity = resolveSeverity();
64
+
65
+ for (const rule of SECURITY_RULES) {
66
+ const re = rule.re;
22
67
  if (re.test(clean)) {
23
- if (severity === THREAT_BLOCK) {
24
- return { valid: false, error: msg };
68
+ if (effectiveSeverity === THREAT_BLOCK) {
69
+ return { valid: false, error: rule.msg };
25
70
  }
26
- if (severity === THREAT_WARN) {
27
- log('WARN: ' + msg);
71
+ if (effectiveSeverity === THREAT_WARN) {
72
+ log('WARN: ' + rule.msg);
28
73
  }
29
74
  }
30
75
  }
31
76
  return { valid: true };
32
77
  }
33
78
 
79
+ export function getEffectiveSeverity() {
80
+ return resolveSeverity();
81
+ }
82
+
83
+ export function stripStringsAndComments(code) {
84
+ const withoutStrings = code.replace(/"([^"\\]*(?:\\.[^"\\]*)*)"/g, '');
85
+ return withoutStrings.replace(/;.*$/gm, '');
86
+ }
87
+
34
88
  export function checkParens(code) {
35
89
  let depth = 0;
36
90
  let inString = false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlisp/mcp",
3
- "version": "1.8.20",
3
+ "version": "1.8.23",
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": {