@atlisp/mcp 1.1.5 → 1.2.0
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 +1 -0
- package/package.json +1 -1
- package/src/atlisp-mcp.js +37 -21
- package/src/handlers/cad-handlers.js +12 -0
- package/src/handlers/function-handlers.js +32 -46
- package/src/handlers/package-handlers.js +42 -2
package/README.md
CHANGED
|
@@ -254,6 +254,7 @@ curl http://localhost:8110/health
|
|
|
254
254
|
| `install_atlisp` | 安装 @lisp | 无 |
|
|
255
255
|
| `get_function_usage` | 获取函数用法 | `name`: 函数名 |
|
|
256
256
|
| `list_functions` | 列出所有函数 | 无 |
|
|
257
|
+
| `load_atlisp_functionlib` | 从网络加载函数库(AI 提示词/编程) | `format`: json/list |
|
|
257
258
|
|
|
258
259
|
### MCP 方法
|
|
259
260
|
|
package/package.json
CHANGED
package/src/atlisp-mcp.js
CHANGED
|
@@ -113,6 +113,30 @@ const mcpLimiter = rateLimit({
|
|
|
113
113
|
|
|
114
114
|
export { CAD_PLATFORMS, FILE_EXTENSIONS, MOCK_PACKAGES } from './constants.js';
|
|
115
115
|
|
|
116
|
+
const FUNCTION_LIB_URL = process.env.FUNCTION_LIB_URL || 'http://s3.atlisp.cn/json/functions.json';
|
|
117
|
+
let cachedFunctionLib = null;
|
|
118
|
+
let cachedAt = null;
|
|
119
|
+
const CACHE_TTL = 5 * 60 * 1000;
|
|
120
|
+
|
|
121
|
+
export async function loadAtlibFunctionLib() {
|
|
122
|
+
const now = Date.now();
|
|
123
|
+
if (cachedFunctionLib && cachedAt && (now - cachedAt) < CACHE_TTL) {
|
|
124
|
+
return cachedFunctionLib;
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
const response = await fetch(FUNCTION_LIB_URL, { headers: { 'User-Agent': 'atlisp-mcp' } });
|
|
128
|
+
if (response.ok) {
|
|
129
|
+
const data = await response.json();
|
|
130
|
+
cachedFunctionLib = data;
|
|
131
|
+
cachedAt = now;
|
|
132
|
+
return data;
|
|
133
|
+
}
|
|
134
|
+
} catch (e) {
|
|
135
|
+
log(`loadAtlibFunctionLib error: ${e.message}`);
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
|
|
116
140
|
const TOOL_HANDLERS = {
|
|
117
141
|
connect_cad: () => cadHandlers.connectCad(),
|
|
118
142
|
eval_lisp: (a) => cadHandlers.evalLisp(a.code),
|
|
@@ -129,17 +153,15 @@ const TOOL_HANDLERS = {
|
|
|
129
153
|
init_atlisp: () => cadHandlers.initAtlisp(),
|
|
130
154
|
get_function_usage: (a) => functionHandlers.getFunctionUsage(a.name, a.package),
|
|
131
155
|
list_functions: (a) => functionHandlers.listFunctions(a.package),
|
|
132
|
-
|
|
133
|
-
const
|
|
134
|
-
if (!
|
|
135
|
-
|
|
156
|
+
load_atlisp_functionlib: async (a) => {
|
|
157
|
+
const data = await loadAtlibFunctionLib();
|
|
158
|
+
if (!data) return { content: [{ type: 'text', text: '加载函数库失败' }], isError: true };
|
|
159
|
+
if (a.format === 'list') {
|
|
160
|
+
const items = data.functions?.map(f => `${f.name}: ${f.description || ''}`) || [];
|
|
161
|
+
return { content: [{ type: 'text', text: items.join('\n') }] };
|
|
162
|
+
}
|
|
163
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
136
164
|
},
|
|
137
|
-
get_install_code: () => ({
|
|
138
|
-
content: [{
|
|
139
|
-
type: 'text',
|
|
140
|
-
text: `复制以下代码到 CAD 命令行加载:\n(load "atlisp" "atlisp")\n(vlax-create-object "atlisp.atlisp.1")`
|
|
141
|
-
}]
|
|
142
|
-
}),
|
|
143
165
|
};
|
|
144
166
|
|
|
145
167
|
function getOrCreateSessionServer(sessionId) {
|
|
@@ -264,7 +286,7 @@ export const tools = [
|
|
|
264
286
|
},
|
|
265
287
|
{
|
|
266
288
|
name: 'get_function_usage',
|
|
267
|
-
description: '获取 @lisp
|
|
289
|
+
description: '获取 @lisp 函数用法',
|
|
268
290
|
inputSchema: {
|
|
269
291
|
type: 'object',
|
|
270
292
|
properties: {
|
|
@@ -285,20 +307,14 @@ export const tools = [
|
|
|
285
307
|
}
|
|
286
308
|
},
|
|
287
309
|
{
|
|
288
|
-
name: '
|
|
289
|
-
description: '
|
|
310
|
+
name: 'load_atlisp_functionlib',
|
|
311
|
+
description: '从 http://s3.atlisp.cn/json/functions.json 加载 @lisp 函数库(用于 AI Agent 的提示词和编程调用)',
|
|
290
312
|
inputSchema: {
|
|
291
313
|
type: 'object',
|
|
292
314
|
properties: {
|
|
293
|
-
|
|
294
|
-
}
|
|
295
|
-
required: ['platform']
|
|
315
|
+
format: { type: 'string', enum: ['json', 'list'], description: '返回格式:json(完整JSON)或 list(函数名:描述 列表),默认 json' }
|
|
316
|
+
}
|
|
296
317
|
}
|
|
297
|
-
},
|
|
298
|
-
{
|
|
299
|
-
name: 'get_install_code',
|
|
300
|
-
description: '获取 @lisp 安装代码',
|
|
301
|
-
inputSchema: { type: 'object', properties: {} }
|
|
302
318
|
}
|
|
303
319
|
];
|
|
304
320
|
|
|
@@ -78,6 +78,18 @@ export async function installAtlisp() {
|
|
|
78
78
|
return { content: [{ type: 'text', text: '已发送 @lisp 安装代码到 CAD' }] };
|
|
79
79
|
}
|
|
80
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
|
+
|
|
81
93
|
export async function initAtlisp() {
|
|
82
94
|
if (!cad.connected) { await cad.connect(); }
|
|
83
95
|
if (!cad.connected) return { content: [{ type: 'text', text: '未连接 CAD' }], isError: true };
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import fs from 'fs/promises';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { log } from '../logger.js';
|
|
4
|
+
import * as cadHandlers from './cad-handlers.js';
|
|
4
5
|
|
|
5
6
|
const FUNCTIONS_DIR = process.env.FUNCTIONS_DIR || '';
|
|
6
|
-
const FUNCTIONS_URL = process.env.FUNCTIONS_URL || '
|
|
7
|
+
const FUNCTIONS_URL = process.env.FUNCTIONS_URL || 'http://s3.atlisp.cn/json/functions.json';
|
|
7
8
|
|
|
8
9
|
async function fileExists(filePath) {
|
|
9
10
|
try { await fs.access(filePath); return true; } catch { return false; }
|
|
@@ -13,40 +14,25 @@ export async function getFunctionUsage(funcName, packageName) {
|
|
|
13
14
|
try {
|
|
14
15
|
let results = [];
|
|
15
16
|
|
|
16
|
-
const url = packageName
|
|
17
|
-
? `${FUNCTIONS_URL}${packageName}.json`
|
|
18
|
-
: `${FUNCTIONS_URL}all.json`;
|
|
19
|
-
|
|
20
17
|
try {
|
|
21
|
-
const response = await fetch(
|
|
18
|
+
const response = await fetch(FUNCTIONS_URL, { headers: { 'User-Agent': 'atlisp-mcp' } });
|
|
22
19
|
if (response.ok) {
|
|
23
20
|
const data = await response.json();
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
21
|
+
const funcs = data.functions || [];
|
|
22
|
+
const func = funcs.find(f => f.name === funcName || f.name === `${packageName}:${funcName}`);
|
|
23
|
+
if (func) results.push(func);
|
|
28
24
|
}
|
|
29
25
|
} catch (e) {
|
|
30
26
|
log(`function-handlers fetch error: ${e.message}`);
|
|
31
27
|
}
|
|
32
28
|
|
|
33
29
|
if (results.length === 0 && FUNCTIONS_DIR && await fileExists(FUNCTIONS_DIR)) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
if (func) results.push(func);
|
|
41
|
-
}
|
|
42
|
-
} else {
|
|
43
|
-
const files = (await fs.readdir(FUNCTIONS_DIR)).filter(f => f.endsWith('.json'));
|
|
44
|
-
for (const file of files) {
|
|
45
|
-
const raw = await fs.readFile(path.join(FUNCTIONS_DIR, file), 'utf-8');
|
|
46
|
-
const data = JSON.parse(raw);
|
|
47
|
-
const func = data.functions?.find(f => f.name === funcName);
|
|
48
|
-
if (func) results.push(func);
|
|
49
|
-
}
|
|
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);
|
|
50
36
|
}
|
|
51
37
|
}
|
|
52
38
|
|
|
@@ -71,18 +57,15 @@ export async function listFunctions(packageName) {
|
|
|
71
57
|
try {
|
|
72
58
|
let allFuncs = [];
|
|
73
59
|
|
|
74
|
-
const url = packageName
|
|
75
|
-
? `${FUNCTIONS_URL}${packageName}.json`
|
|
76
|
-
: `${FUNCTIONS_URL}all.json`;
|
|
77
|
-
|
|
78
60
|
try {
|
|
79
|
-
const response = await fetch(
|
|
61
|
+
const response = await fetch(FUNCTIONS_URL, { headers: { 'User-Agent': 'atlisp-mcp' } });
|
|
80
62
|
if (response.ok) {
|
|
81
63
|
const data = await response.json();
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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 || '-'}`);
|
|
86
69
|
}
|
|
87
70
|
}
|
|
88
71
|
} catch (e) {
|
|
@@ -92,29 +75,32 @@ export async function listFunctions(packageName) {
|
|
|
92
75
|
if (allFuncs.length === 0 && FUNCTIONS_DIR && await fileExists(FUNCTIONS_DIR)) {
|
|
93
76
|
const files = (await fs.readdir(FUNCTIONS_DIR)).filter(f => f.endsWith('.json'));
|
|
94
77
|
for (const file of files) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
+
}
|
|
104
88
|
}
|
|
89
|
+
} catch (err) {
|
|
90
|
+
log(`read file ${file} error: ${err.message}`);
|
|
105
91
|
}
|
|
106
92
|
}
|
|
107
93
|
}
|
|
108
94
|
|
|
109
95
|
if (allFuncs.length === 0) {
|
|
110
|
-
return
|
|
96
|
+
return await cadHandlers.listFunctionsInCad();
|
|
111
97
|
}
|
|
112
98
|
|
|
113
99
|
if (packageName) {
|
|
114
100
|
return { content: [{ type: 'text', text: `${packageName} 包函数 (${allFuncs.length}):\n${allFuncs.map(f => f.name).join('\n')}` }] };
|
|
115
101
|
}
|
|
116
102
|
|
|
117
|
-
return { content: [{ type: 'text', text:
|
|
103
|
+
return { content: [{ type: 'text', text: allFuncs.join('\n') }] };
|
|
118
104
|
} catch (e) {
|
|
119
105
|
return { content: [{ type: 'text', text: `错误: ${e.message}` }], isError: true };
|
|
120
106
|
}
|
|
@@ -1,17 +1,57 @@
|
|
|
1
|
+
const PACKAGES_LIST_URL = process.env.PACKAGES_LIST_URL || 'http://s3.atlisp.cn/packages-list?fmt=json';
|
|
1
2
|
import { cad } from '../cad.js';
|
|
2
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
|
+
}
|
|
3
24
|
|
|
4
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 }] };
|
|
5
30
|
const packages = MOCK_PACKAGES.map(p => `${p.name} v${p.version} - ${p.description}`);
|
|
6
31
|
return { content: [{ type: 'text', text: `已安装的 @lisp 包:\n${packages.join('\n')}` }] };
|
|
7
32
|
}
|
|
8
33
|
|
|
9
34
|
export async function searchPackages(query) {
|
|
10
|
-
const
|
|
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
|
+
}
|
|
11
51
|
if (results.length === 0) {
|
|
12
52
|
return { content: [{ type: 'text', text: `未找到包含 "${query}" 的包` }] };
|
|
13
53
|
}
|
|
14
|
-
return { content: [{ type: 'text', text:
|
|
54
|
+
return { content: [{ type: 'text', text: `搜索结果 (${results.length}):\n${results.map(p => `${p.name} v${p.version || ''} - ${p.description || ''}`).join('\n')}` }] };
|
|
15
55
|
}
|
|
16
56
|
|
|
17
57
|
export async function installPackage(packageName) {
|