@atlisp/mcp 1.0.5 → 1.0.6

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
@@ -11,6 +11,8 @@ MCP (Model Context Protocol) Server for @lisp on CAD - 为 CAD 环境提供 @lis
11
11
  - 获取 CAD 平台信息
12
12
  - 通过 MCP 协议提供标准化接口
13
13
  - 管理 @lisp 包(列出、搜索、安装)
14
+ - 自动检测 CAD 状态(忙碌/空闲)
15
+ - CAD 无文档时自动处理
14
16
 
15
17
 
16
18
  ## 安装
@@ -144,7 +146,8 @@ HTTP 模式:`POST http://localhost:8110/mcp`
144
146
  | 工具名称 | 描述 | 参数 |
145
147
  |---------|------|------|
146
148
  | `connect_cad` | 连接到 CAD | 无 |
147
- | `eval_lisp` | 在 CAD 中执行 AutoLISP 代码 | `code`: 字符串 |
149
+ | `eval_lisp` | 在 CAD 中执行 AutoLISP 代码(不返回结果) | `code`: 字符串 |
150
+ | `eval_lisp_with_result` | 在 CAD 中执行 AutoLISP 代码并返回结果 | `code`: 字符串 |
148
151
  | `get_cad_info` | 获取当前 CAD 信息 | 无 |
149
152
  | `list_packages` | 列出已安装的 @lisp 包 | 无 |
150
153
  | `search_packages` | 搜索 @lisp 包 | `query`: 搜索关键词 |
@@ -153,6 +156,7 @@ HTTP 模式:`POST http://localhost:8110/mcp`
153
156
  | `get_file_extensions` | 获取指定平台的文件扩展名 | `platform`: 平台名称 |
154
157
  | `get_install_code` | 获取 @lisp 安装代码 | 无 |
155
158
  | `at_command` | 执行 @lisp 命令 | `command`: 命令 |
159
+ | `new_document` | 在 CAD 中新建空白文档 | 无 |
156
160
  | `install_atlisp` | 在 CAD 中安装 @lisp | 无 |
157
161
 
158
162
  ## 支持的平台
@@ -168,6 +172,9 @@ HTTP 模式:`POST http://localhost:8110/mcp`
168
172
  2. .NET 代码通过 COM 互操作连接 CAD 应用程序
169
173
  3. 支持获取活动 CAD 实例或启动新实例
170
174
  4. 通过 `SendCommand` 方法向 CAD 发送 LISP 代码
175
+ 5. 执行命令前自动检测 CAD 状态(IsQuiescent)
176
+ 6. CAD 忙碌时自动等待(最多 10 次重试)
177
+ 7. CAD 无打开文档时返回错误
171
178
 
172
179
  ## 示例
173
180
 
@@ -195,6 +202,14 @@ curl -X POST http://localhost:8110/mcp \
195
202
  -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"search_packages","arguments":{"query":"base"}}}'
196
203
  ```
197
204
 
205
+ ### 新建文档
206
+
207
+ ```bash
208
+ curl -X POST http://localhost:8110/mcp \
209
+ -H "Content-Type: application/json" \
210
+ -d '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"new_document","arguments":{}}}'
211
+ ```
212
+
198
213
  ## 注意事项
199
214
 
200
215
  - 仅支持 Windows 平台(依赖 COM 互操作)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlisp/mcp",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "description": "MCP Server for @lisp on CAD",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cad-worker.js CHANGED
@@ -105,7 +105,6 @@ const cadSend = edge.func(function() {/*
105
105
  if (cad != null) {
106
106
  dynamic doc = cad.ActiveDocument;
107
107
  if (doc == null || doc.Name == "") {
108
- // Try default template path
109
108
  string templatePath = @"C:\Users\Public\Documents\AutoCAD 2024\Sample\basics.dwg";
110
109
  if (System.IO.File.Exists(templatePath)) {
111
110
  cad.Documents.Open(templatePath, false);
@@ -125,6 +124,53 @@ const cadSend = edge.func(function() {/*
125
124
  }
126
125
  */});
127
126
 
127
+ const cadSendWithResult = edge.func(function() {/*
128
+ async (input) => {
129
+ dynamic d = input;
130
+ string code = d.code.ToString();
131
+ string platform = d.platform.ToString();
132
+ string progId = platform + ".Application";
133
+
134
+ string tempDir = System.IO.Path.GetTempPath();
135
+ string tempFile = System.IO.Path.Combine(tempDir, "atlisp_result_" + System.Guid.NewGuid().ToString("N") + ".txt");
136
+
137
+ string lispCode = code + "\n(setq _atlisp_result (princ-to-string " + code + "))(progn(setq f(open \"" + tempFile.Replace("\\", "\\\\") + "\" \"w\"))(write-line _atlisp_result f)(close f))\n";
138
+
139
+ try {
140
+ dynamic cad = System.Runtime.InteropServices.Marshal.GetActiveObject(progId);
141
+ if (cad != null) {
142
+ dynamic doc = cad.ActiveDocument;
143
+ if (doc == null || doc.Name == "") {
144
+ string templatePath = @"C:\Users\Public\Documents\AutoCAD 2024\Sample\basics.dwg";
145
+ if (System.IO.File.Exists(templatePath)) {
146
+ cad.Documents.Open(templatePath, false);
147
+ } else {
148
+ cad.Documents.Add("");
149
+ }
150
+ System.Threading.Thread.Sleep(2000);
151
+ }
152
+ doc = cad.ActiveDocument;
153
+ if (doc != null) {
154
+ doc.SendCommand(lispCode + "\n");
155
+ System.Threading.Thread.Sleep(500);
156
+
157
+ if (System.IO.File.Exists(tempFile)) {
158
+ string result = System.IO.File.ReadAllText(tempFile).Trim();
159
+ try { System.IO.File.Delete(tempFile); } catch {}
160
+ return new { success = true, result = result };
161
+ }
162
+ return new { success = true, result = "" };
163
+ }
164
+ }
165
+ } catch (Exception ex) {
166
+ try { if (System.IO.File.Exists(tempFile)) System.IO.File.Delete(tempFile); } catch {}
167
+ return new { success = false, error = ex.Message };
168
+ }
169
+ try { if (System.IO.File.Exists(tempFile)) System.IO.File.Delete(tempFile); } catch {}
170
+ return new { success = false };
171
+ }
172
+ */});
173
+
128
174
  process.stdin.setEncoding('utf8');
129
175
 
130
176
  process.stdin.on('data', (data) => {
@@ -144,22 +190,57 @@ process.stdin.on('data', (data) => {
144
190
  }
145
191
  });
146
192
 
147
- const cadHasDoc = edge.func(function() {/*
193
+ const cadIsBusy = edge.func(function() {/*
148
194
  async (input) => {
149
195
  string platform = input.ToString();
150
196
  string progId = platform + ".Application";
151
197
  try {
152
198
  dynamic cad = System.Runtime.InteropServices.Marshal.GetActiveObject(progId);
153
199
  if (cad != null) {
154
- dynamic doc = cad.ActiveDocument;
155
- if (doc != null && doc.Name != "") {
156
- return new { hasDoc = true, name = doc.Name };
200
+ bool isBusy = false;
201
+ if (platform == "ZWCAD") {
202
+ isBusy = !(cad.GetZcadState().IsQuiescent);
203
+ } else {
204
+ isBusy = !(cad.GetAcadState().IsQuiescent);
157
205
  }
206
+ return new { isBusy = isBusy };
158
207
  }
159
208
  } catch {}
160
- return new { hasDoc = false };
209
+ return new { isBusy = false };
161
210
  }
162
- */});
211
+ */});
212
+
213
+ const cadHasDoc = edge.func(function() {/*
214
+ async (input) => {
215
+ string platform = input.ToString();
216
+ string progId = platform + ".Application";
217
+ try {
218
+ dynamic cad = System.Runtime.InteropServices.Marshal.GetActiveObject(progId);
219
+ if (cad != null) {
220
+ int docCount = cad.Documents.Count;
221
+ return new { hasDoc = docCount > 0, docCount = docCount };
222
+ }
223
+ } catch {}
224
+ return new { hasDoc = false, docCount = 0 };
225
+ }
226
+ */});
227
+
228
+ const cadNewDoc = edge.func(function() {/*
229
+ async (input) => {
230
+ string platform = input.ToString();
231
+ string progId = platform + ".Application";
232
+ try {
233
+ dynamic cad = System.Runtime.InteropServices.Marshal.GetActiveObject(progId);
234
+ if (cad != null) {
235
+ cad.Documents.Add();
236
+ return new { success = true };
237
+ }
238
+ } catch (Exception ex) {
239
+ return new { success = false, error = ex.Message };
240
+ }
241
+ return new { success = false, error = "CAD not found" };
242
+ }
243
+ */});
163
244
 
164
245
  async function handleMessage(msg) {
165
246
  if (msg.type === 'connect') {
@@ -178,20 +259,38 @@ async function handleMessage(msg) {
178
259
  });
179
260
  });
180
261
  }
262
+ if (msg.type === 'newdoc') {
263
+ return new Promise((resolve, reject) => {
264
+ cadNewDoc(msg.platform || 'AutoCAD', (e, r) => {
265
+ if (e) reject(e);
266
+ else resolve(r);
267
+ });
268
+ });
269
+ }
181
270
  if (msg.type === 'send') {
182
- const checkResult = await new Promise((resolve, reject) => {
183
- cadHasDoc(msg.platform, (e, r) => {
271
+ const docCheck = await new Promise((resolve, reject) => {
272
+ cadHasDoc(msg.platform || 'AutoCAD', (e, r) => {
184
273
  if (e) reject(e);
185
274
  else resolve(r);
186
275
  });
187
276
  });
188
- if (!checkResult.hasDoc) {
189
- const newDoc = await new Promise((resolve, reject) => {
190
- cadSend({ code: "\n", platform: msg.platform }, (e, r) => {
277
+ if (!docCheck.hasDoc) {
278
+ return { success: false, error: 'No open document' };
279
+ }
280
+ let retries = 10;
281
+ while (retries > 0) {
282
+ const checkResult = await new Promise((resolve, reject) => {
283
+ cadIsBusy(msg.platform || 'AutoCAD', (e, r) => {
191
284
  if (e) reject(e);
192
285
  else resolve(r);
193
286
  });
194
287
  });
288
+ if (!checkResult.isBusy) break;
289
+ retries--;
290
+ if (retries > 0) await new Promise(resolve => setTimeout(resolve, 500));
291
+ }
292
+ if (retries === 0) {
293
+ return { success: false, error: 'CAD is busy, timeout' };
195
294
  }
196
295
  return new Promise((resolve, reject) => {
197
296
  cadSend({ code: msg.code, platform: msg.platform }, (e, r) => {
@@ -200,5 +299,45 @@ async function handleMessage(msg) {
200
299
  });
201
300
  });
202
301
  }
302
+ if (msg.type === 'sendResult') {
303
+ const docCheck = await new Promise((resolve, reject) => {
304
+ cadHasDoc(msg.platform || 'AutoCAD', (e, r) => {
305
+ if (e) reject(e);
306
+ else resolve(r);
307
+ });
308
+ });
309
+ if (!docCheck.hasDoc) {
310
+ return { success: false, error: 'No open document' };
311
+ }
312
+ let retries = 10;
313
+ while (retries > 0) {
314
+ const checkResult = await new Promise((resolve, reject) => {
315
+ cadIsBusy(msg.platform || 'AutoCAD', (e, r) => {
316
+ if (e) reject(e);
317
+ else resolve(r);
318
+ });
319
+ });
320
+ if (!checkResult.isBusy) break;
321
+ retries--;
322
+ if (retries > 0) await new Promise(resolve => setTimeout(resolve, 500));
323
+ }
324
+ if (retries === 0) {
325
+ return { success: false, error: 'CAD is busy, timeout' };
326
+ }
327
+ return new Promise((resolve, reject) => {
328
+ cadSendWithResult({ code: msg.code, platform: msg.platform }, (e, r) => {
329
+ if (e) reject(e);
330
+ else resolve(r);
331
+ });
332
+ });
333
+ }
334
+ if (msg.type === 'isBusy') {
335
+ return new Promise((resolve, reject) => {
336
+ cadIsBusy(msg.platform || 'AutoCAD', (e, r) => {
337
+ if (e) reject(e);
338
+ else resolve(r);
339
+ });
340
+ });
341
+ }
203
342
  return { error: 'unknown message type' };
204
343
  }
package/src/cad.js CHANGED
@@ -114,8 +114,51 @@ class CadConnection {
114
114
  }
115
115
  }
116
116
 
117
+ async sendCommandWithResult(code) {
118
+ if (!this.connected) throw new Error('未连接 CAD');
119
+ try {
120
+ const result = await sendMessage({ type: 'sendResult', code: code, platform: _platform });
121
+ if (result && result.success) {
122
+ return result.result || '';
123
+ }
124
+ return null;
125
+ } catch (e) {
126
+ return null;
127
+ }
128
+ }
129
+
117
130
  getVersion() { return this.version; }
118
131
  getPlatform() { return this.product; }
132
+
133
+ async isBusy() {
134
+ if (!this.connected) return false;
135
+ try {
136
+ const result = await sendMessage({ type: 'isBusy', platform: _platform });
137
+ return result && result.isBusy;
138
+ } catch (e) {
139
+ return false;
140
+ }
141
+ }
142
+
143
+ async hasDoc() {
144
+ if (!this.connected) return { hasDoc: false, docCount: 0 };
145
+ try {
146
+ const result = await sendMessage({ type: 'hasdoc', platform: _platform });
147
+ return result || { hasDoc: false, docCount: 0 };
148
+ } catch (e) {
149
+ return { hasDoc: false, docCount: 0 };
150
+ }
151
+ }
152
+
153
+ async newDoc() {
154
+ if (!this.connected) throw new Error('未连接 CAD');
155
+ try {
156
+ const result = await sendMessage({ type: 'newdoc', platform: _platform });
157
+ return result && result.success;
158
+ } catch (e) {
159
+ return false;
160
+ }
161
+ }
119
162
 
120
163
  async getInfo() {
121
164
  if (!this.connected) return 'CAD 未连接';
package/src/index.js CHANGED
@@ -48,7 +48,16 @@ const tools = [
48
48
  },
49
49
  {
50
50
  name: 'eval_lisp',
51
- description: '在 CAD 中执行 AutoLISP 代码',
51
+ description: '在 CAD 中执行 AutoLISP 代码(不返回结果)',
52
+ inputSchema: {
53
+ type: 'object',
54
+ properties: { code: { type: 'string', description: '要执行的 LISP 代码' } },
55
+ required: ['code']
56
+ }
57
+ },
58
+ {
59
+ name: 'eval_lisp_with_result',
60
+ description: '在 CAD 中执行 AutoLISP 代码并返回结果',
52
61
  inputSchema: {
53
62
  type: 'object',
54
63
  properties: { code: { type: 'string', description: '要执行的 LISP 代码' } },
@@ -111,6 +120,11 @@ const tools = [
111
120
  required: ['command']
112
121
  }
113
122
  },
123
+ {
124
+ name: 'new_document',
125
+ description: '在 CAD 中新建空白文档',
126
+ inputSchema: { type: 'object', properties: {} }
127
+ },
114
128
  {
115
129
  name: 'install_atlisp',
116
130
  description: '在 CAD 中安装 @lisp',
@@ -140,6 +154,15 @@ async function handleToolCall(name, args) {
140
154
  await cad.sendCommand(args.code + '\n');
141
155
  return { content: [{ type: 'text', text: `已发送命令: ${args.code}` }] };
142
156
 
157
+ case 'eval_lisp_with_result':
158
+ if (!cad.connected) { await cad.connect(); }
159
+ if (!cad.connected) return { content: [{ type: 'text', text: '未连接 CAD' }], isError: true };
160
+ const result = await cad.sendCommandWithResult(args.code);
161
+ if (result !== null) {
162
+ return { content: [{ type: 'text', text: result }] };
163
+ }
164
+ return { content: [{ type: 'text', text: '执行失败或无返回值' }], isError: true };
165
+
143
166
  case 'get_cad_info':
144
167
  if (!cad.connected) { await cad.connect(); }
145
168
  if (!cad.connected) return { content: [{ type: 'text', text: 'CAD 未连接' }] };
@@ -178,6 +201,15 @@ async function handleToolCall(name, args) {
178
201
  await cad.sendCommand(`@${args.command}\n`);
179
202
  return { content: [{ type: 'text', text: `已发送命令: @${args.command}` }] };
180
203
 
204
+ case 'new_document':
205
+ if (!cad.connected) { await cad.connect(); }
206
+ if (!cad.connected) return { content: [{ type: 'text', text: '未连接 CAD' }], isError: true };
207
+ const docResult = await cad.newDoc();
208
+ if (docResult) {
209
+ return { content: [{ type: 'text', text: '已在 CAD 中新建空白文档' }] };
210
+ }
211
+ return { content: [{ type: 'text', text: '新建文档失败' }], isError: true };
212
+
181
213
  case 'install_atlisp':
182
214
  if (!cad.connected) { await cad.connect(); }
183
215
  if (!cad.connected) return { content: [{ type: 'text', text: '未连接 CAD' }], isError: true };