@atlisp/mcp 1.3.0 → 1.3.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.
@@ -1,268 +0,0 @@
1
- import { cad } from '../cad.js';
2
-
3
- const CACHE_TTL = 60000;
4
- const cache = new Map();
5
-
6
- function getCached(key) {
7
- const entry = cache.get(key);
8
- if (entry && Date.now() - entry.timestamp < CACHE_TTL) return entry.data;
9
- cache.delete(key);
10
- return null;
11
- }
12
-
13
- function setCache(key, data) {
14
- cache.set(key, { data, timestamp: Date.now() });
15
- }
16
-
17
- export const PROMPTS = [
18
- {
19
- name: "draw-residential",
20
- description: "生成农村民居平面布置图",
21
- arguments: [
22
- { name: "width", description: "建筑宽度 (mm)", required: true },
23
- { name: "depth", description: "建筑深度 (mm)", required: true },
24
- { name: "rooms", description: "房间数量", required: false }
25
- ]
26
- },
27
- {
28
- name: "draw-floor-plan",
29
- description: "生成建筑楼层平面图",
30
- arguments: [
31
- { name: "floor", description: "楼层名称", required: true },
32
- { name: "scale", description: "图纸比例", required: false }
33
- ]
34
- },
35
- {
36
- name: "analyze-drawing",
37
- description: "分析当前图纸统计信息",
38
- arguments: []
39
- },
40
- {
41
- name: "batch-draw-lines",
42
- description: "批量绘制线段",
43
- arguments: [
44
- { name: "count", description: "线段数量", required: true },
45
- { name: "length", description: "线段长度", required: false }
46
- ]
47
- },
48
- {
49
- name: "generate-dimension",
50
- description: "生成尺寸标注",
51
- arguments: [
52
- { name: "style", description: "标注样式 (horizontal/vertical/aligned)", required: false }
53
- ]
54
- },
55
- {
56
- name: "export-entities",
57
- description: "导出图纸实体统计",
58
- arguments: [
59
- { name: "format", description: "导出格式 (json/list)", required: false }
60
- ]
61
- }
62
- ];
63
-
64
- export async function listPrompts() {
65
- return PROMPTS;
66
- }
67
-
68
- export async function getPrompt(name, arguments_ = {}) {
69
- switch (name) {
70
- case "draw-residential":
71
- return generateResidentialPrompt(arguments_);
72
- case "draw-floor-plan":
73
- return generateFloorPlanPrompt(arguments_);
74
- case "analyze-drawing":
75
- return generateAnalyzePrompt();
76
- case "batch-draw-lines":
77
- return generateBatchLinesPrompt(arguments_);
78
- case "generate-dimension":
79
- return generateDimensionPrompt(arguments_);
80
- case "export-entities":
81
- return generateExportPrompt(arguments_);
82
- default:
83
- throw new Error(`Unknown prompt: ${name}`);
84
- }
85
- }
86
-
87
- async function generateResidentialPrompt(args) {
88
- const { width = 15000, depth = 12000, rooms = 6 } = args;
89
- const code = `(defun c:draw-residential (/ old-layer p)
90
- (setq old-layer (getvar "clayer"))
91
- (setvar "clayer" "0")
92
- (setq p (list 0 0))
93
- (command "_.rectangle" p (list ${width} ${depth}))
94
- ${generateRoomLayout(width, depth, rooms)}
95
- (setvar "clayer" old-layer)
96
- (princ)
97
- )
98
- (c:draw-residential)`;
99
-
100
- return {
101
- messages: [{
102
- role: "user",
103
- content: {
104
- type: "text",
105
- text: `请在 CAD 中绘制农村民居平面图,尺寸 ${width}x${depth}mm,${rooms} 个房间。\n\n生成的 AutoLISP 代码:\n\`\`\`lisp\n${code}\n\`\`\`\n\n直接执行此代码即可生成图纸。`
106
- }
107
- }]
108
- };
109
- }
110
-
111
- function generateRoomLayout(width, depth, rooms) {
112
- if (rooms === 6) {
113
- return `(command "_.line" (list 5000 0) (list 5000 ${depth}) "")
114
- (command "_.line" (list 0 4000) (list ${width} 4000) "")
115
- (command "_.text" "j" "c" "2500,2000" 800 0 "客厅")
116
- (command "_.text" "j" "c" "7500,6000" 800 0 "餐厅")
117
- (command "_.text" "j" "c" "12500,2000" 800 0 "主卧")`;
118
- }
119
- return `(command "_.text" "j" "c" (list (/ ${width} 2) (/ ${depth} 2)) 800 0 "房间")`;
120
- }
121
-
122
- async function generateFloorPlanPrompt(args) {
123
- const { floor = "一层", scale = "1:100" } = args;
124
-
125
- return {
126
- messages: [{
127
- role: "user",
128
- content: {
129
- type: "text",
130
- text: `生成 ${floor} 平面图,比例 ${scale}。\n\n提示:\n1. 使用 RECTANGLE 命令绘制外墙\n2. 使用 LINE 命令绘制内部隔墙\n3. 使用 TEXT 命令添加房间标注\n4. 使用 DIMLINEAR 或 DIMALIGNED 添加尺寸标注\n\n需要我执行哪些操作?`
131
- }
132
- }]
133
- };
134
- }
135
-
136
- async function generateAnalyzePrompt() {
137
- let entities = { total: 0, byType: {} };
138
- let layers = [];
139
-
140
- try {
141
- if (cad.connected) {
142
- const entResult = await cad.sendCommandWithResult(
143
- `(progn(setq ss(ssget "_X"))(if ss(progn(setq elst(mapcar(quote(lambda(x)(cdr(assoc 0(entget x))))) (pickset:to-list ss)))(stat:stat elst))))`
144
- );
145
- if (entResult) {
146
- try { entities = JSON.parse(entResult); } catch {}
147
- }
148
-
149
- const layerResult = await cad.sendCommandWithResult(
150
- `(progn(setq res nil)(while(setq e(tblnext "LAYER"(null res)))(setq res(cons(cdr(assoc 2 e))res)))(reverse res))`
151
- );
152
- if (layerResult) {
153
- try { layers = JSON.parse(layerResult); } catch {}
154
- }
155
- }
156
- } catch (e) {}
157
-
158
- return {
159
- messages: [{
160
- role: "user",
161
- content: {
162
- type: "text",
163
- text: `图纸分析报告:
164
-
165
- **实体统计:** ${entities.total} 个
166
- ${Object.entries(entities.byType).map(([t, c]) => `- ${t}: ${c}`).join('\n')}
167
-
168
- **图层列表:** ${layers.length} 个
169
- ${layers.slice(0, 10).join(', ')}${layers.length > 10 ? '...' : ''}
170
-
171
- **建议:**\n${generateSuggestions(entities, layers)}`
172
- }
173
- }]
174
- };
175
- }
176
-
177
- function generateSuggestions(entities, layers) {
178
- const suggestions = [];
179
- if (entities.total > 1000) suggestions.push("- 图纸实体较多,建议使用图层分类管理");
180
- if (layers.length > 20) suggestions.push("- 图层较多,建议清理未使用的图层");
181
- if (entities.byType.LINE && entities.byType.LINE > 500) suggestions.push("- 线条较多,可考虑使用 BLOCK 减少实体数量");
182
- if (!entities.byType.DIMENSION) suggestions.push("- 未发现尺寸标注,建议添加标注");
183
- return suggestions.length ? suggestions.join('\n') : '- 图纸结构良好';
184
- }
185
-
186
- async function generateBatchLinesPrompt(args) {
187
- const { count = 5, length = 1000 } = args;
188
-
189
- const code = `(defun c:batch-lines (/ i)
190
- (setq i 0)
191
- (repeat ${count}
192
- (command "_.line" (list (* i ${length}) 0) (list (* (1+ i) ${length}) 0) "")
193
- (setq i (1+ i))
194
- )
195
- (princ)
196
- )
197
- (c:batch-lines)`;
198
-
199
- return {
200
- messages: [{
201
- role: "user",
202
- content: {
203
- type: "text",
204
- text: `批量绘制 ${count} 条长度为 ${length} 的水平线段:\n\n\`\`\`lisp\n${code}\n\`\`\`\n\n此代码将绘制 ${count} 条首尾相连的水平线段。`
205
- }
206
- }]
207
- };
208
- }
209
-
210
- async function generateDimensionPrompt(args) {
211
- const { style = "aligned" } = args;
212
-
213
- let dimStyle = "aligned";
214
- let lispStyle = "DIMALIGNED";
215
- if (style === "horizontal") { dimStyle = "水平"; lispStyle = "DIMLINEAR"; }
216
- if (style === "vertical") { dimStyle = "垂直"; lispStyle = "DIMLINEAR"; }
217
-
218
- return {
219
- messages: [{
220
- role: "user",
221
- content: {
222
- type: "text",
223
- text: `生成 ${dimStyle} 尺寸标注代码:\n\n\`\`\`lisp\n(command "_${lispStyle}" "0,0" "1000,0" "500,200" "")
224
- \`\`\`\n\n参数说明:\n- 起点: 0,0\n- 终点: 1000,0\n- 标注位置: 500,200\n\n执行后将创建 ${dimStyle} 尺寸标注。`
225
- }
226
- }]
227
- };
228
- }
229
-
230
- async function generateExportPrompt(args) {
231
- const { format = "json" } = args;
232
-
233
- const code = format === "json"
234
- ? `(progn
235
- (setq ss(ssget "_X"))
236
- (if ss
237
- (progn
238
- (setq elst(mapcar(quote(lambda(x)(list(cdr(assoc 0(entget x)))(vlax-get x 'ObjectName))))(pickset:to-list ss)))
239
- (stat:stat(mapcar(quote car)elst))
240
- )
241
- )
242
- )`
243
- : `(progn
244
- (princ "\\n实体统计:\\n")
245
- (princ "类型 数量\\n")
246
- (princ "---------------------\\n")
247
- (setq ss(ssget "_X"))
248
- (if ss
249
- (progn
250
- (setq elst(mapcar(quote(lambda(x)(cdr(assoc 0(entget x))))) (pickset:to-list ss)))
251
- (foreach t '("LINE" "CIRCLE" "ARC" "TEXT" "MTEXT" "POLYLINE" "LWPOLYLINE" "INSERT")
252
- (setq cnt(length(member t elst)))
253
- (if(> cnt 0)(princ(strcat t " "(itoa cnt) "\\n")))
254
- )
255
- )
256
- )
257
- )`;
258
-
259
- return {
260
- messages: [{
261
- role: "user",
262
- content: {
263
- type: "text",
264
- text: `导出实体统计 (${format} 格式):\n\n\`\`\`lisp\n${code}\n\`\`\`\n\n执行后将输出实体统计信息。`
265
- }
266
- }]
267
- };
268
- }
@@ -1,364 +0,0 @@
1
- import { cad } from '../cad.js';
2
- import { CAD_PLATFORMS, FILE_EXTENSIONS, MOCK_PACKAGES } from '../constants.js';
3
- import { log } from '../logger.js';
4
- import config from '../config.js';
5
-
6
- const CACHE_TTL = config.resourceCacheTtl;
7
- const resourceCache = new Map();
8
-
9
- function getCached(key) {
10
- const entry = resourceCache.get(key);
11
- if (entry && Date.now() - entry.timestamp < CACHE_TTL) {
12
- return entry.data;
13
- }
14
- resourceCache.delete(key);
15
- return null;
16
- }
17
-
18
- function setCache(key, data) {
19
- resourceCache.set(key, { data, timestamp: Date.now() });
20
- }
21
-
22
- function parseLispList(str) {
23
- if (!str || typeof str !== 'string') return null;
24
- const trimmed = str.trim();
25
- if (!trimmed.startsWith('(') || !trimmed.endsWith(')')) return null;
26
- const content = trimmed.slice(1, -1).trim();
27
- if (!content) return [];
28
-
29
- const result = [];
30
- let depth = 0;
31
- let current = '';
32
- let inString = false;
33
- let escaped = false;
34
-
35
- for (let i = 0; i < content.length; i++) {
36
- const ch = content[i];
37
- if (escaped) {
38
- current += ch;
39
- escaped = false;
40
- continue;
41
- }
42
- if (ch === '\\') {
43
- current += ch;
44
- escaped = true;
45
- continue;
46
- }
47
- if (ch === '"') {
48
- current += ch;
49
- inString = !inString;
50
- continue;
51
- }
52
- if (inString) {
53
- current += ch;
54
- continue;
55
- }
56
- if (ch === '(' || ch === '[') {
57
- current += ch;
58
- depth++;
59
- } else if (ch === ')' || ch === ']') {
60
- current += ch;
61
- depth--;
62
- } else if (ch === ' ' && depth === 0) {
63
- if (current.trim()) {
64
- result.push(current.trim());
65
- current = '';
66
- }
67
- continue;
68
- }
69
- current += ch;
70
- }
71
- if (current.trim()) result.push(current.trim());
72
-
73
- return result.map(item => {
74
- const t = item.trim();
75
- if (t === 'nil') return null;
76
- if (t === 't') return true;
77
- if (t === 'T') return true;
78
- const num = Number(t);
79
- if (!isNaN(num) && t !== '') return num;
80
- if (t.startsWith('"') && t.endsWith('"')) return t.slice(1, -1);
81
- return t;
82
- });
83
- }
84
-
85
- export const RESOURCES = [
86
- { uri: 'atlisp://cad/info', name: 'CAD Info', description: 'CAD 连接信息', mimeType: 'application/json', subscribable: true },
87
- { uri: 'atlisp://cad/layers', name: 'Layers', description: '图层列表', mimeType: 'application/json', subscribable: true },
88
- { uri: 'atlisp://cad/entities', name: 'Entities', description: '实体统计', mimeType: 'application/json', subscribable: true },
89
- { uri: 'atlisp://dwg/name', name: 'DWG Name', description: '当前 DWG 文件名', mimeType: 'application/json', subscribable: true },
90
- { uri: 'atlisp://dwg/path', name: 'DWG Path', description: '文件完整路径', mimeType: 'application/json', subscribable: true },
91
- { uri: 'atlisp://packages', name: 'Packages', description: '已安装包列表', mimeType: 'application/json', subscribable: true },
92
- { uri: 'atlisp://platforms', name: 'Platforms', description: '支持的平台信息', mimeType: 'application/json', subscribable: false },
93
- ];
94
-
95
- function parseQuery(uri) {
96
- const qIdx = uri.indexOf('?');
97
- if (qIdx === -1) return { base: uri, params: {} };
98
- const base = uri.substring(0, qIdx);
99
- const params = {};
100
- uri.substring(qIdx + 1).split('&').forEach(p => {
101
- const eqIdx = p.indexOf('=');
102
- const k = eqIdx === -1 ? p : p.substring(0, eqIdx);
103
- const v = eqIdx === -1 ? '' : p.substring(eqIdx + 1);
104
- if (k) params[k] = decodeURIComponent(v);
105
- });
106
- return { base, params };
107
- }
108
-
109
- export async function listResources(subscribedUris = []) {
110
- return RESOURCES.map(r => ({
111
- uri: r.uri,
112
- name: r.name,
113
- description: r.description,
114
- mimeType: r.mimeType,
115
- subscribed: subscribedUris.includes(r.uri)
116
- }));
117
- }
118
-
119
- export async function readResource(uri) {
120
- const { base, params } = parseQuery(uri);
121
-
122
- switch (base) {
123
- case 'atlisp://cad/info':
124
- return await getCadInfo();
125
- case 'atlisp://cad/layers':
126
- return await getLayers(params.name);
127
- case 'atlisp://cad/entities':
128
- return await getEntities(params.type);
129
- case 'atlisp://dwg/name':
130
- return await getDwgName();
131
- case 'atlisp://dwg/path':
132
- return await getDwgPath();
133
- case 'atlisp://packages':
134
- return await getPackages(params.name);
135
- case 'atlisp://platforms':
136
- return getPlatforms();
137
- default:
138
- throw new Error(`资源不存在: ${uri}`);
139
- }
140
- }
141
-
142
- async function getCadInfo() {
143
- const cached = getCached('cad:info');
144
- if (cached) return cached;
145
-
146
- if (!cad.connected) {
147
- await cad.connect();
148
- }
149
-
150
- if (!cad.connected) {
151
- const result = { connected: false, platform: null, version: null, busy: false };
152
- setCache('cad:info', result);
153
- return result;
154
- }
155
-
156
- let busy = false;
157
- try {
158
- busy = await cad.isBusy();
159
- } catch (e) {
160
- log(`Error checking CAD busy status: ${e.message}`);
161
- }
162
-
163
- const result = {
164
- connected: true,
165
- platform: cad.getPlatform(),
166
- version: cad.getVersion(),
167
- busy
168
- };
169
- setCache('cad:info', result);
170
- return result;
171
- }
172
-
173
- async function getLayers(filterName = null) {
174
- const cacheKey = filterName ? `layers:${filterName}` : 'layers:all';
175
- const cached = getCached(cacheKey);
176
- if (cached) return cached;
177
-
178
- if (!cad.connected) await cad.connect();
179
- if (!cad.connected) return [];
180
-
181
- try {
182
- const code = filterName
183
- ? `(tblnext "LAYER" T)`
184
- : `(progn (setq res nil)(while (setq e (tblnext "LAYER" (null res)))(setq res (cons (list (cdr (assoc 2 e)) (cdr (assoc 62 e)) (cdr (assoc 70 e))) res))) (reverse res))`;
185
-
186
- const result = await cad.sendCommandWithResult(code);
187
-
188
- if (!result || result === "" || result === "nil") {
189
- setCache(cacheKey, []);
190
- return [];
191
- }
192
-
193
- let layers = [];
194
- try {
195
- layers = JSON.parse(result);
196
- } catch (e) {
197
- const parsed = parseLispList(result);
198
- if (parsed) {
199
- layers = parsed;
200
- } else {
201
- layers = [];
202
- }
203
- }
204
-
205
- if (!Array.isArray(layers)) {
206
- if (typeof layers === 'number') {
207
- layers = [[result, layers, 0]];
208
- } else if (typeof layers === 'string') {
209
- layers = [[layers, 7, 0]];
210
- }
211
- }
212
-
213
- const mapped = layers.map(l => {
214
- const name = Array.isArray(l) ? l[0] : l;
215
- const color = Array.isArray(l) && l[1] !== undefined ? l[1] : 7;
216
- const flags = Array.isArray(l) && l[2] !== undefined ? l[2] : 0;
217
- return {
218
- name: String(name),
219
- color: color,
220
- locked: (flags & 4) !== 0,
221
- frozen: (flags & 1) !== 0
222
- };
223
- });
224
- setCache(cacheKey, mapped);
225
- return mapped;
226
- } catch (e) {
227
- setCache(cacheKey, []);
228
- return [];
229
- }
230
- }
231
-
232
- async function getEntities(filterType = null) {
233
- const cacheKey = filterType ? `entities:${filterType}` : 'entities:all';
234
- const cached = getCached(cacheKey);
235
- if (cached) return cached;
236
-
237
- if (!cad.connected) await cad.connect();
238
- if (!cad.connected) {
239
- const result = { total: 0, byType: {} };
240
- setCache(cacheKey, result);
241
- return result;
242
- }
243
-
244
- try {
245
- let code;
246
- if (filterType) {
247
- code = `(sslength (ssget "_X" (list (cons 0 "${filterType}"))))`;
248
- const count = await cad.sendCommandWithResult(code);
249
- const result = { type: filterType, count: parseInt(count) || 0 };
250
- setCache(cacheKey, result);
251
- return result;
252
- } else {
253
- code = `(progn(setq ss(ssget "_X"))(if ss(progn(setq elst(mapcar (quote(lambda(x)(cdr(assoc 0 (entget x))))) (pickset:to-list ss)))(stat:stat elst))))`;
254
- const resultStr = await cad.sendCommandWithResult(code);
255
-
256
- if (!resultStr || resultStr === "" || resultStr === "nil") {
257
- const result = { total: 0, byType: {} };
258
- setCache(cacheKey, result);
259
- return result;
260
- }
261
-
262
- let statResult = [];
263
- try {
264
- statResult = JSON.parse(resultStr);
265
- } catch (e) {
266
- const parsed = parseLispList(resultStr);
267
- if (parsed) {
268
- statResult = parsed;
269
- }
270
- }
271
-
272
- if (!Array.isArray(statResult)) {
273
- const result = { total: 0, byType: {} };
274
- setCache(cacheKey, result);
275
- return result;
276
- }
277
-
278
- const byType = {};
279
- statResult.forEach(item => {
280
- if (Array.isArray(item) && item.length >= 2) {
281
- byType[item[0]] = item[1];
282
- }
283
- });
284
-
285
- const total = Object.values(byType).reduce((sum, n) => sum + n, 0);
286
- const result = { total, byType };
287
- setCache(cacheKey, result);
288
- return result;
289
- }
290
- } catch (e) {
291
- const result = { total: 0, byType: {} };
292
- setCache(cacheKey, result);
293
- return result;
294
- }
295
- }
296
-
297
- async function getDwgName() {
298
- if (!cad.connected) await cad.connect();
299
- if (!cad.connected) return { name: null };
300
-
301
- try {
302
- const code = `(vl-princ-to-string (getvar "dwgname"))`;
303
- const name = await cad.sendCommandWithResult(code);
304
- return { name: name || null };
305
- } catch (e) {
306
- return { name: null };
307
- }
308
- }
309
-
310
- async function getDwgPath() {
311
- if (!cad.connected) await cad.connect();
312
- if (!cad.connected) return { path: null, name: null };
313
-
314
- try {
315
- const code = `(progn
316
- (setq prefix (vl-princ-to-string (getvar "dwgprefix")))
317
- (setq name (vl-princ-to-string (getvar "dwgname")))
318
- (list (strcat prefix name) name)
319
- )`;
320
- const result = await cad.sendCommandWithResult(code);
321
-
322
- if (!result || result === "nil" || result === "") {
323
- return { path: null, name: null };
324
- }
325
-
326
- let parsed = null;
327
- try { parsed = JSON.parse(result); } catch {}
328
- if (!parsed) {
329
- const listResult = parseLispList(result);
330
- if (listResult && listResult.length >= 2) parsed = listResult;
331
- }
332
-
333
- if (parsed && Array.isArray(parsed) && parsed.length >= 2) {
334
- return { path: String(parsed[0]), name: String(parsed[1]) };
335
- }
336
-
337
- return { path: result, name: result };
338
- } catch (e) {
339
- return { path: null, name: null };
340
- }
341
- }
342
-
343
- async function getPackages(filterName = null) {
344
- const packages = MOCK_PACKAGES.map(p => ({
345
- name: p.name,
346
- version: p.version,
347
- description: p.description,
348
- loaded: true
349
- }));
350
-
351
- if (filterName) {
352
- const pkg = packages.find(p => p.name === filterName);
353
- return pkg ? [pkg] : [];
354
- }
355
-
356
- return packages;
357
- }
358
-
359
- function getPlatforms() {
360
- return CAD_PLATFORMS.map(name => ({
361
- name,
362
- extensions: FILE_EXTENSIONS[name] || []
363
- }));
364
- }
package/src/logger.js DELETED
@@ -1,30 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import os from 'os';
4
-
5
- const DEBUG_FILE = process.env.DEBUG_FILE || path.join(os.tmpdir(), 'mcp-server-debug.log');
6
-
7
- let stream = null;
8
-
9
- function getStream() {
10
- if (!stream) {
11
- stream = fs.createWriteStream(DEBUG_FILE, { flags: 'a' });
12
- }
13
- return stream;
14
- }
15
-
16
- export function log(msg) {
17
- const entry = `${new Date().toISOString()} ${msg}\n`;
18
- getStream().write(entry);
19
- }
20
-
21
- export function logError(msg) {
22
- log(`ERROR: ${msg}`);
23
- }
24
-
25
- export function closeLog() {
26
- if (stream) {
27
- stream.end();
28
- stream = null;
29
- }
30
- }