@atlisp/mcp 1.0.1

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 ADDED
@@ -0,0 +1,148 @@
1
+ # @lisp MCP Server
2
+
3
+ MCP (Model Context Protocol) Server for @lisp on CAD - 为 CAD 环境提供 @lisp 包管理服务的 MCP 服务器。
4
+
5
+ ## 功能特性
6
+
7
+ - 连接 AutoCAD/ZWCAD/GStarCAD/BricsCAD
8
+ - 执行 AutoLISP 代码
9
+ - 管理 @lisp 包(列出、搜索、安装)
10
+ - 获取 CAD 平台信息
11
+ - 通过 MCP 协议提供标准化接口
12
+
13
+ ## 安装
14
+
15
+ ```bash
16
+ cd tools/mcp-server
17
+ npm install
18
+ ```
19
+
20
+ ## 使用方法
21
+
22
+ ### 启动服务器
23
+
24
+ **HTTP 模式(默认)**:
25
+ ```bash
26
+ npm start
27
+ # 或
28
+ TRANSPORT=http node src/index.js
29
+ ```
30
+
31
+ **Stdio 模式**(用于 MCP 客户端):
32
+ ```bash
33
+ TRANSPORT=stdio node src/index.js
34
+ ```
35
+
36
+ ### 环境变量
37
+
38
+ | 变量 | 默认值 | 说明 |
39
+ |------|--------|------|
40
+ | `TRANSPORT` | `http` | 传输模式:`http` 或 `stdio` |
41
+ | `PORT` | `8110` | HTTP 模式监听端口 |
42
+ | `HOST` | `0.0.0.0` | HTTP 模式监听地址 |
43
+
44
+ ## API
45
+
46
+ ### 健康检查
47
+
48
+ ```bash
49
+ curl http://localhost:8110/health
50
+ ```
51
+
52
+ 返回:
53
+ ```json
54
+ {"status":"ok","cad":"connected"}
55
+ ```
56
+
57
+ ### MCP 端点
58
+
59
+ HTTP 模式:`POST http://localhost:8110/mcp`
60
+
61
+ #### 列出工具
62
+
63
+ ```bash
64
+ curl -X POST http://localhost:8110/mcp \
65
+ -H "Content-Type: application/json" \
66
+ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
67
+ ```
68
+
69
+ #### 调用工具
70
+
71
+ ```bash
72
+ curl -X POST http://localhost:8110/mcp \
73
+ -H "Content-Type: application/json" \
74
+ -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"工具名","arguments":{参数}}}'
75
+ ```
76
+
77
+ ## 可用工具
78
+
79
+ | 工具名称 | 描述 | 参数 |
80
+ |---------|------|------|
81
+ | `connect_cad` | 连接到 CAD | 无 |
82
+ | `eval_lisp` | 在 CAD 中执行 AutoLISP 代码 | `code`: 字符串 |
83
+ | `get_cad_info` | 获取当前 CAD 信息 | 无 |
84
+ | `list_packages` | 列出已安装的 @lisp 包 | 无 |
85
+ | `search_packages` | 搜索 @lisp 包 | `query`: 搜索关键词 |
86
+ | `install_package` | 安装 @lisp 包到 CAD | `packageName`: 包名称 |
87
+ | `get_platform_info` | 获取 CAD 平台信息 | 无 |
88
+ | `get_file_extensions` | 获取指定平台的文件扩展名 | `platform`: 平台名称 |
89
+ | `get_install_code` | 获取 @lisp 安装代码 | 无 |
90
+ | `at_command` | 执行 @lisp 命令 | `command`: 命令 |
91
+ | `install_atlisp` | 在 CAD 中安装 @lisp | 无 |
92
+
93
+ ## 支持的平台
94
+
95
+ - **AutoCAD** (文件扩展名: `.fas`, `.vlx`)
96
+ - **ZWCAD** (文件扩展名: `.zelx`, `.vls`)
97
+ - **GStarCAD** (文件扩展名: `.des`)
98
+ - **BricsCAD** (文件扩展名: `.des`)
99
+
100
+ ## 技术栈
101
+
102
+ - **Runtime**: Node.js >= 18
103
+ - **MCP SDK**: `@modelcontextprotocol/sdk`
104
+ - **CAD 连接**: `edge-js` (通过 .NET COM 互操作)
105
+
106
+ ## 工作原理
107
+
108
+ 1. MCP Server 通过 `edge-js` 调用 .NET 代码
109
+ 2. .NET 代码通过 COM 互操作连接 CAD 应用程序
110
+ 3. 支持获取活动 CAD 实例或启动新实例
111
+ 4. 通过 `SendCommand` 方法向 CAD 发送 LISP 代码
112
+
113
+ ## 示例
114
+
115
+ ### 连接 CAD
116
+
117
+ ```bash
118
+ curl -X POST http://localhost:8110/mcp \
119
+ -H "Content-Type: application/json" \
120
+ -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"connect_cad","arguments":{}}}'
121
+ ```
122
+
123
+ ### 执行 LISP 代码
124
+
125
+ ```bash
126
+ curl -X POST http://localhost:8110/mcp \
127
+ -H "Content-Type: application/json" \
128
+ -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"eval_lisp","arguments":{"code":"(getvar \"acadver\")"}}}'
129
+ ```
130
+
131
+ ### 搜索包
132
+
133
+ ```bash
134
+ curl -X POST http://localhost:8110/mcp \
135
+ -H "Content-Type: application/json" \
136
+ -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"search_packages","arguments":{"query":"base"}}}'
137
+ ```
138
+
139
+ ## 注意事项
140
+
141
+ - 仅支持 Windows 平台(依赖 COM 互操作)
142
+ - 需要安装对应的 CAD 软件
143
+ - CAD 必须正在运行或可被启动
144
+ - 某些操作可能需要以管理员权限运行
145
+
146
+ ## 许可证
147
+
148
+ ISC
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@atlisp/mcp",
3
+ "version": "1.0.1",
4
+ "description": "MCP Server for @lisp on CAD",
5
+ "type": "module",
6
+ "bin": {
7
+ "atlisp-mcp": "./src/cli.js"
8
+ },
9
+ "main": "src/index.js",
10
+ "exports": {
11
+ ".": "./src/index.js",
12
+ "./cad": "./src/cad.js"
13
+ },
14
+ "files": [
15
+ "src"
16
+ ],
17
+ "scripts": {
18
+ "start": "node src/index.js",
19
+ "build": "webpack",
20
+ "test": "echo \"No tests yet\" && exit 0"
21
+ },
22
+ "keywords": [
23
+ "mcp",
24
+ "atlisp",
25
+ "cad",
26
+ "autolisp",
27
+ "package-manager"
28
+ ],
29
+ "author": "vitalgg",
30
+ "license": "ISC",
31
+ "dependencies": {
32
+ "@modelcontextprotocol/sdk": "^1.0.0",
33
+ "edge-js": "^25.0.1"
34
+ },
35
+ "engines": {
36
+ "node": ">=18.0.0"
37
+ }
38
+
39
+ }
@@ -0,0 +1,204 @@
1
+ #!/usr/bin/env node
2
+ import edge from 'edge-js';
3
+ import process from 'process';
4
+
5
+ const cadConnect = edge.func(function() {/*
6
+ async (input) => {
7
+ // Try existing CAD instances
8
+ try {
9
+ dynamic cad = System.Runtime.InteropServices.Marshal.GetActiveObject("AutoCAD.Application");
10
+ if (cad != null) {
11
+ string ver = cad.Version.ToString();
12
+ return new { success = true, platform = "AutoCAD", version = ver };
13
+ }
14
+ } catch {}
15
+ try {
16
+ dynamic cad = System.Runtime.InteropServices.Marshal.GetActiveObject("ZWCAD.Application");
17
+ if (cad != null) {
18
+ string ver = cad.Version.ToString();
19
+ return new { success = true, platform = "ZWCAD", version = ver };
20
+ }
21
+ } catch {}
22
+ try {
23
+ dynamic cad = System.Runtime.InteropServices.Marshal.GetActiveObject("GStarCAD.Application");
24
+ if (cad != null) {
25
+ string ver = cad.Version.ToString();
26
+ return new { success = true, platform = "GStarCAD", version = ver };
27
+ }
28
+ } catch {}
29
+ try {
30
+ dynamic cad = System.Runtime.InteropServices.Marshal.GetActiveObject("BricsCAD.Application");
31
+ if (cad != null) {
32
+ string ver = cad.Version.ToString();
33
+ return new { success = true, platform = "BricsCAD", version = ver };
34
+ }
35
+ } catch {}
36
+
37
+ // Try to create new CAD instance
38
+ string[] cadPaths = new string[] {
39
+ @"C:\Program Files\Autodesk\AutoCAD 2022\acad.exe",
40
+ @"C:\Program Files\Autodesk\AutoCAD 2024\acad.exe",
41
+ @"C:\Program Files\Autodesk\AutoCAD 2023\acad.exe",
42
+ @"C:\Program Files\Autodesk\AutoCAD 2021\acad.exe",
43
+ @"C:\Program Files\Autodesk\AutoCAD 2020\acad.exe",
44
+ @"C:\Program Files\ZWSOFT\ZWCAD 2024\zwcad.exe",
45
+ @"C:\Program Files\ZWSOFT\ZWCAD 2023\zwcad.exe",
46
+ @"C:\Program Files\ZWSOFT\ZWCAD 2022\zwcad.exe",
47
+ @"C:\Program Files\ZWSOFT\ZWCAD 2021\zwcad.exe",
48
+ @"C:\Program Files\GSTARSOFT\GStarCAD 2024\GCAD.exe",
49
+ @"C:\Program Files\GSTARSOFT\GStarCAD 2023\GCAD.exe",
50
+ @"C:\Program Files\Bricsys\BricsCAD\V24\BricsCAD.exe",
51
+ @"C:\Program Files\Bricsys\BricsCAD\V23\BricsCAD.exe"
52
+ };
53
+
54
+ foreach (string path in cadPaths) {
55
+ if (System.IO.File.Exists(path)) {
56
+ try {
57
+ System.Diagnostics.Process.Start(path);
58
+ System.Threading.Thread.Sleep(60000);
59
+
60
+ // Try to connect to newly started CAD
61
+ try {
62
+ dynamic cad = System.Runtime.InteropServices.Marshal.GetActiveObject("AutoCAD.Application");
63
+ if (cad != null) {
64
+ string ver = cad.Version.ToString();
65
+ return new { success = true, platform = "AutoCAD", version = ver };
66
+ }
67
+ } catch {}
68
+ try {
69
+ dynamic cad = System.Runtime.InteropServices.Marshal.GetActiveObject("ZWCAD.Application");
70
+ if (cad != null) {
71
+ string ver = cad.Version.ToString();
72
+ return new { success = true, platform = "ZWCAD", version = ver };
73
+ }
74
+ } catch {}
75
+ try {
76
+ dynamic cad = System.Runtime.InteropServices.Marshal.GetActiveObject("GStarCAD.Application");
77
+ if (cad != null) {
78
+ string ver = cad.Version.ToString();
79
+ return new { success = true, platform = "GStarCAD", version = ver };
80
+ }
81
+ } catch {}
82
+ try {
83
+ dynamic cad = System.Runtime.InteropServices.Marshal.GetActiveObject("BricsCAD.Application");
84
+ if (cad != null) {
85
+ string ver = cad.Version.ToString();
86
+ return new { success = true, platform = "BricsCAD", version = ver };
87
+ }
88
+ } catch {}
89
+ } catch {}
90
+ }
91
+ }
92
+
93
+ return new { success = false, error = "No CAD found" };
94
+ }
95
+ */});
96
+
97
+ const cadSend = edge.func(function() {/*
98
+ async (input) => {
99
+ dynamic d = input;
100
+ string code = d.code.ToString();
101
+ string platform = d.platform.ToString();
102
+ string progId = platform + ".Application";
103
+ try {
104
+ dynamic cad = System.Runtime.InteropServices.Marshal.GetActiveObject(progId);
105
+ if (cad != null) {
106
+ dynamic doc = cad.ActiveDocument;
107
+ if (doc == null || doc.Name == "") {
108
+ // Try default template path
109
+ string templatePath = @"C:\Users\Public\Documents\AutoCAD 2024\Sample\basics.dwg";
110
+ if (System.IO.File.Exists(templatePath)) {
111
+ cad.Documents.Open(templatePath, false);
112
+ } else {
113
+ cad.Documents.Add("");
114
+ }
115
+ System.Threading.Thread.Sleep(2000);
116
+ }
117
+ doc = cad.ActiveDocument;
118
+ if (doc != null) {
119
+ doc.SendCommand(code);
120
+ return new { success = true };
121
+ }
122
+ }
123
+ } catch {}
124
+ return new { success = false };
125
+ }
126
+ */});
127
+
128
+ process.stdin.setEncoding('utf8');
129
+
130
+ process.stdin.on('data', (data) => {
131
+ const lines = data.trim().split('\n');
132
+ for (const line of lines) {
133
+ if (!line) continue;
134
+ try {
135
+ const msg = JSON.parse(line);
136
+ handleMessage(msg).then(result => {
137
+ process.stdout.write(JSON.stringify(result) + '\n');
138
+ }).catch(err => {
139
+ process.stdout.write(JSON.stringify({ error: err.message }) + '\n');
140
+ });
141
+ } catch (e) {
142
+ process.stdout.write(JSON.stringify({ error: 'invalid message' }) + '\n');
143
+ }
144
+ }
145
+ });
146
+
147
+ const cadHasDoc = edge.func(function() {/*
148
+ async (input) => {
149
+ string platform = input.ToString();
150
+ string progId = platform + ".Application";
151
+ try {
152
+ dynamic cad = System.Runtime.InteropServices.Marshal.GetActiveObject(progId);
153
+ if (cad != null) {
154
+ dynamic doc = cad.ActiveDocument;
155
+ if (doc != null && doc.Name != "") {
156
+ return new { hasDoc = true, name = doc.Name };
157
+ }
158
+ }
159
+ } catch {}
160
+ return new { hasDoc = false };
161
+ }
162
+ */});
163
+
164
+ async function handleMessage(msg) {
165
+ if (msg.type === 'connect') {
166
+ return new Promise((resolve, reject) => {
167
+ cadConnect({}, (e, r) => {
168
+ if (e) reject(e);
169
+ else resolve(r);
170
+ });
171
+ });
172
+ }
173
+ if (msg.type === 'hasdoc') {
174
+ return new Promise((resolve, reject) => {
175
+ cadHasDoc(msg.platform || 'AutoCAD', (e, r) => {
176
+ if (e) reject(e);
177
+ else resolve(r);
178
+ });
179
+ });
180
+ }
181
+ if (msg.type === 'send') {
182
+ const checkResult = await new Promise((resolve, reject) => {
183
+ cadHasDoc(msg.platform, (e, r) => {
184
+ if (e) reject(e);
185
+ else resolve(r);
186
+ });
187
+ });
188
+ if (!checkResult.hasDoc) {
189
+ const newDoc = await new Promise((resolve, reject) => {
190
+ cadSend({ code: "\n", platform: msg.platform }, (e, r) => {
191
+ if (e) reject(e);
192
+ else resolve(r);
193
+ });
194
+ });
195
+ }
196
+ return new Promise((resolve, reject) => {
197
+ cadSend({ code: msg.code, platform: msg.platform }, (e, r) => {
198
+ if (e) reject(e);
199
+ else resolve(r);
200
+ });
201
+ });
202
+ }
203
+ return { error: 'unknown message type' };
204
+ }
package/src/cad.js ADDED
@@ -0,0 +1,138 @@
1
+ import { spawn } from 'child_process';
2
+ import path from 'path';
3
+ import os from 'os';
4
+ import fs from 'fs';
5
+ import { fileURLToPath } from 'url';
6
+
7
+ const CAD_PLATFORMS = ['AutoCAD', 'ZWCAD', 'GStarCAD', 'BricsCAD'];
8
+ const FILE_EXTENSIONS = {
9
+ 'AutoCAD': ['.fas', '.vlx'],
10
+ 'ZWCAD': ['.zelx', '.vls'],
11
+ 'BricsCAD': ['.des']
12
+ };
13
+
14
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
15
+ const workerPath = path.join(__dirname, 'cad-worker.js');
16
+
17
+ let worker = null;
18
+ let _platform = null;
19
+ let _version = null;
20
+
21
+ function getWorker() {
22
+ if (!worker || !worker.connected) {
23
+ worker = spawn('node', [workerPath], {
24
+ stdio: ['pipe', 'pipe', 'pipe']
25
+ });
26
+ worker.connected = true;
27
+
28
+ worker.stderr.on('data', (d) => console.error('worker stderr:', d.toString()));
29
+ worker.on('exit', (code) => {
30
+ console.error('worker exited:', code);
31
+ worker.connected = false;
32
+ });
33
+ }
34
+ return worker;
35
+ }
36
+
37
+ function sendMessage(msg) {
38
+ return new Promise((resolve, reject) => {
39
+ const w = getWorker();
40
+ let responded = false;
41
+
42
+ const timeout = setTimeout(() => {
43
+ if (!responded) {
44
+ responded = true;
45
+ reject(new Error('Timeout waiting for worker'));
46
+ }
47
+ }, 60000);
48
+
49
+ const handler = (data) => {
50
+ try {
51
+ const lines = data.toString().split('\n');
52
+ for (const line of lines) {
53
+ if (!line.trim()) continue;
54
+ const result = JSON.parse(line);
55
+ if (result.error) {
56
+ if (!responded) {
57
+ responded = true;
58
+ clearTimeout(timeout);
59
+ w.stdout.off('data', handler);
60
+ reject(new Error(result.error));
61
+ }
62
+ } else {
63
+ if (!responded) {
64
+ responded = true;
65
+ clearTimeout(timeout);
66
+ w.stdout.off('data', handler);
67
+ resolve(result);
68
+ }
69
+ }
70
+ }
71
+ } catch (e) {}
72
+ };
73
+
74
+ w.stdout.on('data', handler);
75
+ w.stdin.write(JSON.stringify(msg) + '\n');
76
+ });
77
+ }
78
+
79
+ class CadConnection {
80
+ constructor() {
81
+ this.connected = false;
82
+ this.version = null;
83
+ this.product = null;
84
+ }
85
+
86
+ isAvailable() {
87
+ return process.platform === 'win32';
88
+ }
89
+
90
+ async connect() {
91
+ if (!this.isAvailable()) return false;
92
+ try {
93
+ const result = await sendMessage({ type: 'connect' });
94
+ if (result && result.success) {
95
+ this.version = result.version;
96
+ this.product = result.platform;
97
+ _platform = result.platform;
98
+ this.connected = true;
99
+ return true;
100
+ }
101
+ } catch (e) {
102
+ console.error('CAD connect error:', e.message);
103
+ }
104
+ return false;
105
+ }
106
+
107
+ async sendCommand(code) {
108
+ if (!this.connected) throw new Error('未连接 CAD');
109
+ try {
110
+ const result = await sendMessage({ type: 'send', code: code, platform: _platform });
111
+ return result && result.success;
112
+ } catch (e) {
113
+ return false;
114
+ }
115
+ }
116
+
117
+ getVersion() { return this.version; }
118
+ getPlatform() { return this.product; }
119
+
120
+ async getInfo() {
121
+ if (!this.connected) return 'CAD 未连接';
122
+ return `Platform: ${this.product}, Version: ${this.version}, Status: Connected`;
123
+ }
124
+
125
+ async disconnect() {
126
+ this.connected = false;
127
+ this.version = null;
128
+ this.product = null;
129
+ _platform = null;
130
+ if (worker) {
131
+ worker.kill();
132
+ worker = null;
133
+ }
134
+ }
135
+ }
136
+
137
+ export const cad = new CadConnection();
138
+ export default CadConnection;
package/src/cli.js ADDED
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from 'child_process';
3
+ import path from 'path';
4
+ import { fileURLToPath } from 'url';
5
+
6
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
+ const serverPath = path.join(__dirname, 'index.js');
8
+
9
+ const args = process.argv.slice(2);
10
+ const env = { ...process.env };
11
+
12
+ for (let i = 0; i < args.length; i++) {
13
+ if (args[i] === '--transport' && args[i + 1]) {
14
+ env.TRANSPORT = args[i + 1];
15
+ i++;
16
+ } else if (args[i] === '--port' && args[i + 1]) {
17
+ env.PORT = args[i + 1];
18
+ i++;
19
+ } else if (args[i] === '--host' && args[i + 1]) {
20
+ env.HOST = args[i + 1];
21
+ i++;
22
+ } else if (args[i] === '--stdio') {
23
+ env.TRANSPORT = 'stdio';
24
+ } else if (args[i] === '--help' || args[i] === '-h') {
25
+ console.log(`
26
+ @lisp MCP Server
27
+
28
+ Usage: atlisp-mcp [options]
29
+
30
+ Options:
31
+ --transport <type> Transport type: http or stdio (default: http)
32
+ --port <port> HTTP port (default: 8110)
33
+ --host <host> HTTP host (default: 0.0.0.0)
34
+ --stdio Shorthand for --transport stdio
35
+ -h, --help Show this help message
36
+
37
+ Examples:
38
+ atlisp-mcp # Start HTTP server on port 8110
39
+ atlisp-mcp --port 3000 # Start HTTP server on port 3000
40
+ atlisp-mcp --stdio # Start in stdio mode for MCP clients
41
+ atlisp-mcp --transport stdio # Same as --stdio
42
+ `);
43
+ process.exit(0);
44
+ }
45
+ }
46
+
47
+ const child = spawn('node', [serverPath], {
48
+ env,
49
+ stdio: 'inherit'
50
+ });
51
+
52
+ child.on('exit', (code) => {
53
+ process.exit(code);
54
+ });
package/src/index.js ADDED
@@ -0,0 +1,279 @@
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 { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
5
+ import {
6
+ ListToolsRequestSchema,
7
+ CallToolRequestSchema,
8
+ } from '@modelcontextprotocol/sdk/types.js';
9
+ import fs from 'fs';
10
+ import path from 'path';
11
+ import os from 'os';
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
+
24
+ const CAD_PLATFORMS = ['AutoCAD', 'ZWCAD', 'GStarCAD', 'BricsCAD'];
25
+ const FILE_EXTENSIONS = {
26
+ 'AutoCAD': ['.fas', '.vlx'],
27
+ 'ZWCAD': ['.zelx', '.vls'],
28
+ 'BricsCAD': ['.des']
29
+ };
30
+
31
+ const MOCK_PACKAGES = [
32
+ { name: 'base', description: '基础函数库', version: '1.0.0' },
33
+ { name: 'at-pm', description: '工程项目管理', version: '2.1.0' },
34
+ { name: 'network', description: '网络功能模块', version: '1.5.0' },
35
+ { name: 'userman', description: '用户管理', version: '1.2.0' },
36
+ { name: 'pkgman', description: '包管理器', version: '2.0.0' },
37
+ { name: 'sidebar', description: '侧边栏工具', version: '1.0.0' },
38
+ { name: 'aibot', description: 'AI 助手', version: '0.9.0' },
39
+ { name: 'tips', description: '每日提示', version: '1.0.0' },
40
+ { name: 'function', description: '函数库', version: '3.0.0' }
41
+ ];
42
+
43
+ const tools = [
44
+ {
45
+ name: 'connect_cad',
46
+ description: '连接到 CAD (AutoCAD/ZWCAD/GStarCAD/BricsCAD)',
47
+ inputSchema: { type: 'object', properties: {} }
48
+ },
49
+ {
50
+ name: 'eval_lisp',
51
+ description: '在 CAD 中执行 AutoLISP 代码',
52
+ inputSchema: {
53
+ type: 'object',
54
+ properties: { code: { type: 'string', description: '要执行的 LISP 代码' } },
55
+ required: ['code']
56
+ }
57
+ },
58
+ {
59
+ name: 'get_cad_info',
60
+ description: '获取当前 CAD 信息',
61
+ inputSchema: { type: 'object', properties: {} }
62
+ },
63
+ {
64
+ name: 'list_packages',
65
+ description: '列出已安装的 @lisp 包',
66
+ inputSchema: { type: 'object', properties: {} }
67
+ },
68
+ {
69
+ name: 'search_packages',
70
+ description: '搜索 @lisp 包',
71
+ inputSchema: {
72
+ type: 'object',
73
+ properties: { query: { type: 'string', description: '搜索关键词' } },
74
+ required: ['query']
75
+ }
76
+ },
77
+ {
78
+ name: 'install_package',
79
+ description: '安装 @lisp 包到 CAD',
80
+ inputSchema: {
81
+ type: 'object',
82
+ properties: { packageName: { type: 'string', description: '包名称' } },
83
+ required: ['packageName']
84
+ }
85
+ },
86
+ {
87
+ name: 'get_platform_info',
88
+ description: '获取 CAD 平台信息',
89
+ inputSchema: { type: 'object', properties: {} }
90
+ },
91
+ {
92
+ name: 'get_file_extensions',
93
+ description: '获取指定平台的文件扩展名',
94
+ inputSchema: {
95
+ type: 'object',
96
+ properties: { platform: { type: 'string', enum: CAD_PLATFORMS, description: 'CAD 平台' } },
97
+ required: ['platform']
98
+ }
99
+ },
100
+ {
101
+ name: 'get_install_code',
102
+ description: '获取 @lisp 安装代码',
103
+ inputSchema: { type: 'object', properties: {} }
104
+ },
105
+ {
106
+ name: 'at_command',
107
+ description: '执行 @lisp 命令',
108
+ inputSchema: {
109
+ type: 'object',
110
+ properties: { command: { type: 'string', description: '@lisp 命令' } },
111
+ required: ['command']
112
+ }
113
+ },
114
+ {
115
+ name: 'install_atlisp',
116
+ description: '在 CAD 中安装 @lisp',
117
+ inputSchema: { type: 'object', properties: {} }
118
+ }
119
+ ];
120
+
121
+ async function handleToolCall(name, args) {
122
+ switch (name) {
123
+ case 'connect_cad':
124
+ log('connect_cad called, cad.connected before: ' + cad.connected);
125
+ try {
126
+ log('calling cad.connect()...');
127
+ const connected = await cad.connect();
128
+ log('cad.connect() returned: ' + connected + ', cad.connected: ' + cad.connected);
129
+ if (connected) {
130
+ return { content: [{ type: 'text', text: `已连接到 ${cad.getPlatform()} ${cad.getVersion()}` }] };
131
+ }
132
+ } catch (e) {
133
+ log('connect_cad error: ' + e.message);
134
+ }
135
+ return { content: [{ type: 'text', text: '未找到运行中的 CAD,请确保 CAD 已启动并启用 COM 接口' }], isError: true };
136
+
137
+ case 'eval_lisp':
138
+ if (!cad.connected) { await cad.connect(); }
139
+ if (!cad.connected) return { content: [{ type: 'text', text: '未连接 CAD' }], isError: true };
140
+ await cad.sendCommand(args.code + '\n');
141
+ return { content: [{ type: 'text', text: `已发送命令: ${args.code}` }] };
142
+
143
+ case 'get_cad_info':
144
+ if (!cad.connected) { await cad.connect(); }
145
+ if (!cad.connected) return { content: [{ type: 'text', text: 'CAD 未连接' }] };
146
+ const info = await cad.getInfo();
147
+ return { content: [{ type: 'text', text: info }] };
148
+
149
+ case 'list_packages':
150
+ const packages = MOCK_PACKAGES.map(p => `${p.name} v${p.version} - ${p.description}`);
151
+ return { content: [{ type: 'text', text: `已安装的 @lisp 包:\n${packages.join('\n')}` }] };
152
+
153
+ case 'search_packages':
154
+ const results = MOCK_PACKAGES.filter(p => p.name.includes(args.query) || p.description.includes(args.query));
155
+ if (results.length === 0) return { content: [{ type: 'text', text: `未找到包含 "${args.query}" 的包` }] };
156
+ return { content: [{ type: 'text', text: `搜索结果:\n${results.map(p => `${p.name} v${p.version} - ${p.description}`).join('\n')}` }] };
157
+
158
+ case 'install_package':
159
+ const pkg = MOCK_PACKAGES.find(p => p.name === args.packageName);
160
+ if (!pkg) return { content: [{ type: 'text', text: `错误: 包 "${args.packageName}" 不存在` }], isError: true };
161
+ if (cad.connected) await cad.sendCommand(`(@::load-module '${args.packageName})\n`);
162
+ return { content: [{ type: 'text', text: `包 "${args.packageName}" 安装命令已发送!` }] };
163
+
164
+ case 'get_platform_info':
165
+ return { content: [{ type: 'text', text: `支持的 CAD 平台:\n${CAD_PLATFORMS.join('\n')}\n当前连接: ${cad.connected ? '已连接' : '未连接'}` }] };
166
+
167
+ case 'get_file_extensions':
168
+ const exts = FILE_EXTENSIONS[args.platform];
169
+ if (!exts) return { content: [{ type: 'text', text: `错误: 不支持的平台 "${args.platform}"` }], isError: true };
170
+ return { content: [{ type: 'text', text: `${args.platform} 文件扩展名: ${exts.join(', ')}` }] };
171
+
172
+ case 'get_install_code':
173
+ 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))))';
174
+ return { content: [{ type: 'text', text: `复制以下代码到 CAD 命令行安装 @lisp:\n\n${code}` }] };
175
+
176
+ case 'at_command':
177
+ if (!cad.connected) return { content: [{ type: 'text', text: '请先连接 CAD' }], isError: true };
178
+ await cad.sendCommand(`@${args.command}\n`);
179
+ return { content: [{ type: 'text', text: `已发送命令: @${args.command}` }] };
180
+
181
+ case 'install_atlisp':
182
+ if (!cad.connected) { await cad.connect(); }
183
+ if (!cad.connected) return { content: [{ type: 'text', text: '未连接 CAD' }], isError: true };
184
+ 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))))';
185
+ await cad.sendCommand(installCode + '\n');
186
+ return { content: [{ type: 'text', text: '已发送 @lisp 安装代码到 CAD' }] };
187
+
188
+ default:
189
+ throw new Error(`Unknown tool: ${name}`);
190
+ }
191
+ }
192
+
193
+ const mcpServer = new Server(
194
+ { name: 'atlisp-mcp-server', version: '1.0.0' },
195
+ { capabilities: { tools: {} } }
196
+ );
197
+
198
+ mcpServer.setRequestHandler(ListToolsRequestSchema, async () => {
199
+ return { tools };
200
+ });
201
+
202
+ mcpServer.setRequestHandler(CallToolRequestSchema, async (request) => {
203
+ console.error('setRequestHandler called, name:', request.params.name);
204
+ const { name, arguments: args } = request.params;
205
+ return await handleToolCall(name, args || {});
206
+ });
207
+
208
+ if (TRANSPORT === 'stdio') {
209
+ const transport = new StdioServerTransport();
210
+ await mcpServer.connect(transport);
211
+ console.error('@lisp MCP Server 运行在 stdio 模式');
212
+ } else {
213
+ const app = express();
214
+ app.use(express.json({ type: ['application/json', 'application/json-rpc'] }));
215
+
216
+ app.get('/health', (req, res) => {
217
+ res.json({ status: 'ok', cad: cad.connected ? 'connected' : 'disconnected' });
218
+ });
219
+
220
+ app.post('/mcp', async (req, res) => {
221
+ log('POST /mcp called, body: ' + JSON.stringify(req.body));
222
+ try {
223
+ const message = req.body;
224
+ const id = message.id;
225
+
226
+ if (!id) {
227
+ log('No id, returning 204');
228
+ return res.status(204).end();
229
+ }
230
+
231
+ if (message.method === 'initialize') {
232
+ return res.json({
233
+ jsonrpc: '2.0',
234
+ id: id,
235
+ result: {
236
+ protocolVersion: '2024-11-05',
237
+ capabilities: { tools: {} },
238
+ serverInfo: { name: 'atlisp-mcp-server', version: '1.0.0' }
239
+ }
240
+ });
241
+ }
242
+
243
+ if (message.method === 'tools/list') {
244
+ return res.json({
245
+ jsonrpc: '2.0',
246
+ id: id,
247
+ result: { tools }
248
+ });
249
+ }
250
+
251
+ if (message.method === 'tools/call') {
252
+ log('tools/call called, name: ' + message.params.name);
253
+ const result = await handleToolCall(message.params.name, message.params.arguments || {});
254
+ log('tools/call result: ' + JSON.stringify(result));
255
+ return res.json({
256
+ jsonrpc: '2.0',
257
+ id: id,
258
+ result
259
+ });
260
+ }
261
+
262
+ res.json({
263
+ jsonrpc: '2.0',
264
+ id: id,
265
+ error: { code: -32601, message: 'Method not found' }
266
+ });
267
+ } catch (e) {
268
+ res.status(500).json({
269
+ jsonrpc: '2.0',
270
+ error: { code: -32603, message: e.message }
271
+ });
272
+ }
273
+ });
274
+
275
+ app.listen(PORT, HOST, () => {
276
+ const status = cad.connected ? '已连接 CAD' : '未连接 CAD';
277
+ console.error(`@lisp MCP Server 启动 - http://${HOST}:${PORT} - ${status}`);
278
+ });
279
+ }