@atlisp/mcp 1.0.10 → 1.0.12

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 CHANGED
@@ -4,6 +4,10 @@ MCP (Model Context Protocol) Server for @lisp on CAD - 为 CAD 环境提供 @lis
4
4
 
5
5
  同时为 AI AGENT 操控 cad 提供调用工具和通道。
6
6
 
7
+ ## 版本
8
+
9
+ **当前版本**: 1.0.10
10
+
7
11
  ## 功能特性
8
12
 
9
13
  - 连接 AutoCAD/ZWCAD/GStarCAD/BricsCAD
@@ -126,6 +130,20 @@ atlisp-mcp
126
130
  | `MESSAGE_TIMEOUT` | `60000` | 消息超时(毫秒) |
127
131
  | `BUSY_RETRIES` | `10` | CAD 忙碌时重试次数 |
128
132
  | `BUSY_DELAY` | `500` | CAD 忙碌重试间隔(毫秒) |
133
+ | `MCP_API_KEY` | (无) | API Key 认证(可选) |
134
+
135
+ ### 运行测试
136
+
137
+ ```bash
138
+ npm test
139
+ ```
140
+
141
+ 测试覆盖 54 个用例,包括:
142
+ - 工具定义完整性
143
+ - 连接/断开 CAD
144
+ - 执行 LISP 代码
145
+ - 包管理操作
146
+ - 错误处理
129
147
 
130
148
  ## API
131
149
 
package/package.json CHANGED
@@ -1,21 +1,21 @@
1
1
  {
2
2
  "name": "@atlisp/mcp",
3
- "version": "1.0.10",
3
+ "version": "1.0.12",
4
4
  "description": "MCP Server for @lisp on CAD",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "atlisp-mcp": "./src/cli.js"
8
8
  },
9
- "main": "src/index.js",
9
+ "main": "src/atlisp-mcp.js",
10
10
  "exports": {
11
- ".": "./src/index.js",
11
+ ".": "./src/atlisp-mcp.js",
12
12
  "./cad": "./src/cad.js"
13
13
  },
14
14
  "files": [
15
15
  "src"
16
16
  ],
17
17
  "scripts": {
18
- "start": "node src/index.js",
18
+ "start": "node src/atlisp-mcp.js",
19
19
  "test": "vitest run",
20
20
  "test:watch": "vitest"
21
21
  },
@@ -0,0 +1,304 @@
1
+ import express from 'express';
2
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
+ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
4
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
5
+ import {
6
+ ListToolsRequestSchema,
7
+ CallToolRequestSchema,
8
+ } from '@modelcontextprotocol/sdk/types.js';
9
+ import { cad } from './cad.js';
10
+ import { CAD_PLATFORMS, FILE_EXTENSIONS, PROTOCOL_VERSION, SERVER_NAME, SERVER_VERSION } from './constants.js';
11
+ import { log } from './logger.js';
12
+ import * as cadHandlers from './handlers/cad-handlers.js';
13
+ import * as packageHandlers from './handlers/package-handlers.js';
14
+ import * as functionHandlers from './handlers/function-handlers.js';
15
+ import { createSseResponseHandler } from './sse-utils.js';
16
+
17
+ const PORT = process.env.PORT || 8110;
18
+ const HOST = process.env.HOST || '0.0.0.0';
19
+ const TRANSPORT = process.env.TRANSPORT || 'http';
20
+ const API_KEY = process.env.MCP_API_KEY || '';
21
+ if (API_KEY) log('API Key authentication enabled');
22
+
23
+ export { CAD_PLATFORMS, FILE_EXTENSIONS, MOCK_PACKAGES } from './constants.js';
24
+
25
+ const TOOL_HANDLERS = {
26
+ connect_cad: () => cadHandlers.connectCad(),
27
+ eval_lisp: (a) => cadHandlers.evalLisp(a.code),
28
+ eval_lisp_with_result: (a) => cadHandlers.evalLisp(a.code, true),
29
+ get_cad_info: () => cadHandlers.getCadInfo(),
30
+ list_packages: () => packageHandlers.listPackages(),
31
+ search_packages: (a) => packageHandlers.searchPackages(a.query),
32
+ install_package: (a) => packageHandlers.installPackage(a.packageName),
33
+ get_platform_info: () => ({ content: [{ type: 'text', text: `支持的 CAD 平台:\n${CAD_PLATFORMS.join('\n')}\n当前连接: ${cad.connected ? '已连接' : '未连接'}` }] }),
34
+ get_file_extensions: (a) => {
35
+ const exts = FILE_EXTENSIONS[a.platform];
36
+ if (!exts) return { content: [{ type: 'text', text: `错误: 不支持的平台 "${a.platform}"` }], isError: true };
37
+ return { content: [{ type: 'text', text: `${a.platform} 文件扩展名: ${exts.join(', ')}` }] };
38
+ },
39
+ get_install_code: () => ({ content: [{ type: 'text', text: `复制以下代码到 CAD 命令行安装 @lisp:\n\n(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))))` }] }),
40
+ at_command: (a) => cadHandlers.atCommand(a.command),
41
+ new_document: () => cadHandlers.newDocument(),
42
+ install_atlisp: () => cadHandlers.installAtlisp(),
43
+ get_function_usage: (a) => functionHandlers.getFunctionUsage(a.name, a.package),
44
+ list_functions: (a) => functionHandlers.listFunctions(a.package),
45
+ };
46
+
47
+ export const tools = [
48
+ {
49
+ name: 'connect_cad',
50
+ description: '连接到 CAD (AutoCAD/ZWCAD/GStarCAD/BricsCAD)',
51
+ inputSchema: { type: 'object', properties: {} }
52
+ },
53
+ {
54
+ name: 'eval_lisp',
55
+ description: '在 CAD 中执行 AutoLISP 代码(不返回结果)',
56
+ inputSchema: {
57
+ type: 'object',
58
+ properties: { code: { type: 'string', description: '要执行的 LISP 代码' } },
59
+ required: ['code']
60
+ }
61
+ },
62
+ {
63
+ name: 'eval_lisp_with_result',
64
+ description: '在 CAD 中执行 AutoLISP 代码并返回结果',
65
+ inputSchema: {
66
+ type: 'object',
67
+ properties: { code: { type: 'string', description: '要执行的 LISP 代码' } },
68
+ required: ['code']
69
+ }
70
+ },
71
+ {
72
+ name: 'get_cad_info',
73
+ description: '获取当前 CAD 信息',
74
+ inputSchema: { type: 'object', properties: {} }
75
+ },
76
+ {
77
+ name: 'list_packages',
78
+ description: '列出已安装的 @lisp 包',
79
+ inputSchema: { type: 'object', properties: {} }
80
+ },
81
+ {
82
+ name: 'search_packages',
83
+ description: '搜索 @lisp 包',
84
+ inputSchema: {
85
+ type: 'object',
86
+ properties: { query: { type: 'string', description: '搜索关键词' } },
87
+ required: ['query']
88
+ }
89
+ },
90
+ {
91
+ name: 'install_package',
92
+ description: '安装 @lisp 包到 CAD',
93
+ inputSchema: {
94
+ type: 'object',
95
+ properties: { packageName: { type: 'string', description: '包名称' } },
96
+ required: ['packageName']
97
+ }
98
+ },
99
+ {
100
+ name: 'get_platform_info',
101
+ description: '获取 CAD 平台信息',
102
+ inputSchema: { type: 'object', properties: {} }
103
+ },
104
+ {
105
+ name: 'get_file_extensions',
106
+ description: '获取指定平台的文件扩展名',
107
+ inputSchema: {
108
+ type: 'object',
109
+ properties: { platform: { type: 'string', enum: CAD_PLATFORMS, description: 'CAD 平台' } },
110
+ required: ['platform']
111
+ }
112
+ },
113
+ {
114
+ name: 'get_install_code',
115
+ description: '获取 @lisp 安装代码',
116
+ inputSchema: { type: 'object', properties: {} }
117
+ },
118
+ {
119
+ name: 'at_command',
120
+ description: '执行 @lisp 命令',
121
+ inputSchema: {
122
+ type: 'object',
123
+ properties: { command: { type: 'string', description: '@lisp 命令' } },
124
+ required: ['command']
125
+ }
126
+ },
127
+ {
128
+ name: 'new_document',
129
+ description: '在 CAD 中新建空白文档',
130
+ inputSchema: { type: 'object', properties: {} }
131
+ },
132
+ {
133
+ name: 'install_atlisp',
134
+ description: '在 CAD 中安装 @lisp',
135
+ inputSchema: { type: 'object', properties: {} }
136
+ },
137
+ {
138
+ name: 'get_function_usage',
139
+ description: '获取 @lisp 函数用法(从本地 JSON 库)',
140
+ inputSchema: {
141
+ type: 'object',
142
+ properties: {
143
+ name: { type: 'string', description: '函数名,如 string:length' },
144
+ package: { type: 'string', description: '包名,如 string(可选,不传则自动搜索)' }
145
+ },
146
+ required: ['name']
147
+ }
148
+ },
149
+ {
150
+ name: 'list_functions',
151
+ description: '列出 @lisp 函数库中的所有函数',
152
+ inputSchema: {
153
+ type: 'object',
154
+ properties: {
155
+ package: { type: 'string', description: '包名,如 string(可选)' }
156
+ }
157
+ }
158
+ }
159
+ ];
160
+
161
+ export async function handleToolCall(name, args) {
162
+ const handler = TOOL_HANDLERS[name];
163
+ if (!handler) return { content: [{ type: 'text', text: `未知工具: ${name}` }], isError: true };
164
+ return handler(args);
165
+ }
166
+
167
+ async function initCadConnection() {
168
+ log('Attempting to connect to CAD on startup...');
169
+ try {
170
+ const connected = await cad.connect();
171
+ if (connected) {
172
+ log('Auto-connected to CAD: ' + cad.getPlatform() + ' ' + cad.getVersion());
173
+ console.error('已自动连接到 CAD: ' + cad.getPlatform() + ' ' + cad.getVersion());
174
+ } else {
175
+ log('No CAD found on startup');
176
+ }
177
+ } catch (e) {
178
+ log('Auto-connect failed: ' + e.message);
179
+ }
180
+ }
181
+
182
+ let server = null;
183
+
184
+ export function createMcpServer() {
185
+ server = new Server(
186
+ { name: SERVER_NAME, version: SERVER_VERSION },
187
+ { capabilities: { tools: {} } }
188
+ );
189
+
190
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
191
+
192
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
193
+ const { name, arguments: args } = request.params;
194
+ return await handleToolCall(name, args || {});
195
+ });
196
+
197
+ return server;
198
+ }
199
+
200
+ export async function startServer() {
201
+ if (TRANSPORT === 'stdio') {
202
+ await initCadConnection();
203
+ const mcpServer = createMcpServer();
204
+ const transport = new StdioServerTransport();
205
+ await mcpServer.connect(transport);
206
+ console.error(`${SERVER_NAME} 运行在 stdio 模式`);
207
+ } else {
208
+ const app = express();
209
+ app.use(express.json({ type: ['application/json', 'application/json-rpc'] }));
210
+
211
+ app.use((req, res, next) => {
212
+ log(`${req.method} ${req.path} - body: ${JSON.stringify(req.body)}`);
213
+ next();
214
+ });
215
+
216
+ const PUBLIC_PATHS = ['/health'];
217
+ if (API_KEY) {
218
+ app.use((req, res, next) => {
219
+ if (PUBLIC_PATHS.includes(req.path)) return next();
220
+ const auth = req.get('Authorization');
221
+ if (auth === `Bearer ${API_KEY}`) return next();
222
+ res.status(401).json({ error: 'Unauthorized' });
223
+ });
224
+ }
225
+
226
+ app.get('/health', (req, res) => {
227
+ res.json({ status: 'ok', cad: cad.connected ? 'connected' : 'disconnected' });
228
+ });
229
+
230
+ if (process.env.ENABLE_CORS) {
231
+ app.use((req, res, next) => {
232
+ res.setHeader('Access-Control-Allow-Origin', process.env.CORS_ORIGIN || '*');
233
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
234
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, Accept, mcp-session-id');
235
+ if (req.method === 'OPTIONS') return res.status(204).end();
236
+ next();
237
+ });
238
+ }
239
+
240
+ createMcpServer();
241
+
242
+ const sessionId = crypto.randomUUID();
243
+
244
+ ['/sse', '/mcp/sse'].forEach(p => app.all(p, (req, res) => {
245
+ res.writeHead(200, { 'Content-Type': 'text/event-stream' });
246
+ const transport = new SSEServerTransport('/mcp', res);
247
+ server.connect(transport);
248
+ }));
249
+
250
+ app.post('/mcp', async (req, res) => {
251
+ const body = req.body || {};
252
+ const { method, params, id } = body;
253
+ const accept = req.get('Accept') || '';
254
+ const wantsSse = accept.includes('text/event-stream');
255
+ log('accept: "' + accept + '", wantsSSE: ' + wantsSse);
256
+
257
+ res.setHeader('mcp-session-id', sessionId);
258
+ const { send } = createSseResponseHandler(res, wantsSse);
259
+
260
+ if (!id) return res.status(204).end();
261
+
262
+ try {
263
+ if (method === 'initialize') {
264
+ send({ jsonrpc: '2.0', id, result: {
265
+ protocolVersion: PROTOCOL_VERSION,
266
+ capabilities: { tools: {} },
267
+ serverInfo: { name: SERVER_NAME, version: SERVER_VERSION }
268
+ } }, true);
269
+ return;
270
+ }
271
+
272
+ if (method === 'tools/list') {
273
+ send({ jsonrpc: '2.0', id, result: { tools } }, true);
274
+ return;
275
+ }
276
+
277
+ if (method === 'tools/call') {
278
+ const toolName = params?.name;
279
+ if (!toolName) {
280
+ send({ jsonrpc: '2.0', id, error: { code: -32602, message: 'Invalid params: missing tool name' } }, true);
281
+ return;
282
+ }
283
+ const result = await handleToolCall(toolName, params?.arguments || {});
284
+ send({ jsonrpc: '2.0', id, result }, true);
285
+ return;
286
+ }
287
+
288
+ send({ jsonrpc: '2.0', id, error: { code: -32601, message: 'Method not found' } }, true);
289
+ } catch (e) {
290
+ log('MCP error: ' + e.message);
291
+ send({ jsonrpc: '2.0', id, error: { code: -32603, message: e.message } }, true);
292
+ }
293
+ });
294
+
295
+ app.listen(PORT, HOST, async () => {
296
+ await initCadConnection();
297
+ console.error(`@lisp MCP Server 启动 - http://${HOST}:${PORT} - Streamable HTTP`);
298
+ });
299
+ }
300
+ }
301
+
302
+ if (!process.env.VITEST) {
303
+ await startServer();
304
+ }
package/src/cad-worker.js CHANGED
@@ -7,103 +7,53 @@ const BUSY_DELAY = parseInt(process.env.BUSY_DELAY || '500', 10);
7
7
 
8
8
  const cadConnect = edge.func(function() {/*
9
9
  async (input) => {
10
- // Try existing CAD instances
11
10
  try {
12
11
  dynamic cad = System.Runtime.InteropServices.Marshal.GetActiveObject("AutoCAD.Application");
13
12
  if (cad != null) {
14
13
  string ver = cad.Version.ToString();
15
14
  return new { success = true, platform = "AutoCAD", version = ver };
16
15
  }
17
- } catch {}
16
+ } catch (Exception ex) {
17
+ System.Diagnostics.Debug.WriteLine("AutoCAD connect error: " + ex.Message);
18
+ }
18
19
  try {
19
20
  dynamic cad = System.Runtime.InteropServices.Marshal.GetActiveObject("ZWCAD.Application");
20
21
  if (cad != null) {
21
22
  string ver = cad.Version.ToString();
22
23
  return new { success = true, platform = "ZWCAD", version = ver };
23
24
  }
24
- } catch {}
25
+ } catch (Exception ex) {
26
+ System.Diagnostics.Debug.WriteLine("ZWCAD connect error: " + ex.Message);
27
+ }
25
28
  try {
26
29
  dynamic cad = System.Runtime.InteropServices.Marshal.GetActiveObject("GStarCAD.Application");
27
30
  if (cad != null) {
28
31
  string ver = cad.Version.ToString();
29
32
  return new { success = true, platform = "GStarCAD", version = ver };
30
33
  }
31
- } catch {}
34
+ } catch (Exception ex) {
35
+ System.Diagnostics.Debug.WriteLine("GStarCAD connect error: " + ex.Message);
36
+ }
32
37
  try {
33
38
  dynamic cad = System.Runtime.InteropServices.Marshal.GetActiveObject("BricsCAD.Application");
34
39
  if (cad != null) {
35
40
  string ver = cad.Version.ToString();
36
41
  return new { success = true, platform = "BricsCAD", version = ver };
37
42
  }
38
- } catch {}
39
-
40
- // Try to create new CAD instance
41
- string[] cadPaths = new string[] {
42
- @"C:\Program Files\Autodesk\AutoCAD 2022\acad.exe",
43
- @"C:\Program Files\Autodesk\AutoCAD 2024\acad.exe",
44
- @"C:\Program Files\Autodesk\AutoCAD 2023\acad.exe",
45
- @"C:\Program Files\Autodesk\AutoCAD 2021\acad.exe",
46
- @"C:\Program Files\Autodesk\AutoCAD 2020\acad.exe",
47
- @"C:\Program Files\ZWSOFT\ZWCAD 2024\zwcad.exe",
48
- @"C:\Program Files\ZWSOFT\ZWCAD 2023\zwcad.exe",
49
- @"C:\Program Files\ZWSOFT\ZWCAD 2022\zwcad.exe",
50
- @"C:\Program Files\ZWSOFT\ZWCAD 2021\zwcad.exe",
51
- @"C:\Program Files\GSTARSOFT\GStarCAD 2024\GCAD.exe",
52
- @"C:\Program Files\GSTARSOFT\GStarCAD 2023\GCAD.exe",
53
- @"C:\Program Files\Bricsys\BricsCAD\V24\BricsCAD.exe",
54
- @"C:\Program Files\Bricsys\BricsCAD\V23\BricsCAD.exe"
55
- };
56
-
57
- foreach (string path in cadPaths) {
58
- if (System.IO.File.Exists(path)) {
59
- try {
60
- System.Diagnostics.Process.Start(path);
61
- System.Threading.Thread.Sleep(60000);
62
-
63
- // Try to connect to newly started CAD
64
- try {
65
- dynamic cad = System.Runtime.InteropServices.Marshal.GetActiveObject("AutoCAD.Application");
66
- if (cad != null) {
67
- string ver = cad.Version.ToString();
68
- return new { success = true, platform = "AutoCAD", version = ver };
69
- }
70
- } catch {}
71
- try {
72
- dynamic cad = System.Runtime.InteropServices.Marshal.GetActiveObject("ZWCAD.Application");
73
- if (cad != null) {
74
- string ver = cad.Version.ToString();
75
- return new { success = true, platform = "ZWCAD", version = ver };
76
- }
77
- } catch {}
78
- try {
79
- dynamic cad = System.Runtime.InteropServices.Marshal.GetActiveObject("GStarCAD.Application");
80
- if (cad != null) {
81
- string ver = cad.Version.ToString();
82
- return new { success = true, platform = "GStarCAD", version = ver };
83
- }
84
- } catch {}
85
- try {
86
- dynamic cad = System.Runtime.InteropServices.Marshal.GetActiveObject("BricsCAD.Application");
87
- if (cad != null) {
88
- string ver = cad.Version.ToString();
89
- return new { success = true, platform = "BricsCAD", version = ver };
90
- }
91
- } catch {}
92
- } catch {}
93
- }
43
+ } catch (Exception ex) {
44
+ System.Diagnostics.Debug.WriteLine("BricsCAD connect error: " + ex.Message);
94
45
  }
95
-
96
46
  return new { success = false, error = "No CAD found" };
97
47
  }
98
48
  */});
99
49
 
100
50
  const cadSend = edge.func(function() {/*
101
51
  async (input) => {
102
- dynamic d = input;
103
- string code = d.code.ToString();
104
- string platform = d.platform.ToString();
105
- string progId = platform + ".Application";
106
52
  try {
53
+ dynamic d = input;
54
+ string code = d.code.ToString();
55
+ string platform = d.platform.ToString();
56
+ string progId = platform + ".Application";
107
57
  dynamic cad = System.Runtime.InteropServices.Marshal.GetActiveObject(progId);
108
58
  if (cad != null) {
109
59
  dynamic doc = cad.ActiveDocument;
@@ -112,49 +62,51 @@ const cadSend = edge.func(function() {/*
112
62
  return new { success = true };
113
63
  }
114
64
  }
115
- } catch {}
65
+ } catch (Exception ex) {
66
+ System.Diagnostics.Debug.WriteLine("cadSend error: " + ex.Message);
67
+ }
116
68
  return new { success = false };
117
69
  }
118
70
  */});
119
71
 
120
72
  const cadSendWithResult = edge.func(function() {/*
121
73
  async (input) => {
122
- dynamic d = input;
123
- string code = d.code.ToString();
124
- string platform = d.platform.ToString();
125
- string progId = platform + ".Application";
126
-
127
- string tempDir = System.IO.Path.GetTempPath();
128
- string tempFile = System.IO.Path.Combine(tempDir, "atlisp_result_" + System.Guid.NewGuid().ToString("N") + ".txt");
129
-
130
- // Use print to write result directly to file
131
- string lispCode = "(progn(setq f(open \"" + tempFile.Replace("\\", "\\\\") + "\" \"w\"))(print " + code + " f)(close f))";
132
-
74
+ string tempFile = null;
133
75
  try {
76
+ dynamic d = input;
77
+ string code = d.code.ToString();
78
+ string platform = d.platform.ToString();
79
+ string progId = platform + ".Application";
80
+ string tempDir = System.IO.Path.GetTempPath();
81
+ tempFile = System.IO.Path.Combine(tempDir, "atlisp_result_" + System.Guid.NewGuid().ToString("N") + ".txt");
82
+ string lispCode = "(progn(setq f(open \"" + tempFile.Replace("\\", "\\\\") + "\" \"w\"))(print " + code + " f)(close f))";
134
83
  dynamic cad = System.Runtime.InteropServices.Marshal.GetActiveObject(progId);
135
84
  if (cad != null) {
136
85
  dynamic doc = cad.ActiveDocument;
137
86
  if (doc != null) {
138
87
  doc.SendCommand(lispCode + "\n");
139
88
  System.Threading.Thread.Sleep(500);
140
-
141
89
  if (System.IO.File.Exists(tempFile)) {
142
- string result = System.IO.File.ReadAllText(tempFile).Trim();
143
- try { System.IO.File.Delete(tempFile); } catch {}
90
+ string result = System.Text.Encoding.GetEncoding("GBK").GetString(System.IO.File.ReadAllBytes(tempFile)).Trim();
91
+ System.IO.File.Delete(tempFile);
144
92
  if (!string.IsNullOrEmpty(result)) {
145
93
  return new { success = true, result = result };
146
94
  }
147
- // Try evaluate result directly if file is empty
148
95
  return new { success = true, result = "" };
149
96
  }
150
97
  return new { success = true, result = "" };
151
98
  }
152
99
  }
153
100
  } catch (Exception ex) {
154
- try { if (System.IO.File.Exists(tempFile)) System.IO.File.Delete(tempFile); } catch {}
101
+ System.Diagnostics.Debug.WriteLine("cadSendWithResult error: " + ex.Message);
102
+ if (tempFile != null && System.IO.File.Exists(tempFile)) {
103
+ try { System.IO.File.Delete(tempFile); } catch {}
104
+ }
155
105
  return new { success = false, error = ex.Message };
156
106
  }
157
- try { if (System.IO.File.Exists(tempFile)) System.IO.File.Delete(tempFile); } catch {}
107
+ if (tempFile != null && System.IO.File.Exists(tempFile)) {
108
+ try { System.IO.File.Delete(tempFile); } catch {}
109
+ }
158
110
  return new { success = false };
159
111
  }
160
112
  */});
@@ -180,9 +132,9 @@ process.stdin.on('data', (data) => {
180
132
 
181
133
  const cadIsBusy = edge.func(function() {/*
182
134
  async (input) => {
183
- string platform = input.ToString();
184
- string progId = platform + ".Application";
185
135
  try {
136
+ string platform = input.ToString();
137
+ string progId = platform + ".Application";
186
138
  dynamic cad = System.Runtime.InteropServices.Marshal.GetActiveObject(progId);
187
139
  if (cad != null) {
188
140
  bool isBusy = false;
@@ -193,37 +145,42 @@ const cadIsBusy = edge.func(function() {/*
193
145
  }
194
146
  return new { isBusy = isBusy };
195
147
  }
196
- } catch {}
148
+ } catch (Exception ex) {
149
+ System.Diagnostics.Debug.WriteLine("cadIsBusy error: " + ex.Message);
150
+ }
197
151
  return new { isBusy = false };
198
152
  }
199
153
  */});
200
154
 
201
155
  const cadHasDoc = edge.func(function() {/*
202
156
  async (input) => {
203
- string platform = input.ToString();
204
- string progId = platform + ".Application";
205
157
  try {
158
+ string platform = input.ToString();
159
+ string progId = platform + ".Application";
206
160
  dynamic cad = System.Runtime.InteropServices.Marshal.GetActiveObject(progId);
207
161
  if (cad != null) {
208
162
  int docCount = cad.Documents.Count;
209
163
  return new { hasDoc = docCount > 0, docCount = docCount };
210
164
  }
211
- } catch {}
165
+ } catch (Exception ex) {
166
+ System.Diagnostics.Debug.WriteLine("cadHasDoc error: " + ex.Message);
167
+ }
212
168
  return new { hasDoc = false, docCount = 0 };
213
169
  }
214
170
  */});
215
171
 
216
172
  const cadNewDoc = edge.func(function() {/*
217
173
  async (input) => {
218
- string platform = input.ToString();
219
- string progId = platform + ".Application";
220
174
  try {
175
+ string platform = input.ToString();
176
+ string progId = platform + ".Application";
221
177
  dynamic cad = System.Runtime.InteropServices.Marshal.GetActiveObject(progId);
222
178
  if (cad != null) {
223
179
  cad.Documents.Add();
224
180
  return new { success = true };
225
181
  }
226
182
  } catch (Exception ex) {
183
+ System.Diagnostics.Debug.WriteLine("cadNewDoc error: " + ex.Message);
227
184
  return new { success = false, error = ex.Message };
228
185
  }
229
186
  return new { success = false, error = "CAD not found" };
@@ -246,7 +203,42 @@ async function waitForIdle(platform) {
246
203
  return false;
247
204
  }
248
205
 
206
+ function checkParens(code) {
207
+ let depth = 0;
208
+ let inString = false;
209
+ let escapeNext = false;
210
+ for (let i = 0; i < code.length; i++) {
211
+ const ch = code[i];
212
+ if (escapeNext) {
213
+ escapeNext = false;
214
+ continue;
215
+ }
216
+ if (ch === '\\') {
217
+ escapeNext = true;
218
+ continue;
219
+ }
220
+ if (ch === '"') {
221
+ inString = !inString;
222
+ continue;
223
+ }
224
+ if (inString) continue;
225
+ if (ch === ';') {
226
+ while (i < code.length && code[i] !== '\n') i++;
227
+ continue;
228
+ }
229
+ if (ch === '(') depth++;
230
+ if (ch === ')') depth--;
231
+ if (depth < 0) return { valid: false, error: '多余右括号 )', pos: i };
232
+ }
233
+ if (depth > 0) return { valid: false, error: `缺少 ${depth} 个右括号 )`, pos: code.length - 1 };
234
+ if (depth < 0) return { valid: false, error: `多余 ${-depth} 个左括号 (` };
235
+ return { valid: true };
236
+ }
237
+
249
238
  async function handleMessage(msg) {
239
+ if (msg.type === 'ping') {
240
+ return { pong: true };
241
+ }
250
242
  if (msg.type === 'connect') {
251
243
  return new Promise((resolve, reject) => {
252
244
  cadConnect({}, (e, r) => {
@@ -281,6 +273,10 @@ async function handleMessage(msg) {
281
273
  if (!docCheck.hasDoc) {
282
274
  return { success: false, error: 'No open document' };
283
275
  }
276
+ const parenCheck = checkParens(msg.code);
277
+ if (!parenCheck.valid) {
278
+ return { success: false, error: `括号错误: ${parenCheck.error} (位置 ${parenCheck.pos})` };
279
+ }
284
280
  if (!(await waitForIdle(msg.platform))) {
285
281
  return { success: false, error: 'CAD is busy, timeout' };
286
282
  }
@@ -301,6 +297,10 @@ async function handleMessage(msg) {
301
297
  if (!docCheck.hasDoc) {
302
298
  return { success: false, error: 'No open document' };
303
299
  }
300
+ const parenCheck = checkParens(msg.code);
301
+ if (!parenCheck.valid) {
302
+ return { success: false, error: `括号错误: ${parenCheck.error} (位置 ${parenCheck.pos})` };
303
+ }
304
304
  if (!(await waitForIdle(msg.platform))) {
305
305
  return { success: false, error: 'CAD is busy, timeout' };
306
306
  }