@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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlisp/mcp",
3
- "version": "1.1.5",
3
+ "version": "1.2.0",
4
4
  "description": "MCP Server for @lisp on CAD",
5
5
  "type": "module",
6
6
  "bin": {
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
- get_file_extensions: (a) => {
133
- const exts = FILE_EXTENSIONS[a.platform];
134
- if (!exts) return { content: [{ type: 'text', text: `平台 ${a.platform} 不支持` }], isError: true };
135
- return { content: [{ type: 'text', text: `${a.platform} 文件扩展名: ${exts.join(', ')}` }] };
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 函数用法(从本地 JSON 库)',
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: 'get_file_extensions',
289
- description: '获取 CAD 平台对应的文件扩展名',
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
- platform: { type: 'string', description: 'CAD 平台,如 AutoCAD, ZWCAD, GStarCAD, BricsCAD' }
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 || 'https://s3.atlisp.cn/json/';
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(url, { headers: { 'User-Agent': 'Mozilla/5.0' } });
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
- if (data.functions) {
25
- const func = data.functions.find(f => f.name === funcName);
26
- if (func) results.push(func);
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
- if (packageName) {
35
- const filePath = path.join(FUNCTIONS_DIR, `${packageName}.json`);
36
- if (await fileExists(filePath)) {
37
- const raw = await fs.readFile(filePath, 'utf-8');
38
- const data = JSON.parse(raw);
39
- const func = data.functions?.find(f => f.name === funcName);
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(url, { headers: { 'User-Agent': 'Mozilla/5.0' } });
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
- if (data.functions) {
83
- allFuncs = packageName
84
- ? data.functions
85
- : data.functions.map(f => `${f.name}: ${f.description || '-'}`);
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
- const raw = await fs.readFile(path.join(FUNCTIONS_DIR, file), 'utf-8');
96
- const data = JSON.parse(raw);
97
- if (packageName) {
98
- if (data.package === packageName) {
99
- allFuncs = data.functions || [];
100
- }
101
- } else {
102
- for (const f of (data.functions || [])) {
103
- allFuncs.push(`${f.name}: ${f.description || '-'}`);
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 { content: [{ type: 'text', text: '函数库为空' }] };
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: `函数库共 ${allFuncs.length} 个函数` }] };
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 results = MOCK_PACKAGES.filter(p => p.name.includes(query) || p.description.includes(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
+ }
11
51
  if (results.length === 0) {
12
52
  return { content: [{ type: 'text', text: `未找到包含 "${query}" 的包` }] };
13
53
  }
14
- return { content: [{ type: 'text', text: `搜索结果:\n${results.map(p => `${p.name} v${p.version} - ${p.description}`).join('\n')}` }] };
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) {