@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 +16 -1
- package/package.json +1 -1
- package/src/cad-worker.js +151 -12
- package/src/cad.js +43 -0
- package/src/index.js +33 -1
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
|
|
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
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
|
|
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
|
-
|
|
155
|
-
if (
|
|
156
|
-
|
|
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 {
|
|
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
|
|
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 (!
|
|
189
|
-
|
|
190
|
-
|
|
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 };
|