@atlisp/mcp 1.0.8 → 1.0.10

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.
Files changed (3) hide show
  1. package/package.json +8 -4
  2. package/src/cad.js +20 -2
  3. package/src/index.js +118 -80
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlisp/mcp",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
4
4
  "description": "MCP Server for @lisp on CAD",
5
5
  "type": "module",
6
6
  "bin": {
@@ -16,7 +16,8 @@
16
16
  ],
17
17
  "scripts": {
18
18
  "start": "node src/index.js",
19
- "test": "echo \"No tests yet\" && exit 0"
19
+ "test": "vitest run",
20
+ "test:watch": "vitest"
20
21
  },
21
22
  "keywords": [
22
23
  "mcp",
@@ -37,10 +38,13 @@
37
38
  },
38
39
  "dependencies": {
39
40
  "@modelcontextprotocol/sdk": "^1.0.0",
40
- "edge-js": "^25.0.1"
41
+ "edge-js": "^25.0.1",
42
+ "express": "^5.2.1"
41
43
  },
42
44
  "engines": {
43
45
  "node": ">=18.0.0"
46
+ },
47
+ "devDependencies": {
48
+ "vitest": "^4.1.5"
44
49
  }
45
-
46
50
  }
package/src/cad.js CHANGED
@@ -20,7 +20,14 @@ let worker = null;
20
20
  let _platform = null;
21
21
  let _version = null;
22
22
 
23
- function getWorker() {
23
+ export function resetWorker() {
24
+ if (worker) {
25
+ worker.kill();
26
+ worker = null;
27
+ }
28
+ }
29
+
30
+ export function getWorker() {
24
31
  if (!worker || !worker.connected) {
25
32
  worker = spawn('node', [workerPath], {
26
33
  stdio: ['pipe', 'pipe', 'pipe']
@@ -36,7 +43,7 @@ function getWorker() {
36
43
  return worker;
37
44
  }
38
45
 
39
- function sendMessage(msg) {
46
+ export function sendMessage(msg) {
40
47
  return new Promise((resolve, reject) => {
41
48
  const w = getWorker();
42
49
  let responded = false;
@@ -177,6 +184,17 @@ class CadConnection {
177
184
  worker = null;
178
185
  }
179
186
  }
187
+
188
+ _reset() {
189
+ this.connected = false;
190
+ this.version = null;
191
+ this.product = null;
192
+ _platform = null;
193
+ if (worker) {
194
+ worker.kill();
195
+ worker = null;
196
+ }
197
+ }
180
198
  }
181
199
 
182
200
  export const cad = new CadConnection();
package/src/index.js CHANGED
@@ -20,15 +20,19 @@ log('Server starting, cad loaded: ' + typeof cad);
20
20
  const PORT = process.env.PORT || 8110;
21
21
  const HOST = process.env.HOST || '0.0.0.0';
22
22
  const TRANSPORT = process.env.TRANSPORT || 'http';
23
+ const API_KEY = process.env.MCP_API_KEY || '';
24
+ if (API_KEY) {
25
+ log('API Key authentication enabled');
26
+ }
23
27
 
24
- const CAD_PLATFORMS = ['AutoCAD', 'ZWCAD', 'GStarCAD', 'BricsCAD'];
25
- const FILE_EXTENSIONS = {
28
+ export const CAD_PLATFORMS = ['AutoCAD', 'ZWCAD', 'GStarCAD', 'BricsCAD'];
29
+ export const FILE_EXTENSIONS = {
26
30
  'AutoCAD': ['.fas', '.vlx'],
27
31
  'ZWCAD': ['.zelx', '.vls'],
28
32
  'BricsCAD': ['.des']
29
33
  };
30
34
 
31
- const MOCK_PACKAGES = [
35
+ export const MOCK_PACKAGES = [
32
36
  { name: 'base', description: '基础函数库', version: '1.0.0' },
33
37
  { name: 'at-pm', description: '工程项目管理', version: '2.1.0' },
34
38
  { name: 'network', description: '网络功能模块', version: '1.5.0' },
@@ -40,7 +44,7 @@ const MOCK_PACKAGES = [
40
44
  { name: 'function', description: '函数库', version: '3.0.0' }
41
45
  ];
42
46
 
43
- const tools = [
47
+ export const tools = [
44
48
  {
45
49
  name: 'connect_cad',
46
50
  description: '连接到 CAD (AutoCAD/ZWCAD/GStarCAD/BricsCAD)',
@@ -132,7 +136,7 @@ const tools = [
132
136
  }
133
137
  ];
134
138
 
135
- async function handleToolCall(name, args) {
139
+ export async function handleToolCall(name, args) {
136
140
  switch (name) {
137
141
  case 'connect_cad':
138
142
  log('connect_cad called, cad.connected before: ' + cad.connected);
@@ -198,8 +202,8 @@ async function handleToolCall(name, args) {
198
202
 
199
203
  case 'at_command':
200
204
  if (!cad.connected) return { content: [{ type: 'text', text: '请先连接 CAD' }], isError: true };
201
- await cad.sendCommand(`@${args.command}\n`);
202
- return { content: [{ type: 'text', text: `已发送命令: @${args.command}` }] };
205
+ await cad.sendCommand(`${args.command}\n`);
206
+ return { content: [{ type: 'text', text: `已发送命令: ${args.command}` }] };
203
207
 
204
208
  case 'new_document':
205
209
  if (!cad.connected) { await cad.connect(); }
@@ -218,7 +222,7 @@ async function handleToolCall(name, args) {
218
222
  return { content: [{ type: 'text', text: '已发送 @lisp 安装代码到 CAD' }] };
219
223
 
220
224
  default:
221
- throw new Error(`Unknown tool: ${name}`);
225
+ return { content: [{ type: 'text', text: `未知工具: ${name}` }], isError: true };
222
226
  }
223
227
  }
224
228
 
@@ -237,9 +241,9 @@ async function initCadConnection() {
237
241
  }
238
242
  }
239
243
 
240
- function createMcpServer() {
244
+ export function createMcpServer() {
241
245
  const server = new Server(
242
- { name: 'atlisp-mcp-server', version: '1.0.8' },
246
+ { name: 'atlisp-mcp-server', version: '1.0.10' },
243
247
  { capabilities: { tools: {} } }
244
248
  );
245
249
 
@@ -253,89 +257,123 @@ function createMcpServer() {
253
257
  return server;
254
258
  }
255
259
 
256
- if (TRANSPORT === 'stdio') {
257
- await initCadConnection();
258
- const mcpServer = createMcpServer();
259
- const transport = new StdioServerTransport();
260
- await mcpServer.connect(transport);
261
- console.error('@lisp MCP Server 运行在 stdio 模式');
262
- } else {
263
- const app = express();
264
- app.use(express.json({ type: ['application/json', 'application/json-rpc'] }));
265
-
266
- app.get('/health', (req, res) => {
267
- return res.json({ status: 'ok', cad: cad.connected ? 'connected' : 'disconnected' });
268
- });
269
-
270
- let mcpServer = null;
271
- const sessionId = crypto.randomUUID();
272
-
273
- app.post('/mcp', async (req, res) => {
274
- if (!mcpServer) {
275
- mcpServer = createMcpServer();
260
+ export async function startServer() {
261
+ if (TRANSPORT === 'stdio') {
262
+ await initCadConnection();
263
+ const mcpServer = createMcpServer();
264
+ const transport = new StdioServerTransport();
265
+ await mcpServer.connect(transport);
266
+ console.error('@lisp MCP Server 运行在 stdio 模式');
267
+ } else {
268
+ const app = express();
269
+ app.use(express.json({ type: ['application/json', 'application/json-rpc'] }));
270
+
271
+ if (API_KEY) {
272
+ app.use((req, res, next) => {
273
+ if (req.path === '/health') return next();
274
+ const auth = req.get('Authorization');
275
+ if (auth === `Bearer ${API_KEY}`) return next();
276
+ res.status(401).json({ error: 'Unauthorized' });
277
+ });
276
278
  }
277
279
 
278
- const { method, params, id } = req.body || {};
280
+ app.get('/health', (req, res) => {
281
+ res.json({ status: 'ok', cad: cad.connected ? 'connected' : 'disconnected' });
282
+ });
279
283
 
280
- res.setHeader('Content-Type', 'application/json');
281
- res.setHeader('mcp-session-id', sessionId);
284
+ let mcpServer = null;
285
+ const sessionId = crypto.randomUUID();
282
286
 
283
- if (!id) {
284
- return res.status(204).end();
285
- }
286
-
287
- try {
288
- if (method === 'initialize') {
289
- return res.json({
290
- jsonrpc: '2.0',
291
- id,
292
- result: {
293
- protocolVersion: '2024-11-05',
294
- capabilities: { tools: {} },
295
- serverInfo: { name: 'atlisp-mcp-server', version: '1.0.8' }
296
- }
297
- });
287
+ app.post('/mcp', async (req, res) => {
288
+ if (!mcpServer) {
289
+ mcpServer = createMcpServer();
298
290
  }
299
291
 
300
- if (method === 'tools/list') {
301
- return res.json({
302
- jsonrpc: '2.0',
303
- id,
304
- result: { tools }
305
- });
292
+ const body = req.body || {};
293
+ const method = body.method;
294
+ const params = body.params;
295
+ const id = body.id;
296
+ const accept = req.get('Accept') || '';
297
+ log('accept: "' + accept + '", wantsSSE: ' + accept.includes('text/event-stream'));
298
+
299
+ res.setHeader('mcp-session-id', sessionId);
300
+
301
+ const sendResponse = (data) => {
302
+ const json = JSON.stringify(data);
303
+ if (accept.includes('text/event-stream')) {
304
+ res.setHeader('Content-Type', 'text/event-stream');
305
+ return res.end('data: ' + json + '\n\n');
306
+ } else {
307
+ res.setHeader('Content-Type', 'application/json');
308
+ return res.end(json);
309
+ }
310
+ };
311
+
312
+ if (!id) {
313
+ return res.status(204).end();
306
314
  }
307
315
 
308
- if (method === 'tools/call') {
309
- const toolName = params?.name;
310
- if (!toolName) {
311
- return res.json({
316
+ try {
317
+ if (method === 'initialize') {
318
+ sendResponse({
319
+ jsonrpc: '2.0',
320
+ id,
321
+ result: {
322
+ protocolVersion: '2024-11-05',
323
+ capabilities: { tools: {} },
324
+ serverInfo: { name: 'atlisp-mcp-server', version: '1.0.10' }
325
+ }
326
+ });
327
+ return;
328
+ }
329
+
330
+ if (method === 'tools/list') {
331
+ sendResponse({
312
332
  jsonrpc: '2.0',
313
333
  id,
314
- error: { code: -32602, message: 'Invalid params: missing tool name' }
334
+ result: { tools }
315
335
  });
336
+ return;
337
+ }
338
+
339
+ if (method === 'tools/call') {
340
+ const toolName = params?.name;
341
+ if (!toolName) {
342
+ sendResponse({
343
+ jsonrpc: '2.0',
344
+ id,
345
+ error: { code: -32602, message: 'Invalid params: missing tool name' }
346
+ });
347
+ return;
348
+ }
349
+ const toolArgs = params?.arguments || {};
350
+ const result = await handleToolCall(toolName, toolArgs);
351
+ sendResponse({ jsonrpc: '2.0', id, result });
352
+ return;
316
353
  }
317
- const toolArgs = params?.arguments || {};
318
- const result = await handleToolCall(toolName, toolArgs);
319
- return res.json({ jsonrpc: '2.0', id, result });
354
+
355
+ sendResponse({
356
+ jsonrpc: '2.0',
357
+ id,
358
+ error: { code: -32601, message: 'Method not found' }
359
+ });
360
+ } catch (e) {
361
+ log('MCP error: ' + e.message);
362
+ return res.status(500).json({
363
+ jsonrpc: '2.0',
364
+ id,
365
+ error: { code: -32603, message: e.message }
366
+ });
320
367
  }
368
+ });
321
369
 
322
- return res.json({
323
- jsonrpc: '2.0',
324
- id,
325
- error: { code: -32601, message: 'Method not found' }
326
- });
327
- } catch (e) {
328
- log('MCP error: ' + e.message);
329
- return res.status(500).json({
330
- jsonrpc: '2.0',
331
- id,
332
- error: { code: -32603, message: e.message }
333
- });
334
- }
335
- });
370
+ app.listen(PORT, HOST, async () => {
371
+ await initCadConnection();
372
+ console.error(`@lisp MCP Server 启动 - http://${HOST}:${PORT} - Streamable HTTP`);
373
+ });
374
+ }
375
+ }
336
376
 
337
- app.listen(PORT, HOST, async () => {
338
- await initCadConnection();
339
- console.error(`@lisp MCP Server 启动 - http://${HOST}:${PORT} - Streamable HTTP`);
340
- });
377
+ if (!process.env.VITEST) {
378
+ await startServer();
341
379
  }