@atlisp/mcp 1.4.1 → 1.4.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.
@@ -1,9 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/atlisp-mcp.js
4
- import path5 from "path";
5
- import { fileURLToPath as fileURLToPath3 } from "url";
4
+ import path7 from "path";
5
+ import fs6 from "fs";
6
+ import { fileURLToPath as fileURLToPath4 } from "url";
6
7
  import { createRequire } from "module";
8
+ import os4 from "os";
7
9
  import express from "express";
8
10
  import rawBody from "raw-body";
9
11
  import iconv from "iconv-lite";
@@ -34,8 +36,7 @@ var SessionTransport = class {
34
36
  this._started = true;
35
37
  }
36
38
  async send(message) {
37
- if (this._closed)
38
- return;
39
+ if (this._closed) return;
39
40
  if (this._pendingRequest) {
40
41
  const { resolve, reject, timeout } = this._pendingRequest;
41
42
  clearTimeout(timeout);
@@ -44,8 +45,7 @@ var SessionTransport = class {
44
45
  }
45
46
  }
46
47
  async close() {
47
- if (this._closed)
48
- return;
48
+ if (this._closed) return;
49
49
  this._closed = true;
50
50
  if (this._pendingRequest) {
51
51
  const { reject, timeout } = this._pendingRequest;
@@ -119,7 +119,7 @@ var FILE_EXTENSIONS = {
119
119
  "BricsCAD": [".des"]
120
120
  };
121
121
  var SERVER_NAME = "atlisp-mcp-server";
122
- var SERVER_VERSION = "1.4.0";
122
+ var SERVER_VERSION = "1.4.1";
123
123
  var MOCK_PACKAGES = [
124
124
  { name: "base", description: "\u57FA\u7840\u51FD\u6570\u5E93", version: "1.0.0" },
125
125
  { name: "at-pm", description: "\u5DE5\u7A0B\u9879\u76EE\u7BA1\u7406", version: "2.1.0" },
@@ -176,6 +176,8 @@ var config_default = config;
176
176
  // src/cad.js
177
177
  var MESSAGE_TIMEOUT = config_default.messageTimeout;
178
178
  var HEARTBEAT_INTERVAL = config_default.heartbeatInterval;
179
+ var MAX_RESTART_ATTEMPTS = 5;
180
+ var RESTART_BACKOFF_BASE = 1e3;
179
181
  var __dirname = path.dirname(fileURLToPath(import.meta.url));
180
182
  var workerPath = path.join(__dirname, "cad-worker.js");
181
183
  var worker = null;
@@ -184,6 +186,8 @@ var heartbeatTimer = null;
184
186
  var pendingRequests = /* @__PURE__ */ new Map();
185
187
  var stdoutHandlerSetup = false;
186
188
  var MAX_BUFFER_SIZE = 1024 * 1024;
189
+ var restartCount = 0;
190
+ var restartTimer = null;
187
191
  function resetWorker() {
188
192
  for (const [id, { reject, timeout }] of pendingRequests) {
189
193
  clearTimeout(timeout);
@@ -200,6 +204,27 @@ function resetWorker() {
200
204
  heartbeatTimer = null;
201
205
  }
202
206
  }
207
+ function scheduleRestart() {
208
+ if (restartTimer) {
209
+ clearTimeout(restartTimer);
210
+ restartTimer = null;
211
+ }
212
+ if (restartCount >= MAX_RESTART_ATTEMPTS) {
213
+ console.error("Max worker restart attempts reached, giving up");
214
+ return;
215
+ }
216
+ const delay = Math.min(RESTART_BACKOFF_BASE * Math.pow(2, restartCount), 3e4);
217
+ restartCount++;
218
+ console.error(`Scheduling worker restart in ${delay}ms (attempt ${restartCount}/${MAX_RESTART_ATTEMPTS})`);
219
+ restartTimer = setTimeout(() => {
220
+ restartTimer = null;
221
+ console.error("Attempting worker restart...");
222
+ getWorker(true);
223
+ }, delay);
224
+ }
225
+ function resetRestartCount() {
226
+ restartCount = 0;
227
+ }
203
228
  function setupStdoutHandler(w) {
204
229
  let buffer = "";
205
230
  stdoutHandlerSetup = true;
@@ -212,8 +237,7 @@ function setupStdoutHandler(w) {
212
237
  const lines = buffer.split("\n");
213
238
  buffer = lines.pop() || "";
214
239
  for (const line of lines) {
215
- if (!line.trim())
216
- continue;
240
+ if (!line.trim()) continue;
217
241
  try {
218
242
  const result = JSON.parse(line);
219
243
  const rid = result.requestId;
@@ -235,21 +259,19 @@ function setupStdoutHandler(w) {
235
259
  console.error("Unexpected error in worker stdout handler:", e.message);
236
260
  }
237
261
  });
262
+ w.stderr.on("data", (d) => console.error("worker stderr:", d.toString()));
238
263
  }
239
- function getWorker() {
240
- if (!worker || !worker.connected) {
241
- if (worker)
242
- worker.kill();
264
+ function getWorker(force = false) {
265
+ if (!worker || !worker.connected || force) {
266
+ if (worker) worker.kill();
243
267
  worker = spawn("node", [workerPath], {
244
268
  stdio: ["pipe", "pipe", "pipe"]
245
269
  });
246
270
  worker.connected = true;
247
- worker.stderr.on("data", (d) => console.error("worker stderr:", d.toString()));
248
271
  const w = worker;
249
272
  w.on("exit", (code) => {
250
- console.error("worker exited:", code);
251
- if (w !== worker)
252
- return;
273
+ console.error("worker exited with code:", code);
274
+ if (w !== worker) return;
253
275
  w.connected = false;
254
276
  for (const [id, { reject, timeout }] of pendingRequests) {
255
277
  clearTimeout(timeout);
@@ -261,15 +283,16 @@ function getWorker() {
261
283
  clearInterval(heartbeatTimer);
262
284
  heartbeatTimer = null;
263
285
  }
286
+ scheduleRestart();
264
287
  });
265
288
  setupStdoutHandler(w);
266
289
  startHeartbeat();
290
+ resetRestartCount();
267
291
  }
268
292
  return worker;
269
293
  }
270
294
  function startHeartbeat() {
271
- if (heartbeatTimer)
272
- clearInterval(heartbeatTimer);
295
+ if (heartbeatTimer) clearInterval(heartbeatTimer);
273
296
  heartbeatTimer = setInterval(async () => {
274
297
  try {
275
298
  const result = await sendMessage({ type: "ping" });
@@ -313,11 +336,11 @@ var CadConnection = class {
313
336
  isAvailable() {
314
337
  return process.platform === "win32";
315
338
  }
316
- async connect() {
317
- if (!this.isAvailable())
318
- return false;
339
+ async connect(platform = null) {
340
+ if (!this.isAvailable()) return false;
319
341
  try {
320
- const result = await sendMessage({ type: "connect" });
342
+ const msg = platform ? { type: "connect", platform } : { type: "connect" };
343
+ const result = await sendMessage(msg);
321
344
  if (result && result.success) {
322
345
  this.version = result.version;
323
346
  this.product = result.platform;
@@ -331,16 +354,13 @@ var CadConnection = class {
331
354
  return false;
332
355
  }
333
356
  async sendCommand(code) {
334
- if (!this.connected)
335
- throw new Error("\u672A\u8FDE\u63A5 CAD");
357
+ if (!this.connected) throw new Error("\u672A\u8FDE\u63A5 CAD");
336
358
  const result = await sendMessage({ type: "send", code, platform: _platform });
337
- if (result.success)
338
- return true;
359
+ if (result.success) return true;
339
360
  throw new Error(result.error || "\u53D1\u9001\u5931\u8D25");
340
361
  }
341
362
  async sendCommandWithResult(code, encoding = null) {
342
- if (!this.connected)
343
- throw new Error("\u672A\u8FDE\u63A5 CAD");
363
+ if (!this.connected) throw new Error("\u672A\u8FDE\u63A5 CAD");
344
364
  const result = await sendMessage({ type: "sendResult", code, platform: _platform, encoding: encoding || "" });
345
365
  if (result.success) {
346
366
  return result.result || "";
@@ -354,8 +374,7 @@ var CadConnection = class {
354
374
  return this.product;
355
375
  }
356
376
  async isBusy() {
357
- if (!this.connected)
358
- return false;
377
+ if (!this.connected) return false;
359
378
  try {
360
379
  const result = await sendMessage({ type: "isBusy", platform: _platform });
361
380
  return result && result.isBusy;
@@ -364,8 +383,7 @@ var CadConnection = class {
364
383
  }
365
384
  }
366
385
  async hasDoc() {
367
- if (!this.connected)
368
- return { hasDoc: false, docCount: 0 };
386
+ if (!this.connected) return { hasDoc: false, docCount: 0 };
369
387
  try {
370
388
  const result = await sendMessage({ type: "hasdoc", platform: _platform });
371
389
  return result || { hasDoc: false, docCount: 0 };
@@ -374,8 +392,7 @@ var CadConnection = class {
374
392
  }
375
393
  }
376
394
  async newDoc() {
377
- if (!this.connected)
378
- throw new Error("\u672A\u8FDE\u63A5 CAD");
395
+ if (!this.connected) throw new Error("\u672A\u8FDE\u63A5 CAD");
379
396
  try {
380
397
  const result = await sendMessage({ type: "newdoc", platform: _platform });
381
398
  return result && result.success;
@@ -384,8 +401,7 @@ var CadConnection = class {
384
401
  }
385
402
  }
386
403
  async bringToFront() {
387
- if (!this.connected)
388
- throw new Error("\u672A\u8FDE\u63A5 CAD");
404
+ if (!this.connected) throw new Error("\u672A\u8FDE\u63A5 CAD");
389
405
  try {
390
406
  const result = await sendMessage({ type: "bringToFront", platform: _platform });
391
407
  return result && result.success;
@@ -394,8 +410,7 @@ var CadConnection = class {
394
410
  }
395
411
  }
396
412
  async getInfo() {
397
- if (!this.connected)
398
- return "CAD \u672A\u8FDE\u63A5";
413
+ if (!this.connected) return "CAD \u672A\u8FDE\u63A5";
399
414
  return `Platform: ${this.product}, Version: ${this.version}, Status: Connected`;
400
415
  }
401
416
  async disconnect() {
@@ -428,9 +443,35 @@ import fs from "fs";
428
443
  import path2 from "path";
429
444
  import os from "os";
430
445
  var DEBUG_FILE = process.env.DEBUG_FILE || path2.join(os.tmpdir(), "mcp-server-debug.log");
446
+ var MAX_LOG_SIZE = 10 * 1024 * 1024;
447
+ var MAX_LOG_FILES = 5;
431
448
  var stream = null;
449
+ function rotateLog() {
450
+ if (!fs.existsSync(DEBUG_FILE)) return;
451
+ const stat = fs.statSync(DEBUG_FILE);
452
+ if (stat.size < MAX_LOG_SIZE) return;
453
+ if (stream) {
454
+ stream.end();
455
+ stream = null;
456
+ }
457
+ for (let i = MAX_LOG_FILES - 1; i > 0; i--) {
458
+ const oldPath = `${DEBUG_FILE}.${i}`;
459
+ const newPath = `${DEBUG_FILE}.${i + 1}`;
460
+ if (fs.existsSync(oldPath)) {
461
+ try {
462
+ fs.renameSync(oldPath, newPath);
463
+ } catch {
464
+ }
465
+ }
466
+ }
467
+ try {
468
+ fs.renameSync(DEBUG_FILE, `${DEBUG_FILE}.1`);
469
+ } catch {
470
+ }
471
+ }
432
472
  function getStream() {
433
473
  if (!stream) {
474
+ rotateLog();
434
475
  stream = fs.createWriteStream(DEBUG_FILE, { flags: "a" });
435
476
  }
436
477
  return stream;
@@ -448,11 +489,11 @@ function closeLog() {
448
489
  }
449
490
 
450
491
  // src/handlers/cad-handlers.js
451
- async function connectCad() {
452
- log("connect_cad called, cad.connected before: " + cad.connected);
492
+ async function connectCad(platform = null) {
493
+ log(`connect_cad called${platform ? ` (target: ${platform})` : ""}, cad.connected before: ${cad.connected}`);
453
494
  try {
454
495
  log("calling cad.connect()...");
455
- const connected = await cad.connect();
496
+ const connected = await cad.connect(platform);
456
497
  log("cad.connect() returned: " + connected + ", cad.connected: " + cad.connected);
457
498
  if (connected) {
458
499
  return { content: [{ type: "text", text: `\u5DF2\u8FDE\u63A5\u5230 ${cad.getPlatform()} ${cad.getVersion()}` }] };
@@ -470,8 +511,7 @@ async function evalLisp(code, withResult = false, encoding = null) {
470
511
  return { content: [{ type: "text", text: "\u672A\u8FDE\u63A5 CAD" }], isError: true };
471
512
  }
472
513
  const trimmed = (code || "").trim();
473
- if (!trimmed)
474
- return { content: [{ type: "text", text: "\u547D\u4EE4\u4E3A\u7A7A\uFF0C\u672A\u53D1\u9001" }] };
514
+ if (!trimmed) return { content: [{ type: "text", text: "\u547D\u4EE4\u4E3A\u7A7A\uFF0C\u672A\u53D1\u9001" }] };
475
515
  try {
476
516
  if (withResult) {
477
517
  const result = await cad.sendCommandWithResult(trimmed, encoding);
@@ -491,17 +531,14 @@ async function getCadInfo() {
491
531
  if (!cad.connected) {
492
532
  await cad.connect();
493
533
  }
494
- if (!cad.connected)
495
- return { content: [{ type: "text", text: "CAD \u672A\u8FDE\u63A5" }] };
534
+ if (!cad.connected) return { content: [{ type: "text", text: "CAD \u672A\u8FDE\u63A5" }] };
496
535
  const info = await cad.getInfo();
497
536
  return { content: [{ type: "text", text: info }] };
498
537
  }
499
538
  async function atCommand(command) {
500
- if (!cad.connected)
501
- return { content: [{ type: "text", text: "\u8BF7\u5148\u8FDE\u63A5 CAD" }], isError: true };
539
+ if (!cad.connected) return { content: [{ type: "text", text: "\u8BF7\u5148\u8FDE\u63A5 CAD" }], isError: true };
502
540
  const trimmed = (command || "").trim();
503
- if (!trimmed)
504
- return { content: [{ type: "text", text: "\u547D\u4EE4\u4E3A\u7A7A\uFF0C\u672A\u53D1\u9001" }] };
541
+ if (!trimmed) return { content: [{ type: "text", text: "\u547D\u4EE4\u4E3A\u7A7A\uFF0C\u672A\u53D1\u9001" }] };
505
542
  await cad.sendCommand(trimmed + "\n");
506
543
  return { content: [{ type: "text", text: "\u5DF2\u53D1\u9001\u547D\u4EE4" }] };
507
544
  }
@@ -509,8 +546,7 @@ async function newDocument() {
509
546
  if (!cad.connected) {
510
547
  await cad.connect();
511
548
  }
512
- if (!cad.connected)
513
- return { content: [{ type: "text", text: "\u672A\u8FDE\u63A5 CAD" }], isError: true };
549
+ if (!cad.connected) return { content: [{ type: "text", text: "\u672A\u8FDE\u63A5 CAD" }], isError: true };
514
550
  const result = await cad.newDoc();
515
551
  if (result) {
516
552
  return { content: [{ type: "text", text: "\u5DF2\u5728 CAD \u4E2D\u65B0\u5EFA\u7A7A\u767D\u6587\u6863" }] };
@@ -521,8 +557,7 @@ async function bringToFront() {
521
557
  if (!cad.connected) {
522
558
  await cad.connect();
523
559
  }
524
- if (!cad.connected)
525
- return { content: [{ type: "text", text: "\u672A\u8FDE\u63A5 CAD" }], isError: true };
560
+ if (!cad.connected) return { content: [{ type: "text", text: "\u672A\u8FDE\u63A5 CAD" }], isError: true };
526
561
  const result = await cad.bringToFront();
527
562
  if (result) {
528
563
  return { content: [{ type: "text", text: "\u5DF2\u5C06 CAD \u7A97\u53E3\u5207\u6362\u5230\u524D\u53F0" }] };
@@ -533,8 +568,7 @@ async function installAtlisp() {
533
568
  if (!cad.connected) {
534
569
  await cad.connect();
535
570
  }
536
- if (!cad.connected)
537
- return { content: [{ type: "text", text: "\u672A\u8FDE\u63A5 CAD" }], isError: true };
571
+ if (!cad.connected) return { content: [{ type: "text", text: "\u672A\u8FDE\u63A5 CAD" }], isError: true };
538
572
  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))))`;
539
573
  await cad.sendCommand(installCode + "\n");
540
574
  return { content: [{ type: "text", text: "\u5DF2\u53D1\u9001 @lisp \u5B89\u88C5\u4EE3\u7801\u5230 CAD" }] };
@@ -543,12 +577,10 @@ async function listFunctionsInCad() {
543
577
  if (!cad.connected) {
544
578
  await cad.connect();
545
579
  }
546
- if (!cad.connected)
547
- return { content: [{ type: "text", text: "\u672A\u8FDE\u63A5 CAD" }], isError: true };
580
+ if (!cad.connected) return { content: [{ type: "text", text: "\u672A\u8FDE\u63A5 CAD" }], isError: true };
548
581
  try {
549
582
  const result = await cad.sendCommandWithResult("(atoms-family 0)", null);
550
- if (result)
551
- return { content: [{ type: "text", text: result }] };
583
+ if (result) return { content: [{ type: "text", text: result }] };
552
584
  return { content: [{ type: "text", text: "CAD \u51FD\u6570\u5217\u8868\u4E3A\u7A7A" }] };
553
585
  } catch (e) {
554
586
  return { content: [{ type: "text", text: `\u9519\u8BEF: ${e.message}` }], isError: true };
@@ -603,8 +635,7 @@ async function initAtlisp() {
603
635
  if (!cad.connected) {
604
636
  await cad.connect();
605
637
  }
606
- if (!cad.connected)
607
- return { content: [{ type: "text", text: "\u672A\u8FDE\u63A5 CAD" }], isError: true };
638
+ if (!cad.connected) return { content: [{ type: "text", text: "\u672A\u8FDE\u63A5 CAD" }], isError: true };
608
639
  const code = `(if (null @::load-module)
609
640
  (progn
610
641
  (vl-load-com)
@@ -641,38 +672,46 @@ async function fetchPackages() {
641
672
  }
642
673
  } catch (e) {
643
674
  }
644
- return null;
675
+ return cachedPackages || MOCK_PACKAGES;
676
+ }
677
+ function flattenPackages(data) {
678
+ if (Array.isArray(data)) return data;
679
+ if (data && Array.isArray(data.packages)) return data.packages;
680
+ return [];
645
681
  }
646
682
  async function listPackages() {
647
- if (!cad.connected)
648
- await cad.connect();
649
- if (!cad.connected)
650
- return { content: [{ type: "text", text: "\u672A\u8FDE\u63A5 CAD" }], isError: true };
683
+ if (!cad.connected) await cad.connect();
684
+ if (!cad.connected) {
685
+ const data2 = await fetchPackages();
686
+ const items2 = flattenPackages(data2);
687
+ const text2 = items2.length ? items2.map((p) => `${p.name} v${p.version || ""} - ${p.description || ""}`).join("\n") : "\u65E0\u53EF\u7528\u5305";
688
+ return { content: [{ type: "text", text: text2 }] };
689
+ }
651
690
  const result = await cad.sendCommandWithResult("(@::package-list-in-local)");
652
- if (result && result !== "nil")
653
- return { content: [{ type: "text", text: result }] };
654
- const packages = MOCK_PACKAGES.map((p) => `${p.name} v${p.version} - ${p.description}`);
691
+ if (result && result !== "nil") return { content: [{ type: "text", text: result }] };
692
+ const data = await fetchPackages();
693
+ const items = flattenPackages(data);
694
+ const text = items.length ? `\u4ECE\u6CE8\u518C\u8868\u83B7\u53D6\u7684 @lisp \u5305:
695
+ ${items.map((p) => `${p.name} v${p.version || ""} - ${p.description || ""}`).join("\n")}` : MOCK_PACKAGES.map((p) => `${p.name} v${p.version} - ${p.description}`).join("\n");
655
696
  return { content: [{ type: "text", text: `\u5DF2\u5B89\u88C5\u7684 @lisp \u5305:
656
- ${packages.join("\n")}` }] };
697
+ ${text}` }] };
657
698
  }
658
699
  async function searchPackages(query) {
659
700
  const data = await fetchPackages();
660
701
  if (!data) {
661
702
  return { content: [{ type: "text", text: "\u65E0\u6CD5\u83B7\u53D6\u5305\u5217\u8868" }], isError: true };
662
703
  }
704
+ const items = flattenPackages(data);
663
705
  const results = [];
664
- const items = Array.isArray(data) ? data : data.packages || [];
665
706
  if (query) {
666
707
  const q = query.toLowerCase();
667
708
  for (const p of items) {
668
709
  const name = (p.name || "").toLowerCase();
669
710
  const desc = (p.description || "").toLowerCase();
670
- if (name.includes(q) || desc.includes(q))
671
- results.push(p);
711
+ if (name.includes(q) || desc.includes(q)) results.push(p);
672
712
  }
673
713
  } else {
674
- for (const p of items)
675
- results.push(p);
714
+ for (const p of items) results.push(p);
676
715
  }
677
716
  if (results.length === 0) {
678
717
  return { content: [{ type: "text", text: `\u672A\u627E\u5230\u5305\u542B "${query}" \u7684\u5305` }] };
@@ -681,7 +720,9 @@ async function searchPackages(query) {
681
720
  ${results.map((p) => `${p.name} v${p.version || ""} - ${p.description || ""}`).join("\n")}` }] };
682
721
  }
683
722
  async function installPackage(packageName) {
684
- const pkg = MOCK_PACKAGES.find((p) => p.name === packageName);
723
+ const data = await fetchPackages();
724
+ const items = flattenPackages(data);
725
+ const pkg = items.find((p) => p.name === packageName);
685
726
  if (!pkg) {
686
727
  return { content: [{ type: "text", text: `\u9519\u8BEF: \u5305 "${packageName}" \u4E0D\u5B58\u5728` }], isError: true };
687
728
  }
@@ -693,41 +734,67 @@ async function installPackage(packageName) {
693
734
  }
694
735
 
695
736
  // src/handlers/function-handlers.js
696
- import fs2 from "fs/promises";
737
+ import fs2 from "fs";
697
738
  import path3 from "path";
739
+ import os2 from "os";
698
740
  var FUNCTIONS_DIR = process.env.FUNCTIONS_DIR || "";
699
741
  var FUNCTIONS_URL = process.env.FUNCTIONS_URL || "http://s3.atlisp.cn/json/functions.json";
700
- async function fileExists(filePath) {
742
+ var CACHE_DIR = path3.join(os2.homedir(), ".atlisp", "cache");
743
+ var FUNCLIB_CACHE_FILE = path3.join(CACHE_DIR, "functions.json");
744
+ var FUNCLIB_CACHE_TTL = 24 * 60 * 60 * 1e3;
745
+ function readCacheFromDisk() {
701
746
  try {
702
- await fs2.access(filePath);
703
- return true;
747
+ const stat = fs2.statSync(FUNCLIB_CACHE_FILE);
748
+ if (Date.now() - stat.mtimeMs < FUNCLIB_CACHE_TTL) {
749
+ const raw = fs2.readFileSync(FUNCLIB_CACHE_FILE, "utf-8");
750
+ return JSON.parse(raw);
751
+ }
704
752
  } catch {
705
- return false;
706
753
  }
754
+ return null;
755
+ }
756
+ function writeCacheToDisk(data) {
757
+ try {
758
+ fs2.mkdirSync(CACHE_DIR, { recursive: true });
759
+ fs2.writeFileSync(FUNCLIB_CACHE_FILE, JSON.stringify(data), "utf-8");
760
+ } catch (e) {
761
+ log(`function-handlers writeCache error: ${e.message}`);
762
+ }
763
+ }
764
+ async function fetchFunctions() {
765
+ const diskCache = readCacheFromDisk();
766
+ if (diskCache) return diskCache;
767
+ try {
768
+ const response = await fetch(FUNCTIONS_URL, { headers: { "User-Agent": "atlisp-mcp" } });
769
+ if (response.ok) {
770
+ const data = await response.json();
771
+ writeCacheToDisk(data);
772
+ return data;
773
+ }
774
+ } catch (e) {
775
+ log(`function-handlers fetch error: ${e.message}`);
776
+ }
777
+ return diskCache;
707
778
  }
708
779
  async function getFunctionUsage(funcName, packageName) {
709
780
  try {
710
781
  let results = [];
711
- try {
712
- const response = await fetch(FUNCTIONS_URL, { headers: { "User-Agent": "atlisp-mcp" } });
713
- if (response.ok) {
714
- const data = await response.json();
715
- const funcs = Object.values(data.all_functions || {});
716
- const func = funcs.find((f2) => f2.name === funcName || f2.name === `${packageName}:${funcName}`);
717
- if (func)
718
- results.push(func);
719
- }
720
- } catch (e) {
721
- log(`function-handlers fetch error: ${e.message}`);
782
+ const data = await fetchFunctions();
783
+ if (data) {
784
+ const funcs = Object.values(data.all_functions || {});
785
+ const func = funcs.find((f2) => f2.name === funcName || f2.name === `${packageName}:${funcName}`);
786
+ if (func) results.push(func);
722
787
  }
723
- if (results.length === 0 && FUNCTIONS_DIR && await fileExists(FUNCTIONS_DIR)) {
724
- const files = (await fs2.readdir(FUNCTIONS_DIR)).filter((f2) => f2.endsWith(".json"));
725
- for (const file of files) {
726
- const raw = await fs2.readFile(path3.join(FUNCTIONS_DIR, file), "utf-8");
727
- const data = JSON.parse(raw);
728
- const func = data.functions?.find((f2) => f2.name === funcName);
729
- if (func)
730
- results.push(func);
788
+ if (results.length === 0 && FUNCTIONS_DIR) {
789
+ try {
790
+ const files = fs2.readdirSync(FUNCTIONS_DIR).filter((f2) => f2.endsWith(".json"));
791
+ for (const file of files) {
792
+ const raw = fs2.readFileSync(path3.join(FUNCTIONS_DIR, file), "utf-8");
793
+ const data2 = JSON.parse(raw);
794
+ const func = data2.functions?.find((f2) => f2.name === funcName);
795
+ if (func) results.push(func);
796
+ }
797
+ } catch {
731
798
  }
732
799
  }
733
800
  if (results.length === 0) {
@@ -747,27 +814,22 @@ async function getFunctionUsage(funcName, packageName) {
747
814
  async function listSymbols(packageName) {
748
815
  try {
749
816
  let allFuncs = [];
750
- try {
751
- const response = await fetch(FUNCTIONS_URL, { headers: { "User-Agent": "atlisp-mcp" } });
752
- if (response.ok) {
753
- const data = await response.json();
754
- const funcs = Object.values(data.all_functions || {});
755
- if (packageName) {
756
- allFuncs = funcs.filter((f) => f.name.startsWith(`${packageName}:`));
757
- } else {
758
- allFuncs = funcs.map((f) => `${f.name}: ${f.description || "-"}`);
759
- }
817
+ const data = await fetchFunctions();
818
+ if (data) {
819
+ const funcs = Object.values(data.all_functions || {});
820
+ if (packageName) {
821
+ allFuncs = funcs.filter((f) => f.name.startsWith(`${packageName}:`));
822
+ } else {
823
+ allFuncs = funcs.map((f) => `${f.name}: ${f.description || "-"}`);
760
824
  }
761
- } catch (e) {
762
- log(`function-handlers list fetch error: ${e.message}`);
763
825
  }
764
- if (allFuncs.length === 0 && FUNCTIONS_DIR && await fileExists(FUNCTIONS_DIR)) {
765
- const files = (await fs2.readdir(FUNCTIONS_DIR)).filter((f) => f.endsWith(".json"));
766
- for (const file of files) {
767
- try {
768
- const raw = await fs2.readFile(path3.join(FUNCTIONS_DIR, file), "utf-8");
769
- const data = JSON.parse(raw);
770
- const funcs = Object.values(data.all_functions || {});
826
+ if (allFuncs.length === 0 && FUNCTIONS_DIR) {
827
+ try {
828
+ const files = fs2.readdirSync(FUNCTIONS_DIR).filter((f) => f.endsWith(".json"));
829
+ for (const file of files) {
830
+ const raw = fs2.readFileSync(path3.join(FUNCTIONS_DIR, file), "utf-8");
831
+ const data2 = JSON.parse(raw);
832
+ const funcs = Object.values(data2.all_functions || {});
771
833
  if (packageName) {
772
834
  allFuncs = funcs.filter((f) => f.name.startsWith(`${packageName}:`));
773
835
  } else {
@@ -775,9 +837,9 @@ async function listSymbols(packageName) {
775
837
  allFuncs.push(`${f.name}: ${f.description || "-"}`);
776
838
  }
777
839
  }
778
- } catch (err) {
779
- log(`read file ${file} error: ${err.message}`);
780
840
  }
841
+ } catch (err) {
842
+ log(`listSymbols readDir error: ${err.message}`);
781
843
  }
782
844
  }
783
845
  if (allFuncs.length === 0) {
@@ -824,14 +886,11 @@ function clearCache(uri) {
824
886
  }
825
887
  }
826
888
  function parseLispList(str) {
827
- if (!str || typeof str !== "string")
828
- return null;
889
+ if (!str || typeof str !== "string") return null;
829
890
  const trimmed = str.trim();
830
- if (!trimmed.startsWith("(") || !trimmed.endsWith(")"))
831
- return null;
891
+ if (!trimmed.startsWith("(") || !trimmed.endsWith(")")) return null;
832
892
  const content = trimmed.slice(1, -1).trim();
833
- if (!content)
834
- return [];
893
+ if (!content) return [];
835
894
  const result = [];
836
895
  let depth = 0;
837
896
  let current = "";
@@ -873,28 +932,244 @@ function parseLispList(str) {
873
932
  }
874
933
  current += ch;
875
934
  }
876
- if (current.trim())
877
- result.push(current.trim());
935
+ if (current.trim()) result.push(current.trim());
878
936
  return result.map((item) => {
879
937
  const t = item.trim();
880
- if (t === "nil")
881
- return null;
882
- if (t === "t")
883
- return true;
884
- if (t === "T")
885
- return true;
938
+ if (t === "nil") return null;
939
+ if (t === "t") return true;
940
+ if (t === "T") return true;
886
941
  const num = Number(t);
887
- if (!isNaN(num) && t !== "")
888
- return num;
889
- if (t.startsWith('"') && t.endsWith('"'))
890
- return t.slice(1, -1);
942
+ if (!isNaN(num) && t !== "") return num;
943
+ if (t.startsWith('"') && t.endsWith('"')) return t.slice(1, -1);
891
944
  return t;
892
945
  });
893
946
  }
947
+ function parseLispRecursive(str) {
948
+ if (!str || typeof str !== "string") return null;
949
+ str = str.trim();
950
+ if (!str) return null;
951
+ let pos = 0;
952
+ function skipWs() {
953
+ while (pos < str.length && /\s/.test(str[pos])) pos++;
954
+ }
955
+ function parseValue() {
956
+ skipWs();
957
+ if (pos >= str.length) return null;
958
+ const ch = str[pos];
959
+ if (ch === "(") return parseList();
960
+ if (ch === '"') return parseString();
961
+ if (ch === "'") {
962
+ pos++;
963
+ return parseValue();
964
+ }
965
+ return parseAtom();
966
+ }
967
+ function parseString() {
968
+ pos++;
969
+ let r = "";
970
+ while (pos < str.length && str[pos] !== '"') {
971
+ if (str[pos] === "\\" && pos + 1 < str.length) {
972
+ pos++;
973
+ if (str[pos] === '"') r += '"';
974
+ else if (str[pos] === "n") r += "\n";
975
+ else if (str[pos] === "t") r += " ";
976
+ else if (str[pos] === "\\") r += "\\";
977
+ else r += str[pos];
978
+ } else {
979
+ r += str[pos];
980
+ }
981
+ pos++;
982
+ }
983
+ pos++;
984
+ return r;
985
+ }
986
+ function parseAtom() {
987
+ const start = pos;
988
+ while (pos < str.length && !/\s/.test(str[pos]) && str[pos] !== ")" && str[pos] !== "(") pos++;
989
+ const atom = str.slice(start, pos);
990
+ if (atom === "nil" || atom === "NIL") return null;
991
+ if (atom === "t" || atom === "T") return true;
992
+ const num = Number(atom);
993
+ if (!isNaN(num) && atom !== "") return num;
994
+ return atom;
995
+ }
996
+ function parseList() {
997
+ pos++;
998
+ const items = [];
999
+ while (pos < str.length) {
1000
+ skipWs();
1001
+ if (pos >= str.length) break;
1002
+ if (str[pos] === ")") {
1003
+ pos++;
1004
+ return items;
1005
+ }
1006
+ const left = parseValue();
1007
+ if (left === void 0) break;
1008
+ skipWs();
1009
+ if (str[pos] === "." && (pos + 1 >= str.length || /\s/.test(str[pos + 1]) || str[pos + 1] === ")")) {
1010
+ pos++;
1011
+ skipWs();
1012
+ if (pos < str.length && str[pos] !== ")") {
1013
+ const right = parseValue();
1014
+ items.push([left, right]);
1015
+ skipWs();
1016
+ } else {
1017
+ items.push(left);
1018
+ }
1019
+ } else {
1020
+ items.push(left);
1021
+ }
1022
+ }
1023
+ return items;
1024
+ }
1025
+ return parseValue();
1026
+ }
1027
+ function lispPairsToObject(pairs) {
1028
+ if (!Array.isArray(pairs)) return pairs;
1029
+ const obj = {};
1030
+ for (let i = 0; i < pairs.length - 1; i += 2) {
1031
+ const key = pairs[i];
1032
+ const val = pairs[i + 1];
1033
+ if (typeof key === "string" || typeof key === "number") {
1034
+ obj[String(key)] = Array.isArray(val) ? lispPairsToObject(val) : val;
1035
+ }
1036
+ }
1037
+ return obj;
1038
+ }
1039
+ var ENTITY_SCHEMAS = {
1040
+ LINE: { fields: ["x1", "y1", "z1", "x2", "y2", "z2", "length"], strings: [] },
1041
+ CIRCLE: { fields: ["cx", "cy", "cz", "radius"], strings: [] },
1042
+ ARC: { fields: ["cx", "cy", "cz", "radius", "startAngle", "endAngle"], strings: [] },
1043
+ LWPOLYLINE: { fields: ["vertices", "closed"], strings: [] },
1044
+ TEXT: { fields: ["x", "y", "z", "text", "height", "rotation"], strings: ["text"] },
1045
+ MTEXT: { fields: ["x", "y", "z", "text", "height", "rotation"], strings: ["text"] },
1046
+ ATTRIB: { fields: ["x", "y", "z", "text", "height", "rotation"], strings: ["text"] },
1047
+ TCH_TEXT: { fields: ["x", "y", "z", "text", "height", "rotation"], strings: ["text"] },
1048
+ INSERT: { fields: ["x", "y", "z", "blockName", "rotation", "scaleX", "scaleY"], strings: ["blockName"] },
1049
+ POINT: { fields: ["x", "y", "z"], strings: [] }
1050
+ };
1051
+ function parseEntity(arr) {
1052
+ if (!Array.isArray(arr) || arr.length < 5) return null;
1053
+ const [type, handle, layer, color, linetype, ...rest] = arr;
1054
+ const schema = ENTITY_SCHEMAS[String(type)] || { fields: [], strings: [] };
1055
+ const props = {};
1056
+ schema.fields.forEach((name, i) => {
1057
+ if (i < rest.length) {
1058
+ let val = rest[i];
1059
+ if (schema.strings.includes(name) && val !== null && val !== void 0) val = String(val);
1060
+ props[name] = val;
1061
+ }
1062
+ });
1063
+ return { type: String(type), handle: String(handle), layer: String(layer), color, linetype: String(linetype), ...props };
1064
+ }
1065
+ function buildEntityCode({ type, layer, bbox, offset, limit }) {
1066
+ const items = [];
1067
+ if (type) items.push(`(cons 0 "${type.replace(/"/g, '\\"')}")`);
1068
+ if (layer) items.push(`(cons 8 "${layer.replace(/"/g, '\\"')}")`);
1069
+ if (bbox) {
1070
+ const parts = bbox.split(",").map((s) => parseFloat(s.trim()));
1071
+ if (parts.length === 4) {
1072
+ const [x1, y1, x2, y2] = parts;
1073
+ items.push(
1074
+ `'(-4 . "<AND")`,
1075
+ `'(-4 . ">=,>=,*") (list 10 ${x1} ${y1} 0.0)`,
1076
+ `'(-4 . "<=,<=,*") (list 10 ${x2} ${y2} 0.0)`,
1077
+ `'(-4 . "AND>")`
1078
+ );
1079
+ }
1080
+ }
1081
+ const filterExpr = items.length ? `(list ${items.join(" ")})` : "nil";
1082
+ const ssgetExpr = items.length ? `(ssget "_X" ${filterExpr})` : '(ssget "_X")';
1083
+ return `(progn
1084
+ (defun @e:props (ent / ed typ result item s e c verts closed ins)
1085
+ (setq ed (entget ent) typ (cdr (assoc 0 ed)))
1086
+ (setq result (list (vl-prin1-to-string (cdr (assoc 5 ed))) (vl-prin1-to-string (cdr (assoc 8 ed)))
1087
+ (if (setq item (assoc 62 ed)) (cdr item) "ByLayer")
1088
+ (vl-prin1-to-string (if (setq item (assoc 6 ed)) (cdr item) "ByLayer"))))
1089
+ (cond
1090
+ ((= typ "LINE")
1091
+ (setq s (cdr (assoc 10 ed)) e (cdr (assoc 11 ed)))
1092
+ (append result (list (car s) (cadr s) (caddr s) (car e) (cadr e) (caddr e) (distance s e))))
1093
+ ((= typ "CIRCLE")
1094
+ (setq c (cdr (assoc 10 ed)))
1095
+ (append result (list (car c) (cadr c) (caddr c) (cdr (assoc 40 ed)))))
1096
+ ((= typ "ARC")
1097
+ (setq c (cdr (assoc 10 ed)))
1098
+ (append result (list (car c) (cadr c) (caddr c) (cdr (assoc 40 ed)) (cdr (assoc 50 ed)) (cdr (assoc 51 ed)))))
1099
+ ((= typ "LWPOLYLINE")
1100
+ (setq verts nil closed nil)
1101
+ (foreach g ed
1102
+ (if (= (car g) 10) (setq verts (append verts (list (list (cadr g) (caddr g))))))
1103
+ (if (= (car g) 70) (setq closed (= (logand (cdr g) 1) 1))))
1104
+ (append result (list verts (if closed "T" nil))))
1105
+ ((= typ "TEXT")
1106
+ (setq ins (cdr (assoc 10 ed)))
1107
+ (append result (list (car ins) (cadr ins) (caddr ins) (vl-prin1-to-string (cdr (assoc 1 ed))) (cdr (assoc 40 ed)) (cdr (assoc 50 ed))))))
1108
+ ((= typ "MTEXT")
1109
+ (setq ins (cdr (assoc 10 ed)))
1110
+ (append result (list (car ins) (cadr ins) (caddr ins) (vl-prin1-to-string (cdr (assoc 1 ed))) (cdr (assoc 40 ed)) (cdr (assoc 50 ed))))))
1111
+ ((= typ "INSERT")
1112
+ (setq ins (cdr (assoc 10 ed)))
1113
+ (append result (list (car ins) (cadr ins) (caddr ins) (vl-prin1-to-string (cdr (assoc 2 ed))) (cdr (assoc 50 ed)) (cdr (assoc 41 ed)) (cdr (assoc 42 ed))))))
1114
+ ((= typ "POINT")
1115
+ (setq p (cdr (assoc 10 ed)))
1116
+ (append result (list (car p) (cadr p) (caddr p))))
1117
+ (t result)))
1118
+ (defun @e:run nil
1119
+ (setq ss ${ssgetExpr})
1120
+ (if (null ss)
1121
+ (list "total" 0 "offset" ${offset} "limit" ${limit})
1122
+ (progn
1123
+ (setq total (sslength ss) entities nil i ${offset})
1124
+ (while (and (< i total) (< (length entities) ${limit}))
1125
+ (setq entities (append entities (list (@e:props (ssname ss i)))) i (1+ i)))
1126
+ (list "total" total "offset" ${offset} "limit" ${limit} "entities" entities))))
1127
+ (@e:run))
1128
+ `;
1129
+ }
1130
+ function buildTextCode({ layer, bbox, offset, limit }) {
1131
+ const items = [`'(0 . "TEXT,MTEXT,ATTRIB,TCH_TEXT")`];
1132
+ if (layer) items.push(`(cons 8 "${layer.replace(/"/g, '\\"')}")`);
1133
+ if (bbox) {
1134
+ const parts = bbox.split(",").map((s) => parseFloat(s.trim()));
1135
+ if (parts.length === 4) {
1136
+ const [x1, y1, x2, y2] = parts;
1137
+ items.push(
1138
+ `'(-4 . "<AND")`,
1139
+ `'(-4 . ">=,>=,*") (list 10 ${x1} ${y1} 0.0)`,
1140
+ `'(-4 . "<=,<=,*") (list 10 ${x2} ${y2} 0.0)`,
1141
+ `'(-4 . "AND>")`
1142
+ );
1143
+ }
1144
+ }
1145
+ const filterExpr = `(list ${items.join(" ")})`;
1146
+ return `(progn
1147
+ (defun @t:props (ent / ed typ result item ins)
1148
+ (setq ed (entget ent) typ (cdr (assoc 0 ed)))
1149
+ (setq result (list (vl-prin1-to-string typ) (vl-prin1-to-string (cdr (assoc 5 ed))) (vl-prin1-to-string (cdr (assoc 8 ed)))
1150
+ (if (setq item (assoc 62 ed)) (cdr item) "ByLayer")
1151
+ (vl-prin1-to-string (if (setq item (assoc 6 ed)) (cdr item) "ByLayer"))))
1152
+ (setq ins (cdr (assoc 10 ed)))
1153
+ (append result (list (car ins) (cadr ins) (caddr ins)
1154
+ (vl-prin1-to-string (cdr (assoc 1 ed))) (cdr (assoc 40 ed)) (cdr (assoc 50 ed))
1155
+ (vl-prin1-to-string (if (setq item (assoc 7 ed)) (cdr item) "Standard")))))
1156
+ (defun @t:run nil
1157
+ (setq ss (ssget "_X" ${filterExpr}))
1158
+ (if (null ss)
1159
+ (list "total" 0 "offset" ${offset} "limit" ${limit})
1160
+ (progn
1161
+ (setq total (sslength ss) texts nil i ${offset})
1162
+ (while (and (< i total) (< (length texts) ${limit}))
1163
+ (setq texts (append texts (list (@t:props (ssname ss i)))) i (1+ i)))
1164
+ (list "total" total "offset" ${offset} "limit" ${limit} "texts" texts))))
1165
+ (@t:run))
1166
+ `;
1167
+ }
894
1168
  var RESOURCES = [
895
1169
  { uri: "atlisp://cad/info", name: "CAD Info", description: "CAD \u8FDE\u63A5\u4FE1\u606F", mimeType: "application/json", subscribable: true },
896
1170
  { uri: "atlisp://dwg/layers", name: "Layers", description: "\u56FE\u5C42\u5217\u8868", mimeType: "application/json", subscribable: true },
897
- { uri: "atlisp://dwg/entities", name: "Entities", description: "\u5B9E\u4F53\u7EDF\u8BA1", mimeType: "application/json", subscribable: true },
1171
+ { uri: "atlisp://dwg/entities", name: "Entities", description: "\u5B9E\u4F53\u5C5E\u6027\u5217\u8868\uFF0C\u652F\u6301 ?type=LINE&layer=0&bbox=0,0,100,100&limit=100&offset=0", mimeType: "application/json", subscribable: true },
1172
+ { uri: "atlisp://dwg/texts", name: "Texts", description: "\u6587\u672C\u5B9E\u4F53\u5185\u5BB9\u5217\u8868 (TEXT/MTEXT/ATTRIB/TCH_TEXT)\uFF0C\u652F\u6301 ?layer=0&bbox=0,0,100,100&limit=500&offset=0", mimeType: "application/json", subscribable: true },
898
1173
  { uri: "atlisp://dwg/name", name: "DWG Name", description: "\u5F53\u524D DWG \u6587\u4EF6\u540D", mimeType: "application/json", subscribable: true },
899
1174
  { uri: "atlisp://dwg/path", name: "DWG Path", description: "\u6587\u4EF6\u5B8C\u6574\u8DEF\u5F84", mimeType: "application/json", subscribable: true },
900
1175
  { uri: "atlisp://cad/dwgs", name: "DWG List", description: "\u6240\u6709\u6253\u5F00\u7684 DWG \u6587\u4EF6\u5217\u8868", mimeType: "application/json", subscribable: true },
@@ -905,16 +1180,14 @@ var RESOURCES = [
905
1180
  ];
906
1181
  function parseQuery(uri) {
907
1182
  const qIdx = uri.indexOf("?");
908
- if (qIdx === -1)
909
- return { base: uri, params: {} };
1183
+ if (qIdx === -1) return { base: uri, params: {} };
910
1184
  const base = uri.substring(0, qIdx);
911
1185
  const params = {};
912
1186
  uri.substring(qIdx + 1).split("&").forEach((p) => {
913
1187
  const eqIdx = p.indexOf("=");
914
1188
  const k = eqIdx === -1 ? p : p.substring(0, eqIdx);
915
1189
  const v = eqIdx === -1 ? "" : p.substring(eqIdx + 1);
916
- if (k)
917
- params[k] = decodeURIComponent(v);
1190
+ if (k) params[k] = decodeURIComponent(v);
918
1191
  });
919
1192
  return { base, params };
920
1193
  }
@@ -935,7 +1208,9 @@ async function readResource(uri) {
935
1208
  case "atlisp://dwg/layers":
936
1209
  return await getLayers(params.name);
937
1210
  case "atlisp://dwg/entities":
938
- return await getEntities(params.type);
1211
+ return await getEntities(params);
1212
+ case "atlisp://dwg/texts":
1213
+ return await getTexts(params);
939
1214
  case "atlisp://dwg/name":
940
1215
  return await getDwgName();
941
1216
  case "atlisp://dwg/path":
@@ -956,8 +1231,7 @@ async function readResource(uri) {
956
1231
  }
957
1232
  async function getCadInfo2() {
958
1233
  const cached = getCached("cad:info");
959
- if (cached)
960
- return cached;
1234
+ if (cached) return cached;
961
1235
  if (!cad.connected) {
962
1236
  await cad.connect();
963
1237
  }
@@ -984,12 +1258,9 @@ async function getCadInfo2() {
984
1258
  async function getLayers(filterName = null) {
985
1259
  const cacheKey = filterName ? `dwg:layers:${filterName}` : "dwg:layers:all";
986
1260
  const cached = getCached(cacheKey);
987
- if (cached)
988
- return cached;
989
- if (!cad.connected)
990
- await cad.connect();
991
- if (!cad.connected)
992
- return [];
1261
+ if (cached) return cached;
1262
+ if (!cad.connected) await cad.connect();
1263
+ if (!cad.connected) return [];
993
1264
  try {
994
1265
  const code = filterName ? `(tblnext "LAYER" T)` : `(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))`;
995
1266
  const result = await cad.sendCommandWithResult(code);
@@ -1033,70 +1304,97 @@ async function getLayers(filterName = null) {
1033
1304
  return [];
1034
1305
  }
1035
1306
  }
1036
- async function getEntities(filterType = null) {
1037
- const cacheKey = filterType ? `dwg:entities:${filterType}` : "dwg:entities:all";
1307
+ async function getEntities(params = {}) {
1308
+ const cacheKey = `dwg:entities:${JSON.stringify(params)}`;
1038
1309
  const cached = getCached(cacheKey);
1039
- if (cached)
1040
- return cached;
1041
- if (!cad.connected)
1042
- await cad.connect();
1310
+ if (cached) return cached;
1311
+ if (!cad.connected) await cad.connect();
1043
1312
  if (!cad.connected) {
1044
- const result = { total: 0, byType: {} };
1313
+ const result = { total: 0, entities: [] };
1045
1314
  setCache(cacheKey, result);
1046
1315
  return result;
1047
1316
  }
1317
+ const offset = Math.max(0, parseInt(params.offset) || 0);
1318
+ const limit = Math.min(Math.max(1, parseInt(params.limit) || 100), 1e3);
1048
1319
  try {
1049
- let code;
1050
- if (filterType) {
1051
- code = `(sslength (ssget "_X" (list (cons 0 "${filterType}"))))`;
1052
- const count = await cad.sendCommandWithResult(code);
1053
- const result = { type: filterType, count: parseInt(count) || 0 };
1320
+ const code = buildEntityCode({
1321
+ type: params.type || null,
1322
+ layer: params.layer || null,
1323
+ bbox: params.bbox || null,
1324
+ offset,
1325
+ limit
1326
+ });
1327
+ const resultStr = await cad.sendCommandWithResult(code);
1328
+ if (!resultStr || resultStr === "" || resultStr === "nil" || resultStr === "0") {
1329
+ const result = { total: 0, offset, limit, entities: [] };
1054
1330
  setCache(cacheKey, result);
1055
1331
  return result;
1056
- } else {
1057
- 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))))`;
1058
- const resultStr = await cad.sendCommandWithResult(code);
1059
- if (!resultStr || resultStr === "" || resultStr === "nil") {
1060
- const result2 = { total: 0, byType: {} };
1061
- setCache(cacheKey, result2);
1062
- return result2;
1063
- }
1064
- let statResult = [];
1065
- try {
1066
- statResult = JSON.parse(resultStr);
1067
- } catch (e) {
1068
- const parsed = parseLispList(resultStr);
1069
- if (parsed) {
1070
- statResult = parsed;
1071
- }
1072
- }
1073
- if (!Array.isArray(statResult)) {
1074
- const result2 = { total: 0, byType: {} };
1075
- setCache(cacheKey, result2);
1076
- return result2;
1077
- }
1078
- const byType = {};
1079
- statResult.forEach((item) => {
1080
- if (Array.isArray(item) && item.length >= 2) {
1081
- byType[item[0]] = item[1];
1082
- }
1083
- });
1084
- const total = Object.values(byType).reduce((sum, n) => sum + n, 0);
1085
- const result = { total, byType };
1332
+ }
1333
+ const parsed = parseLispRecursive(resultStr);
1334
+ if (!parsed || !Array.isArray(parsed)) {
1335
+ const result = { total: 0, offset, limit, entities: [] };
1336
+ setCache(cacheKey, result);
1337
+ return result;
1338
+ }
1339
+ const entitiesIdx = parsed.indexOf("entities");
1340
+ const rawEntities = entitiesIdx !== -1 && Array.isArray(parsed[entitiesIdx + 1]) ? parsed[entitiesIdx + 1] : [];
1341
+ const data = lispPairsToObject(parsed);
1342
+ data.entities = rawEntities.length > 0 ? rawEntities.map(parseEntity).filter(Boolean) : [];
1343
+ data.offset = offset;
1344
+ data.limit = limit;
1345
+ setCache(cacheKey, data);
1346
+ return data;
1347
+ } catch (e) {
1348
+ log(`getEntities error: ${e.message}`);
1349
+ const result = { total: 0, offset, limit, entities: [] };
1350
+ setCache(cacheKey, result);
1351
+ return result;
1352
+ }
1353
+ }
1354
+ async function getTexts(params = {}) {
1355
+ const cacheKey = `dwg:texts:${JSON.stringify(params)}`;
1356
+ const cached = getCached(cacheKey);
1357
+ if (cached) return cached;
1358
+ if (!cad.connected) await cad.connect();
1359
+ if (!cad.connected) {
1360
+ const result = { total: 0, texts: [] };
1361
+ setCache(cacheKey, result);
1362
+ return result;
1363
+ }
1364
+ const offset = Math.max(0, parseInt(params.offset) || 0);
1365
+ const limit = Math.min(Math.max(1, parseInt(params.limit) || 500), 5e3);
1366
+ try {
1367
+ const code = buildTextCode({
1368
+ layer: params.layer || null,
1369
+ bbox: params.bbox || null,
1370
+ offset,
1371
+ limit
1372
+ });
1373
+ const resultStr = await cad.sendCommandWithResult(code);
1374
+ if (!resultStr || resultStr === "" || resultStr === "nil" || resultStr === "0") {
1375
+ const result = { total: 0, offset, limit, texts: [] };
1086
1376
  setCache(cacheKey, result);
1087
1377
  return result;
1088
1378
  }
1379
+ const parsed = parseLispRecursive(resultStr);
1380
+ const textsIdx = parsed.indexOf("texts");
1381
+ const rawTexts = textsIdx !== -1 && Array.isArray(parsed[textsIdx + 1]) ? parsed[textsIdx + 1] : [];
1382
+ const data = lispPairsToObject(parsed);
1383
+ data.texts = rawTexts.length > 0 ? rawTexts.map(parseEntity).filter(Boolean) : [];
1384
+ data.offset = offset;
1385
+ data.limit = limit;
1386
+ setCache(cacheKey, data);
1387
+ return data;
1089
1388
  } catch (e) {
1090
- const result = { total: 0, byType: {} };
1389
+ log(`getTexts error: ${e.message}`);
1390
+ const result = { total: 0, offset, limit, texts: [] };
1091
1391
  setCache(cacheKey, result);
1092
1392
  return result;
1093
1393
  }
1094
1394
  }
1095
1395
  async function getDwgName() {
1096
- if (!cad.connected)
1097
- await cad.connect();
1098
- if (!cad.connected)
1099
- return { name: null };
1396
+ if (!cad.connected) await cad.connect();
1397
+ if (!cad.connected) return { name: null };
1100
1398
  try {
1101
1399
  const code = `(vl-princ-to-string (getvar "dwgname"))`;
1102
1400
  const name = await cad.sendCommandWithResult(code);
@@ -1106,10 +1404,8 @@ async function getDwgName() {
1106
1404
  }
1107
1405
  }
1108
1406
  async function getDwgPath() {
1109
- if (!cad.connected)
1110
- await cad.connect();
1111
- if (!cad.connected)
1112
- return { path: null, name: null };
1407
+ if (!cad.connected) await cad.connect();
1408
+ if (!cad.connected) return { path: null, name: null };
1113
1409
  try {
1114
1410
  const code = `(progn
1115
1411
  (setq prefix (vl-princ-to-string (getvar "dwgprefix")))
@@ -1127,8 +1423,7 @@ async function getDwgPath() {
1127
1423
  }
1128
1424
  if (!parsed) {
1129
1425
  const listResult = parseLispList(result);
1130
- if (listResult && listResult.length >= 2)
1131
- parsed = listResult;
1426
+ if (listResult && listResult.length >= 2) parsed = listResult;
1132
1427
  }
1133
1428
  if (parsed && Array.isArray(parsed) && parsed.length >= 2) {
1134
1429
  return { path: String(parsed[0]), name: String(parsed[1]) };
@@ -1141,12 +1436,9 @@ async function getDwgPath() {
1141
1436
  async function getPackages(filterName = null) {
1142
1437
  const cacheKey = filterName ? `packages:${filterName}` : "packages:all";
1143
1438
  const cached = getCached(cacheKey);
1144
- if (cached)
1145
- return cached;
1146
- if (!cad.connected)
1147
- await cad.connect();
1148
- if (!cad.connected)
1149
- return [];
1439
+ if (cached) return cached;
1440
+ if (!cad.connected) await cad.connect();
1441
+ if (!cad.connected) return [];
1150
1442
  try {
1151
1443
  const code = `(mapcar 'cdr @::*local-pkgs*)`;
1152
1444
  const result = await cad.sendCommandWithResult(code);
@@ -1160,8 +1452,7 @@ async function getPackages(filterName = null) {
1160
1452
  } catch {
1161
1453
  raw = parseLispList(result) || [];
1162
1454
  }
1163
- if (!Array.isArray(raw))
1164
- raw = [];
1455
+ if (!Array.isArray(raw)) raw = [];
1165
1456
  const packages = raw.map((item) => {
1166
1457
  if (Array.isArray(item)) {
1167
1458
  const pkg = { name: "", version: "0.0.0", description: "", loaded: true };
@@ -1170,20 +1461,13 @@ async function getPackages(filterName = null) {
1170
1461
  const [[key, val]] = Object.entries(entry);
1171
1462
  const k = String(key);
1172
1463
  const v = String(val);
1173
- if (k === ":NAME")
1174
- pkg.name = v;
1175
- else if (k === ":VERSION")
1176
- pkg.version = v;
1177
- else if (k === ":DESCRIPTION")
1178
- pkg.description = v;
1179
- else if (k === ":FULL-NAME")
1180
- pkg.fullName = v;
1181
- else if (k === ":AUTHOR")
1182
- pkg.author = v;
1183
- else if (k === ":CATEGORY")
1184
- pkg.category = v;
1185
- else if (k === ":URL")
1186
- pkg.url = v;
1464
+ if (k === ":NAME") pkg.name = v;
1465
+ else if (k === ":VERSION") pkg.version = v;
1466
+ else if (k === ":DESCRIPTION") pkg.description = v;
1467
+ else if (k === ":FULL-NAME") pkg.fullName = v;
1468
+ else if (k === ":AUTHOR") pkg.author = v;
1469
+ else if (k === ":CATEGORY") pkg.category = v;
1470
+ else if (k === ":URL") pkg.url = v;
1187
1471
  }
1188
1472
  }
1189
1473
  return pkg;
@@ -1191,6 +1475,20 @@ async function getPackages(filterName = null) {
1191
1475
  if (typeof item === "string" || typeof item === "number") {
1192
1476
  return { name: String(item), version: "0.0.0", loaded: true };
1193
1477
  }
1478
+ if (item && typeof item === "object") {
1479
+ const pkg = { name: "", version: "0.0.0", description: "", loaded: true };
1480
+ const nameVal = item.name || item.Name || item.NAME || item[":NAME"];
1481
+ if (nameVal) pkg.name = String(nameVal);
1482
+ const verVal = item.version || item.Version || item.VERSION || item[":VERSION"];
1483
+ if (verVal) pkg.version = String(verVal);
1484
+ const descVal = item.description || item.Description || item.DESCRIPTION || item[":DESCRIPTION"];
1485
+ if (descVal) pkg.description = String(descVal);
1486
+ if (item.fullName || item.full_name) pkg.fullName = String(item.fullName || item.full_name);
1487
+ if (item.author || item.Author) pkg.author = String(item.author || item.Author);
1488
+ if (item.category || item.Category) pkg.category = String(item.category || item.Category);
1489
+ if (item.url || item.Url || item.URL) pkg.url = String(item.url || item.Url || item.URL);
1490
+ return pkg;
1491
+ }
1194
1492
  return null;
1195
1493
  }).filter(Boolean);
1196
1494
  if (filterName) {
@@ -1214,12 +1512,9 @@ function getPlatforms() {
1214
1512
  async function getDwgList() {
1215
1513
  const cacheKey = "cad:dwgs";
1216
1514
  const cached = getCached(cacheKey);
1217
- if (cached)
1218
- return cached;
1219
- if (!cad.connected)
1220
- await cad.connect();
1221
- if (!cad.connected)
1222
- return [];
1515
+ if (cached) return cached;
1516
+ if (!cad.connected) await cad.connect();
1517
+ if (!cad.connected) return [];
1223
1518
  try {
1224
1519
  const code = `(progn
1225
1520
  (vl-load-com)
@@ -1243,8 +1538,7 @@ async function getDwgList() {
1243
1538
  parsed = listResult;
1244
1539
  }
1245
1540
  }
1246
- if (!Array.isArray(parsed))
1247
- parsed = [];
1541
+ if (!Array.isArray(parsed)) parsed = [];
1248
1542
  const dwgList = parsed.map((item) => {
1249
1543
  if (Array.isArray(item) && item.length >= 2) {
1250
1544
  return { name: String(item[0]), path: String(item[1]) };
@@ -1271,8 +1565,7 @@ function loadStandardsFile(filename) {
1271
1565
  var standardsCache = /* @__PURE__ */ new Map();
1272
1566
  function getCachedStandards(key) {
1273
1567
  const entry = standardsCache.get(key);
1274
- if (entry && Date.now() - entry.timestamp < 3e4)
1275
- return entry.data;
1568
+ if (entry && Date.now() - entry.timestamp < 3e4) return entry.data;
1276
1569
  standardsCache.delete(key);
1277
1570
  return null;
1278
1571
  }
@@ -1281,24 +1574,57 @@ function setCachedStandards(key, data) {
1281
1574
  }
1282
1575
  function getDraftingStandards() {
1283
1576
  const cached = getCachedStandards("drafting");
1284
- if (cached)
1285
- return cached;
1577
+ if (cached) return cached;
1286
1578
  const data = loadStandardsFile("drafting.json");
1287
- if (data)
1288
- setCachedStandards("drafting", data);
1579
+ if (data) setCachedStandards("drafting", data);
1289
1580
  return data || { error: "\u5236\u56FE\u89C4\u8303\u6587\u4EF6\u52A0\u8F7D\u5931\u8D25" };
1290
1581
  }
1291
1582
  function getCodingStandards() {
1292
1583
  const cached = getCachedStandards("coding");
1293
- if (cached)
1294
- return cached;
1584
+ if (cached) return cached;
1295
1585
  const data = loadStandardsFile("coding.json");
1296
- if (data)
1297
- setCachedStandards("coding", data);
1586
+ if (data) setCachedStandards("coding", data);
1298
1587
  return data || { error: "\u7F16\u7801\u89C4\u8303\u6587\u4EF6\u52A0\u8F7D\u5931\u8D25" };
1299
1588
  }
1300
1589
 
1301
1590
  // src/handlers/prompt-handlers.js
1591
+ import fs4 from "fs";
1592
+ import path5 from "path";
1593
+ import { fileURLToPath as fileURLToPath3 } from "url";
1594
+ var __dirname3 = path5.dirname(fileURLToPath3(import.meta.url));
1595
+ var PROMPTS_DIR = path5.join(__dirname3, "..", "prompts");
1596
+ var externalPrompts = null;
1597
+ var externalPromptsAt = 0;
1598
+ var EXTERNAL_PROMPTS_TTL = 3e4;
1599
+ function loadExternalPrompts() {
1600
+ const now = Date.now();
1601
+ if (externalPrompts && now - externalPromptsAt < EXTERNAL_PROMPTS_TTL) {
1602
+ return externalPrompts;
1603
+ }
1604
+ externalPrompts = [];
1605
+ try {
1606
+ if (!fs4.existsSync(PROMPTS_DIR)) {
1607
+ fs4.mkdirSync(PROMPTS_DIR, { recursive: true });
1608
+ return externalPrompts;
1609
+ }
1610
+ const files = fs4.readdirSync(PROMPTS_DIR).filter((f) => f.endsWith(".json"));
1611
+ for (const file of files) {
1612
+ try {
1613
+ const raw = fs4.readFileSync(path5.join(PROMPTS_DIR, file), "utf-8");
1614
+ const prompt = JSON.parse(raw);
1615
+ if (prompt && prompt.name) {
1616
+ externalPrompts.push(prompt);
1617
+ }
1618
+ } catch (e) {
1619
+ console.error(`Error loading prompt file ${file}: ${e.message}`);
1620
+ }
1621
+ }
1622
+ } catch (e) {
1623
+ console.error(`Error reading prompts directory: ${e.message}`);
1624
+ }
1625
+ externalPromptsAt = now;
1626
+ return externalPrompts;
1627
+ }
1302
1628
  var PROMPTS = [
1303
1629
  {
1304
1630
  name: "analyze-drawing",
@@ -1414,10 +1740,23 @@ var PROMPTS = [
1414
1740
  ]
1415
1741
  }
1416
1742
  ];
1743
+ function findExternalPrompt(name) {
1744
+ const ext = loadExternalPrompts();
1745
+ return ext.find((p) => p.name === name);
1746
+ }
1417
1747
  async function listPrompts() {
1418
- return PROMPTS;
1748
+ const ext = loadExternalPrompts();
1749
+ const extNames = new Set(ext.map((p) => p.name));
1750
+ const builtIn = PROMPTS.filter((p) => !extNames.has(p.name));
1751
+ return [...builtIn, ...ext];
1419
1752
  }
1420
1753
  async function getPrompt(name, arguments_ = {}) {
1754
+ const extPrompt = findExternalPrompt(name);
1755
+ if (extPrompt) {
1756
+ return {
1757
+ messages: extPrompt.messages || []
1758
+ };
1759
+ }
1421
1760
  switch (name) {
1422
1761
  case "analyze-drawing":
1423
1762
  return generateAnalyzePrompt();
@@ -1498,14 +1837,10 @@ ${generateSuggestions(entities, layers)}`
1498
1837
  }
1499
1838
  function generateSuggestions(entities, layers) {
1500
1839
  const suggestions = [];
1501
- if (entities.total > 1e3)
1502
- suggestions.push("- \u56FE\u7EB8\u5B9E\u4F53\u8F83\u591A\uFF0C\u5EFA\u8BAE\u4F7F\u7528\u56FE\u5C42\u5206\u7C7B\u7BA1\u7406");
1503
- if (layers.length > 20)
1504
- suggestions.push("- \u56FE\u5C42\u8F83\u591A\uFF0C\u5EFA\u8BAE\u6E05\u7406\u672A\u4F7F\u7528\u7684\u56FE\u5C42");
1505
- if (entities.byType.LINE && entities.byType.LINE > 500)
1506
- suggestions.push("- \u7EBF\u6761\u8F83\u591A\uFF0C\u53EF\u8003\u8651\u4F7F\u7528 BLOCK \u51CF\u5C11\u5B9E\u4F53\u6570\u91CF");
1507
- if (!entities.byType.DIMENSION)
1508
- suggestions.push("- \u672A\u53D1\u73B0\u5C3A\u5BF8\u6807\u6CE8\uFF0C\u5EFA\u8BAE\u6DFB\u52A0\u6807\u6CE8");
1840
+ if (entities.total > 1e3) suggestions.push("- \u56FE\u7EB8\u5B9E\u4F53\u8F83\u591A\uFF0C\u5EFA\u8BAE\u4F7F\u7528\u56FE\u5C42\u5206\u7C7B\u7BA1\u7406");
1841
+ if (layers.length > 20) suggestions.push("- \u56FE\u5C42\u8F83\u591A\uFF0C\u5EFA\u8BAE\u6E05\u7406\u672A\u4F7F\u7528\u7684\u56FE\u5C42");
1842
+ if (entities.byType.LINE && entities.byType.LINE > 500) suggestions.push("- \u7EBF\u6761\u8F83\u591A\uFF0C\u53EF\u8003\u8651\u4F7F\u7528 BLOCK \u51CF\u5C11\u5B9E\u4F53\u6570\u91CF");
1843
+ if (!entities.byType.DIMENSION) suggestions.push("- \u672A\u53D1\u73B0\u5C3A\u5BF8\u6807\u6CE8\uFF0C\u5EFA\u8BAE\u6DFB\u52A0\u6807\u6CE8");
1509
1844
  return suggestions.length ? suggestions.join("\n") : "- \u56FE\u7EB8\u7ED3\u6784\u826F\u597D";
1510
1845
  }
1511
1846
  async function generateBatchLinesPrompt(args) {
@@ -2233,11 +2568,9 @@ var SseSession = class {
2233
2568
  return lines.join("\n") + "\n\n";
2234
2569
  }
2235
2570
  _setupDrain() {
2236
- if (typeof this.#res.on !== "function")
2237
- return;
2571
+ if (typeof this.#res.on !== "function") return;
2238
2572
  this.#drainHandler = () => {
2239
- if (!this.#active)
2240
- return;
2573
+ if (!this.#active) return;
2241
2574
  if (this.#backpressureBuffer.length > 0) {
2242
2575
  this._drainBuffer();
2243
2576
  }
@@ -2259,8 +2592,7 @@ var SseSession = class {
2259
2592
  }
2260
2593
  }
2261
2594
  _write(content) {
2262
- if (!this.#active)
2263
- return false;
2595
+ if (!this.#active) return false;
2264
2596
  try {
2265
2597
  if (this.#backpressureBuffer.length > 0) {
2266
2598
  this.#backpressureBuffer.push(content);
@@ -2309,8 +2641,8 @@ var SseSession = class {
2309
2641
  sendMessage(data) {
2310
2642
  return this.sendEvent(SSE_EVENTS.MESSAGE, data);
2311
2643
  }
2312
- sendEndpoint(path6) {
2313
- return this.sendEvent(SSE_EVENTS.ENDPOINT, path6);
2644
+ sendEndpoint(path8) {
2645
+ return this.sendEvent(SSE_EVENTS.ENDPOINT, path8);
2314
2646
  }
2315
2647
  sendError(code, message, id = null) {
2316
2648
  return this.sendEvent(SSE_EVENTS.ERROR, { code, message }, id);
@@ -2364,6 +2696,42 @@ var SseSession = class {
2364
2696
  };
2365
2697
 
2366
2698
  // src/sse-session-manager.js
2699
+ import fs5 from "fs";
2700
+ import path6 from "path";
2701
+ import os3 from "os";
2702
+ var SESSION_STORE_DIR = path6.join(os3.homedir(), ".atlisp", "sessions");
2703
+ var SESSION_STORE_TTL = 24 * 60 * 60 * 1e3;
2704
+ function getSessionStorePath(clientId) {
2705
+ return path6.join(SESSION_STORE_DIR, `${clientId}.json`);
2706
+ }
2707
+ function persistSessionData(clientId, sessionData) {
2708
+ try {
2709
+ fs5.mkdirSync(SESSION_STORE_DIR, { recursive: true });
2710
+ const store = {
2711
+ clientId,
2712
+ sessionData,
2713
+ savedAt: Date.now()
2714
+ };
2715
+ fs5.writeFileSync(getSessionStorePath(clientId), JSON.stringify(store), "utf-8");
2716
+ } catch (e) {
2717
+ console.error("Failed to persist session:", e.message);
2718
+ }
2719
+ }
2720
+ function loadPersistedSessionData(clientId) {
2721
+ try {
2722
+ const filePath = getSessionStorePath(clientId);
2723
+ if (!fs5.existsSync(filePath)) return null;
2724
+ const raw = fs5.readFileSync(filePath, "utf-8");
2725
+ const store = JSON.parse(raw);
2726
+ if (Date.now() - store.savedAt > SESSION_STORE_TTL) {
2727
+ fs5.unlinkSync(filePath);
2728
+ return null;
2729
+ }
2730
+ return store.sessionData || {};
2731
+ } catch (e) {
2732
+ return null;
2733
+ }
2734
+ }
2367
2735
  var SseSessionManager = class {
2368
2736
  #sessions = /* @__PURE__ */ new Map();
2369
2737
  #eventHandlers = /* @__PURE__ */ new Map();
@@ -2375,12 +2743,19 @@ var SseSessionManager = class {
2375
2743
  if (!(session instanceof SseSession)) {
2376
2744
  throw new Error("session must be an SseSession instance");
2377
2745
  }
2746
+ const persisted = loadPersistedSessionData(clientId);
2747
+ if (persisted) {
2748
+ for (const [key, value] of Object.entries(persisted)) {
2749
+ session.setSessionData(key, value);
2750
+ }
2751
+ }
2378
2752
  this.#sessions.set(clientId, session);
2379
2753
  session.startHeartbeat();
2380
2754
  }
2381
2755
  remove(clientId) {
2382
2756
  const session = this.#sessions.get(clientId);
2383
2757
  if (session) {
2758
+ persistSessionData(clientId, session.sessionData);
2384
2759
  session.close();
2385
2760
  this.#sessions.delete(clientId);
2386
2761
  return true;
@@ -2469,6 +2844,7 @@ var SseSessionManager = class {
2469
2844
  }
2470
2845
  closeAll() {
2471
2846
  for (const [clientId, session] of this.#sessions) {
2847
+ persistSessionData(clientId, session.sessionData);
2472
2848
  session.close();
2473
2849
  }
2474
2850
  this.#sessions.clear();
@@ -2476,9 +2852,12 @@ var SseSessionManager = class {
2476
2852
  };
2477
2853
 
2478
2854
  // src/atlisp-mcp.js
2479
- var __dirname3 = path5.dirname(fileURLToPath3(import.meta.url));
2855
+ var __dirname4 = path7.dirname(fileURLToPath4(import.meta.url));
2480
2856
  var require2 = createRequire(import.meta.url);
2481
- var { version } = require2(path5.join(__dirname3, "..", "package.json"));
2857
+ var { version } = require2(path7.join(__dirname4, "..", "package.json"));
2858
+ var CACHE_DIR2 = path7.join(os4.homedir(), ".atlisp", "cache");
2859
+ var FUNCLIB_CACHE_FILE2 = path7.join(CACHE_DIR2, "functions.json");
2860
+ var FUNCLIB_CACHE_TTL2 = 24 * 60 * 60 * 1e3;
2482
2861
  var isMcpScript = process.argv[1]?.includes("atlisp-mcp");
2483
2862
  if (isMcpScript) {
2484
2863
  const args = process.argv.slice(2);
@@ -2530,8 +2909,7 @@ var SERVER_CAPABILITIES = {
2530
2909
  prompts: {}
2531
2910
  };
2532
2911
  var { port, host, transport, apiKey, rateLimitWindow, rateLimitMax } = config_default;
2533
- if (apiKey)
2534
- log("API Key authentication enabled");
2912
+ if (apiKey) log("API Key authentication enabled");
2535
2913
  var mcpLimiter = rateLimit({
2536
2914
  windowMs: rateLimitWindow,
2537
2915
  max: rateLimitMax,
@@ -2543,26 +2921,52 @@ var FUNCTION_LIB_URL = process.env.FUNCTION_LIB_URL || "http://s3.atlisp.cn/json
2543
2921
  var cachedFunctionLib = null;
2544
2922
  var cachedAt2 = null;
2545
2923
  var CACHE_TTL3 = 5 * 60 * 1e3;
2924
+ async function readCacheFromDisk2() {
2925
+ try {
2926
+ const stat = fs6.statSync(FUNCLIB_CACHE_FILE2);
2927
+ if (Date.now() - stat.mtimeMs < FUNCLIB_CACHE_TTL2) {
2928
+ const raw = fs6.readFileSync(FUNCLIB_CACHE_FILE2, "utf-8");
2929
+ return JSON.parse(raw);
2930
+ }
2931
+ } catch {
2932
+ }
2933
+ return null;
2934
+ }
2935
+ function writeCacheToDisk2(data) {
2936
+ try {
2937
+ fs6.mkdirSync(CACHE_DIR2, { recursive: true });
2938
+ fs6.writeFileSync(FUNCLIB_CACHE_FILE2, JSON.stringify(data), "utf-8");
2939
+ } catch (e) {
2940
+ log(`writeCacheToDisk error: ${e.message}`);
2941
+ }
2942
+ }
2546
2943
  async function loadAtlibFunctionLib() {
2547
2944
  const now = Date.now();
2548
2945
  if (cachedFunctionLib && cachedAt2 && now - cachedAt2 < CACHE_TTL3) {
2549
2946
  return cachedFunctionLib;
2550
2947
  }
2948
+ const diskCache = await readCacheFromDisk2();
2949
+ if (diskCache) {
2950
+ cachedFunctionLib = diskCache;
2951
+ cachedAt2 = now;
2952
+ return diskCache;
2953
+ }
2551
2954
  try {
2552
2955
  const response = await fetch(FUNCTION_LIB_URL, { headers: { "User-Agent": "atlisp-mcp" } });
2553
2956
  if (response.ok) {
2554
2957
  const data = await response.json();
2555
2958
  cachedFunctionLib = data;
2556
2959
  cachedAt2 = now;
2960
+ writeCacheToDisk2(data);
2557
2961
  return data;
2558
2962
  }
2559
2963
  } catch (e) {
2560
2964
  log(`loadAtlibFunctionLib error: ${e.message}`);
2561
2965
  }
2562
- return null;
2966
+ return cachedFunctionLib;
2563
2967
  }
2564
2968
  var TOOL_HANDLERS = {
2565
- connect_cad: () => connectCad(),
2969
+ connect_cad: (a) => connectCad(a?.platform),
2566
2970
  eval_lisp: (a) => evalLisp(a.code),
2567
2971
  eval_lisp_with_result: (a) => evalLisp(a.code, true, a.encoding),
2568
2972
  get_cad_info: () => getCadInfo(),
@@ -2580,10 +2984,17 @@ ${CAD_PLATFORMS.join("\n")}
2580
2984
  get_function_usage: (a) => getFunctionUsage(a.name, a.package),
2581
2985
  list_symbols: (a) => listSymbols(a.package),
2582
2986
  get_system_status: () => getSystemStatus(),
2987
+ list_prompts: async () => {
2988
+ const prompts = await listPrompts();
2989
+ return { content: [{ type: "text", text: JSON.stringify(prompts) }] };
2990
+ },
2991
+ get_prompt: async (a) => {
2992
+ const prompt = await getPrompt(a.name, a.args || {});
2993
+ return { content: prompt.messages.map((m) => ({ type: "text", text: m.content.text || m.content })) };
2994
+ },
2583
2995
  import_funlib: async (a) => {
2584
2996
  const data = await loadAtlibFunctionLib();
2585
- if (!data)
2586
- return { content: [{ type: "text", text: "\u52A0\u8F7D\u51FD\u6570\u5E93\u5931\u8D25" }], isError: true };
2997
+ if (!data) return { content: [{ type: "text", text: "\u52A0\u8F7D\u51FD\u6570\u5E93\u5931\u8D25" }], isError: true };
2587
2998
  if (a.format === "list") {
2588
2999
  const items = Object.values(data.all_functions || {}).map((f) => `${f.name}: ${f.description || ""}`);
2589
3000
  return { content: [{ type: "text", text: items.join("\n") }] };
@@ -2599,7 +3010,7 @@ function getOrCreateSessionServer(sessionId) {
2599
3010
  server.connect(transport2);
2600
3011
  entry = { server, transport: transport2 };
2601
3012
  sessionServers.set(sessionId, entry);
2602
- sessionTranports.set(sessionId, transport2);
3013
+ sessionTransports.set(sessionId, transport2);
2603
3014
  }
2604
3015
  return entry;
2605
3016
  }
@@ -2611,7 +3022,7 @@ function removeSessionServer(sessionId) {
2611
3022
  entry.server.close().catch(() => {
2612
3023
  });
2613
3024
  sessionServers.delete(sessionId);
2614
- sessionTranports.delete(sessionId);
3025
+ sessionTransports.delete(sessionId);
2615
3026
  }
2616
3027
  }
2617
3028
  function broadcastToAllSessions(uri) {
@@ -2625,7 +3036,12 @@ var tools = [
2625
3036
  {
2626
3037
  name: "connect_cad",
2627
3038
  description: "\u8FDE\u63A5\u5230 CAD (AutoCAD/ZWCAD/GStarCAD/BricsCAD)",
2628
- inputSchema: { type: "object", properties: {} }
3039
+ inputSchema: {
3040
+ type: "object",
3041
+ properties: {
3042
+ platform: { type: "string", description: "\u6307\u5B9A CAD \u5E73\u53F0: AutoCAD, ZWCAD, GStarCAD, BricsCAD\uFF08\u53EF\u9009\uFF0C\u81EA\u52A8\u68C0\u6D4B\uFF09" }
3043
+ }
3044
+ }
2629
3045
  },
2630
3046
  {
2631
3047
  name: "eval_lisp",
@@ -2746,12 +3162,28 @@ var tools = [
2746
3162
  name: "get_system_status",
2747
3163
  description: "\u83B7\u53D6\u5B8C\u6574\u7CFB\u7EDF\u8BCA\u65AD\u4FE1\u606F\uFF1AMCP \u72B6\u6001\u3001CAD \u8FDE\u63A5\u3001Lisp \u6267\u884C\u3001\u5305\u5217\u8868",
2748
3164
  inputSchema: { type: "object", properties: {} }
3165
+ },
3166
+ {
3167
+ name: "list_prompts",
3168
+ description: "\u5217\u51FA\u6240\u6709\u53EF\u7528\u7684 @lisp \u63D0\u793A\u6A21\u677F",
3169
+ inputSchema: { type: "object", properties: {} }
3170
+ },
3171
+ {
3172
+ name: "get_prompt",
3173
+ description: "\u83B7\u53D6\u6307\u5B9A\u63D0\u793A\u6A21\u677F\u7684\u5185\u5BB9",
3174
+ inputSchema: {
3175
+ type: "object",
3176
+ properties: {
3177
+ name: { type: "string", description: "\u63D0\u793A\u6A21\u677F\u540D\u79F0" },
3178
+ args: { type: "object", description: "\u6A21\u677F\u53C2\u6570\uFF08\u53EF\u9009\uFF09" }
3179
+ },
3180
+ required: ["name"]
3181
+ }
2749
3182
  }
2750
3183
  ];
2751
3184
  async function handleToolCall(name, args) {
2752
3185
  const handler = TOOL_HANDLERS[name];
2753
- if (!handler)
2754
- return { content: [{ type: "text", text: `\u672A\u77E5\u5DE5\u5177: ${name}` }], isError: true };
3186
+ if (!handler) return { content: [{ type: "text", text: `\u672A\u77E5\u5DE5\u5177: ${name}` }], isError: true };
2755
3187
  try {
2756
3188
  const result = await handler(args || {});
2757
3189
  notifyResourceChanges(name);
@@ -2764,7 +3196,7 @@ async function handleToolCall(name, args) {
2764
3196
  function notifyResourceChanges(toolName) {
2765
3197
  const resourceMap = {
2766
3198
  connect_cad: ["atlisp://cad/info", "atlisp://dwg/layers", "atlisp://dwg/name", "atlisp://dwg/path", "atlisp://cad/dwgs", "atlisp://packages"],
2767
- new_document: ["atlisp://dwg/name", "atlisp://dwg/path", "atlisp://cad/dwgs", "atlisp://dwg/layers", "atlisp://dwg/entities"],
3199
+ new_document: ["atlisp://dwg/name", "atlisp://dwg/path", "atlisp://cad/dwgs", "atlisp://dwg/layers", "atlisp://dwg/entities", "atlisp://dwg/texts"],
2768
3200
  install_package: ["atlisp://packages"],
2769
3201
  install_atlisp: ["atlisp://packages", "atlisp://cad/info"],
2770
3202
  init_atlisp: ["atlisp://packages", "atlisp://cad/info"]
@@ -2794,7 +3226,7 @@ async function initCadConnection() {
2794
3226
  }
2795
3227
  }
2796
3228
  var sessionServers = /* @__PURE__ */ new Map();
2797
- var sessionTranports = /* @__PURE__ */ new Map();
3229
+ var sessionTransports = /* @__PURE__ */ new Map();
2798
3230
  function createMcpServer() {
2799
3231
  const server = new Server(
2800
3232
  { name: SERVER_NAME, version: SERVER_VERSION },
@@ -2874,8 +3306,7 @@ async function startServer() {
2874
3306
  const JSON_CONTENT_TYPES = ["application/json", "application/json-rpc", "application/vnd.api+json"];
2875
3307
  app.use(async (req, res, next) => {
2876
3308
  const ct = (req.headers["content-type"] || "").toLowerCase();
2877
- if (!JSON_CONTENT_TYPES.some((t) => ct.startsWith(t)))
2878
- return next();
3309
+ if (!JSON_CONTENT_TYPES.some((t) => ct.startsWith(t))) return next();
2879
3310
  try {
2880
3311
  const buf = await rawBody(req, { limit: "10mb" });
2881
3312
  const m = ct.match(/charset\s*=\s*([^\s;]+)/i);
@@ -2896,11 +3327,9 @@ async function startServer() {
2896
3327
  const PUBLIC_PATHS = ["/health"];
2897
3328
  if (apiKey) {
2898
3329
  app.use((req, res, next) => {
2899
- if (PUBLIC_PATHS.includes(req.path))
2900
- return next();
3330
+ if (PUBLIC_PATHS.includes(req.path)) return next();
2901
3331
  const auth = req.get("Authorization");
2902
- if (auth === `Bearer ${apiKey}`)
2903
- return next();
3332
+ if (auth === `Bearer ${apiKey}`) return next();
2904
3333
  res.status(401).json({ error: "Unauthorized" });
2905
3334
  });
2906
3335
  }
@@ -2915,8 +3344,7 @@ async function startServer() {
2915
3344
  res.setHeader("Access-Control-Expose-Headers", "mcp-session-id, content-type, x-accel-buffering");
2916
3345
  res.setHeader("Access-Control-Max-Age", "86400");
2917
3346
  res.setHeader("Vary", "Accept");
2918
- if (req.method === "OPTIONS")
2919
- return res.status(204).end();
3347
+ if (req.method === "OPTIONS") return res.status(204).end();
2920
3348
  next();
2921
3349
  });
2922
3350
  }