@atlisp/mcp 1.0.9 → 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 +112 -88
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlisp/mcp",
3
- "version": "1.0.9",
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);
@@ -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.9' },
246
+ { name: 'atlisp-mcp-server', version: '1.0.10' },
243
247
  { capabilities: { tools: {} } }
244
248
  );
245
249
 
@@ -253,103 +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
- 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 || {};
279
- const accept = req.get('Accept') || '';
280
- log('accept: "' + accept + '", wantsSSE: ' + accept.includes('text/event-stream'));
280
+ app.get('/health', (req, res) => {
281
+ res.json({ status: 'ok', cad: cad.connected ? 'connected' : 'disconnected' });
282
+ });
281
283
 
282
- res.setHeader('mcp-session-id', sessionId);
284
+ let mcpServer = null;
285
+ const sessionId = crypto.randomUUID();
283
286
 
284
- const sendResponse = (data) => {
285
- const json = JSON.stringify(data);
286
- if (accept.includes('text/event-stream')) {
287
- res.setHeader('Content-Type', 'text/event-stream');
288
- return res.end('data: ' + json + '\n\n');
289
- } else {
290
- res.setHeader('Content-Type', 'application/json');
291
- return res.end(json);
287
+ app.post('/mcp', async (req, res) => {
288
+ if (!mcpServer) {
289
+ mcpServer = createMcpServer();
292
290
  }
293
- };
294
291
 
295
- if (!id) {
296
- return res.status(204).end();
297
- }
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
+ };
298
311
 
299
- try {
300
- if (method === 'initialize') {
301
- sendResponse({
302
- jsonrpc: '2.0',
303
- id,
304
- result: {
305
- protocolVersion: '2024-11-05',
306
- capabilities: { tools: {} },
307
- serverInfo: { name: 'atlisp-mcp-server', version: '1.0.9' }
308
- }
309
- });
312
+ if (!id) {
313
+ return res.status(204).end();
310
314
  }
311
315
 
312
- if (method === 'tools/list') {
313
- sendResponse({
314
- jsonrpc: '2.0',
315
- id,
316
- result: { tools }
317
- });
318
- }
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
+ }
319
329
 
320
- if (method === 'tools/call') {
321
- const toolName = params?.name;
322
- if (!toolName) {
330
+ if (method === 'tools/list') {
323
331
  sendResponse({
324
332
  jsonrpc: '2.0',
325
333
  id,
326
- error: { code: -32602, message: 'Invalid params: missing tool name' }
334
+ result: { tools }
327
335
  });
328
336
  return;
329
337
  }
330
- const toolArgs = params?.arguments || {};
331
- const result = await handleToolCall(toolName, toolArgs);
332
- sendResponse({ jsonrpc: '2.0', id, result });
333
- return;
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;
353
+ }
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
+ });
334
367
  }
368
+ });
335
369
 
336
- sendResponse({
337
- jsonrpc: '2.0',
338
- id,
339
- error: { code: -32601, message: 'Method not found' }
340
- });
341
- } catch (e) {
342
- log('MCP error: ' + e.message);
343
- return res.status(500).json({
344
- jsonrpc: '2.0',
345
- id,
346
- error: { code: -32603, message: e.message }
347
- });
348
- }
349
- });
370
+ app.listen(PORT, HOST, async () => {
371
+ await initCadConnection();
372
+ console.error(`@lisp MCP Server 启动 - http://${HOST}:${PORT} - Streamable HTTP`);
373
+ });
374
+ }
375
+ }
350
376
 
351
- app.listen(PORT, HOST, async () => {
352
- await initCadConnection();
353
- console.error(`@lisp MCP Server 启动 - http://${HOST}:${PORT} - Streamable HTTP`);
354
- });
377
+ if (!process.env.VITEST) {
378
+ await startServer();
355
379
  }