@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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlisp/mcp",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "MCP Server for @lisp on CAD",
5
5
  "type": "module",
6
6
  "bin": {
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
- 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";
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
- return new { success = true, result = result };
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
- let retries = 10;
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
- 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) {
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
- }, 60000);
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
- log('POST /mcp called, body: ' + JSON.stringify(req.body));
270
- try {
271
- const message = req.body;
272
- const id = message.id;
274
+ if (!mcpServer) {
275
+ mcpServer = createMcpServer();
276
+ }
273
277
 
274
- if (!id) {
275
- log('No id, returning 204');
276
- return res.status(204).end();
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
- if (message.method === 'initialize') {
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: 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.0' }
295
+ serverInfo: { name: 'atlisp-mcp-server', version: '1.0.8' }
287
296
  }
288
297
  });
289
298
  }
290
299
 
291
- if (message.method === 'tools/list') {
300
+ if (method === 'tools/list') {
292
301
  return res.json({
293
302
  jsonrpc: '2.0',
294
- id: id,
303
+ id,
295
304
  result: { tools }
296
305
  });
297
306
  }
298
307
 
299
- if (message.method === 'tools/call') {
300
- log('tools/call called, name: ' + message.params.name);
301
- const result = await handleToolCall(message.params.name, message.params.arguments || {});
302
- log('tools/call result: ' + JSON.stringify(result));
303
- return res.json({
304
- jsonrpc: '2.0',
305
- id: id,
306
- result
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: id,
324
+ id,
313
325
  error: { code: -32601, message: 'Method not found' }
314
326
  });
315
327
  } catch (e) {
316
- res.status(500).json({
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
- const status = cad.connected ? '已连接 CAD' : '未连接 CAD';
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
+ }