@atlisp/mcp 1.3.0 → 1.3.2
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/dist/atlisp-mcp.js +2314 -0
- package/{src → dist}/cad-worker.js +2 -2
- package/package.json +11 -6
- package/src/atlisp-mcp.js +0 -630
- package/src/cad.js +0 -271
- package/src/config.js +0 -50
- package/src/constants.js +0 -23
- package/src/errors.js +0 -55
- package/src/handlers/cad-handlers.js +0 -110
- package/src/handlers/function-handlers.js +0 -107
- package/src/handlers/package-handlers.js +0 -66
- package/src/handlers/prompt-handlers.js +0 -268
- package/src/handlers/resource-handlers.js +0 -364
- package/src/logger.js +0 -30
- package/src/session-manager.js +0 -89
- package/src/session-transport.js +0 -89
- package/src/sse-session-manager.js +0 -133
- package/src/sse-session.js +0 -314
- package/src/subscription-manager.js +0 -52
package/src/cad.js
DELETED
|
@@ -1,271 +0,0 @@
|
|
|
1
|
-
import { spawn } from 'child_process';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { fileURLToPath } from 'url';
|
|
4
|
-
import { randomUUID } from 'crypto';
|
|
5
|
-
import { CAD_PLATFORMS, FILE_EXTENSIONS } from './constants.js';
|
|
6
|
-
import config from './config.js';
|
|
7
|
-
|
|
8
|
-
const MESSAGE_TIMEOUT = config.messageTimeout;
|
|
9
|
-
const HEARTBEAT_INTERVAL = config.heartbeatInterval;
|
|
10
|
-
|
|
11
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
12
|
-
const workerPath = path.join(__dirname, 'cad-worker.js');
|
|
13
|
-
|
|
14
|
-
let worker = null;
|
|
15
|
-
let _platform = null;
|
|
16
|
-
let _version = null;
|
|
17
|
-
|
|
18
|
-
let heartbeatTimer = null;
|
|
19
|
-
const pendingRequests = new Map();
|
|
20
|
-
let stdoutHandlerSetup = false;
|
|
21
|
-
|
|
22
|
-
const MAX_BUFFER_SIZE = 1024 * 1024;
|
|
23
|
-
|
|
24
|
-
export function resetWorker() {
|
|
25
|
-
for (const [id, { reject, timeout }] of pendingRequests) {
|
|
26
|
-
clearTimeout(timeout);
|
|
27
|
-
reject(new Error('Worker reset'));
|
|
28
|
-
}
|
|
29
|
-
pendingRequests.clear();
|
|
30
|
-
stdoutHandlerSetup = false;
|
|
31
|
-
|
|
32
|
-
if (worker) {
|
|
33
|
-
worker.kill();
|
|
34
|
-
worker = null;
|
|
35
|
-
}
|
|
36
|
-
if (heartbeatTimer) {
|
|
37
|
-
clearInterval(heartbeatTimer);
|
|
38
|
-
heartbeatTimer = null;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function setupStdoutHandler(w) {
|
|
43
|
-
let buffer = '';
|
|
44
|
-
stdoutHandlerSetup = true;
|
|
45
|
-
|
|
46
|
-
w.stdout.on('data', (data) => {
|
|
47
|
-
try {
|
|
48
|
-
buffer += data.toString();
|
|
49
|
-
if (buffer.length > MAX_BUFFER_SIZE) {
|
|
50
|
-
buffer = buffer.slice(-MAX_BUFFER_SIZE);
|
|
51
|
-
}
|
|
52
|
-
const lines = buffer.split('\n');
|
|
53
|
-
buffer = lines.pop() || '';
|
|
54
|
-
for (const line of lines) {
|
|
55
|
-
if (!line.trim()) continue;
|
|
56
|
-
try {
|
|
57
|
-
const result = JSON.parse(line);
|
|
58
|
-
const rid = result.requestId;
|
|
59
|
-
if (rid && pendingRequests.has(rid)) {
|
|
60
|
-
const { resolve, reject, timeout } = pendingRequests.get(rid);
|
|
61
|
-
clearTimeout(timeout);
|
|
62
|
-
pendingRequests.delete(rid);
|
|
63
|
-
if (result.error) {
|
|
64
|
-
reject(new Error(result.error));
|
|
65
|
-
} else {
|
|
66
|
-
resolve(result);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
} catch (parseErr) {
|
|
70
|
-
console.error('JSON parse error in worker output:', parseErr.message);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
} catch (e) {
|
|
74
|
-
console.error('Unexpected error in worker stdout handler:', e.message);
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export function getWorker() {
|
|
80
|
-
if (!worker || !worker.connected) {
|
|
81
|
-
if (worker) worker.kill();
|
|
82
|
-
worker = spawn('node', [workerPath], {
|
|
83
|
-
stdio: ['pipe', 'pipe', 'pipe']
|
|
84
|
-
});
|
|
85
|
-
worker.connected = true;
|
|
86
|
-
|
|
87
|
-
worker.stderr.on('data', (d) => console.error('worker stderr:', d.toString()));
|
|
88
|
-
const w = worker;
|
|
89
|
-
w.on('exit', (code) => {
|
|
90
|
-
console.error('worker exited:', code);
|
|
91
|
-
if (w !== worker) return;
|
|
92
|
-
w.connected = false;
|
|
93
|
-
|
|
94
|
-
for (const [id, { reject, timeout }] of pendingRequests) {
|
|
95
|
-
clearTimeout(timeout);
|
|
96
|
-
reject(new Error(`Worker exited with code ${code}`));
|
|
97
|
-
}
|
|
98
|
-
pendingRequests.clear();
|
|
99
|
-
stdoutHandlerSetup = false;
|
|
100
|
-
|
|
101
|
-
if (heartbeatTimer) {
|
|
102
|
-
clearInterval(heartbeatTimer);
|
|
103
|
-
heartbeatTimer = null;
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
setupStdoutHandler(w);
|
|
107
|
-
startHeartbeat();
|
|
108
|
-
}
|
|
109
|
-
return worker;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function startHeartbeat() {
|
|
113
|
-
if (heartbeatTimer) clearInterval(heartbeatTimer);
|
|
114
|
-
heartbeatTimer = setInterval(async () => {
|
|
115
|
-
try {
|
|
116
|
-
const result = await sendMessage({ type: 'ping' });
|
|
117
|
-
if (!result?.pong) {
|
|
118
|
-
console.error('heartbeat failed, worker may be dead');
|
|
119
|
-
resetWorker();
|
|
120
|
-
}
|
|
121
|
-
} catch (e) {
|
|
122
|
-
console.error('heartbeat error:', e.message);
|
|
123
|
-
resetWorker();
|
|
124
|
-
}
|
|
125
|
-
}, HEARTBEAT_INTERVAL);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
export function sendMessage(msg) {
|
|
129
|
-
const w = getWorker();
|
|
130
|
-
const requestId = randomUUID();
|
|
131
|
-
|
|
132
|
-
return new Promise((resolve, reject) => {
|
|
133
|
-
const timeout = setTimeout(() => {
|
|
134
|
-
const pending = pendingRequests.get(requestId);
|
|
135
|
-
if (pending) {
|
|
136
|
-
pendingRequests.delete(requestId);
|
|
137
|
-
reject(new Error('Timeout waiting for worker'));
|
|
138
|
-
}
|
|
139
|
-
}, MESSAGE_TIMEOUT);
|
|
140
|
-
|
|
141
|
-
pendingRequests.set(requestId, { resolve, reject, timeout });
|
|
142
|
-
try {
|
|
143
|
-
w.stdin.write(JSON.stringify({ ...msg, requestId }) + '\n');
|
|
144
|
-
} catch (e) {
|
|
145
|
-
clearTimeout(timeout);
|
|
146
|
-
pendingRequests.delete(requestId);
|
|
147
|
-
reject(new Error(`Failed to write to worker: ${e.message}`));
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
class CadConnection {
|
|
153
|
-
constructor() {
|
|
154
|
-
this.connected = false;
|
|
155
|
-
this.version = null;
|
|
156
|
-
this.product = null;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
isAvailable() {
|
|
160
|
-
return process.platform === 'win32';
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
async connect() {
|
|
164
|
-
if (!this.isAvailable()) return false;
|
|
165
|
-
try {
|
|
166
|
-
const result = await sendMessage({ type: 'connect' });
|
|
167
|
-
if (result && result.success) {
|
|
168
|
-
this.version = result.version;
|
|
169
|
-
this.product = result.platform;
|
|
170
|
-
_platform = result.platform;
|
|
171
|
-
this.connected = true;
|
|
172
|
-
return true;
|
|
173
|
-
}
|
|
174
|
-
} catch (e) {
|
|
175
|
-
console.error('CAD connect error:', e.message);
|
|
176
|
-
}
|
|
177
|
-
return false;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
async sendCommand(code) {
|
|
181
|
-
if (!this.connected) throw new Error('未连接 CAD');
|
|
182
|
-
const result = await sendMessage({ type: 'send', code: code, platform: _platform });
|
|
183
|
-
if (result.success) return true;
|
|
184
|
-
throw new Error(result.error || '发送失败');
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
async sendCommandWithResult(code, encoding = null) {
|
|
188
|
-
if (!this.connected) throw new Error('未连接 CAD');
|
|
189
|
-
const result = await sendMessage({ type: 'sendResult', code: code, platform: _platform, encoding: encoding || '' });
|
|
190
|
-
if (result.success) {
|
|
191
|
-
return result.result || '';
|
|
192
|
-
}
|
|
193
|
-
throw new Error(result.error || '发送失败');
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
getVersion() { return this.version; }
|
|
197
|
-
getPlatform() { return this.product; }
|
|
198
|
-
|
|
199
|
-
async isBusy() {
|
|
200
|
-
if (!this.connected) return false;
|
|
201
|
-
try {
|
|
202
|
-
const result = await sendMessage({ type: 'isBusy', platform: _platform });
|
|
203
|
-
return result && result.isBusy;
|
|
204
|
-
} catch (e) {
|
|
205
|
-
return false;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
async hasDoc() {
|
|
210
|
-
if (!this.connected) return { hasDoc: false, docCount: 0 };
|
|
211
|
-
try {
|
|
212
|
-
const result = await sendMessage({ type: 'hasdoc', platform: _platform });
|
|
213
|
-
return result || { hasDoc: false, docCount: 0 };
|
|
214
|
-
} catch (e) {
|
|
215
|
-
return { hasDoc: false, docCount: 0 };
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
async newDoc() {
|
|
220
|
-
if (!this.connected) throw new Error('未连接 CAD');
|
|
221
|
-
try {
|
|
222
|
-
const result = await sendMessage({ type: 'newdoc', platform: _platform });
|
|
223
|
-
return result && result.success;
|
|
224
|
-
} catch (e) {
|
|
225
|
-
return false;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
async bringToFront() {
|
|
230
|
-
if (!this.connected) throw new Error('未连接 CAD');
|
|
231
|
-
try {
|
|
232
|
-
const result = await sendMessage({ type: 'bringToFront', platform: _platform });
|
|
233
|
-
return result && result.success;
|
|
234
|
-
} catch (e) {
|
|
235
|
-
return false;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
async getInfo() {
|
|
240
|
-
if (!this.connected) return 'CAD 未连接';
|
|
241
|
-
return `Platform: ${this.product}, Version: ${this.version}, Status: Connected`;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
async disconnect() {
|
|
245
|
-
this.connected = false;
|
|
246
|
-
this.version = null;
|
|
247
|
-
this.product = null;
|
|
248
|
-
_platform = null;
|
|
249
|
-
resetWorker();
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
async ping() {
|
|
253
|
-
try {
|
|
254
|
-
const result = await sendMessage({ type: 'ping' });
|
|
255
|
-
return result && result.pong;
|
|
256
|
-
} catch (e) {
|
|
257
|
-
return false;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
_reset() {
|
|
262
|
-
this.connected = false;
|
|
263
|
-
this.version = null;
|
|
264
|
-
this.product = null;
|
|
265
|
-
_platform = null;
|
|
266
|
-
resetWorker();
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
export const cad = new CadConnection();
|
|
271
|
-
export default CadConnection;
|
package/src/config.js
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
function parseArgs() {
|
|
2
|
-
const args = process.argv.slice(2);
|
|
3
|
-
const result = { transport: 'http', port: '8110', host: '0.0.0.0' };
|
|
4
|
-
for (let i = 0; i < args.length; i++) {
|
|
5
|
-
if (args[i] === '--transport' && args[i + 1]) {
|
|
6
|
-
result.transport = args[i + 1];
|
|
7
|
-
i++;
|
|
8
|
-
} else if (args[i] === '--stdio') {
|
|
9
|
-
result.transport = 'stdio';
|
|
10
|
-
} else if (args[i] === '--port' && args[i + 1]) {
|
|
11
|
-
result.port = args[i + 1];
|
|
12
|
-
i++;
|
|
13
|
-
} else if (args[i] === '--host' && args[i + 1]) {
|
|
14
|
-
result.host = args[i + 1];
|
|
15
|
-
i++;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
return result;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const cliArgs = parseArgs();
|
|
22
|
-
|
|
23
|
-
const config = {
|
|
24
|
-
port: parseInt(process.env.PORT || cliArgs.port, 10),
|
|
25
|
-
host: process.env.HOST || cliArgs.host,
|
|
26
|
-
transport: process.env.TRANSPORT || cliArgs.transport,
|
|
27
|
-
apiKey: process.env.MCP_API_KEY || '',
|
|
28
|
-
enableSse: process.env.ENABLE_SSE !== 'false',
|
|
29
|
-
messageTimeout: parseInt(process.env.MESSAGE_TIMEOUT || '60000', 10),
|
|
30
|
-
heartbeatInterval: parseInt(process.env.HEARTBEAT_INTERVAL || '30000', 10),
|
|
31
|
-
busyRetries: parseInt(process.env.BUSY_RETRIES || '10', 10),
|
|
32
|
-
busyDelay: parseInt(process.env.BUSY_DELAY || '500', 10),
|
|
33
|
-
enableCors: process.env.ENABLE_CORS !== 'false',
|
|
34
|
-
corsOrigin: process.env.CORS_ORIGIN || '*',
|
|
35
|
-
rateLimitWindow: parseInt(process.env.RATE_LIMIT_WINDOW || '60000', 10),
|
|
36
|
-
rateLimitMax: parseInt(process.env.RATE_LIMIT_MAX || '100', 10),
|
|
37
|
-
resourceCacheTtl: parseInt(process.env.RESOURCE_CACHE_TTL || '5000', 10),
|
|
38
|
-
debug: process.env.DEBUG === 'true',
|
|
39
|
-
debugFile: process.env.DEBUG_FILE || '',
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
export function getConfig(key) {
|
|
43
|
-
return config[key];
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export function getAllConfig() {
|
|
47
|
-
return { ...config };
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export default config;
|
package/src/constants.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
export const CAD_PLATFORMS = ['AutoCAD', 'ZWCAD', 'GStarCAD', 'BricsCAD'];
|
|
2
|
-
export const FILE_EXTENSIONS = {
|
|
3
|
-
'AutoCAD': ['.fas', '.vlx'],
|
|
4
|
-
'ZWCAD': ['.zelx', '.vls'],
|
|
5
|
-
'GStarCAD': ['.fas', '.vlx'],
|
|
6
|
-
'BricsCAD': ['.des']
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
export const PROTOCOL_VERSION = '2024-11-05';
|
|
10
|
-
export const SERVER_NAME = 'atlisp-mcp-server';
|
|
11
|
-
export const SERVER_VERSION = '1.1.2';
|
|
12
|
-
|
|
13
|
-
export const MOCK_PACKAGES = [
|
|
14
|
-
{ name: 'base', description: '基础函数库', version: '1.0.0' },
|
|
15
|
-
{ name: 'at-pm', description: '工程项目管理', version: '2.1.0' },
|
|
16
|
-
{ name: 'network', description: '网络功能模块', version: '1.5.0' },
|
|
17
|
-
{ name: 'userman', description: '用户管理', version: '1.2.0' },
|
|
18
|
-
{ name: 'pkgman', description: '包管理器', version: '2.0.0' },
|
|
19
|
-
{ name: 'sidebar', description: '侧边栏工具', version: '1.0.0' },
|
|
20
|
-
{ name: 'aibot', description: 'AI 助手', version: '0.9.0' },
|
|
21
|
-
{ name: 'tips', description: '每日提示', version: '1.0.0' },
|
|
22
|
-
{ name: 'function', description: '函数库', version: '3.0.0' }
|
|
23
|
-
];
|
package/src/errors.js
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
export const ERROR_CODES = {
|
|
2
|
-
CAD_NOT_CONNECTED: 'CAD_NOT_CONNECTED',
|
|
3
|
-
CAD_BUSY: 'CAD_BUSY',
|
|
4
|
-
CAD_NO_DOCUMENT: 'CAD_NO_DOCUMENT',
|
|
5
|
-
LISP_PAREN_MISMATCH: 'LISP_PAREN_MISMATCH',
|
|
6
|
-
TIMEOUT: 'TIMEOUT',
|
|
7
|
-
INVALID_PARAMS: 'INVALID_PARAMS',
|
|
8
|
-
METHOD_NOT_FOUND: 'METHOD_NOT_FOUND',
|
|
9
|
-
RESOURCE_NOT_FOUND: 'RESOURCE_NOT_FOUND',
|
|
10
|
-
PACKAGE_NOT_FOUND: 'PACKAGE_NOT_FOUND',
|
|
11
|
-
INTERNAL_ERROR: 'INTERNAL_ERROR',
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export const ERROR_MESSAGES = {
|
|
15
|
-
[ERROR_CODES.CAD_NOT_CONNECTED]: '未连接 CAD,请先调用 connect_cad',
|
|
16
|
-
[ERROR_CODES.CAD_BUSY]: 'CAD 忙碌,请稍后重试',
|
|
17
|
-
[ERROR_CODES.CAD_NO_DOCUMENT]: 'CAD 中没有打开的文档',
|
|
18
|
-
[ERROR_CODES.LISP_PAREN_MISMATCH]: 'LISP 代码括号不匹配',
|
|
19
|
-
[ERROR_CODES.TIMEOUT]: '操作超时',
|
|
20
|
-
[ERROR_CODES.INVALID_PARAMS]: '无效的参数',
|
|
21
|
-
[ERROR_CODES.METHOD_NOT_FOUND]: '方法不存在',
|
|
22
|
-
[ERROR_CODES.RESOURCE_NOT_FOUND]: '资源不存在',
|
|
23
|
-
[ERROR_CODES.PACKAGE_NOT_FOUND]: '包不存在',
|
|
24
|
-
[ERROR_CODES.INTERNAL_ERROR]: '内部错误',
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
export class MCPError extends Error {
|
|
28
|
-
constructor(code, message, details = {}) {
|
|
29
|
-
super(message);
|
|
30
|
-
this.code = code;
|
|
31
|
-
this.details = details;
|
|
32
|
-
this.name = 'MCPError';
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
toJSON() {
|
|
36
|
-
return {
|
|
37
|
-
code: this.code,
|
|
38
|
-
message: this.message,
|
|
39
|
-
details: this.details
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function createError(code, details = {}) {
|
|
45
|
-
const message = ERROR_MESSAGES[code] || ERROR_MESSAGES[ERROR_CODES.INTERNAL_ERROR];
|
|
46
|
-
return new MCPError(code, message, details);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function toMcpError(error) {
|
|
50
|
-
if (error instanceof MCPError) return error;
|
|
51
|
-
if (error instanceof Error) {
|
|
52
|
-
return new MCPError(ERROR_CODES.INTERNAL_ERROR, error.message, { stack: error.stack });
|
|
53
|
-
}
|
|
54
|
-
return new MCPError(ERROR_CODES.INTERNAL_ERROR, String(error));
|
|
55
|
-
}
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import { cad } from '../cad.js';
|
|
2
|
-
import { log } from '../logger.js';
|
|
3
|
-
|
|
4
|
-
export async function connectCad() {
|
|
5
|
-
log('connect_cad called, cad.connected before: ' + cad.connected);
|
|
6
|
-
try {
|
|
7
|
-
log('calling cad.connect()...');
|
|
8
|
-
const connected = await cad.connect();
|
|
9
|
-
log('cad.connect() returned: ' + connected + ', cad.connected: ' + cad.connected);
|
|
10
|
-
if (connected) {
|
|
11
|
-
return { content: [{ type: 'text', text: `已连接到 ${cad.getPlatform()} ${cad.getVersion()}` }] };
|
|
12
|
-
}
|
|
13
|
-
} catch (e) {
|
|
14
|
-
log('connect_cad error: ' + e.message);
|
|
15
|
-
}
|
|
16
|
-
return { content: [{ type: 'text', text: '未能连接或启动 CAD,请确保 CAD 已安装' }], isError: true };
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export async function evalLisp(code, withResult = false, encoding = null) {
|
|
20
|
-
if (!cad.connected) { await cad.connect(); }
|
|
21
|
-
if (!cad.connected) {
|
|
22
|
-
return { content: [{ type: 'text', text: '未连接 CAD' }], isError: true };
|
|
23
|
-
}
|
|
24
|
-
try {
|
|
25
|
-
if (withResult) {
|
|
26
|
-
const result = await cad.sendCommandWithResult(code, encoding);
|
|
27
|
-
if (result !== null) {
|
|
28
|
-
return { content: [{ type: 'text', text: result }] };
|
|
29
|
-
}
|
|
30
|
-
return { content: [{ type: 'text', text: '执行失败或无返回值' }], isError: true };
|
|
31
|
-
} else {
|
|
32
|
-
await cad.sendCommand(code + '\n');
|
|
33
|
-
return { content: [{ type: 'text', text: `已发送命令: ${code}` }] };
|
|
34
|
-
}
|
|
35
|
-
} catch (e) {
|
|
36
|
-
return { content: [{ type: 'text', text: `错误: ${e.message}` }], isError: true };
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export async function getCadInfo() {
|
|
41
|
-
if (!cad.connected) { await cad.connect(); }
|
|
42
|
-
if (!cad.connected) return { content: [{ type: 'text', text: 'CAD 未连接' }] };
|
|
43
|
-
const info = await cad.getInfo();
|
|
44
|
-
return { content: [{ type: 'text', text: info }] };
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export async function atCommand(command) {
|
|
48
|
-
if (!cad.connected) return { content: [{ type: 'text', text: '请先连接 CAD' }], isError: true };
|
|
49
|
-
await cad.sendCommand(`${command}\n`);
|
|
50
|
-
return { content: [{ type: 'text', text: `已发送命令: ${command}` }] };
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export async function newDocument() {
|
|
54
|
-
if (!cad.connected) { await cad.connect(); }
|
|
55
|
-
if (!cad.connected) return { content: [{ type: 'text', text: '未连接 CAD' }], isError: true };
|
|
56
|
-
const result = await cad.newDoc();
|
|
57
|
-
if (result) {
|
|
58
|
-
return { content: [{ type: 'text', text: '已在 CAD 中新建空白文档' }] };
|
|
59
|
-
}
|
|
60
|
-
return { content: [{ type: 'text', text: '新建文档失败' }], isError: true };
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export async function bringToFront() {
|
|
64
|
-
if (!cad.connected) { await cad.connect(); }
|
|
65
|
-
if (!cad.connected) return { content: [{ type: 'text', text: '未连接 CAD' }], isError: true };
|
|
66
|
-
const result = await cad.bringToFront();
|
|
67
|
-
if (result) {
|
|
68
|
-
return { content: [{ type: 'text', text: '已将 CAD 窗口切换到前台' }] };
|
|
69
|
-
}
|
|
70
|
-
return { content: [{ type: 'text', text: '切换前台失败' }], isError: true };
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export async function installAtlisp() {
|
|
74
|
-
if (!cad.connected) { await cad.connect(); }
|
|
75
|
-
if (!cad.connected) return { content: [{ type: 'text', text: '未连接 CAD' }], isError: true };
|
|
76
|
-
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))))';
|
|
77
|
-
await cad.sendCommand(installCode + '\n');
|
|
78
|
-
return { content: [{ type: 'text', text: '已发送 @lisp 安装代码到 CAD' }] };
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export async function listFunctionsInCad() {
|
|
82
|
-
if (!cad.connected) { await cad.connect(); }
|
|
83
|
-
if (!cad.connected) return { content: [{ type: 'text', text: '未连接 CAD' }], isError: true };
|
|
84
|
-
try {
|
|
85
|
-
const result = await cad.sendCommandWithResult('(atoms-family 0)', null);
|
|
86
|
-
if (result) return { content: [{ type: 'text', text: result }] };
|
|
87
|
-
return { content: [{ type: 'text', text: 'CAD 函数列表为空' }] };
|
|
88
|
-
} catch (e) {
|
|
89
|
-
return { content: [{ type: 'text', text: `错误: ${e.message}` }], isError: true };
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export async function initAtlisp() {
|
|
94
|
-
if (!cad.connected) { await cad.connect(); }
|
|
95
|
-
if (!cad.connected) return { content: [{ type: 'text', text: '未连接 CAD' }], isError: true };
|
|
96
|
-
const code = `(if (null @::load-module)
|
|
97
|
-
(progn
|
|
98
|
-
(vl-load-com)
|
|
99
|
-
(setq s strcat h"http"o(vlax-create-object (s"win"h".win"h"request.5.1"))
|
|
100
|
-
v vlax-invoke e eval r read)(v o'open "get" (s h"://""atlisp.""cn/cloud"):vlax-true)
|
|
101
|
-
(v o'send)
|
|
102
|
-
(v o'WaitforResponse 1000)
|
|
103
|
-
(e(r(vlax-get o'ResponseText)))))`;
|
|
104
|
-
try {
|
|
105
|
-
await cad.sendCommand(code + '\n');
|
|
106
|
-
return { content: [{ type: 'text', text: '已发送 @lisp 函数库加载代码到 CAD' }] };
|
|
107
|
-
} catch (e) {
|
|
108
|
-
return { content: [{ type: 'text', text: `错误: ${e.message}` }], isError: true };
|
|
109
|
-
}
|
|
110
|
-
}
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import fs from 'fs/promises';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { log } from '../logger.js';
|
|
4
|
-
import * as cadHandlers from './cad-handlers.js';
|
|
5
|
-
|
|
6
|
-
const FUNCTIONS_DIR = process.env.FUNCTIONS_DIR || '';
|
|
7
|
-
const FUNCTIONS_URL = process.env.FUNCTIONS_URL || 'http://s3.atlisp.cn/json/functions.json';
|
|
8
|
-
|
|
9
|
-
async function fileExists(filePath) {
|
|
10
|
-
try { await fs.access(filePath); return true; } catch { return false; }
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export async function getFunctionUsage(funcName, packageName) {
|
|
14
|
-
try {
|
|
15
|
-
let results = [];
|
|
16
|
-
|
|
17
|
-
try {
|
|
18
|
-
const response = await fetch(FUNCTIONS_URL, { headers: { 'User-Agent': 'atlisp-mcp' } });
|
|
19
|
-
if (response.ok) {
|
|
20
|
-
const data = await response.json();
|
|
21
|
-
const funcs = data.functions || [];
|
|
22
|
-
const func = funcs.find(f => f.name === funcName || f.name === `${packageName}:${funcName}`);
|
|
23
|
-
if (func) results.push(func);
|
|
24
|
-
}
|
|
25
|
-
} catch (e) {
|
|
26
|
-
log(`function-handlers fetch error: ${e.message}`);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
if (results.length === 0 && FUNCTIONS_DIR && await fileExists(FUNCTIONS_DIR)) {
|
|
30
|
-
const files = (await fs.readdir(FUNCTIONS_DIR)).filter(f => f.endsWith('.json'));
|
|
31
|
-
for (const file of files) {
|
|
32
|
-
const raw = await fs.readFile(path.join(FUNCTIONS_DIR, file), 'utf-8');
|
|
33
|
-
const data = JSON.parse(raw);
|
|
34
|
-
const func = data.functions?.find(f => f.name === funcName);
|
|
35
|
-
if (func) results.push(func);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
if (results.length === 0) {
|
|
40
|
-
return { content: [{ type: 'text', text: `未找到函数: ${funcName}` }], isError: true };
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const f = results[0];
|
|
44
|
-
const text = `函数: ${f.name}
|
|
45
|
-
描述: ${f.description || '-'}
|
|
46
|
-
参数: ${f.params?.join(', ') || '-'}
|
|
47
|
-
返回值: ${f.return || '-'}
|
|
48
|
-
示例: ${f.usage || '-'}`;
|
|
49
|
-
|
|
50
|
-
return { content: [{ type: 'text', text }] };
|
|
51
|
-
} catch (e) {
|
|
52
|
-
return { content: [{ type: 'text', text: `错误: ${e.message}` }], isError: true };
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export async function listFunctions(packageName) {
|
|
57
|
-
try {
|
|
58
|
-
let allFuncs = [];
|
|
59
|
-
|
|
60
|
-
try {
|
|
61
|
-
const response = await fetch(FUNCTIONS_URL, { headers: { 'User-Agent': 'atlisp-mcp' } });
|
|
62
|
-
if (response.ok) {
|
|
63
|
-
const data = await response.json();
|
|
64
|
-
const funcs = data.functions || [];
|
|
65
|
-
if (packageName) {
|
|
66
|
-
allFuncs = funcs.filter(f => f.name.startsWith(`${packageName}:`));
|
|
67
|
-
} else {
|
|
68
|
-
allFuncs = funcs.map(f => `${f.name}: ${f.description || '-'}`);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
} catch (e) {
|
|
72
|
-
log(`function-handlers list fetch error: ${e.message}`);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (allFuncs.length === 0 && FUNCTIONS_DIR && await fileExists(FUNCTIONS_DIR)) {
|
|
76
|
-
const files = (await fs.readdir(FUNCTIONS_DIR)).filter(f => f.endsWith('.json'));
|
|
77
|
-
for (const file of files) {
|
|
78
|
-
try {
|
|
79
|
-
const raw = await fs.readFile(path.join(FUNCTIONS_DIR, file), 'utf-8');
|
|
80
|
-
const data = JSON.parse(raw);
|
|
81
|
-
const funcs = data.functions || [];
|
|
82
|
-
if (packageName) {
|
|
83
|
-
allFuncs = funcs.filter(f => f.name.startsWith(`${packageName}:`));
|
|
84
|
-
} else {
|
|
85
|
-
for (const f of funcs) {
|
|
86
|
-
allFuncs.push(`${f.name}: ${f.description || '-'}`);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
} catch (err) {
|
|
90
|
-
log(`read file ${file} error: ${err.message}`);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (allFuncs.length === 0) {
|
|
96
|
-
return await cadHandlers.listFunctionsInCad();
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (packageName) {
|
|
100
|
-
return { content: [{ type: 'text', text: `${packageName} 包函数 (${allFuncs.length}):\n${allFuncs.map(f => f.name).join('\n')}` }] };
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
return { content: [{ type: 'text', text: allFuncs.join('\n') }] };
|
|
104
|
-
} catch (e) {
|
|
105
|
-
return { content: [{ type: 'text', text: `错误: ${e.message}` }], isError: true };
|
|
106
|
-
}
|
|
107
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
const PACKAGES_LIST_URL = process.env.PACKAGES_LIST_URL || 'http://s3.atlisp.cn/packages-list?fmt=json';
|
|
2
|
-
import { cad } from '../cad.js';
|
|
3
|
-
import { MOCK_PACKAGES } from '../constants.js';
|
|
4
|
-
let cachedPackages = null;
|
|
5
|
-
let cachedAt = null;
|
|
6
|
-
const CACHE_TTL = 10 * 60 * 1000;
|
|
7
|
-
|
|
8
|
-
async function fetchPackages() {
|
|
9
|
-
const now = Date.now();
|
|
10
|
-
if (cachedPackages && cachedAt && (now - cachedAt) < CACHE_TTL) {
|
|
11
|
-
return cachedPackages;
|
|
12
|
-
}
|
|
13
|
-
try {
|
|
14
|
-
const response = await fetch(PACKAGES_LIST_URL, { headers: { 'User-Agent': 'atlisp-mcp' } });
|
|
15
|
-
if (response.ok) {
|
|
16
|
-
const data = await response.json();
|
|
17
|
-
cachedPackages = data;
|
|
18
|
-
cachedAt = now;
|
|
19
|
-
return data;
|
|
20
|
-
}
|
|
21
|
-
} catch (e) {}
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export async function listPackages() {
|
|
26
|
-
if (!cad.connected) await cad.connect();
|
|
27
|
-
if (!cad.connected) return { content: [{ type: 'text', text: '未连接 CAD' }], isError: true };
|
|
28
|
-
const result = await cad.sendCommandWithResult('(@::package-list-in-local)');
|
|
29
|
-
if (result && result !== 'nil') return { content: [{ type: 'text', text: result }] };
|
|
30
|
-
const packages = MOCK_PACKAGES.map(p => `${p.name} v${p.version} - ${p.description}`);
|
|
31
|
-
return { content: [{ type: 'text', text: `已安装的 @lisp 包:\n${packages.join('\n')}` }] };
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export async function searchPackages(query) {
|
|
35
|
-
const data = await fetchPackages();
|
|
36
|
-
if (!data) {
|
|
37
|
-
return { content: [{ type: 'text', text: '无法获取包列表' }], isError: true };
|
|
38
|
-
}
|
|
39
|
-
const results = [];
|
|
40
|
-
const items = Array.isArray(data) ? data : (data.packages || []);
|
|
41
|
-
if (query) {
|
|
42
|
-
const q = query.toLowerCase();
|
|
43
|
-
for (const p of items) {
|
|
44
|
-
const name = (p.name || '').toLowerCase();
|
|
45
|
-
const desc = (p.description || '').toLowerCase();
|
|
46
|
-
if (name.includes(q) || desc.includes(q)) results.push(p);
|
|
47
|
-
}
|
|
48
|
-
} else {
|
|
49
|
-
for (const p of items) results.push(p);
|
|
50
|
-
}
|
|
51
|
-
if (results.length === 0) {
|
|
52
|
-
return { content: [{ type: 'text', text: `未找到包含 "${query}" 的包` }] };
|
|
53
|
-
}
|
|
54
|
-
return { content: [{ type: 'text', text: `搜索结果 (${results.length}):\n${results.map(p => `${p.name} v${p.version || ''} - ${p.description || ''}`).join('\n')}` }] };
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export async function installPackage(packageName) {
|
|
58
|
-
const pkg = MOCK_PACKAGES.find(p => p.name === packageName);
|
|
59
|
-
if (!pkg) {
|
|
60
|
-
return { content: [{ type: 'text', text: `错误: 包 "${packageName}" 不存在` }], isError: true };
|
|
61
|
-
}
|
|
62
|
-
if (cad.connected) {
|
|
63
|
-
await cad.sendCommand(`(@::load-module '${packageName})\n`);
|
|
64
|
-
}
|
|
65
|
-
return { content: [{ type: 'text', text: `包 "${packageName}" 安装命令已发送!` }] };
|
|
66
|
-
}
|