@atlisp/mcp 1.0.6 → 1.0.8
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 +3 -0
- package/package.json +1 -1
- package/src/cad-worker.js +30 -50
- package/src/cad.js +3 -1
- package/src/index.js +58 -45
package/README.md
CHANGED
|
@@ -123,6 +123,9 @@ atlisp-mcp
|
|
|
123
123
|
| `TRANSPORT` | `http` | 传输模式:`http` 或 `stdio` |
|
|
124
124
|
| `PORT` | `8110` | HTTP 模式监听端口 |
|
|
125
125
|
| `HOST` | `0.0.0.0` | HTTP 模式监听地址 |
|
|
126
|
+
| `MESSAGE_TIMEOUT` | `60000` | 消息超时(毫秒) |
|
|
127
|
+
| `BUSY_RETRIES` | `10` | CAD 忙碌时重试次数 |
|
|
128
|
+
| `BUSY_DELAY` | `500` | CAD 忙碌重试间隔(毫秒) |
|
|
126
129
|
|
|
127
130
|
## API
|
|
128
131
|
|
package/package.json
CHANGED
package/src/cad-worker.js
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
import edge from 'edge-js';
|
|
3
3
|
import process from 'process';
|
|
4
4
|
|
|
5
|
+
const BUSY_RETRIES = parseInt(process.env.BUSY_RETRIES || '10', 10);
|
|
6
|
+
const BUSY_DELAY = parseInt(process.env.BUSY_DELAY || '500', 10);
|
|
7
|
+
|
|
5
8
|
const cadConnect = edge.func(function() {/*
|
|
6
9
|
async (input) => {
|
|
7
10
|
// Try existing CAD instances
|
|
@@ -104,16 +107,6 @@ const cadSend = edge.func(function() {/*
|
|
|
104
107
|
dynamic cad = System.Runtime.InteropServices.Marshal.GetActiveObject(progId);
|
|
105
108
|
if (cad != null) {
|
|
106
109
|
dynamic doc = cad.ActiveDocument;
|
|
107
|
-
if (doc == null || doc.Name == "") {
|
|
108
|
-
string templatePath = @"C:\Users\Public\Documents\AutoCAD 2024\Sample\basics.dwg";
|
|
109
|
-
if (System.IO.File.Exists(templatePath)) {
|
|
110
|
-
cad.Documents.Open(templatePath, false);
|
|
111
|
-
} else {
|
|
112
|
-
cad.Documents.Add("");
|
|
113
|
-
}
|
|
114
|
-
System.Threading.Thread.Sleep(2000);
|
|
115
|
-
}
|
|
116
|
-
doc = cad.ActiveDocument;
|
|
117
110
|
if (doc != null) {
|
|
118
111
|
doc.SendCommand(code);
|
|
119
112
|
return new { success = true };
|
|
@@ -122,7 +115,7 @@ const cadSend = edge.func(function() {/*
|
|
|
122
115
|
} catch {}
|
|
123
116
|
return new { success = false };
|
|
124
117
|
}
|
|
125
|
-
*/});
|
|
118
|
+
*/});
|
|
126
119
|
|
|
127
120
|
const cadSendWithResult = edge.func(function() {/*
|
|
128
121
|
async (input) => {
|
|
@@ -134,22 +127,13 @@ const cadSendWithResult = edge.func(function() {/*
|
|
|
134
127
|
string tempDir = System.IO.Path.GetTempPath();
|
|
135
128
|
string tempFile = System.IO.Path.Combine(tempDir, "atlisp_result_" + System.Guid.NewGuid().ToString("N") + ".txt");
|
|
136
129
|
|
|
137
|
-
|
|
130
|
+
// Use print to write result directly to file
|
|
131
|
+
string lispCode = "(progn(setq f(open \"" + tempFile.Replace("\\", "\\\\") + "\" \"w\"))(print " + code + " f)(close f))";
|
|
138
132
|
|
|
139
133
|
try {
|
|
140
134
|
dynamic cad = System.Runtime.InteropServices.Marshal.GetActiveObject(progId);
|
|
141
135
|
if (cad != null) {
|
|
142
136
|
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
137
|
if (doc != null) {
|
|
154
138
|
doc.SendCommand(lispCode + "\n");
|
|
155
139
|
System.Threading.Thread.Sleep(500);
|
|
@@ -157,7 +141,11 @@ const cadSendWithResult = edge.func(function() {/*
|
|
|
157
141
|
if (System.IO.File.Exists(tempFile)) {
|
|
158
142
|
string result = System.IO.File.ReadAllText(tempFile).Trim();
|
|
159
143
|
try { System.IO.File.Delete(tempFile); } catch {}
|
|
160
|
-
|
|
144
|
+
if (!string.IsNullOrEmpty(result)) {
|
|
145
|
+
return new { success = true, result = result };
|
|
146
|
+
}
|
|
147
|
+
// Try evaluate result directly if file is empty
|
|
148
|
+
return new { success = true, result = "" };
|
|
161
149
|
}
|
|
162
150
|
return new { success = true, result = "" };
|
|
163
151
|
}
|
|
@@ -169,7 +157,7 @@ const cadSendWithResult = edge.func(function() {/*
|
|
|
169
157
|
try { if (System.IO.File.Exists(tempFile)) System.IO.File.Delete(tempFile); } catch {}
|
|
170
158
|
return new { success = false };
|
|
171
159
|
}
|
|
172
|
-
*/});
|
|
160
|
+
*/});
|
|
173
161
|
|
|
174
162
|
process.stdin.setEncoding('utf8');
|
|
175
163
|
|
|
@@ -242,6 +230,22 @@ const cadNewDoc = edge.func(function() {/*
|
|
|
242
230
|
}
|
|
243
231
|
*/});
|
|
244
232
|
|
|
233
|
+
async function waitForIdle(platform) {
|
|
234
|
+
let retries = BUSY_RETRIES;
|
|
235
|
+
while (retries > 0) {
|
|
236
|
+
const checkResult = await new Promise((resolve, reject) => {
|
|
237
|
+
cadIsBusy(platform || 'AutoCAD', (e, r) => {
|
|
238
|
+
if (e) reject(e);
|
|
239
|
+
else resolve(r);
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
if (!checkResult.isBusy) return true;
|
|
243
|
+
retries--;
|
|
244
|
+
if (retries > 0) await new Promise(resolve => setTimeout(resolve, BUSY_DELAY));
|
|
245
|
+
}
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
|
|
245
249
|
async function handleMessage(msg) {
|
|
246
250
|
if (msg.type === 'connect') {
|
|
247
251
|
return new Promise((resolve, reject) => {
|
|
@@ -277,19 +281,7 @@ async function handleMessage(msg) {
|
|
|
277
281
|
if (!docCheck.hasDoc) {
|
|
278
282
|
return { success: false, error: 'No open document' };
|
|
279
283
|
}
|
|
280
|
-
|
|
281
|
-
while (retries > 0) {
|
|
282
|
-
const checkResult = await new Promise((resolve, reject) => {
|
|
283
|
-
cadIsBusy(msg.platform || 'AutoCAD', (e, r) => {
|
|
284
|
-
if (e) reject(e);
|
|
285
|
-
else resolve(r);
|
|
286
|
-
});
|
|
287
|
-
});
|
|
288
|
-
if (!checkResult.isBusy) break;
|
|
289
|
-
retries--;
|
|
290
|
-
if (retries > 0) await new Promise(resolve => setTimeout(resolve, 500));
|
|
291
|
-
}
|
|
292
|
-
if (retries === 0) {
|
|
284
|
+
if (!(await waitForIdle(msg.platform))) {
|
|
293
285
|
return { success: false, error: 'CAD is busy, timeout' };
|
|
294
286
|
}
|
|
295
287
|
return new Promise((resolve, reject) => {
|
|
@@ -309,19 +301,7 @@ async function handleMessage(msg) {
|
|
|
309
301
|
if (!docCheck.hasDoc) {
|
|
310
302
|
return { success: false, error: 'No open document' };
|
|
311
303
|
}
|
|
312
|
-
|
|
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) {
|
|
304
|
+
if (!(await waitForIdle(msg.platform))) {
|
|
325
305
|
return { success: false, error: 'CAD is busy, timeout' };
|
|
326
306
|
}
|
|
327
307
|
return new Promise((resolve, reject) => {
|
package/src/cad.js
CHANGED
|
@@ -11,6 +11,8 @@ const FILE_EXTENSIONS = {
|
|
|
11
11
|
'BricsCAD': ['.des']
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
+
const MESSAGE_TIMEOUT = parseInt(process.env.MESSAGE_TIMEOUT || '60000', 10);
|
|
15
|
+
|
|
14
16
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
15
17
|
const workerPath = path.join(__dirname, 'cad-worker.js');
|
|
16
18
|
|
|
@@ -44,7 +46,7 @@ function sendMessage(msg) {
|
|
|
44
46
|
responded = true;
|
|
45
47
|
reject(new Error('Timeout waiting for worker'));
|
|
46
48
|
}
|
|
47
|
-
},
|
|
49
|
+
}, MESSAGE_TIMEOUT);
|
|
48
50
|
|
|
49
51
|
const handler = (data) => {
|
|
50
52
|
try {
|
package/src/index.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import express from 'express';
|
|
2
2
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
3
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
-
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
|
|
5
4
|
import {
|
|
6
5
|
ListToolsRequestSchema,
|
|
7
6
|
CallToolRequestSchema,
|
|
@@ -9,6 +8,7 @@ import {
|
|
|
9
8
|
import fs from 'fs';
|
|
10
9
|
import path from 'path';
|
|
11
10
|
import os from 'os';
|
|
11
|
+
import crypto from 'crypto';
|
|
12
12
|
import { cad } from './cad.js';
|
|
13
13
|
|
|
14
14
|
const DEBUG_FILE = path.join(os.tmpdir(), 'mcp-server-debug.log');
|
|
@@ -222,21 +222,6 @@ async function handleToolCall(name, args) {
|
|
|
222
222
|
}
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
-
const mcpServer = new Server(
|
|
226
|
-
{ name: 'atlisp-mcp-server', version: '1.0.0' },
|
|
227
|
-
{ capabilities: { tools: {} } }
|
|
228
|
-
);
|
|
229
|
-
|
|
230
|
-
mcpServer.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
231
|
-
return { tools };
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
mcpServer.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
235
|
-
console.error('setRequestHandler called, name:', request.params.name);
|
|
236
|
-
const { name, arguments: args } = request.params;
|
|
237
|
-
return await handleToolCall(name, args || {});
|
|
238
|
-
});
|
|
239
|
-
|
|
240
225
|
async function initCadConnection() {
|
|
241
226
|
log('Attempting to connect to CAD on startup...');
|
|
242
227
|
try {
|
|
@@ -252,8 +237,25 @@ async function initCadConnection() {
|
|
|
252
237
|
}
|
|
253
238
|
}
|
|
254
239
|
|
|
240
|
+
function createMcpServer() {
|
|
241
|
+
const server = new Server(
|
|
242
|
+
{ name: 'atlisp-mcp-server', version: '1.0.8' },
|
|
243
|
+
{ capabilities: { tools: {} } }
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
|
|
247
|
+
|
|
248
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
249
|
+
const { name, arguments: args } = request.params;
|
|
250
|
+
return await handleToolCall(name, args || {});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
return server;
|
|
254
|
+
}
|
|
255
|
+
|
|
255
256
|
if (TRANSPORT === 'stdio') {
|
|
256
257
|
await initCadConnection();
|
|
258
|
+
const mcpServer = createMcpServer();
|
|
257
259
|
const transport = new StdioServerTransport();
|
|
258
260
|
await mcpServer.connect(transport);
|
|
259
261
|
console.error('@lisp MCP Server 运行在 stdio 模式');
|
|
@@ -262,59 +264,71 @@ if (TRANSPORT === 'stdio') {
|
|
|
262
264
|
app.use(express.json({ type: ['application/json', 'application/json-rpc'] }));
|
|
263
265
|
|
|
264
266
|
app.get('/health', (req, res) => {
|
|
265
|
-
res.json({ status: 'ok', cad: cad.connected ? 'connected' : 'disconnected' });
|
|
267
|
+
return res.json({ status: 'ok', cad: cad.connected ? 'connected' : 'disconnected' });
|
|
266
268
|
});
|
|
267
269
|
|
|
270
|
+
let mcpServer = null;
|
|
271
|
+
const sessionId = crypto.randomUUID();
|
|
272
|
+
|
|
268
273
|
app.post('/mcp', async (req, res) => {
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
const id = message.id;
|
|
274
|
+
if (!mcpServer) {
|
|
275
|
+
mcpServer = createMcpServer();
|
|
276
|
+
}
|
|
273
277
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
+
const { method, params, id } = req.body || {};
|
|
279
|
+
|
|
280
|
+
res.setHeader('Content-Type', 'application/json');
|
|
281
|
+
res.setHeader('mcp-session-id', sessionId);
|
|
278
282
|
|
|
279
|
-
|
|
283
|
+
if (!id) {
|
|
284
|
+
return res.status(204).end();
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
try {
|
|
288
|
+
if (method === 'initialize') {
|
|
280
289
|
return res.json({
|
|
281
290
|
jsonrpc: '2.0',
|
|
282
|
-
id
|
|
291
|
+
id,
|
|
283
292
|
result: {
|
|
284
293
|
protocolVersion: '2024-11-05',
|
|
285
294
|
capabilities: { tools: {} },
|
|
286
|
-
serverInfo: { name: 'atlisp-mcp-server', version: '1.0.
|
|
295
|
+
serverInfo: { name: 'atlisp-mcp-server', version: '1.0.8' }
|
|
287
296
|
}
|
|
288
297
|
});
|
|
289
298
|
}
|
|
290
299
|
|
|
291
|
-
if (
|
|
300
|
+
if (method === 'tools/list') {
|
|
292
301
|
return res.json({
|
|
293
302
|
jsonrpc: '2.0',
|
|
294
|
-
id
|
|
303
|
+
id,
|
|
295
304
|
result: { tools }
|
|
296
305
|
});
|
|
297
306
|
}
|
|
298
307
|
|
|
299
|
-
if (
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
}
|
|
308
|
+
if (method === 'tools/call') {
|
|
309
|
+
const toolName = params?.name;
|
|
310
|
+
if (!toolName) {
|
|
311
|
+
return res.json({
|
|
312
|
+
jsonrpc: '2.0',
|
|
313
|
+
id,
|
|
314
|
+
error: { code: -32602, message: 'Invalid params: missing tool name' }
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
const toolArgs = params?.arguments || {};
|
|
318
|
+
const result = await handleToolCall(toolName, toolArgs);
|
|
319
|
+
return res.json({ jsonrpc: '2.0', id, result });
|
|
308
320
|
}
|
|
309
321
|
|
|
310
|
-
res.json({
|
|
322
|
+
return res.json({
|
|
311
323
|
jsonrpc: '2.0',
|
|
312
|
-
id
|
|
324
|
+
id,
|
|
313
325
|
error: { code: -32601, message: 'Method not found' }
|
|
314
326
|
});
|
|
315
327
|
} catch (e) {
|
|
316
|
-
|
|
328
|
+
log('MCP error: ' + e.message);
|
|
329
|
+
return res.status(500).json({
|
|
317
330
|
jsonrpc: '2.0',
|
|
331
|
+
id,
|
|
318
332
|
error: { code: -32603, message: e.message }
|
|
319
333
|
});
|
|
320
334
|
}
|
|
@@ -322,7 +336,6 @@ if (TRANSPORT === 'stdio') {
|
|
|
322
336
|
|
|
323
337
|
app.listen(PORT, HOST, async () => {
|
|
324
338
|
await initCadConnection();
|
|
325
|
-
|
|
326
|
-
console.error(`@lisp MCP Server 启动 - http://${HOST}:${PORT} - ${status}`);
|
|
339
|
+
console.error(`@lisp MCP Server 启动 - http://${HOST}:${PORT} - Streamable HTTP`);
|
|
327
340
|
});
|
|
328
|
-
}
|
|
341
|
+
}
|