@atlisp/mcp 1.0.10 → 1.0.11

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/src/index.js DELETED
@@ -1,379 +0,0 @@
1
- import express from 'express';
2
- import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
- import {
5
- ListToolsRequestSchema,
6
- CallToolRequestSchema,
7
- } from '@modelcontextprotocol/sdk/types.js';
8
- import fs from 'fs';
9
- import path from 'path';
10
- import os from 'os';
11
- import crypto from 'crypto';
12
- import { cad } from './cad.js';
13
-
14
- const DEBUG_FILE = path.join(os.tmpdir(), 'mcp-server-debug.log');
15
- function log(msg) {
16
- fs.writeFileSync(DEBUG_FILE, new Date().toISOString() + ' ' + msg + '\n', { flag: 'a' });
17
- }
18
- log('Server starting, cad loaded: ' + typeof cad);
19
-
20
- const PORT = process.env.PORT || 8110;
21
- const HOST = process.env.HOST || '0.0.0.0';
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
- }
27
-
28
- export const CAD_PLATFORMS = ['AutoCAD', 'ZWCAD', 'GStarCAD', 'BricsCAD'];
29
- export const FILE_EXTENSIONS = {
30
- 'AutoCAD': ['.fas', '.vlx'],
31
- 'ZWCAD': ['.zelx', '.vls'],
32
- 'BricsCAD': ['.des']
33
- };
34
-
35
- export const MOCK_PACKAGES = [
36
- { name: 'base', description: '基础函数库', version: '1.0.0' },
37
- { name: 'at-pm', description: '工程项目管理', version: '2.1.0' },
38
- { name: 'network', description: '网络功能模块', version: '1.5.0' },
39
- { name: 'userman', description: '用户管理', version: '1.2.0' },
40
- { name: 'pkgman', description: '包管理器', version: '2.0.0' },
41
- { name: 'sidebar', description: '侧边栏工具', version: '1.0.0' },
42
- { name: 'aibot', description: 'AI 助手', version: '0.9.0' },
43
- { name: 'tips', description: '每日提示', version: '1.0.0' },
44
- { name: 'function', description: '函数库', version: '3.0.0' }
45
- ];
46
-
47
- export const tools = [
48
- {
49
- name: 'connect_cad',
50
- description: '连接到 CAD (AutoCAD/ZWCAD/GStarCAD/BricsCAD)',
51
- inputSchema: { type: 'object', properties: {} }
52
- },
53
- {
54
- name: 'eval_lisp',
55
- description: '在 CAD 中执行 AutoLISP 代码(不返回结果)',
56
- inputSchema: {
57
- type: 'object',
58
- properties: { code: { type: 'string', description: '要执行的 LISP 代码' } },
59
- required: ['code']
60
- }
61
- },
62
- {
63
- name: 'eval_lisp_with_result',
64
- description: '在 CAD 中执行 AutoLISP 代码并返回结果',
65
- inputSchema: {
66
- type: 'object',
67
- properties: { code: { type: 'string', description: '要执行的 LISP 代码' } },
68
- required: ['code']
69
- }
70
- },
71
- {
72
- name: 'get_cad_info',
73
- description: '获取当前 CAD 信息',
74
- inputSchema: { type: 'object', properties: {} }
75
- },
76
- {
77
- name: 'list_packages',
78
- description: '列出已安装的 @lisp 包',
79
- inputSchema: { type: 'object', properties: {} }
80
- },
81
- {
82
- name: 'search_packages',
83
- description: '搜索 @lisp 包',
84
- inputSchema: {
85
- type: 'object',
86
- properties: { query: { type: 'string', description: '搜索关键词' } },
87
- required: ['query']
88
- }
89
- },
90
- {
91
- name: 'install_package',
92
- description: '安装 @lisp 包到 CAD',
93
- inputSchema: {
94
- type: 'object',
95
- properties: { packageName: { type: 'string', description: '包名称' } },
96
- required: ['packageName']
97
- }
98
- },
99
- {
100
- name: 'get_platform_info',
101
- description: '获取 CAD 平台信息',
102
- inputSchema: { type: 'object', properties: {} }
103
- },
104
- {
105
- name: 'get_file_extensions',
106
- description: '获取指定平台的文件扩展名',
107
- inputSchema: {
108
- type: 'object',
109
- properties: { platform: { type: 'string', enum: CAD_PLATFORMS, description: 'CAD 平台' } },
110
- required: ['platform']
111
- }
112
- },
113
- {
114
- name: 'get_install_code',
115
- description: '获取 @lisp 安装代码',
116
- inputSchema: { type: 'object', properties: {} }
117
- },
118
- {
119
- name: 'at_command',
120
- description: '执行 @lisp 命令',
121
- inputSchema: {
122
- type: 'object',
123
- properties: { command: { type: 'string', description: '@lisp 命令' } },
124
- required: ['command']
125
- }
126
- },
127
- {
128
- name: 'new_document',
129
- description: '在 CAD 中新建空白文档',
130
- inputSchema: { type: 'object', properties: {} }
131
- },
132
- {
133
- name: 'install_atlisp',
134
- description: '在 CAD 中安装 @lisp',
135
- inputSchema: { type: 'object', properties: {} }
136
- }
137
- ];
138
-
139
- export async function handleToolCall(name, args) {
140
- switch (name) {
141
- case 'connect_cad':
142
- log('connect_cad called, cad.connected before: ' + cad.connected);
143
- try {
144
- log('calling cad.connect()...');
145
- const connected = await cad.connect();
146
- log('cad.connect() returned: ' + connected + ', cad.connected: ' + cad.connected);
147
- if (connected) {
148
- return { content: [{ type: 'text', text: `已连接到 ${cad.getPlatform()} ${cad.getVersion()}` }] };
149
- }
150
- } catch (e) {
151
- log('connect_cad error: ' + e.message);
152
- }
153
- return { content: [{ type: 'text', text: '未找到运行中的 CAD,请确保 CAD 已启动并启用 COM 接口' }], isError: true };
154
-
155
- case 'eval_lisp':
156
- if (!cad.connected) { await cad.connect(); }
157
- if (!cad.connected) return { content: [{ type: 'text', text: '未连接 CAD' }], isError: true };
158
- await cad.sendCommand(args.code + '\n');
159
- return { content: [{ type: 'text', text: `已发送命令: ${args.code}` }] };
160
-
161
- case 'eval_lisp_with_result':
162
- if (!cad.connected) { await cad.connect(); }
163
- if (!cad.connected) return { content: [{ type: 'text', text: '未连接 CAD' }], isError: true };
164
- const result = await cad.sendCommandWithResult(args.code);
165
- if (result !== null) {
166
- return { content: [{ type: 'text', text: result }] };
167
- }
168
- return { content: [{ type: 'text', text: '执行失败或无返回值' }], isError: true };
169
-
170
- case 'get_cad_info':
171
- if (!cad.connected) { await cad.connect(); }
172
- if (!cad.connected) return { content: [{ type: 'text', text: 'CAD 未连接' }] };
173
- const info = await cad.getInfo();
174
- return { content: [{ type: 'text', text: info }] };
175
-
176
- case 'list_packages':
177
- const packages = MOCK_PACKAGES.map(p => `${p.name} v${p.version} - ${p.description}`);
178
- return { content: [{ type: 'text', text: `已安装的 @lisp 包:\n${packages.join('\n')}` }] };
179
-
180
- case 'search_packages':
181
- const results = MOCK_PACKAGES.filter(p => p.name.includes(args.query) || p.description.includes(args.query));
182
- if (results.length === 0) return { content: [{ type: 'text', text: `未找到包含 "${args.query}" 的包` }] };
183
- return { content: [{ type: 'text', text: `搜索结果:\n${results.map(p => `${p.name} v${p.version} - ${p.description}`).join('\n')}` }] };
184
-
185
- case 'install_package':
186
- const pkg = MOCK_PACKAGES.find(p => p.name === args.packageName);
187
- if (!pkg) return { content: [{ type: 'text', text: `错误: 包 "${args.packageName}" 不存在` }], isError: true };
188
- if (cad.connected) await cad.sendCommand(`(@::load-module '${args.packageName})\n`);
189
- return { content: [{ type: 'text', text: `包 "${args.packageName}" 安装命令已发送!` }] };
190
-
191
- case 'get_platform_info':
192
- return { content: [{ type: 'text', text: `支持的 CAD 平台:\n${CAD_PLATFORMS.join('\n')}\n当前连接: ${cad.connected ? '已连接' : '未连接'}` }] };
193
-
194
- case 'get_file_extensions':
195
- const exts = FILE_EXTENSIONS[args.platform];
196
- if (!exts) return { content: [{ type: 'text', text: `错误: 不支持的平台 "${args.platform}"` }], isError: true };
197
- return { content: [{ type: 'text', text: `${args.platform} 文件扩展名: ${exts.join(', ')}` }] };
198
-
199
- case 'get_install_code':
200
- const code = '(progn(vl-load-com)(setq s strcat h "http" o(vlax-create-object (s"win"h".win"h"request.5.1"))v vlax-invoke e eval r read)(v o\'open "get" (s h"://atlisp.""cn/@"):vlax-true)(v o\'send)(v o\'WaitforResponse 1000)(e(r(vlax-get-property o\'ResponseText))))';
201
- return { content: [{ type: 'text', text: `复制以下代码到 CAD 命令行安装 @lisp:\n\n${code}` }] };
202
-
203
- case 'at_command':
204
- if (!cad.connected) return { content: [{ type: 'text', text: '请先连接 CAD' }], isError: true };
205
- await cad.sendCommand(`${args.command}\n`);
206
- return { content: [{ type: 'text', text: `已发送命令: ${args.command}` }] };
207
-
208
- case 'new_document':
209
- if (!cad.connected) { await cad.connect(); }
210
- if (!cad.connected) return { content: [{ type: 'text', text: '未连接 CAD' }], isError: true };
211
- const docResult = await cad.newDoc();
212
- if (docResult) {
213
- return { content: [{ type: 'text', text: '已在 CAD 中新建空白文档' }] };
214
- }
215
- return { content: [{ type: 'text', text: '新建文档失败' }], isError: true };
216
-
217
- case 'install_atlisp':
218
- if (!cad.connected) { await cad.connect(); }
219
- if (!cad.connected) return { content: [{ type: 'text', text: '未连接 CAD' }], isError: true };
220
- const installCode ='(progn(vl-load-com)(setq s strcat h "http" o(vlax-create-object (s"win"h".win"h"request.5.1"))v vlax-invoke e eval r read)(v o\'open "get" (s h"://atlisp.""cn/@"):vlax-true)(v o\'send)(v o\'WaitforResponse 1000)(e(r(vlax-get-property o\'ResponseText))))';
221
- await cad.sendCommand(installCode + '\n');
222
- return { content: [{ type: 'text', text: '已发送 @lisp 安装代码到 CAD' }] };
223
-
224
- default:
225
- return { content: [{ type: 'text', text: `未知工具: ${name}` }], isError: true };
226
- }
227
- }
228
-
229
- async function initCadConnection() {
230
- log('Attempting to connect to CAD on startup...');
231
- try {
232
- const connected = await cad.connect();
233
- if (connected) {
234
- log('Auto-connected to CAD: ' + cad.getPlatform() + ' ' + cad.getVersion());
235
- console.error('已自动连接到 CAD: ' + cad.getPlatform() + ' ' + cad.getVersion());
236
- } else {
237
- log('No CAD found on startup');
238
- }
239
- } catch (e) {
240
- log('Auto-connect failed: ' + e.message);
241
- }
242
- }
243
-
244
- export function createMcpServer() {
245
- const server = new Server(
246
- { name: 'atlisp-mcp-server', version: '1.0.10' },
247
- { capabilities: { tools: {} } }
248
- );
249
-
250
- server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
251
-
252
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
253
- const { name, arguments: args } = request.params;
254
- return await handleToolCall(name, args || {});
255
- });
256
-
257
- return server;
258
- }
259
-
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
- });
278
- }
279
-
280
- app.get('/health', (req, res) => {
281
- res.json({ status: 'ok', cad: cad.connected ? 'connected' : 'disconnected' });
282
- });
283
-
284
- let mcpServer = null;
285
- const sessionId = crypto.randomUUID();
286
-
287
- app.post('/mcp', async (req, res) => {
288
- if (!mcpServer) {
289
- mcpServer = createMcpServer();
290
- }
291
-
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();
314
- }
315
-
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({
332
- jsonrpc: '2.0',
333
- id,
334
- result: { tools }
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;
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
- });
367
- }
368
- });
369
-
370
- app.listen(PORT, HOST, async () => {
371
- await initCadConnection();
372
- console.error(`@lisp MCP Server 启动 - http://${HOST}:${PORT} - Streamable HTTP`);
373
- });
374
- }
375
- }
376
-
377
- if (!process.env.VITEST) {
378
- await startServer();
379
- }